wlmaker-0.8/0000755000175100017510000000000015203543557012442 5ustar runnerrunnerwlmaker-0.8/protocols/0000755000175100017510000000000015203543557014466 5ustar runnerrunnerwlmaker-0.8/protocols/ext-input-observation-v1.xml0000644000175100017510000000755415203543557022035 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/protocols/wlmaker-icon-unstable-v1.xml0000644000175100017510000000636715203543557021753 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/protocols/CMakeLists.txt0000644000175100017510000000317515203543557017234 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/.git/0000755000175100017510000000000015203543560013275 5ustar runnerrunnerwlmaker-0.8/.git/refs/0000755000175100017510000000000015203543557014242 5ustar runnerrunnerwlmaker-0.8/.git/refs/tags/0000755000175100017510000000000015203543557015200 5ustar runnerrunnerwlmaker-0.8/.git/refs/tags/v0.80000644000175100017510000000005115203543557015612 0ustar runnerrunner7407625b606e3df0c02d4d2597a8c6ca4a85da24 wlmaker-0.8/.git/refs/heads/0000755000175100017510000000000015203543557015326 5ustar runnerrunnerwlmaker-0.8/.git/objects/0000755000175100017510000000000015203543557014734 5ustar runnerrunnerwlmaker-0.8/.git/objects/pack/0000755000175100017510000000000015203543557015652 5ustar runnerrunnerwlmaker-0.8/.git/objects/pack/pack-d308464289dee20247e8596c8f6d17611358a29e.idx0000444000175100017510000002522415203543557024410 0ustar runnerrunnertOc  #%&))*+-00344699:<<=@BBCEGILNNNPQRUWWWXYY[\]```behhjlmoruuyz{||~   !!"#&()**++-/2246::<>>@@AACEFHILPPPQQVWXYZ]]]_ 1Ϭ 5wjL[Ӟ#<JJvO,}k`ڗV`liCUgr3L5+C^Ej9EnDcQ>NV;;Ty\rDU9Sb,v9]ou~PKGL؍E F)a=YA1(q]ZQ@;C%VF*_Jh]!ŷHđkNȖ"dU*jjm ,nc#zy9,DOf9K#@~s;Q&I$|"U*18њcD$Rtγ +.V>Ͻp~pahd ղ>A^=~sq3?ڴ~fJ,?|9|yʕ?BBWtr^y\ 6b߬C$ .c2rQEdjӐ8aEz{=8HShrWFh0]G/Τ{6) 8זOH>]iNNES>T!FΫ =K\\jc=O+--~KM9g.eڀ~2WLQ=i;<<`\dLRj\>cYL hKKOf$M+h@<ղopvM,61Yö8F @M0&Ul'n aOeze ZNz?'O-6"L7Du8$PpJh)H%:eSPEE:IהIZ~QCKhZ5 R|ᅐ,)R߅ MUo'A,S =mPYaȦnLSyG 4hYJ/?S/3J,k 9yP"WVLn@C>~EW}"W{7 XI|NuYYY߃װWT8?LX[^Bw7$1<&9&>[QԩZޕ#%A7\&WKk67dhs& T]79JNfXkԿ]*p#b ǹ>j_.DG9cOA6_&#QZye3=g`1)~i7O<`ռBL EZH5z]N=)s8+*{-|p\ow2{퀜4;ҸsAP;{ycF>]!iLB⇿7G:@eQ: dkS:AO-=[q V9&/*@SXXr9?w΄؃ };vh\1T1Z Ml}" ~n/Z9Mo,$]IPm7NkxVJv1-=̥1KX00]H(W=9؏9[,ugz2U :;"HGq? h[XLKP?Z": ϖEs8CKo׾U?\$Ga+Y2[/ZL,>}B yq+: g,QY:C_+bn3Zkآz͝9E Lnق܉^jח$mM/^yVH瘫1‡w3H &/xY6UiL+ ǘŘ9c\DAR{!sHgc%Oyvx9hlzErۑfߝł[cRS!ܣ`%Dmp3W~.g&mvnqJQr0r=DU0)\@ݛ~g'뻮0mL>MY☳% oΉ)C K'^_dڑ."YD:?^գQR-zi);俌X|񨇍R ]< j +?Xb8 %ߤxtiZ@%,y/(hS_-~h[ O#\x ~7 {Vh Eݤwkip0VGhYawP'{XlN93)aϨ,CyjaB߫H.?TODGFg;kXo2vLm%D5.&}lś+M nc[}&kTGf,^_6pfn#M͒=4h2)Ԡ3odXMݙ*WO3V~TI-c YGkA[MIW\m^ EP?x%т ۳ب yvg\ =RR'QIXE0L)6bxϑBo³UM6$QaM[C#I&<⤿afyBBxlPbֿn)1 dԬWXJ$"g1*D5h?[w#p?"B4eQ:{ R zym^$ jX7anÞOH-ǥ{6]bdļPc2D|ۗ:p=e";9-Dc^k$ c⛲CK)wZSYk7S0N }Zq*0:Hr fR')pD`?ҽ85i8"y̸u>6Rw5$ý~fېg[IW?1[Ҿ1-E ߽`k%f 7\D~~hNo&f "}h32_$Q`3Z~HJPnF+ݏ.y'DZu98kv= G>jtzs7{b2ODm)\uj[ȷ6ϒu3Ĕd1 S"6iXK k ^h=73%)+bI4fw\#[X>G/|o֖CfWZv53!g.dN,Q^ïzkd;o#Mñmʁ)mw*3'!aD\&.hTpIBSU3DG+*L-* H͟]†Y t}rwp8~ً0U۩f6icxsTQ>>Ն~VY3&t{y@1̓JΥHqH9/Xf$TٹPcWݻDv[ -X >pݢX7pVQA':DT*{ %%̊UXpõ|]6ᶶaaaY*ƹ YTu2ijKga,1S;ZiX M U灓= | {bk#RtsHAJ6l|_J,JT]|7sSӹT^M1S{<.^9[CKi!TE5vwپ iVr\AlIgtX}SOgr[1yx fCKcQ|FCk.Y!'[X,ZwKwTyDc%AŞ:޾iwIUXZ F" .תp!7푎+Igov^ۃ{&h zm7ޝ=RUجEeNdA@ؤׅwm^fSh`wi q0DҰ<.p/3 Lp*?+YiP|0\r3QgD՗"1d0h~'4ϫ:EY #UREVjcQixI$۹4lXߕx"Nќ]thՏ#j*'SLH0QmF~ ,nv)L+חkj?)1 .%bDe+2^9KJ(ۇ1KR"E) ՟e&C2\řwQu$uYܑ9'ITi[kUQʂ1H#l0 OKlf 96JQ Fb \ { Yxp>ܢ.3f 7! xD Zz6Y&5x,cov1^k, \ oGm z&T >xd  } z& Eyܓ` - K"7 sYj :! q jG kp> ݤ Qd X! H n16 qԓ0 ka R,c0 X@'@ ' x\n@ [F,s(d,5KX ؜NB2 F bP VF K0 KxAyU@ 4 x=N, ;VSOU9h(V E" R3AyѺ[hH jD0L |,nH(@]|` @i ^* Y'|1 ڵyV42: q4}\   I9ǽ R v!QxAGrrw zz! ڸ9% D R9 @ "S9(diy$%  .]7 Q(=ZI}4/ Rm%}Q,n R ~\ĨV,O| A;-& d |C {b<> `x- 9 ̓ `ОxFBGYlmaX IᗪX5/LW"wlmaker-0.8/.git/objects/pack/pack-d308464289dee20247e8596c8f6d17611358a29e.rev0000444000175100017510000000266015203543557024417 0ustar runnerrunnerRIDX70P aV *@Z!O*$)Lto"(T~6 BCIJI,ARz<VQg8 6F+`1nB;&AZ,!5c &G?k|GY Y3'v%^:-EHbKN51 F.[]LUq@<T%/K>^[8\_(y=R'W M"xiND2+0;{U. /#j4s} Df97rOlH$XpCX4J>d)w3=\P]M2 SQ:?hWm-SEue#9 FBGYlmaX >.O lwlmaker-0.8/.git/objects/pack/pack-d308464289dee20247e8596c8f6d17611358a29e.pack0000444000175100017510000257167415203543557024563 0ustar runnerrunnerPACK_xeMj0>SČdYV(%fUJCȓı-z!]u~x/O"Ү&*=h}DQPE KWɕ Lb("Zɲ1ivv}ewYTl_j6h8n]K\Y2fgYeV8Iեj%q p ^gjB Nc#Pժ)VJx31̒$=D7`KHÑK XIfz^~Q*ÄV_{~>.0/*7?4'!Zpl=[S6e} a*sMN,.)+(a|jYflםN0,5-%9D/7̴uwj,M WRzeSO#WJ}<]]9p~VnՔBP%~!@E Z &z.alV WG_WMN'O]1iVښf,o|\XV \q"I[C7AUDtL~hU>aʧm|M[X ڶiQJo.Ymػª\Z,I,((f4)S/=7Խ T!43'E789 U73/%B8aYzg^M~67z/yɹb;kWgg Kէ.\}3=˱ '3lڹ} ug^T'D6$Gޅ ht/X;*[8W>>01+c,,;o!J2sJSR \D_MFoKL R\`de0 ͖7yFLm;vm*LJN,M+IKL+.-(`x C/: ?4^r0DMqF"0G]yʞrl&=Z/JfhP^{ˢ[VʋX3pʖ&r clSY5*brJ]"`+)f8:ۉͷSؘ+,J/H,*dHO񀏐KߗCU0(>?G㑉볗-{ X-KI/&HP5du_Ɉ5\PMwx31촜b3HT0&z_ط2M^Q~`x340031QH*IM/MIML1+*ssO09P-4m!]%E9E`+W׽k5՛[ӿϴ>p8dR%˶fڤ$ݚ(!'Y"tbW1%u9|IЮČ̓X(558ᆥۮ ۭ/I~?:ic)ԼTݒܤT`7sY;VFLvbY TݢԜbwϛ{{I}1yήU`Kr23tSJ/hcgrmڳJp x340031Qq q qKf8dO t?3-6bSh2ڝo{ϟp}_GB?fh?x340031QpMN,.)+(a0^Oɟ7'7 _;~,JD2"1 '5$H%3_[tL21̤Լ<+_]$[m ﻻUE%e <WE/x#:]CqƧ%dg$-ݶeÕ]9xfLĥ:C[u3LTr\@NJ,)I-]PWpBUug00@KOZ#N%hw]UIUA)vK7y$ * B>ZB(LL-*zb`!ߐ:P^ OkrSsa&״Q򐚽dUJ`;y%0w7-:\!.߇ =Z'x340031QpMN,.)+(a ݏN}x!DerNfj^^24>r+KojRrJӀj̯~֞sEtZjT5 ? Rmj2|\0x 8i `X$`c5Y?QUTutCy'N6wsZq| UX__ZcaYVƿ X$>U 6 K>+uTɞ2hvKx340031QpMN,.)+(ax;qw}(\Ӊ3,(,,K-Kfx|z5STٮT^Fej +g۹N85=75$>% 'h(i$1I:wcWOW36]vZPT^.Vix340031QpMJMqK,KMNeXeƵ3=ݪ57|!DqxbeNb^J@Q~I~r~T7S٘? ݬ[Ր 2L/3AmەSf|]{}g!O:8~Q .Lo,hB:6  fu3tf٢kZ#x340031Qp qMa~JgCg?nQ웘Y\RWRQfϽ>#Iwg}tɯLIcxKL~{}J*ssu r ٻ|O+%mbgؚ˛3 ʂ]|@lE<bTO xjP|#vmU R)ٳ,Ѷ\cgkq WRϿy{?EY=ЖBhVib^rq1{38mq>>TU@/x31Լ |Lǹ)S$??';aV ]]*-HLZ x340031QHJLNK`Xҽg/%drXzD(w¡WEH?w[ݢrңEM|r~^Zf:Pi3E)s[Ie JsSjG.3{^fcQ[Vyo*BNx340031QHJ,N`0~񊥯XãU~]ʾ!bU_ZT%t޾Fp~* eϼt`Sue2VҴ"sLybM[xznՔդ/'g${?%jJ3FE=Treÿ6jrjvPe)@f6k_<0AU]dc,G*mn0*J+*Xr\A#{oNoЭ۵TAzZg@5#][TFz%'9TBd&yOuO<\|1I;d^Qkw**I{ڵ_&eY ^oO;(%7,T ?@+]_tcg6GBRY Uѻ;^pSV@iKPe%@ė%&g-Q|D3L#N? 1/5f֡Tu |1-)/(-9li; N?ӄ/s̀XPYYZ T&u3o7ԲEB.}+JM.IKgxkɗͪVオ|7\YqfUjR"a%oxOox,]Y|bQj"(͛6n/ -凩ŐҺi{ݻKugn<UQ\R v-vfAoqxoݾ`JJA6I%}9ȥ$o? $d=ї;or8gbLE>6HII $k O{秌imZTsE͜ҟ><_I Pne{Oq >ߝ;w}9Q2/1tAT(?~7[|'N(K/y* ЈE de@-[ھDe 8n-M x340031QpMN,.)+(a`nr_A۬ Z0L(+(-O*N-*K,-3ԫa[n9IX :+ZQ T^W\ ӷ\+CjY\s&oyN9:_ H'%U"oYqug}-3>L&ldmI{|iٻ$9[h+(M/J,@yQI;wUjL$t`_n:RK0t3jW;rvI_P uF]G䶾mxK|\FzGBM?w B=8T_?z Mu\X\揤H%4|*[`wu- ER(ܯE Vq[gbXH"i=P\kN_}. 4Iî2=R;RHh` x340031QpIML+,.aviO 0DV_ Um3xoO{&ߺo jĢ"XpI7-?z4}\uZbiN TI 7kȼ]}ļBѠx31bc3mW.5־` ֤x340031QrutuMaxlO~K/Y_cQQkjVajWp-wU§Ry j3KsT2?/4w|ppas2 T̾gAM{\宍UY_rx$gqǛ.FCf% v[%\q`˽3 =afoxu{8i;)JZ1rXDƜ3VrMJ)J 5xJ3*idsLʹ#s}w$)~4gڜ{-o`{FO@,[7z&4F ,RoDpmF(1E 0c4>  " /bϛ ï @ľFXhYn .=}C$"'# JlXI UQJB,2CO&$M;6 DF+:y%Kkj_Qp|.D6zxrKoE6A|αѦy+Y3SQS@`L']\|'g 2}H2lV 2`1,~1e]<]ⴿ?g焭1ygeQpN*!FP&я5M_t-J7T2DS_#Y"7]Gyv`>yg*AcPxp^Gs:g#TMW=#S\'rUo$ԏr(z#K,CO/zE0Jj诰VՒPJ +[)}Ր@bs^Fcc(q-.3+hu8>@x#?y'%< 7S]'1ȵ!ptO$񏝤ksc,"Qԕon LRA@jbY( !tR/E ۆgR.wnu~S8-K#㵗ʤ C}C*ex&%rm.rG)DIy Ctb l>CN`U{ %(zWUmY nY6"&Qx<<̯͢c¾|nC =-<`: ,E<|gO(slV;MS#I2MVi^tˬc ~5c+H :q-N]._y+)Js;ѭf0KYv݋[IǠE5 qhLG߱o KPL(=ZB4Opypi&zBgELœ)`Uw[2;01gў;4?k{hyA8ъ8 aseŻ)[W)x"afj@NjD]rq %5=^B` :-qΞgY= x340031QpMN,.)+(aXDNS,JtoQ̐ꄡf323_ TQ~iIAi P/g.CυEM|r~^Zf:Pw3ʽ7Uinb^bzjP9ZhLJ{sunjRKx340031QpMN,.)+(a`)[ >MzS{\1L.-*/Kf,Ht>sցj2ʅ(X$Ư.Rh P5٩IE)@T=<t\vKYz`P3TqiU9-FrSAMt{^{"YJe0DFo> !+-}nEUTW6֖5ץyNNh2c&'^֖8r5F6eճQx340031QpMN,.)+(a~[^ .&m6fQXDjy3w͑=)0LE~QJjQj P#ZO%?en %~h D9_r\2cgEj 0iiE@5L ߷sޤuCå"p5%%y@5;/^ 2TU>UW6*}o6K=UOsǿfXB'gU>2E.[*;n=+5'575(Ix<0mLԝͺ \_3۶]w#&ۡ =3e}Б;7 P5@_}srU]VefduQҟ|=k´PuE%y9 ;czS>z.|iθ=7G-x340031QpMN,.)+(a亶MG;=3ecQ_Z\̐nKforWVe8RK> 1}ĺJOoYy5(%8$ 8fF]kg)9}D<TCf^Ai L>3W-VOJs3*meu}%ppj1s|;P9@o>rë́gDRQJ7P 2r5ĝlx31b>j?;<+ +UYJmBrNfA|AQjqqj^A^:Cވ,;ڗk dE0Rds$2{tОn73Rݍq rӗ^]z8ԨʤĢ\ﹽ˺MP5yyE%z9% aso[sGeuS%y9)0q=c8g۵R CR U)ʹGM ?[Rd۾!o&d14e[[ݑalsm Js3KnFGV99Ē=qbEjPsrSRRK $py7Y\-w\Kx340031Q(HM5+,.a8S^ ϑI ˟p"33(2 _q'uRv%x340031QH.-*/KI+HMe%y+{{qq?6o;>٤Ԉ^__BP6q֙悢̲̒</\W;vfǖ$:;:I,JO댽@Zv[1=_3-3''>1%(1%35QF5{onJoNJΦFT}GN^>eo.5w*/Ei[XxVI2p]Uv}9|\vzPp(cjcGLЗdV!oo,XkCmo˺aZ3KR+ .|Qu;^!+?O:TWQj1QIEE@~14A:+a,z2ӬmYc5u&´MWys*WW?nne蹣B$lɄ.VZУϽp3L!I9EEPS3ZQ2[ '~괵ibS__ +Љk12Z\R߀؝[PW=1#EM?MOZ~r)(P2K6m$.W3Jy@r]Nѯ:SWr/E FK{Ҿ'<x31bٿ{t~*:uCOx340031QpMN,.)+(apgsuI .*[lQYSXZ[[W\[fWpM܄MKwpO=)]0sq8'Ǡx340031QpMN,.)+(a8~N% E;-o~}ib )%EũE _Zn_3YK#3KRsް.ؽ,ᗟ!bUd0,x4cj?\P?ש97TEnj^)Ќc3z?OgT"*#c' uُY/yԲpܒ1Ͼf,gϨ}YmDdڢx340031QpMN,.)+(aHLV~cNlV2NkQZ]_[XTZ̠sAI4nHѪ+[* B!oFxJ1 ! 0TxTM0W ^ꖏVBā]BpT941u~h8ɶMˉ^"ϳ߼y3SkanPG1lU@bW 4\yYfM4W P8nD>02#^zwDq1e " Z<8xrߝNnFB0B, i!ޚ@ qOj*.ٚYb`Z>֏УD#IGssʛJ I".ªgd{qDqYi L.[`3Pp웧#蟂. ɒܣTn%԰Ԇփn٠E @8&Ruvʨ݊>%AY㧢~VZjO|WjW8ܠ VDPܑ'@m'D3Z{0%mN@v̒Cp iļibXV0Lgwu~'6sf)oPЬҳΨN0"H;[S@bIFy(et}ڕ Qx;ǵK>/17J43'E!-H-5%(Q'3Bp}4dMFՔ4J҂ĔT.( g$LǨYZ3ycv&~ҢbiL剕9y)E%9P9]9E%XtJ6_g*gCx;ǵS9/17J43'E!-H! 5/84U!47)'<55e]$kIjqħ TdMFʂ"]ݼ<̼Ԣ̲T̼Ĝ3uSRRs&b HNʙSsb '3";)9?7h Xt^riQqj1ʜļ<2E%9(arK R2Am89%d5 ԴfxVo6~_A}؀rNcQ'.\ݰ EgT#vRӑ+!ttGA KB#s"B-gц mr%Ѩ}q ؜QPiu~4c:BNA?vyTecĭ'7t~Pٗ>Y39N.-aIN1Q4QcCh|}X,.&Cg7o&o snTOHgDN)cd]I1PJ5m1,:Esvv͉H2 ll8:m*Vt )4Fk!`0mH5ATDLwkBxy%)3Ȗ dkF|~߃ Rx}ֳr ̍m )♠1J@N1H W I.srXF\Mf.Jko "&L3cdߐ;/i4Ʈy$alSdmgmhf$V等C+۶h$1,^ c!`\2=sY*Y*i$m g;]Kk>KPf |a]8\XfkSs {8 a6&ߏ_.XzI0vq;/@wQ4u)nX [ֱضʠ5/> lt9v}QU#)i ᱥQoo6U|>tWZ:lέ@Bnlm3_:vH`gu&axgFCx/KlF,,IOe%Så9%EɓoL>ΑQ\ZTY6?\FgKI-HKIKL-WuR@!+"T2]b3&3CxMKJA&I$ Y&Q#q!23ϤNЏY9xyo94EjUTkKz Bh5ַ]?rt-\dUEc4e~M4[>ᾑ .'YA8 _%bss?%Tv`N(.qĦC,q\+PQZ0T!` )f.{\ 8B#$x2H%asO%Fi|^\ݝw;{c/ns2Gx}Sn0 +^|(!"v dv؊@[,4>~I$H||,oVAS V?aq32 T@8neh ^cO0fSP!fQPsU `}`'ȫ뼫l0xX4ԶJ{ Z,Q4%\z y Ǿzc *;/vbYѻm{F9Pp-^%HDE[h8ow|Rvq[!.a'$;H'4||.xMJ`r*w^9s(ۋ e5U!Lƾ/DDNZ^%I,#Rw3i0YߐCR;XYFggJ\uEeG)mK)Eon?/~x';evۮ%,Rɍ2(jN Hν٠[!C*`42BXOamdqI]$=N`R5k6So 6=nSʪ+T" zRN2Pmi5tIL)vښ0bX7-`EHmJfq^؂BSKL^EN4yPCG -ЅAbϜ!v$W:I n1F :XCjez |irڪVlpbÎĚXW &uUM-O,J|׸}~н|+kw^pXKCYT\Olhܳ6jcȍUPq.-u,8f6I5\уwc1 RQIZihZ \Diu{wR$߯./~8s(Tɔ9[Gdi@>37_Ok-_r n#-QEWbx3z*vFȒ'$M8i|NͺkE)aiVij3IM(SA/>}Jk]nKrϟoQjĚ,iޫEye?Qf#=|r% z_r*pz s2߳zq{nu1"J$}+~"TxתwMNuI-ׂ3RJ*J@"I9)I0S79'1/(̕\_2/-  'λ x SHI>IaKyiSRO7 T,H$quސ19s87: <~Z`о}Ҏ}? bfEM|2{/ m8JY=sUߩ\,Рf'%!xE. ~+HQuٖOG|xXks_1MmAoqַ٫WZٕ#ɬ(<,+{N !;Og{zq ovIzX1͆}" k??BhcϬǫPh} D ٣`{)w"I8b9kRn)?.ٚXg,OL)[8Cxb b^ބr,s63ƣXe}F3&'#^6 ')`S!XƅCFx2ph`Y$6"Y)e2{>:x&W\J%ÌdEWXI0g k>ۼ&swݍkW cho\ƕ }p=6mbɚ?g^ր7l!9mB9vCJJtE)ZK:它(Xk*Ϧ=Eg&^n1 I!_r9wGlHLe:V4Ȩ>tSϠ4eFuPyK5-#j-3vP20?X#~p{5ȯ:&ղLa*Cw3`Vlԙޫҁ_k4jح2e qA_sVzµ 5g>Ih"0: Gɒ4_썝/=*jeU_n{~i%"REhmPT;Zl0hP"O\dLzOWl(" { %-Ka ;` $6`DiP5H$4,(l:نYԕaY!ExbXJ1á>T˅W fH=<2pw)~ ~2Cto&)S:6`ӉnR6t0>`HS(Zmo޳h¿?zo|U*k^M&OVCBf#<:c2Bq,/ڏ3oJyh<Z]`h~euwG `_탮<Çpr4*4hGH6*QHCu4ʑ ZȈٔ0H60E?YF/HR MӜWSȴt%C`CiYB0cQP1F:o&'޾Ѧ*uwZw꽼ӯ+Y7bDH[U+'^\zv6K\MEO'H5_2 よaH I9yHߘ2\G(Ь1q#72ژF0%*ݹzƆy1`v#B t..:0H>x9yܛ ~J C]]LF҆eP̆UtZ{qؤ= ?̜Gr#ODztbxZ ~٥~JCJBfgd=_ >'0ߩk >-vZKMx}W+P'[M'zl*) )!C J+|k05 4_~xe߄Vll&}?Gv/Ƽm>c()si~qoh$#$}cK c~7,O{ңlxB7~rهG%Mn8lGك.%xE+&8(c#p6Z9J{[ַ8{3chBÝؔ]Ǝٕ1u$6LtB(I>XuKYm=e|BɘOnNa(\kZ7Q`ySH>\'V5浵ĥ]w~/lB`+,:z9M|H Ť' #ڦI=)Gg5ѓ/i ͬ;'Fu; [.OpR N,f IsKnBaFu3\]&׮x1~C,ɝǂ,w93.DsֵGC# MsOAްw(`mÙS b95/܇y׃e*˂tP Xj]G>,507u8'=ك`oj$- !̙TYtZnz,$xCځ,;QT\:=R(I&`k }aiA5aе|(Jm]iz}w&,d\ZQUgeYldjE\9cB}T4Q7"O>zz{ Oő'DQeg(4d./0/^W 29KRfUL*87VD(4,=vE5"GIYƍ1&QŻmE[l(#hHQShlzN>(å1YEET 46Y|hѷ gT-(!UJ6w"vUlRC!e܁3YK #1w7o@̥mL`x Zi*hcޢB/U@nV:OR(՟KSX _Z_) B \E[]euJ~H*1(0Y敒p3-Zd$ >hӉg5윮=[-BYiYK;AP NH Pڿ'0ԽZÆB qfJX;W.Q?M2LUm mgaC?1KfF i:[VgZOa-$:{ Qڭ}*g~}ҟ:.Ab (V;(E`!htq 5|&3KZխg}tPѫK?Zơd DD}|Z\F.F/ײ](Y;U&Bc+>K!-3K\b&5'p0 ɾJC7KVDGܠid 2a4vR^jA;r?Ս-mU{T s%L=3B>S(S+ T,tY wJLJNøy.iEGoXEuA{]`2np{Δ\ NNKr8`ӣJ'Sk938? w nz |G)ay1x"o#mr3(l/o`Ҭ]Q}08^d(R- Bqg耧bl )G{T|MOKx86RC^'/twrn}s53U5_owZ .CSt:mͻ݆￾pW\R?\`FWXvĶҵ \kD 7WtIo%wQQk(UuyP* wFw~9x8CTI3<%boV:rf'7^nׄ$ ^vM&.furG#f/&+j˩T˚qjF˜l'd\Z3]m86nXXJxCeSSA7[2\JCjJ r儍CR,2XY[%랥0c~ /T=xZ[s~rSi4i牱䆭CiDn&% . =dwVM5Iٳ E/˝u:^ qw}{ws-~]SW j.T;=rnk3' M#Z%;1Ie['dWtbtVTc_^>[i7X{!pKUAUB֌۝N>hxΔcX/cO+Mzw P  8s+lv[z!S@me#Ic$핐%I ZY/^Ao i ! RcWҴ$arxùxg,я71ɪG3/eFGqB_RWY*;w!#J N,`E+;U<׍+VN$ٹe \hЄvGI%/i;aA:d AFu`R+'3=6L\Z]^&Qy|x[<'$ְ[ )GZoUkM[p4IY3htqw3r!8#A^ ?Pv;Q|lpcl(?jkZ Q9|RoRyH\1=qtLH^cBRs g' NPn*-pc4 (KҘp#-1bZYdąn`x8[ dV9 X[BA[/eΰœⓋW#$Sc Wh7; $AP|#:Tp~*܊܅r8kiՃ&Wbi|66|y6yaX唃H!K4LouNՓ/ı0HVRT)h:Fj'2㤓 ND%"Ѩ'Ju"9S6/'\Q6 pHd6 ~J"KQMm7n;h#ZLjDe}5ie^f^˰H5腍l(uD>[_`FWPhd!R+K:"7@)AZV"r75wTXBJ v?V>f+kF/2DAfmprtTiǖȏx4`YC ʾo48U+lv8" ɭquNZMY[@(j_`)_DVOˎq-$obn.5?B c::e[VAn%L T"ƹd0)Y"(Ifb<2Aŗ5sgJ2%S/I 6}ԔAddITgu>Iy=@ ]'MTũezٔgTe Y V/s0#Pwa_fM[8"5P":LflȪ[w̤ս$ Gg~ UjlmDL;1 `D*虘80O[5Q*Ұ +s UƑF:ag|흹2b1]hSY<<ъӹJ$ΦyI۪IgD1&cةu|K͎ ^5@7:aRcK 88l]%;rmӚyEjf"Vׯ̀ ՗ vKR͍P_ad.1)X1D[(>C#S 7Ī|t{?3]B΁)wv#jL_5lqn N}:CC6ժFo; tr)~4jVQ+bӁړlȦ7_ ΔԟJ;jҶ]1 7yc`HKa>]Rušq?ˉs/q?[r=?,u07bu^ZX~_h~K'ф+U6&MDsRpM."{ `z\[z?]߽\|B~u^N~Y_so@6t@73NOB˝1<I䲝fjExPGʮT|ǠwՒ4c6Awa6Boy̗ZcsmhJЮ `ՙѹ-!Q ъ)Gkm&CW*QӹF:-l>ƌ6F]8WWXWϽG/no?ߠ iZz/o>%.(kiBB>F:Zr $;o)D_gh2!Nz..L@A.uԦ: @;vv7"zVp6/i?' (Nr@!c嶋hfqZݨ ݐM.r4F a|_PM<½kϤ!o9e/UBzG>HgO*.("#\E1!NqTJa2]~}yps+=}sždy2W_>٫ˇXm,މ* N90_B7׷0{a2K!E%xeRK0_qPf>0搉S_Dhlm\˭۟or 62sF[( aJ]Q!`Ue@{d:o ]H{<ڱ3HSc93z3Έ YPmPR=LOy n@l}f"ء.7=j$5h/ K̴x}Vr6}Wĝƽ8YT6$'I04 =\ߎ~flOF ~2r;sJ_! %jzóe+Qe?fP+R@qi|( ZPH&{oqm9K]Ĭ$Mʖ'&;a 3)D: 9tE=,e>g) Pp ]s5[AE$ԂO,KՑh}*!܋K,yN!T`r/<žZ x- { nEqhG[od:ngwSxXs7lv_܈Flɛ&jmaz0*s,fdofÙSF~kh߻vT6{oWc<[ `Lф* X[ȗ-t~8j4t O9{|>LA4?[3a|&{b@[06gL\\؇-j+F)Eǁ\tu-%s :iw$WYev3gӕB>v5_L$j2UOZӛ!ȍDc%JbW"%z+JmT:uM1/G8_.K!21j/=CkRG &HHU@0!Vnw/&$4=X= 5ŷ|06G}aurlu QK)g˒Uj]aeH/CX zG݃agﰠlf2MLJi0]prD-vqWcA+R֜ڋ;8pe ̜ v*CO*}<  I_{rx=sȒ篘$qںs6em|bC:W! !$a~!Q5H35=====3M~W<~ql Mq)shNM-xX~ <_g9훷?su|2iDCruLʼU@rXԋM 09%x"0r|m!X@:hC+Ix~LNDWޢALX"pӳ(s9#Wß&7 NZ zAtz||ww6m?\t|;G#XԸ\E$tBv"fYt;Ĝ>R|:Z$Rc;Q:eV LtF7: uFQ cr;q;"!9{ޠ.H|[BtPfrQ!asZԱ5o4g[z hp"l,،٣_ŋ9SbөQ;4>v~_n:_/;,wi =?Pxq hԞPdbFX;b{n!>3W}=Ctˡq=ѸeWnjcfa>ݡ^`O"(i"* #DwPx0=h+aY 2/1Z-rN- E?j"#*{-tllu, aiĸswV1vD~ ARڢkgK\Cv@ >{"xП߳K@~L`l]Ii" LxѺShf*M҂4@ \dVM|% K ͦF"g.67ߊB2\ M|;=oh(?o̖! T Q[hNHS)zG46b'vw=Vu,x;CnS8 t̨q.E vXDW 5fn7@+\r1͌pKGZT[d~Tӊ[v}j/?`kt麑osҺdaރW&mQS1(S7g0#5M|N,uJ}{>*:NfM͈\e4bSȹy S=mv3VC sV ]}`Is1&[ f%aI%xZg3X'?'-˟j!5ܥwsl|G#!w^:WC -gOͺWU ~!VQ\*_(~!ķW 6Ai4ǔtK*Ah{0J(h%u^ղ ! a;.:oUPWӞߋ[k4Q\b^$N,ќ'WՊ˸tݪzZ{cBNqPXRXO"U}FF1h3SieZ'epb%U:K 0Wv@`\s~!΅sA7 D6Fc`^5:Tt]1r1u[HTqlR59l.S>UwRqբ[^r VXb>2'CR:j҇I9ƶؽ_EFGfEt$l|aKsom]\5<1Ua\GsG6U>ǝ^;4A*̤iFV<}{r1i{RiMgM2Ur 9vb28%gug|Ja?^0={psːᾕ*~PeN1 R+crr=>sG Xsj}'wV'9c!r$%(kާ|p ^ #MQ 6>q sI .\vTn4^myg.t,X1 }1fSO)HvL1+òSg +myw55f0WPԀ0 OC\6um2IoK[.(2hZ̠V܀ZR#VZYZW,wf}fpI4ZS=ܒझ( UJfX[t:ٌl'BAHM4m] 1BXMq78[PPC{kESG6>)Y!C5E,cnUH  ģ8@=ܴK~9=kqMKWla|N@y%9õJL19,"i3C;2s|.JnqWТۺX2j J}ZQn}r3k҅;cHe4){Lwf$V@-ح)zɻ?1E%2I'#c޴`6' z3zI(I簱Q*W휯򾾆ʋjK5S f:l%ǖrN5Y% SW(kS5)d@b rWUfbdKfu0Wk^ӄ4CV Wqu7{SNė# KWc%3ŧURCg^;ɅS}(H__Ut^dE":hGZJ\B0% S;Yg5FN%K3F-'{u\AI"[\qk3(\lT N@janܟ¿`==SP\dg32xJIJaһju(%kӉ(~n`=Q8-FE(S #TKYO$%WP̚$Z$Y8Yj͑uڢWc04_PԶ"8H'DpNv' Ev#%[D6#-ާB{Œ]kr.^E9X*)iX'oEJQ%WoO*59k?٬ j蘮哑oe>շo5h 9Χ{#Uc뛶TKiSFB(^2܅?[@]F<`FvN\˫ΧhBѶ< ;6 sx;3X̜Tݼ\ Y\_TTZ LԐ_X[ R\RPZ ԑRZ\!-,WIN>,4E#pb_k^B~IC Z\HjxVQo6~ׯ88K O*R`6uՈ{($Ex"N;~V4Qm&JZ S1S‚U%jM&{`5!,!%kA*`BiJQ23-5ƦI6J$9(XhdtM7&[l?~tn0]]/tn ZYSRbmx!LpV rUEFڶTԦ)Nf:^(892ۛx=,~z>?qpF~QK!e"X$a#NmDʒDQG#EeȆ/c異6"/Ⱦy;L?i71;FKZpq[̗No<f_ՠ#t0b40Jekm.m'f#+8*YG|̌n?Q#ךJwd\50b(vLaiDay՗G: J>eb|Ji55q)QK-hڛIu̳Ɏ}ʃ>SHmf_:I}\[|}k}\O{7"92w|xo u}q&Ҥ1Pn{,MS='0@W't{p|k4'%Փ;xTw8scOo"uIxk5!V7A#-3'U7/1Hd&U*h$¥55&*5&de+(F*i&($u&f&'ldTQZXFxqb^njEIj^qf~6]ϥg*X4ŊI`Yz}}Q_Z y) i dJ р>dxU_oH9`K9Hp})\p<x`bUo @֕l33윁)F$˿fS^pPCah3Vъ+"D",茢 a02 @oEj#pRqLqO?V-BC 5'|sDVBiAfqBP3f_h EUU3( \YbaY&H^rI>-D,$)@H`DiQ$[r|bihAGLwLq6`G;$eCWPTD[Ѥc :ꟕոm~u1$Yڢlpn3RうC=-0ucj8P֍2ah@C!v0oMTT+P*cN&ex<ijaS}NfRQbQFAQfnfIfYjBpc&WIbQzjI|q~iQrj 0WA dN2P85=75$>% 'Mdr(V';I#jNMJ,NJLIOHM.-ILI@32$n0VGau(^ 'dƧe l#RkpHccgm,21JJo2˚]3?'dH4B NwKL 7r}]:(UU+3Mcn94g6x/=ڀxXms6_M2"Ke"r*]%LI˜"Xeq{-gl;M8.hv:4 wpL6d ǰBC/d4RL`R~gqd!CB%`ݙA⺴-8h&1FD>PK xiGp٫}Mly++H$ELn<+`xe$0L-<.|lpW'} QR|61h\0Ѱ?8ӸB*%kznuQ3$k5 Nqx-bQjMf|&`njU ћpրl8ki#9|M|8d j8Nt Z@1\8B{0%Mf T2[0]~OEALŊILVLe'E^<o//*C._ZG] w Md^ҡ/(3L\Sѭܮ7@)ѨlgX'l<"U-q3:<{ : \uĩ14ģ.t K1&zx sa}9(]zX~!$xwUStMu|R1v+Uˋ,rlp8t4BզOrFX ECLoP !Ph)kM*ѩD@DV㷿]8;H'MZ'bV`eԑo/,`6^K9e1zSԐbZҹ|q#1ŭ`?(}O{%.״N%F^3[eU I+AŝIzXPQ$=p<Rj5;Y%~52 ?y t4buOX#mO25Yp5iA^Ę%6;Kީ*$}CJoR4'(.|ڴyoG#t`ABIksIRO9 QYZEGS-AOt\Osg6O?F%-T> >Ν*×ϚMϪ9Q}WUYd]Ꭾ=[{5ܤgզYΏb-]x3,o>8n-Kq4&wȶ,QXmޟ݂[VVK5[ej/2YYE$4-F ya2VID_[P/sii^jLYSNnQFz$ &є4u \HG޶muQjŸ3XBOիmT|\|S7ri+'bZ7'<}\ZԤuit6BP@%7,eȂx<¦SEhkEQs64n#XT_AaF!+uGLs{!uÄ@,Ӏ >\)41~LK7Idn ,1ZkqAnolxi6]z~H]< 8ʵ^=3psDQcAVCJNUk`^^!un6q‰v+Ȕf{݆%n ^c0烖'p\`w'K1M7bk왰>1Wp׎b pJ:pUoM+ނ5U$Pԯq/թ=Z|I5cSZ!N}_s{BȌ2;gy`J}Gٞ0 ѝ`:Lq- uZރ#3jpW@.iou{eF} uUO6sóc>g`6̙ ݪo{E5G Ixn6~Ӄ &zHѢ$jɕ9DDdQ%FwPvlŞlr?NFbpg.x(SL^4iFb;;'.FdN>XXM^0kOg9&e?6%@%44W0x[g+IJ.92+5%: jk\xu.j5-Hb\n7!ҶdJd7\bh0M+h!ڴ0+5SPbWT2TɹҒ> w\ v ^܇+;SxpEvxn aNL \]<LNaGHnrre|3*Z0( 5KL%R%T;zْ>3_6KˠF1b*+YtS)D X,E`Gɷ q| ?g^NQNHbG7n0À+ۯgs(LB';Bet/;ЂiB%7\Ε\~<x.Ehʍr qb'inR|zd(.Q=<-f>Z_'ѮyAKJMG$,昐ãyylhU {`:f!=q#~D#cʾrg;=gf8W%. { s پLٕ\{k8UuTJ-;h,xAM.U Vt=!ZoДd?c$$g㒉3zȫC7e[L8f2݃{ܾD7L Y.'[A8itVKsӔOAr2A-4zALD>[uo<32Wv"SͿaP(Bx*g ɛ&1ۀh`נ0נɿ8&a+N-*K-5323ᘙSXP_ŕTXTQZ_.V q rstv/.gW'Ǘ\d| ywrv ~}^II#FKJ &GiON`J4E #"9-q,-$<ƅnlWI.:Ԟ|O"F8f)J+,I̒2]VEA1m]gYbD'2.+w70-Yaw<q;uZV8[vn(mj}y]d׈N9IZDa+r7: h_+3HZE}JTﺘ! &0 1edK}T;2i@ėίȿ7|X'ȂJh E )QIXǷPњr?@kwo@km+\ GVC B 0 D8 B)A1$.lp ǛF+ՍVf"$G#lM*I$d?B0w`GBޡ(3{'S;KPY[ hDLQgYĩs>&(ȀQ #^A 鴍,i /`}%B#-gLsM)ǖ I\5 Gt@UMHj E+Ս,hi=GT⨍$i2隡WsG"Ϯv~`.f`f-ZI P;f6_椗n<[Hv镋]ˁ4P'Vr]g=6{ ()7M+ߩCaq3=dsAJLlÁfC7eCQ5̚Q̇dӁƁ /ٞ+wl΁hC J?Mk%AB:hێ*@D.3bYll!gB0sӂЭ@P#=p|?S^LS;ȯ6kv[h0 PكDZРLeFB}8 oIȳ'tK"ꢐRD]JoGrqCI ZJEʋ?AA3^w>ypPBBY3Pa>fB0XTρN1ڪg7Fk9}ܚO*B-"IIᘫ=7\mVoU=qbphL䊃Hضb~ngByP(M"{bfʉSs?% $(oىSg1`ׅߢ z(J','18kzyJYR$u<›֓iz )/06cj XM)1H( Mib\ʏwҌG&_0FlTצjŒHEv/> ޾o WA\QѺ]lW]σ4۽G Ȁ8f+v@sv{flvX +@]c`!#-"?w)R|j2:ϫ(l5:JbSͥ|qq~Ag| E>mM%Ck 볎rEž|"yǔY8ʛߜ~%jgt2 G|#5s9%O vQ_=ƚFyI~4#M>mı x~vu+@{:rm]@)I~o< MqL{):a Hh}DE͡㮠|:Xn̬/20jϧ=Iـ)QkFoAl=.f,2W92[md &BIN#|_W:t!'5]#v&$KG~.Mhw0W^< 5t9YcLpQKa_yZƏ2r,qJ4 erp\vB|xS,{ۈ.+h3y Lӓ3gBUzѪs6Ľu[(ĚK˗ 9"'gW'ͺ@ O4)^{RAآ5͒ hhN}_Ƥz|.IldBl3jz'nFpV3 ?<|ʵ.h u>4jnpdZ<(9ɴ6;L+<.yif}7p7 L^_G'vrK-,/K!n\',R'I]f7mb'd]>g5̗8D$ְq-0'7>l9 |NtԈA#/ ?n9lo\nK/8HSv9}Ts )X6"1QFۊq8Y|5ngUl-p3-Nk:S-!a*Ub25l-KքYء܍C鄅§fWOLuwHi3? h0@18Wʜ^33 07ɁKYXl3k@ſ$wYi Ց/ceQyy;,~Zny?;cr -Ңuo̎1HDW+UZMjnVN! gVM].*5KP[U( Z,sLLV S32ZFLJey]4ȌO,oڹصuJ֜˯VdM<*j=sN爙@53^w\E-WYV'9쌑aOѯ'Z#6:f/ [w x8t8 qH*htMat:ڄ1{dK땗+lK.4%``]|s'=>jSqqQ69*i!L$!gW(x*O%EiRҰ ZY2=KZ:W1^h6smɳJwgX!ѝR|ɂsFv9&wvzu<)*nCYEG;ӇZk@3LIj&IIݗH8?0 D 0.*ցl?99=~rvu|-"UYið=X1I!Ik SV/ڗZYaR78HhoejO Р~0WeJĮ)e*AS1{w˛őCA?uUǕ Gݒ%gR*CZf#A-|"N( \b-mR&+ۊ:UU"@e1R|Ǝ ~|Zyq'_EhI_DQQ9ŋdJ~damy+Ig}ه#ozv&sBs'ժu^]\AFl|7?aoe _QIt>iY.>} ԭ,mb&'/ZdOd"mޞ'!9L~-l`{Iݑ-~=TU?᪛+v|5>T,܁Ĝ(SB{#^Ɨ1 a E92DRyq<>y|u!eKm|rz|/ĞIH<a|BC}ĸH/Eg-`i0V;<>|-Jd X~ZV\v;WlIC {}w cK"UYgo5qi6r+S;wHllti9f=af1$@z.3xܹ*L __f W7ǧc+ ?[LSoutV{yt+G4`8 }!-5H 91]YzheoCꯁfY{(U5Un )հK' +7kp=P*C0u%.{Sd.sZoZ eyU+:ZK7E`~҉<>VH5ϲxksG~ń(ueY T0J\ew9/}3BXvRW7PDBYG.ؚ%v»JtF݃x /t0:_(kb~lC{=61 VX'. J]з{Sm-p=v~rFA8GN4u,zA3DZ=on;̾x @\+xv$Yyl@W-(swZ ?'M_t|Jк^ܼF7ɩ#1G E0M\ [ɉ?4<FĉE.e}VkvO=pCSAdrqۑJ=CVS7YB pd/QLD$gS8 lYum^9V~?[Z 0ma!rH crm4(p!HИK@~@Nr<1d ͨ)yng &5עh-awVl|\Kq.(X#w\CP+QȌ8,6VaV6CliezK","{-i#GIfL2XUlՃY% |,D]\#fXfڿ, ~w x$lM[.yBJkVhv+0-Z'f65tOX֮SnTڇ}*'١  5w@#0VJ?-#Zc$;&2t%O^he;8`|-/ޕ+Bb\4^B;-wϫGܱ,`dDLo?me·ǃO^qhG#+RM_#_iyHSSD=jzA60+M~'^C V !AU\?V106if:3<ĔzfA"ZE3DZ)j< f-!OrrvuQkYUTW  ;{I|'n[} gAyQ̬q ywΫ`:TwMM}iKVKǵ+632CM pf9+<>o+ YEO{e; 1Bm O xm.)v[A``*4N(qūaqir='R_G}̼Uyo'ߜs!kg^lξ^`Y 2-j6Qaz2p ߝo #&2?2k'eIx|m߃Z$dnF?3)EIDiQ#(ư}wK0`Gwi>&|2ާR HbW,Z .,@wYdPKDW1^0 \^"[q1k`s]rGoF| o./T,3Fc1 zl% TE.(ǟfg c16[; vlvPY#'^3Y׻RIVpxp.|k0t$N(vi 08;Wڷy]3潝iU8gz=nn<JX FUI{`%z%C0S,3&k#m VfFL"32\? {r[pvvJ _Wnn_]ovo7|ϫ҅vsi(tS"m@w[ .S1"X& O$R eG0JcA'qBB}]/ՇS .pY^**̊y;lZZ+Z$; /Ueo \")|;3@+Vwﺯ fFLf[դE&ČbvR3 XT+Paw%S*f;H * l%ɐ%{YbPypgѤoDAx4|bespgW:3OZ&0Z|G@,rH'4I&(DP $T";*Hzg6L\yE S͡Ĕc^S c=̭S~Z%Jӕ†Rˠ' H3K!7'$.g']8 f$+AZ`I<\@)g^z^+gWԝȰᔠ}^bR /kSqF?A:b+I4.Gc02VA'&J`IcRC᭼wbz` V  nɗZDGj|ub  qBKoix!I=\9pY`!4B_=*$Bւixl<Ny~/h"k,d䍛M7: TR⾾xn8_qEKb! &Ԙ v%Z&*KZɿ!EQv. [ L:>w9іQoE=ߗԧQ$& zG/Yp5 =s}}\S1q! \#/530;]5 0 a$;d㐈 WO!|)((c㛂ιmC0KΣd2Yc[R;7uzzqu3!jC8FPη`GHcϑN^C9 ׌rxC_ی0.9чl PdvLg]t>Άۇ{z~ww~s?\\N立7ooӛ!! L*DI\)!aJR.^b{9Jc$`|r—@4tFO\Te 8F$/1F}:1d]/-d0ࣈ^~ OF$"Jy!t%3řl,-l;OEc]DP<03ai7Mv\`snT=4֘5܌Q C+ *O)F:pPp0s3jBr wI2+l+[~>ͬۻ!t[]d"Nиx :|ӌC "22N=$\J{ `J̠/&6ך!FyYXOȔܳ%=Ͷisu䌋 @yQNv||S&ꏻC dWbmOdUV(z2?/<‹q E<"A_֨6UKu-밋ó,*МlIWsϐU5+ WZX/^ 8ɘr7%R+YASyCTaAep W9XО&VX-4E.V'zq<"+wVz?YpZT5gx vɣ],iá4wŽꃒVP&"sH );X†?0 {hԃ|T.5HjoTv|v!\|U h`:I0jS|+2% FTBңՌw4V.0`~?0^װ{("a( >ITD*qZ1[ 8A8w١!ג^;WanapMgbzܖZ l=IBlAA9͌F9Uc:s[aaReQI  dd Ѳ&h9fhvwb͖p}Ut =|Wc@*]e8-(dd+*SR+Z~DSRFP(-D0eBm΋ {*#Eq<>ͧhXюqtsjr/eQ?*IŰ}_֥ }2.]!DwPo{x@>kʪ-{s4}we2}h,J`<p!ܾBCce׭ a4ּgʙNcD84x9M+nmj 1A~\32;a"eMO Oac'͡>g|@[?M/1|ޣVNu:7`k[ťINK]ںi7-zaDgb$B>4̣~@fayn4m $uQ,bFlOD--?Ӥ `Nػ]ED偨N_k̙HҨW:Fp8vٶ|Qd8i&WZdCO,hȀmM&$~W:Q%*z*濲_W x‘++г7h+Gg=ޛ`FMj/R7JcxTOkA&1Z)<uZQPbk`QDtg4 b"/= :6Uo͛;# on}yڱZZyLQ!V᮵i[;$(|ޅҖ!qaQH0H2]gi T6( T<-=dHB)t62=O¨&PEpJ_ZP.ȺKT qJnZ" g 0O&[ {DL-_.-ղSޞ b$?XˉXIj"kyAn uÅ-}:(XP죀ȧ#hB0Lʮ]UB#&ې^Ӹw8&n@XY" S(*hf#x Cp~Q? HarUur(v }mtP3.c<9MO.5inAd$Q$B؀ i*xF-FhdXeۆ*HmL`@'}#m )*~N7x4y2Dh \PdwVfɥyd!24PJL'[ɳLi*9$kqC<8uAY֕Oc͞J1i[ }A()PWQxsWs4K4T:ȚB+OS3d\0QJpvEL5Z#ף%d7 ZC/pLe*Z :םБpQC lׁm_Pk^AZ_lt]!4&(|M 7jBJ!7ijJTd_ngC ZX`Y)^éY !FS<[ !֊EHi3c(4[)5Ij[D{;Zb nm(kM(pܤXxO[s{rCk,}*dUj [u'XMx:iB{rR$*I=܂]o7tI~U:OĎn2Tb|Z$G{PR2TT.dƧt ^$'k?D!]P OSDY*eᒯn45j_ t(y\׭.|B/4F_L|0^nNd1̴1Ql.BPt*cٹ_/NcV.~)qucQq5s?m{Hn>?@DW2b/5O~\}#Ow۩oA@%}{x-uWj-%K_wY{ xks6ԒFseu:,EB'aY}[/Pr2,"!: 'G ї ]Zi c+'\K;軕{Qqc@rx2XO. %sp`e 0F_qz3>E}2^g6ޢ LQ`% #߳> ʘ mDhGB!J(y;l6Mrv6=يIbk@|0r9^ޥ!x{,G( Ǝ1zI{,U 2;@Wو᛻t-5}{}{5B(&a%v)f+(,$Ž -Xfe(B^BD.{k/S:Tl49:8~m]W4F8F}o>m[v[y `1D]5% G!_ ;3I忈C3PwH6~)KA0E!xlÉX R=C*Jc C pR/ rdc Kj0gdXC`']I5DI4|b ADV`~ 6SiYa@I*o c6-!L{zl1#4Z0pdp7T>qG5mr␍jji*` !`Lbģ1 FP{5vj@c@Yx ā46e#Zn)6+Kq#QvDn BDXO:r0`/O I0xa2 Tj޾OVS;(d&NRH7&򠟫z1DQ>{o:x KP_Z$ؠkl %@(w=t}*X5;Ŗ76xA!0&U2hgoʡJsM@ֽbxN.V5 \o_ ZXʑap#GP!=jr8'e@gZ B8T5, Lס:y쀝Kazsӕ:䨧@)&4q3_?Lha f5Ȟ; VCI>im#.Wo5$GcyOzc бESnT'"8PރFB 3H:gGPlj[@,v2y9 3i݀c5[M!r\G- ˑgԂ ^8\rS 8vbBZY߅`FHbkv*mȝCFlz]qjOY2twH03G'/Q rkyUV'r̥A+E9 C ]bŮs7.إ|Nпpѯ4`o77DZJtfq| pCҔ N8ºYoZ0` 3=/*Q%r DZM=J)2FvJI_fπBЮ"3`XW3kzw?Bm'Jքh0Q?, S6^T Ӑ;,-̲M򣎢MVkmYaYj ]bʫhXM"* l|XI,:) XsZek$&rvZ }I?eخ+R~ 5v$|ڜVKspVbZA׆hi դJ0AmW&7`BoMNF0}ru]*>?)jܥ ;0R +TVLT()yY<6 d֑:{%AOB{g+I8O)5-6WaofdJE BH|&=FԞ̜JZL.!Ovd/W;Ofyɢ^S )(/ P;FjZbs >?KKR[JnQzy;ϻIjK4YlSs‹\%PTuYtW +- rU:}q{"u Oc(\i^NjʒЕU֗;|* s28vwuj<|JfnpFc337!uRVS޲tUH0/8rLw14^C8F,Meoue~XŮ[)C"Xq\yQ|]d97[8[yЌmp|F?yꈽ2gV;mҊD哖㏚t]/VtVY%痚ueQ;z2vfk~VuLok`3*e'x(MYrx[s:=#yڦ{$!-S }@׹[&2~+_#$휧 FZVv%Q>@zAy|poq:NoXP*,8v}AEkU+ceDy&>x>>1~4y>06ޚ& <Ĉ-kJp2U *JBXU'YXx\&PA}En- D]dPZM~B.-9Fr ҄P\NVˎyխa+!8o@9?C,95Z%D@]I*%$#€i|Xt@-; !ˆGҤ>hK6ha\ߩk9 OauTHUӡ>I94FSm2e|I~1.5&blaK HFm4up3iaʙ v{] ^H!36P}ޟ)~" ztV0Q;seN *jA`l) FD4A2qJJRe JywƔ2'&3>`=lY8BdM VaX '8~ aa]Fq>|/fGp_{n[nm7o|9 c Д/TJs<6|P۰@?$|D 84ڣ2, bv)6} yjp?tjzv̋V?XS!6%07RmS Ÿ 9Dh8R4 %x:$Ȍ#M\K L1fXiCœL3<뚟khtsHY4j6U6zzG-rwQ8":lX\zf \?UGF؅ a@Y+8w#a9t.lgH:uZuMT.gjjC'Ӂo{%+0 ѸYs %KM(TLVyhLTKbGCāgV *5js<*]%k`dү.Ŋ]P׺s[9xs 5זd 3!GGk9h2oРzՉex p?poϦf,BPbRv^a6K- O9*jըۆ _ *2ږ-T̃ly^kte4;Wׯ-}qqz j~/L~=_~¯x 0{*8am8f%:-O ͵Y5^*?naaorFn׾U/;gUx`Mx9vF\ྕh=YHRfSSP%s+ԑEsst'Uafq1̓}2'S_cIѿRoއ.<llfQrlRүZ8XTt2\iyIm [=۬VL9UOtT$~4tCCB.XUl5SdPїZ^1QK4`+O}S}-~$ZC7"6Kn(ܔ~^A֤6rP-|# zD@,OA$eH73!ʝႴXXݕRJQSEYHa=*4[L׾h/{:䐸[ 1yn@7 1w&m0L qzKIUJFC*FAaucBZ֏dXdmU,>뚽z!Wnv_!kcYD Ղu#nzOZCnȲ% )t(kI.9 To(gF#XOBi\~3[v{WV1꣄ZWf %l3>3FۡZb FC!GI2QI}=̒EN(J,C ɶȈ }Q^+IŪ܅'6wk\-K// 5򏳫KU5>$KtD=97Tͤs ?2NÊ kNt굆B3k*5ɔ ;CUW0Y R _v#kّ't~A񐹠sIJjZzXw{zww{MU+,, O?gf+|h8Y#R{j8o 6#y,O!6=V=`uiƾczDXa _?q{SQۥyXm0e_W~HlHw ]"$,|mMdϞSdx:(K(#qXc'K+=Qۦs XO@yC mNcB=>gBd&ZHzՆzǞynFa):zU'<^yDNJ赌ci5]!rNmfA]anwPo4ӫ9߻@?eu ڕ]J06q Lv@0c zA^*~8b\Tv.۽~[%Q!(p f,s8]s~($ <1FU|'!alDqHӸ*3=8h?~_eL~ y};Z77ޠ \^u]A~UpB&¤LzRR:BѼ3z($Ј4B6c ͘b KQr6asfDW_{->e(\g嚡+ELT f>͹!*Ghp(8 :.02fĊclQoF43]̘swbTA+lʝxxdg63Ƒn?&8EMq"Sˈ\4`NCX;(C)sĦ&DVj+#Jwww%8 ү گfiƇ5I_v̆qhpio)3a"3qHe^IXT]KT>uoU>PM*PUK%cfSQٟdT-6$06j{tqkWsqjO'"1JS8,URoHivyNhυtRِ# 5<\ x4E_otRI"qBof9ClPL[O vt,B:+˕w LvΒGY) ~}3(s2m783.˚Za[nblˇ/hJ?+JxY1 l+[!s3PҷAYrWƙbw&$ML4K8}91+aBp uְGĕkC+~$')RBQIlD4a pnP58?H^e.""2ԅPjwY-.i}PgƊPھ%a6,k n$Rl}@!xq}KFZ?Nωƾأ/K'{Nz/_װOvn!$>wY*[H'OP'QVT ށwyv [ #^ ET |,V"oE8.{6]\yRm!:XRוrxԟ!a츁@8-v)(- ;!J8Mx'-,$A3+LJpp_x~ y"/-`фsda%ɏ+ǘ;-yYY'$LD%e1e[PMڷdH'ϓBC ;A",%e)9L\T}x^z8Iv K/!IvdDz;;-M*jNߊj>DWy 1셪,dh 1uܲ Ȯ˨'׆{ǎ7[:G$Z.b8ȶիtXs7ЪprTSᾚc$2ˡ'&VcE?he6ޠkw˕eo9{FN7aoD ֣!\X-zE=;=++Fb8euá|n frOOg2Zm"U[間UQW&u$=i([6)*w9mw䏇c1%=&,,סlYDƟsQ.擖KBU(q,RʟuUWh/ZYN"oGտ^ݶ} rLiյ xD "bbqk~z05ŢQ+Z82_xL-$'H^8 cRc{xL_-*ԚB\rOL O1vaLxVmO9_1 _ YHOzj )"ήwcc޼p@=z%Br<sXLyc"/t67"9:_|n̓܌Y_S֏pu&HWD\YCbp#ESJ+fvIZ{19v[‚E2.O)ɭ΅2d!l K Gxj*Չ2 Cna ~xUtSPmӯVӯ}gnVoiί{A纇__{oE8 YfHS+y|B J6HDWSiRpFq3Bj`L0Rco= *;"AAax߽ ˫vo^t7C5 +;( *yLY҈ io%sR3)D-Uz^*;\"5;a9*[tm{BȌF9dLaX!4"$SZ!1z)&q%Gñ)?%k#(Ӿ߾$,K)rGbNW†6$q3h6P::t/r?:(ΆW]kop4Z'x|PNG  IHDR}V5bKGD1IDATh홱j@+4ج\%1IWs66k@7w9?ؓF|p+a?hbAXWfl,Yό8` kgt7ͿKKdx fq8~3Q~?89<ظY̊oc&ɅRh<i93 ρ,;Qrhd}> ?8xQjO.3:iʭ77gg_o{U  hdƢoc(N۟L~\gk֫b8W4弜^-Hȯ4ko;܄/7!"(8EPp @)r"ON&Ť~E1z~x=`ƻD3m6W_~Ӱ#IENDB`2;rxbPNG  IHDR<bKGDIDAT81k1J&P\J_Mp?Z RIp4m.[y/ofK)Y֢]n4!gG5< >>ȧ\w>Sq>_>N}qټ<{I֕1+M/1+<^tuKt:wz0H~sh4tiQ9Nr_?e_uώ T_EZsV#4 l>xIw0Oi? s.qZIENDB`@MSxcPNG  IHDRF  (bKGDQIDATH1k1 J&P\J_Mp?Z"nQ9&.VLf:޼_B_Ŕ2XVZCtll^G,3j8|h=]fo"MIFQoZ[sr/+5m.s1V+4@;LF幘|ZC,ZJ}%ɋlov8[@s'3|#s|# ,tR{)SC& fLݞ1ZC`ǽ;Է^ ޽{R>u>*32+IENDB`i϶xWs8~^!8ɔ l:Wkj,W \{l!K3?$\=-fZ5h)$T'|-ZyWo ]bpKy6N5wP79r1Ⰰ&%!bB7%NB2EZnK , 1< h0,"I K܎AhOAq:vೲ eJ Z.{bnŅ~zl4ILA?gL`5= 2/S,AZA5LȤl6ae$Pwݶ?c&Ѩ; 􆃛 t?iE]BGn2M% s\Jw\%ҀX%)*R*LJt001[0ET6dj$_iT/tJ$̻]6 q=_ %&?^c:4/?ȄbnWbU `ao nha w% )̘v`"H*uXY_ O -_󀔫k!݂ڮcV:^)+f\EQ(%h֦t)"G %)H֚.c}cJ/ ?e)wuſd$dAIDʻ|+w MwxZEXGG\Aaz]3)tDQQO7^\Q*mx\6aRE4]OmfGg6?Q~@_H,YlfGg.TH@ݳk=]2(8@Q}tKP84pVRR._WI&3D4?Iw}l?6XxÛxx11,^]y3g0.αoj^"~L mTK8GL9\?8<42iB$։,uxuQj@A|'ḅ%B"}胥[D\Ul2 MfI_P_9nKyL9{/<5ĝŷaz{K-pPR5>i%]^y|0hٖid!k(.Jh 6JMX΍C=L.Y0?x6G, CL:mA8, 4XM ߊC&q(%q֔'G1„mN G:1$wY'(FMIThr}}FQyi=ih.-! =t2bg 묵ܡ-p'x׎mD?tV;ße*Zݍ8䯗sOHK}ӗY&ҷS۠DuÊ0\Md[~}I/S,Ǐw'xij{BhrbfQ~|qjznj^I|JfqANbĤX~##cTOSR2츸3sJSR4e(qqki)e()hs%($ǃė(hѱ \ @VM@":`~>>:\֛g32r`3q!!.!.A`Zk0UZRZb 0kbVqLKK& jAdz x3{B,_JjqvI~A|AbQqjĤFӍ#q}T^dQfm4 slfF). H+.QH*/I-./N-/QКEٞɁYy&w+k)*()l^DAM-}.B<7xJ@A-b~`)KL&1t ۤU^9#x74iz0Z=bm`VVc I:6'y,7 WT 60 ?AVHwbbQгQ`IGER1 I#"TyLr 2i3̲luGco{eFohH͠ҹxV'm5Ħ$^=;.ӢsUuTgx?hi}wp\$3{_mˈ5~wzS檾^^`1!r+ 뙠FP]k.ska}^>Wulͅ0rpޏy;q= ;ɤqIa0&k0Bu mU򿼌*x[=kB,{Rbrvj^ĢFӍ+\PZ/CK93/94%UA &_ZRPZR¥U(¬U_Z\_ZR`Pͥj9IJt%Q@SUk=9B,VZRZTQfd=VCW'LT`QTW?Nx}J0`ti^>Jd9BӤ$')WPx->ٱ5Yo圜z>z TJA jQ+829.7uw{tfv}}h{䊼x"?'J3|EgUQIw`$R.T$ сYIa?V/Cx}kwƮJB9bMNqk;NҦ$ E$e[?!]D`0 0V}}o~#V$WdpӋAqѸ?w~ z'(GyY'qG8r~r1F{tZUDyoyAX` ,EZos4p-@Y /bCkb`B+6k40 u0X[|cKn6)4>Q[ ,FX 3cIZ n^$It\#&Lka]ЎV Bi ;slq!OU>Ih@77wk]+k>^+."DrbWDYft )w GdOr|ࡇ:J4šz3Rs a\&a~tQL2EQ >ϦD}aSdFG 7`Bᔠ l*MfP"XX{yv (}UUC3=zR *Q(@8% tM0+K@6qI GW-5̳= 1ZjN !f Qq$ K*c: 0 _T V-fyԓd0rr\30|ozZd6s\F) % 䰅@lue|0S3rU%gK xÜj uGER9"+jk)(č|$Җ%EΒxLbA"0x`;MZ7G?|rw|NdzZ q{"r4F1iL0Ik%M#yv ?>d4 .a Rz25Z,E+rO,.?"hޚ69kL7?FۺūCc&F%q^-{+/>1m|Ƨ!?F߷섲uܰuy-#cfj@a1V06o$Yq-+8P"3jFꚔ3[w2QZaˤ`]HSb`hՖo`X Ϯ3+{Wju6_ W}[3Ҟ,>F@@~4S:(Y::`D>Ciwa,t7d>:U5$L9Ȧv'~J =$+k(Huu~%>hDyad%4ُ6;E ГjWQ{*yT]ӝY ̦DՠJyTTGmlRvD5t}gjQ*TK.v43m&noG̒QeŮX_cv[) K3+sGmN6XCJX2G@L]FusX`"/DJ'B rbx,iקT#~, $v= lhlÜm8[`ΖY&dZٚ<&<@xWxRyao>iP/#Avs4ߖ4𬤥Xa.~unU;1,"-Va(DP6KuSj] xL>0D$]VJb?vZc(5@Aj :d׬fП//)ZqHݫg S9E wnGe'Yȷ|\:,Q7It:TtMݷ=g0륻0̒= ^hZoLH]ė(֌UXKα1ώ/J)or"J뭕?ďP@;WH558~BNolXl.FqkQ)lruǥ TDE'q2Qp 1U} N0Gu/D}|{f۝X}W D1ޯ8[N-!613).~3)6qld70թfvdA$%(lFeXmF Պ^rݑkkrCM^8 q(];R \TVWevSͦV6 뾂\5fCWmv[V|S6N&-K-lv:>]hXh:m7,'7.C:ߧ4M4ǰ^dCV/$j$9^`IcM'&c .ؘ JP;T4+NKQeu+n 2\-F\qz4(P됋LhnYڳi+0;Bc2|*HN!;\"*#BG8 3H3<B@9lWf͹Ùq6:w^㼜8BܦCkդ_LݱrSQ|Z:{nxM*K~~jUTV7!u~`{I2Uع'!EԵ䂨{VCpUl{jY yL8pcD6p=p_mHKrKR}N-Y=E9m:X-: Zod'۬zAE5;;+",N je#u&qJ֐ NSo$+V <GqAv0itJeZLpxo`8/剨:CS]C1GG} TQ}A-VYb+HV[3tn̋:g򼶌IS eѐSy:.믪bSݥtc=4hv긦]H F]b̚L}1ŮGvQ[쉇-SO*(eiOښy4tE1bƖ].ULr\SWs@QH70Y$>$? U&y4bmQ.~1|P!(lX>vNXo^bWqSe6%l0%ZʬԒOehMFa3+]8?kјϢ#zΚn3RXϞ{3ASx<ڋ_M8/޴AjayYH$.%/n%Wf@[ğL|Ҡ^>=[Q6,eɬo 8r[I̦ᘮV0f)23nh1_]96jҩFXzbnVZWg<󏳄 \i^r/ J[$&{Aƅ&8ҕE}Iv#a9AiX^RRXU@3MA_pAV;>d6Dvu\#J"/#u?(\]et.켆.PJAt7<n>*& Ff~u#n~uRo>(;.G龄>)+s~{ò&Lf|}KTZNd6)iF.Cݫō[I%7Ncn{E}8 zqVrJ[o}ʤ`[ڎjײڵ ?Clwi x}կyO9՗΄I:YerאQ1wu:6aƨ&QRr>n:f{CuF90/'85rBGA곀~ @-^LѬ=g(/?f[L1/'9x蛦6OK=w LQSb8b+ٓ'J7—$0j 7"P&҈, ED{BL$׃AגH Uvs@wMɻxi5Jpɨ&NALN\/Z}zxlQeXVa9,5V<%<d25`Dy]tc~5㔊ȁ>ӲX`SmYwX~riL*&)!fZ19Fv[_Tu7)bi{5*YUڗ&*M(:j>E%ffJ*s$H'ƘfnlS25=l (31)$EQd|ܴ&qMx4;l<+ɖzh'iȺ]Vb٤kFe'1lD-$}60 BCEHlg\ŌTtSϗ۔^!#˯x6<[L7cc Y3,rS\U8]t"披<+հг9MĤdNe6=Jex"}$ϡWjEW¦tZOf gx!C!ocRG쨪Re/^ :ʵ;io]wp%S?z.rF8=6*",$ 뗋j@WN3sZ~1V{&%bȭB|x8)JY^e|WS8*޺Q *"J~-B~ׯ1Aܺk+,%1է}FBNgж%ؽeBˮyoDV)R,KR?3vl! ~ڒlβł+UdG5Urk(2j5xd ®+o]ͪS5lm1k5P{_VvMkomҽSZ%GpC D4I>_L8f\pxmẍn% fLJi߷UVi2IJ%h-E~;+bD VH)mM[aXi{<-xxb4%r'+ԔkN٫QFp3As4(?3}L{f=*SgPuC3(K\Z?RXdRR;y ?p0WW.MxoXxp @( fSvZs.xMѓF7{GgmJ7ܺ)pFv坭a|iK \p ߽ Ѽ-V]׷'m]fTMTƅ]RN?﮼<4g^H2REOgx="7uHJKۛAU[ϱBM0o\yB& f.h a^fxEh!o^2A٩Mm&M}6sZzx䢓}ë0Zw ѡKg$>}sٷW%zWGw[MoU 1#,⵴J]I{Y/+*Y'AJ|aTnTʶ{ utNh22XM>',m)?{f}&7M=4a K|B\)6#y%9;G+sxS1'П[VXoǺjIda>k.(Y7]R#`ܐetCY<[#ᱦf"a"Izp~jig{nkSW`єVKspY^V{/ &`5'H48( $}wj*QyU /F{ ( ǔ8W4h6KE-~ ?uiRl,Q;䴟$ü^{^jh^WsX1O?qIy佈H9| 6Q%ܧiL(0k]+# ϳƧ7r1a .ϒ9|;;9kkAlPLP8 zrKhՆ %v3,A4 jvk2^ڭ7\\xYPY­m1QnQ =H(QFi) :rD߁cI|3{0sd<mFq+En1:Ž#2uGB]UYYo2lIcw%Ӵת{'OOLjs F>Ld/;<ԨУ´UOD? ۥj.ۿ2sh4tw [I#`zwNޞu׳/\f)*XLGSPb z;X7(޺ʎ%NQ% y/xkjtdt2#FyAmjt\3%1Lld_z1֥"½iteDw$n.k+}d*Q-+9ho,M FAl͙&,<ѡͅu]e $ťvH0 #2U 2d/Kd8*l$yno R籨o KoÍ8%k7=X H/kbkM@h,j*O q60&@ҝw9x6COwϔ <ʚvi^Ve,/koHQG6o'|g{q^b Ѭb8ܳlԛq2ʹSil۷쌫րغ嗴oxX]s۶}qdm2VcM#MHH5I hYgd7z,X=P:D*I #X #$biTҪDY% kûŽ4#VF:_XZu~P#jk]BN! hܻ﷋r6h涼z/CU$J٥^."|Uo4uO Ws%bMT2,c`c$sK*HyD-eJ K+zf bIϷKkj mWI'׃p@j]Ȣ(d&@ b="xB,;Y㕁E =+ĪFJXfo/d"ޘczmi0ЧhN1݌fx9 n+`x& p| [5C 9I\E0-[b!iɘ~4*إYLReuPsӧt|/WG/\e+lˢ%lwHKCY4pI,8 $ڬPzX8v+ص`Bf+5Ͻ+DRVqZXٚ`Y9;>҉{2ϵ5gfHWv 4ʄ9\N6 Z;{=1I[,߅5󥘥1.E< 8[!z jSi4B:/Xq!imDq"3mR9Q7 z+5W:T/b&GL{˭|_rŠD jQVUUo سESSfKt,OV/e&Aij6Q&RI ]ttta5R:UԸ@2\Z:?xR&9tѾUts]ڼJ5v35޿a6K{EV;{b]Xd (3!yvD}w7fp;p$'n9#]fDs B䜳&̮ٳm.HSFDlI^e +UTɨ>Uh5/k|)'>ۇ d+c=hdfAsہ>k>eMɆM_SĜ}ݞ!3lx䋯ե k*$DE1p\6ǁ'ºAb&W^*JƆʀ9 d2;&OOÛ UB|^ UV%l:ԗ'F@l\(t`$ܭ>)1S]|v" 9 r$7X!$e1H\Gt/<ͧb "<,um;ᴪq <OQV oom5YwjSRMSgnsIDk\1GѥޫS |mu[.SvOwJ{~BSآinrDr zwS|QG\d&[V0pYesE/BS],˻7F? |݅4r6Ŭ\#,cQ^.ww$ىs: ya.9\;], S,{ЊN, ]FфzA~Gfi$.x!ͲA߰͊$ HJNb0IɅ󁲌?̋E># *@łE$_0)K3+F4^ނFs Ӝ, 8bXd cDs|}12Oi┬ <t2K4$+Z 48'g,]9" <1,hW)rL5 nO|{{b^ܥ|c8^<~@.0H#g"1Ƈ9Drx,{)Š?I,'OБ:CqdIHȘ v$e4`=|H(eH4\Xpe#$zIb0SNm?NPt(#eIA !t tjy: 7),s4,74e< En1L6;@: M:p2Ȼ՛+nxy9_N'_OFW1:#/G $RJ1Z% f z!쎂Ac>ApI3 G0aʅ<  vvD^"Nі>M絶 5ږYv["E jw87!d e`|Nv)@˟Pi*$Z4 E$4_d=B|\Z=W6X>fh/jxuuzvxitLw .A)%C)>q@W4,XF0ZX~8؃o\F(W3(2"I"~Q]Ejxu=q?/ލ5=?~3|=n|x2}>Z|㡘[4HD$8GnX'KQRs-%& awR3@"iȖ氓`IHٌyG y 4gyZ$ a };>F Y2Hu>/D@@ɇD֓ ;] ~Jv1j!h^N,㻮ҟ?pA9݂q6x4B`KIxd+caJCCed"r21ѮJIɁ`5[S*FcmR6~ h^ݔxOyp0g(ҖA%dPR':ZkTLjv3$oLI>H*ľM0)4)[ps97iD ,`к gEN5͹'88$=# =dXEѹF8\x[tn$YŁj]id% j n7cawnT] 8k C* 4gPy@<6P\ Yn)7^퉽Aڰ Zf+h-|liZ]v=/"K8L7Xa<3߅5 cy&$/95|?(ͽu*ptZOF9xOӠ4%SRMWG:g #ON,eFXĆ;&'E.f+#OK|u,|&J [8x9;]s9xNS-LP[,̣ZְTSI-yd{/#O;cŒϨY˹5PR0IuDwʿ^,L `hjD+jt8T!z0G? p[j\Yz 6x=ׂ(WՋjTS<} skA=ӜeUL,CӕDzL,n|22_ JˋMO/WҀ[_PU,cPp/D7ZJ] ;_ GḴtY7葚Q|]˸5JgVU5eنi Q0NWr_D%4pA7."f\"-!WTj[lO\ P{\,kcxC,PK9p2\ϠJ1U~:Gu&xYTl̠  r )M'炍Ϛj|u3N  ?K=}*$}C7G$MZKCPUB"7VA?Nrٶ]r3+4.'!x18D'u'!B'7>^(l*l#F[c?(.F"InĿWRd\^϶Cy9Vr%W>cz;YGv|Z5y LUE2|Hkͅ <rkP/GY!z)lFV`|-UǔnB jfNI6h̊H+%MoiE"X[*+{tFfCzrzv {x: d^.{<-T )(Gãj7,LI\di U\2<^2,%ɫz*|o6ji@M OYoR~r^[IA ȱ</j@lFu~#կN&K,iC GbO*9+| )&6 0p,BK#R?ȳ_f>ek3 [ċӒA_aFc ):HdAng)P-PA5G܍.Qˠtql0Ap9~1d+-Qqk"[3p2dĞ(pC=CwX*`~;3lJs4Cp%+̛>}y\"7*2hK'Z6)a[l9֢ťb %_Cql LIHٰ!Rªj1 *7%_[^З c4lA@Ԋh=Qz - 2ɨsI,؎7|ێkڊDԈÍ2Rk~\`".ҬTaVX9~W π3a!qmRKKӍ/~Nn#~cOD]~[~ɨ۞vQ.jHh8mՉSZxX˭Xzu}HhHn\o Utc>wdCCIpۇ{ u]FBK*~ZMl5º@YzDžǨ5+"r\D3yX@+L8Wu)A[`M=j< ͣx NӜ%E5!A-U>{/>nfGscD,g&jrL)*Hhdrzy2v1cE1G3[`[?S| tow,,;*Ta S ]݇|,8R^&CB ޝΨ<+&GʝyO4:;~s徺8?-GK LϞ,4 j飯݆2v]h-6@j6 ,-93vÛly{9߭:/9񍛳[KMT=/ ֡AⲼXGOk$Zϲ&v鳬nK軬=lFu_?=[տw Qo:$O5FN`CMˍ2_:N}0k۴ʺ5$cR[zu$7:+qǜ2X5+Y)*HƀC~_~]7DvS6x\*~#~)g{=C2\א it7fO‡^#1I9:Ÿ:\  ?d<ƝvZޕr6__Z>)XlS"vb򵥝ڄ|^6Vz0/o$`aQY aS awGw .J߀~-bz^+mAG эw5m[)b%6ZǭwBׂ>u%z!6@֗٠<ð9/jb7,_d6QA}&)Y~VC%xYms6_"S/7j2qlq:^ÁHH˜"Xv߻ $vpѐ\.buﺠmtLVH#9_Hi eq"eo|1_hz;+^[0 DžE+skot]Ѫ!{)hYB5|C"qzaR]%!!;fsZeZ\fue2^F!ZB$T$t ,F|6E;C'iI1<PrW,$&?1Mu>tf@X 'Mx{:O:$7x4Wp||=xoa|yp~')J&WLws_̄E9O"J'K( HL(B3mH[~noapg"E tG#ݵLKUJUyˢ|B=@uO`*qfՉn DF[˚eCjpwx'>O$?Hb3S>R@~zw~3"(r>)lLblvt4khm4xp(g3!'dxe2գO7 5t =8'l5fihZ`j#*A僸rOh8 DpZ?Wb3UVMiA=sSpZƟfQB1 c+4xRG1K{{N,bn&, )ḟjhotGϻ=#hg 6cıCW!$f`}8|4z6qi1lA&7ʹv⍮>^uyeV * o)d2$6/N),2bXsaB3ꟙOi'hնyS #s'iJ -r;˛ li1ASjĭ8= %Ӏh; % ^WNHter`sָ&jţ[AC̩KCDN'u%Qq)&>wJ;{ zqkg_}|ȝ[KzC?<lY'b*);,uGs<]f]Xt+5Pic!M&嚾Y΁ܲrɗj+;;*RV^AbBSq08~ǂ%*S/~ͨ*B "/u9sIozatc"gdGLo>6,/jŠV5b){'A7 sMǸf \Nsa\*TTe\į(7Jvׯ@j6zqnÇIs\,=)KL5o̾;8zGx '_mGE3jF`YOl秖`[$kqLi;s=f{yTut;XӬ DdѥWV=WȊ,c+JveQcW뺴ĭi2 L2(d/c{S~haҳеrr4QȽPD3؞ JLY}nx~UD%űqqaKO14n9Tqޏc<៑ƼؒKC@fqVZ[lV) %c&MuRi]E[`1ق6U6Dj^tԱv{QUK?7O5SeO}S:Ggb9S8p9m9'TPlbGsV=p:v)yq0lq͘P/nFu1S=wuêRjw,kk:+!N4:@K$:֙Ѱ;AeHιB9yݐվx磷7ׁ`Y@ib?n j8Elg|;_\jU2z,'$4,<[{ TbxeFԝ&$43B,/f3JځHWPtr>-}؜C&%IهrKp.Aΐn؟R{@7)x)lCSl|./fB>P&јF 2Yb!۟n|>N r='׳z]f2{$E @Ldۂ#[&@x+ƠIXq. lBfjiG/hxt 0SWוk0?XlZ"Yq&vBl Y@ ͧڅ<+Gv$:f̈jT)YDodϚɧ>9".LBVS=^*}Ŀ>P~7$ !GyTs\"dB (2#Qf2!}=6qa:u_,i-4  F[uQ6ipŔnj, J0I7DBYl쾾_cr%\ e%!嫌G2,V@rn `!XIUCkԕQ!>?vp<25iihbHgaޠFr( MS̟$V fȲmا9W<"MMY11eAERwc}(DXih\uit՗,928KȳD%@(^::p{PS RXOek8Vt < ^/ k|3ilaa@B|(+HR\`hCpx.>B-n>Ю$5.)M4Im /GApR7j0z,2k}v}jΨ/rBocST'U0s{zRZB``FAJXBx*s`Q٬eN V,^V&tCelmPg: %L DhsKA.}"}gF)>>7Vce0˼q|zwn孕yAoߺ}7DS6.Oxq4=K MO*{cIJ';U-U2}T*c4o\T]xc3ܾD֓CT;aȩlE({;#P&FBSe*+6hv~.{BW0G*(f qm\|=||qt? ڴKH=T[ =5A̔ TL 5)-=N Fo|zCm-[[ .kkڅ|m%`H؈翪J`K P3 US8B̍Bgp{/nS7(uNQ{5LP.Eu0U﹊8fH`=Ca(nh7A  T>qLJZk%a*t] Ȭ}nhv ,}Vp(8'yorq[ Fd攢-z֝]օώ8 ŋXǀ]l$0{w!seb2ve=D&\ 5d=غŠ}O@1++a9CU8k0)6Eʸ4]y=x K{뢧~0%U UX֡=C/x%+uv .)Z ^d o- =Nndz77Nb%q44J$s]YQJ=3 U5ff+ݍ5鵷KqprP_۠V˟Pzm5Q## ƣ󻫻v^]_b|3fxKm!I0c:4UIZ{E~U`)S'T`q&9:80!c2$qߟt:޼i/%/[!5!CH1,#x$sFz1i0Q8H0S1HŒ,hJ"b+-bk "9<0's-p &)AۉU M$ՏjPmGȠft;G>hlV\F!SHi%X;M A#NAϐ\"P 6Qbd*|L,UpH7!IM62up7Flp1877H Fgm] c]| c 3b(XҀ@0E$frn}dOhBAݽ >#?yKљ |:u_{rA=*D5ȣd{Ll {lES0߇+=!Y=2RS΅5KUP"AIV{%ݵCv@`cUy7PRcЇф~9 -!wgQ'snBj>b:Nf4g xQ43ȍйaN{V\X( &5k_,1w| f ئB Cq~z,~sx`xƔ<1_0%}qT9Xj^9ӵiǧ!` N:U.kהc~!7~^F/\hCh:{[3/VU*_?G-}߻*mT4J{0[I?t`8hHldc!/UL퀼}5 㺗Iuక?!nh-*5,0Tq B8_|FoW`~#YXys*IDã~?.NB7nɜ lC'f~ B8ћ6i_f@#V52FL҆wwn_y4f3[:?(LZʪ d <]VթBۯ?g;Gԭq#|'u=Օ_K3IWJjΡxNBbi@WkԀZRG7Îȍc.IWWiYL [`W˴W9!\"iB R+~$t uZUXm7$@*ΖtJ,\LtZCܼ\_ZM6A޻8s۰8(L띑Țy=_IyM' 5B͔xE|?I]$w-cjT6;l K>{IVHR$IMtdaN0u~<`acU≷9 ]jB) ̛e[-xRss:4Y(#k>8 Yk4{5 TH҇CzTٝsw{/Gg…an"2âьx,,r̜MK<4l d'I[ƳzW;r2*҃])ٓ ͝Ip ^.]e~; 3jZؿ5RezK|-x~nV ~MxLX=pnd_-.ckrq׊Sm>SY~vm^G5)PeQʄNYN)|e;!% `-ipU9׃SwEseWug_P d3@6ۺ%/˘-$4b8| 2~hoBfԀ&s Xi3B7!{M;[ :@el`_Ua'sX4W19~;~~wGnbkgX-iRsT4`J|&qUFojp u^&z\þQ=c,᫯:H =V΃U=hϞ0 N=ig8@+| عI^}Ҟ<'_s6*ptp#]ol[U\6u0r+n:I"SaQ\("qkvYqG0@KnB  Dqwcπ ;,$AӑX/s@wSKyd]tĹ?Ɨq97Q RO ^"}({2c.s?Zu Nq,Oek+kPdv& ɢCHM__\û|>p5zruap+\uOR(`Lc̈g s|wjѪbi7J3d%4{w~G;Z-ee/vooJr׏xk k)Okjh\4DvzeFpz, lpy/ug͂>, M6J2'-BoQKTu|qnV$dgqu #qM3!pAL[pG |Dc@)PƳu>OϬ5<'- 4,)y7k4_\=fCnqh!εBG! y',@њ;@d+a/%B*v/u`l HH>e҃{;J ^&-VkKyﳌib XB2O\0Z28 y95͕M47C-͆pxptbz(z0wa@{uhUn"e )?$mUM pr% \%=^ܥs">LЦ-KN*j.Bάul/_EkA-^ X==(08ݶnT[d3TQb ̎BYеs22}-ܣ0((1R#Gqk,?#1$L+$*𢆢2QK>G[3lqv>59B!yXtPd7ͶPPFX 2|W!!L2`0-4IFu}xhᄉAwJnXr3ۭS{m);FKRoiq¹COTu)JGbTNdH4/r>\>[yM0>f UυYWohf$ucw<_K8*zT]/+9Wvt(p>Ñ,ev3HիfAV%iZr,VdgniLKVUO8HR(HRFmsI2I JѪ*[r9V3mL8-Uoܻ}"u5$Dl7=@Ffm2>T4M=5ղj44( mHK^yUF*~XU"tq/5 lW-25\jxUJI˔@]"^KnJ^nWeFC.37'Q!H5z3As|d쓿@%5 =aSKn8ee J5QDԤGW:XVu<$x%ViR]tIJ*z~4ᩭPtYz\Q̶Thߢvr:ZƥOe-gyS1R^hiV2k+32xe :?!ZzRj/k|0Mk|;B)2Iջ5mĿD,_eJ~Y+CHnH5_YTIGU VPcrm)B0v Imfv3EfWgckq=)Z[Eu<MEaPT%)Ox6>PbRoNxF̿kzqLf,EMZ gŏI0_puǯޏfhJ܉dB[n/Ky'jQJ$/(ƞ_IB4`ku5V?@,#KDop)R>9 "e^h(,.yp #;a3x\ g*x4ƒO4H{LI\ypp0.-I!\F5.-*Yk:gI@&Rt ěIѺ%ҋA ? ,QM#Hy0#CithzfC[Գ{{g 8!H(ɽTV&Ou4V"61Qd>7Dz\̿P`d9mYs !M'r<ġyAj29"#Ѓr/W0rl BQ܈CPn{uK.ۀ^xJ⊟H?ɮ4+H0`p*&*h긐S[_+ ess[+ PmE|LsSLfIRG@29J}u&uPt1 H?A{NF4Π@L MO9D⺃jKFQw_Nt} 2gq;I` 9^iޯcɮgE5|A +>_>rgqMBA8IY&π A*Q%pz}]i)T7& l=K!bQIaqR2>t%yU:ba,@ ŠM^,p`!+ q$)/MKz{>޼uޠ'sU'7)Y*_,v4aNDk^|Wl]f0T fT}tbʁZKNaK}aHyEFQ(Uow@ AuJ 2_.C Y3M/I2Ҫ2EװE:2߮q p[2i: C|1c=x~ɣ|%d6,69 z<^C/ K lfj]dH9]*B=EVw\ԝE=o^uɎ&ZIP.дԋ3=k 9K(n ;)lbS™ϒ㖶A%EMC!KS(L~ eǹt#I4fko1O2%lY*(Tກ&[)NTF3O4rg',x]}$͖)mӖc·e"%BS #A81,8v ,Ӎc#Lg%ӈ޲0+s, "2eiY\vp)Jb;beBV9E rkY*c };\8&,Y0c57d;+td$[cbeOimsR+ yiC~C^PGPTˠ<qŜ6T'&~=?~5+|ZCNJ`qOsÝ\%,X1yv*%߸Ș}0iڈ#rCڡ/ϨH=Ч[PFsm@_4nwpjDRǶ~ך`ʡPM:.ƛCq|aH~8rq[rM HHwZq4rjּ V/y=ܵBnh 0D^hP6%=*o{_{&&.2Y3}}[SSr/o(cpœbbvc硺M~Y,İDSdJ h+URGd3A<ƦvCtWxVmoHίH5mz:өq[+ Y jﺻ.&|xg晙gY;kKBn?$NwN0w"*0{YJTe@'LCJi"f X UҔ-Y}UEVV'ہʂ)3IeiɓNZ4.gEU-k?ayAQ% Bo>$]~-8Q*y$zmB"ڶMϋ3D5 sBLQݣEB8am)q$tOl׵Y2!QD j3n.8%sPN%&7ʪ8XSbhK)5Zm7fnĔLYY:МnJ%rE56q|[3, ͈ƅC"7t4Eš(N~ƨ?'yuWκqgyAαϿ?v4(}-mMsbr )A&8)l BfV8_JosT⩑|-[3}Jj>QEэ2 {ިvNz9wՈ-?l,n<࿸;Y;?yWqedJRKuc 8uG8aW>Mc&VJ"ƗSU zwiu/QMSMpt0#N_. xOpލqB5\1fh_ű0e^*ULL/ֵ!Ch,[s)V<;`9f[K`iqO:m)sBmY%Rb^S0ce`9!'tsĸu&n p%!LN]GG T:~ Rhy 9odc1敥K9/3L ^nԕ^(k),~g!elcwna0fYKJ!Y <{3tEO&Ά+$eɃ.~b۸c æK4Zs*~YPVy p5#z 437 &r7:AyݡNȪ|}+,F!^p,C}`%b]'axM X$z-Q j y( T~oQ}<) tn."G- !O WBى޵265|-B _O~g ^Jx.`?3~D+gHP혂 goV8QnrPCAt7%z|I6wsv7E}m5<?ka0viK[xS]o@|ϯX]r ICԥ C[.953wgg%i%D^|3BVrEem8럽RDY'5xwPT9e*^GB ['t TrAv<_Q$9_{bة |RY 0 K BBJp!l]m$=S/iZҼ;=l6=^+)]9a'A\1g%4$&sPxrb7ZX! H, a%u/ (+. ~& 4+24 M`84Ŵ?Oa| nKH2ą ,A<F)1ȗ,*^ jIvDMҐZXn'z-7Cos!р~)iQZ -g_\g?^Qw/x7w8X,{/Z"Ŗd4{hq09f1J2q ŁvlY @FU:ÿf۟w -ҟֳ%1Da&OhDţԏo'95s *w5K/Ohg2uЎC{w>]ceW -%E=Pd#z:4_{:ںja錞tA/cNuSLY޵Mxao0W/[UiB1@ڰE+-Z: I.ɉCZ~=,VАȗȾ{؟&T#Mk E:pK 0RBƬ35u8;={*iZA wPi^VqKTdr>YR0H+l0kDƯ!2Ϩ ) g);xOΝ^{Bk=@92l,Lm:U_gpR+X.x*aƼҢ|xw[u*'^'pHN@$1geN3KB;Ko' % cxʹcB*[q~iQrLRy9) J%%)EJ\\2**TäRKj2:,Z@̪4 . HMJMIIMOK,w((JMˬpjRUpKQOS q,4xWnF}߯āc"}\%lm[RZd6('Q{˙3g 7W(OQ3gXspZkQ.@I8Tpكe0Ƿ1BNXͳ) H^gPVȀ-\EXڎaX d*GeE\\P{;;-EQ{6RBE3̅-(K}BZdGc,ۭ nd(""ci>%ѐ5#ؿ:݊$MБIm:\jxQ=vswk4!ȫC67'L`dwASXhd6O5fA̬ R{͖ nk~ڑ5㵍&h*AO ̹c*j{0ɲ+%&f\h#p֭ȬҼJY{\຦ +Dπ4wp?[{sߢn1jq!@1SbV*PX[$ bRSNɼtimeS#ӤjιE"!WMRlOW c*S]3 ~èFFQӫ6}iҡOkCJCBgSخu|~zw*ݮG*`7jwr2<<{!Y6s ϐ-}g ))ݾbL's* cߦu =tpsUfqy{KY\vѻAV\Z#W4}MZlG^BvB^mwѼy`8Sm.g MS'w1XYVCYU>׎{Y@[/1!Nd,D%|^cGֿ7S*EY͑("y 3[5盛 NF'LVTrat|~5^ {ۯV_|0??;|x  &옂J?;>u+K`wfkԯ 8 ':UzǟWA?H_̦'7A@0-]/DaDjs7~'SҩX B7|qQ-@C&Z!IVT!Gc}Z(g(Qi[n1/8Á],ZqKDS(8Qq$ yz]܅Yt3?qI4c\]ax H |:{{ҹgohtWgBQ]iIBf]h R}fHQt@_l*wRK%m:h x39cy\Liٳi *.utFu Oz쿗,eO*<}չ X#QB{6qb62( 8R2 ,$E(#>}u[x|͐feTt\ԫn5m,M",'w8Q6׫2$F$!N& Fe>"ZA8L<|E+) Es0"8s7৸x8y`2Mӛ%HF\"$'IϙOl1 &Q|31l$iDFw?ESF.Ò<T^T(6L_s8~^(Crp~9:^SJmT8.q-] wq*h)RY7.LPW`@- 99g>9tuyf!vSsMC'aފ\X6W ]\55p !} gsէ7W}z28ӹ7Ov@!6'77uIQaQ'&&q)aDC\< ( fqX[A.j!4UeUDA:? c6v?aq4WYk<_"/yԄAc}B_ѤIEv@*Q3CP8Qu i3x <;vQ= Nx(c7EfpHn EtOjh8GَWqIo N&=a":E-e؞]tC.]5~Ni >,nt<Q6e؟G  w@"‰@n6 [$C{tuAI?H~D @APŋe\Q{ѢQe@LOqidzx V6oP)_հm\L.:@w8YFz@)o(P@zsG]GEp3sJ \Tpxyv;- rM! wJJG)R".AxÆ^V" , HQe>z܉*p RX%yS"tTnȤt蠃" ) mknam($yF  u0 NPj JQ&4ҥߒu ȉĜ z*4匰3;R:Xp~bߩb.Y6 &;UZT n3:r'xqMs$qƚT ]@̌"«W]\'FߓJ}wYou랱5)=cpɺo\9>A{9wʤYIqt٬Hec1E&\PO)j5wY]wPһg'ݳUscHm7rDX) W-"AMs:>̀~]+8D)4[7 йMӑ6NyUDk\.R$\ H`K@Z2H__\[*=.eqVa1Es$ 1?\ $th4"YDʳ]+vEE3&́} }yfwĨ@Jm-}BP "e"{֘r"-FMŪ[Yistl&RmOԞNLN,$`9E7yjpmG;|[+e/ xCdBWF ┑BigF:U鏕+d)T/:r}r)ܜ P!Fk5RRe([>A p#IԈK%d 1JK>JGsc5 +H7'9p:N<.AlJm9X7{6vF2Al^߈u գ(|@XɠKUm0OI3`/oB{ t8MGݸp@!<880pKPYʑj4c*d~.Ta*Ld6c5BG@!Iv"4pC8.Vd*t4fQd.Z{i$&E.Y37\8ji^<_Ƅwl-qϧᒥH6sR2V*W)A0#N*K0BwvYO0id55$tCb$ωzRTK*QIV5"ɍ֫jpn njā{lDdٷM;y-JbA=ojU@k7t{@nT}IݥGHWWwjIBnX "O[js& "U">#o>1gz ;5/gM栟oE-lѬ23GwfB9=Jqcl(G>6+bgD1_' ]_2G85 !yZ({~\,~ٸ)oժl-kM'lDp> @P!EZ!̄W9*XC:IFZ582_x0ِ2GC>d4A@!FWD:AN憝$X} /K(d?5KHJX[=>Crn8ىmĂi /aY?fm}eΨ}!A.㕸"_.bGfB(8gvw(A)L%7@RFvKZ7㣓m>~p{rf'ζavr7?g RZgHGQ7Yun8ȖN:&O qVfZDInNM 6+BZٍ$R? kwLaOdB*` $6Ypk&N>RMB  iӔF.RQF"4jSV> y{Ljz38j ͎08] L &rnVFuqw*_^M#.Xü0cbIC.dp"90PP R [vJK^0ƽE:(H:{[aiɈgYk?) p^!_(\k"ItH51%3 b ЌK}GE=`й8۸VQVd(OZ;YUoHNbAeV7q 0lu* 2AM6q aV»LĉAl3V aNqf-2m1@QUCI5d90޿;=t$v#( 0 6?A3Bb] h*gX2R z@;<\wژTЄk$1PIp8x ~l.W𶯍)'|tH  Ƿ aO N$`Z\.b ᇊ--Зε dzΜ^5K>QJ{iBZ8Ƅ871ΜW9'gqYe N1nmdс+b2'~Z+Ǒt3MCkEwI쯖$G[U[$^W aC^_`oH`L )qtDtVP1&L? s9b[`.Bͣ0YG@lG-?8zȫǞY"nYL$:Pq:䤎cqe6AT_Ce9 LbedЅ68qٟƣ,ѯrOdkFi{ Em$!Š)[ f+gEAw=Z @'ț #tA~iA {DIe[9,9Tur %&%un> E-`p<*,k\(FhEu4dXCr Gx(y3qn2QV,3hy>JYgП`XKgHR4qAG 4aO(*aJ1/$(Z,RTC%?H M7O t;kfZ'vu~;\ ;'zq/✓\rCnW՛9:4oTeZ}kLm{i9 c5϶+~+8Ђaz^^w@SRAIlw xŃ0y R{Vq.,AǢ|Q%؆Qva ,h5] {*`03DLRq|\CAscawyvԯW$--ǵɍ'⅃v]\!$hbhSm9ڈ.@ $`T_BћӥV!43<ޮ !>$3J :j$Nƃ:iQFJfHE¥>8ۇ1Tq.}$mI8dڮ՞8I:] ld66jSQ~dI858-bzl(\Y|%N0N4ߛkE!| \\jBwC'I*BrX~aR Ye6%5[(FФXd3i9Be!2 xb]:G:0/ϔGM i3";mޚFbC('r*<T(N(eA_'vi2z_ |8{# x-* Iv[Y1>; 80Y"qcqb-Rp8:,}DYe\Rؿ('W "bADnsI\ ?&0e&ܹQ8DnP"Sߓ^ۇ ``'d RKyܜ+b*&1//`ULVrHH "r$ȺR g_%͏Q4GU*ˊǺpފ&;B0>YD*M cXL@]"̥pPdG+ZL*l+|F1۸vŊi)\ Ty7ëE _5L⬊ Zm"Yl5JaAq OKıiBY ކ#Zkyآ%JGd:@F <+?jjKZ3%XK_%Ǫ˖;|@ 䈦x^C+).l:O#₶@3@kt0Vl-0#b2թƀp~H]AYEg^"}B;;Udsk2or$egKF264a3^J$;2t[K٥ت_AЦ S#[Y#ȩXMp!Cyqm?/ȓRr9S1VPs!,y& 7>#l(roͫz۽\W6ΊExl+1w`ֶzÿ:[L4%dm+5u K HKE Nz-L$.LTxb[opTS8={ -!e xM`bJ 栚oGrHw7G$Pk_ \ O,;]p5: 6Լzjq_\ބ$M39S lPUOcIons:E [23\oH4ͽ55[:mf3{1:2/ޖ{ή@:2MZu5PB[P<`GE6YYv); Z f5Kt6WINb&L>B#iBjJ;m&TXv4"`W~uQ-dޔ#7r6!5E}tr ȍc-rpz5|yj # j}h,Lb{6,FT_je{PDc0p|}휹ޥmA\gq$ 6@&M)k$l-vϮ^ޯs^(HIU\0/EV- P t8r'm;yI0X!TOj2Щ[s%+n&N5 !'ŝPFv4+M,fdчt\;g}-lZtRthSm.(&R R.r1bu |C"G/aIurILsSL Q~1i̐+^~?BtK"8Wp\׿, Ndk(kY"~c l.mY0\KB_ `I|V;w- Z C&҆瘨( /I9Ǝu_PԀp)l|vKD0l%(~=q˂s ֑SL$Ԑ'qo2?w:9miDsY(Qd:%a^޲j)7Wg:θiYa^6)9*V [IוjrElk^xɈPڲu dG-p `/arp(웏W&;,j?ݕ`?d"1oaKad?#PiYk+iV jP}U20HKx`-`,0MF) Ja.f,*YzsmV* .; ;c:]YMfM-M}jOSyӜ5h] YW|[YpO0}ph H..I}i955B|+NNfWؽE[t> AR&eɩ_Srojyk• _BICc.z8rc!n֋誺!kUuDzVG ;oFADV#<55*Ė@۪CBz '\_(B^tecQ_SGE'r=g"PjMTō: 뮪14(Xk\oB= #{ 3shݷ7{G3zW.jr4OX OitHo 1O6c&X޵/FqUՠ⧭T듏GԶD;\taNۃc >Ķ͂kL6|<р~M![ SSlYL5,P"2&ˤ4*%[ ].N7i5T!7,,yf$EeRZ+=h ?~VSr7Gh^}iߥ=rA+ jt:=N*ȸ L߰{",_jO~ݲXT]/;;Y$4ol~_#mKn[G!v cdM:olٶnx휵_I߀Aw_pS7)۟wߟ䋫)韱3mp,?c5[^~]wmtD4*~R$V̛wJ\2L£33a10 f^j@c0M /^Ϣ0SȜv½O䳧7SP3@y嚡gMoi6l9آ5:YqjW)y_Qw æhԽHyehtdN&#q:d D6Kr~7M'5ա XmLuM1\598~/IrM콲sܶoT;۪cT6-hX7IAE 5< ca餉HRhӒ%m:^rY^>-V)Q2{="SS/(f,kr%\3ӷ!/5 5K\o=m:Z7D#7vt짤`{.j(GnC.K>ia}Hxl=LetjZWy85˞MVj\T9 Bdv- TXeKe5þ`^fxƎI.J ۻ&L6❊'֧ 1pMsRNs%)qژ2M`e5HTd;0J41`+vQr 4yRk;X?SFH [2#1 %y72\et_]\UȖEKy4V_!f`:sqqJ.KeYN@xGޟtiZcn((ʦD'R22)>ĉ7vu1U_EWeդ2l ͤg ۩a0 ֺ% BtKAPf(Yf;]ڑL;CΞ펣,4zM^y1[*o'Ly6Mܝ"r5K7 +wcW@yy9tr(>օJM@.Z>Cyz]yQ}i1%UrְYbr3ǒEE&4~HҤ+]ScYftQp9x:3rc7Fӈ_ԞC-- r8` -B3xoY:WaY׿&f6sv;rkQu8d;,鵲p2{$ "xv9`]b_Pٯ[=IlB$PKQs] 1*D4ukT[S9s m3(ةp\mL{bkL M83LzIN[Ad&*Gh_4>5tZ#lugju+P ~$NcL?#l2? =G9O[,f@aԱjI*{ #ꃄ$E@I`=z^3Pe>=.0E\ZQr Pv(9]7Ka!1sIQFw&$b%uϧhTJ6.pXoEV6C#o,ӚZ{h,(Am\NZ1T32Kc [b2N.u -h9;}$cZ@ aݠf/[R0DEٲ5gұa:bAӒ)LPJ caF{=(˚q&fz&:)uv.M}x)1Af{A5Աۣv;+7}w,7e+D9*7D>ʻ, šTUJG*ĈEݓG/$bkATWm'KyɿKIAأ Cifa,_>9?n ܑAj/$A&&E(& Bmf#U@ GzlOM%>6~R"] kg7g @;񛯉u5PҖQ^\;6S[ZP#2߂@Lx1xD4_4u`mW^q"~uջ"V]$ax1̗*jTx.K`cP&rJ"@θ`( O=(7gBЂɵĺ7Nj|x@(폀Ъ@aUm_.Є%-(׀Љ[EH#enͅYdΩ4[4(.V'QTTh8GpcZg%ݨEzVAtv;?ȳ.Pɡ26 ilFٜ:uwͰ+:YvMnr{)df[o (cK;eJ Gc/:e F[H+*#1l39eRleWr];ڝ _󛌭$*Ǻh QSZQCXZ]pRccE5Ǹrⵘz'iw+v; KPӌű$9PZnIA"V}1G ]yTJܩ/@4 :`ARh[u>w(ODm+@(Uuk ӫʣ4YGs궹GA q?Khg-[:=-@<^Pm!. 㠘ZrYqx8';frF^(xK!ߗ9+=֮ u8bmF"IB ٷgCYߑve%_V^qk 6{ 9rʋ96gK2 6t|/Fy״D,) C) QAOVS̹'1)"ng&&K- -^Ae,6Wp mɰ v@Rnj*"BV+F4n߁rqIuYAE 0LI:C8璁 'nX$lGF~/ <gMNnhrNzݫˎI2G45WcGxR~^}Ƕ5/,DŽARܐ4F+8^D~q%0 A#,|}pN;nK|0'OqUcM?hϿ?0P3St,TO~eUko󧏃/%| _33C40XiFJqA*Vq܆@i.KKScywR3bc +HPH?{s͗8*Hi h퍨9b'd#Oȶp]sQxb`8- VV Pc%'3I9]赾 c3mb _7X8*S6G(߻ M30{E UZBzl]vl1ny|j=u`W/ڹڑxY?!?z)m62?5M>w7@vޱmԉ-`o"|:z>^v.׆Uo΃|ș%!+ȰI_~%暰-JU H}U9PךmSFSnmm2FcI̍뫃` ?AW,y_e=Cv5& BYV0zizcu1Lc^G m`;y;FW՛=DŽm7'ezxWgWEj6L_I mU(CE*՘Jc 8"ݎM$K|s@ ˚Ny$ޕB_ 0|`A|_,plʶ:ICH 9. \}GoU8Â3un1=&< 3YZSNsUC ˆrnQXʅ14-C2y~_Np"YT/|ӏGb2ʻK>AZnH}( PLjsm %kʓ@5QŠZ )|&NZzwwIlY|1z9?$?*VCv'+6vj @dEG_Z{B׽7:s_S6I p0[)l#wwW SǀcF0+sT86:|1 al>ef%|K&`Pvl[`K#hޡQ&˵Jm/BJPSzgp@bg*+7v  揯^w:G=IO<+!Z͝1E(1: [ {Q®Y/OG+F`ܚ-ҭ%_NrAKK^ 4i\z<_]=2pNvyIܹr |1mH=UeڙuG*u•ЦAܖ YǕ{[2Su9YɏKwI@h$/$oAH{ɶy= K/BiIuM|Oiuة>ӗE"?{Egcҧ_>,`{eT3˗ ,XpkSW!%+)VղtNtS 52tVH2$(8<>%[ R}DA ?PBvvE>BvFɁu~@_ɫCwhS׀h@W֑z٩Šqs@ }ȢMϨ|Q؋#ɪҲ @;ZO\)4W!Yl3q;ֵ5`\e,Bc/tTYLBz*=mQd4p{7 b M#$ˏG>uz'*e9yj f'W+5Wd͡\IH̳5Hu󆋢V_+`>ߝώ~L}\)%R+E44XR۫U/W/?dh_0ήS+v\'Q_>_x?ovx^ .HqBYff^UKV5 G+ YC4ɋ[/$!~M (ňޣ\cI7>tc M2:)t$r8wk7*X`|?w] q5_KVGk@#9 !AsT"x0vK4VuXhũ_o[WmG׎YZ`{tob?PpSxgo`'D▇g|ל'P&푀˾VxtL9Ը9U3(PGz-u? μ}ѸM]ÍD8ۃZ "AX#+"oݳx 45 K1۵= 誠qA 5*QͶdgJ-,zvONQ>QVL5$4F?>&N]i]XOߐ N©kdsb ݰPfa/Ԗ0);6v æ#2/+YkPU*?$b3C6a"δ)q/ CR +COF<8dI-ĥSa;`r-0'#( 2%%OWq2WDc]z1[b 2KQrq_*+!&C -D*e(VhtsI}9T)Vwǒ 3d=uuW3'J*Az00=*EtR6FU:Z.{,[0;Iؕ^B$5q5Mnj_@<\TbĶ]l eljM?5ZY@Li} &6T̡P hL஑@2k\٨9hZc$%3Y.e[4$tH$8$#mR]}A l((tdq"A1KX]Y'ȵ mC@l,%Up(򵤲`n lZl4CU<*Y8q/I{\P{Nss/᳡^In LsxL$|@9FO476`O/ *8@컧U=CHuW?uS/qp؀J:£gG78ʭuc2TA`.y5]Q/zWKI~U(7%(m+o@ \ )d AUN^J_Q8{QB?ǔ=Cv1 ClMotٛ\J6w'8ap*G(御LKXoZD29vsg§8f.b*)WAAƈ =VE /`pD  l6ǿ= ;{"6u?B)Z=usN6j9oj"Ѧ;N e$\-<Y)"*YsѱmU}_!jUIڳϧhw E{ԞXc  .W$zOr?\6-dac.8!cpJ8lg) v?r6o$l+lI>"t)ypa!/#_ǎX! 6w;TN9m^I?zJ\y޲$T{7VmmX !q%iHFM  %FVaRt)X\L8:ª8⩿b~ßn)ViVf< t4n_aU@0 8u%[&26#&* h5uwRωC̩E̥uELh?=aĦէnqvONJrr:3;nF 䠓Khǜ/*'mKAESH7RaM(Y˔DW kBkkl!{hPmU;1E!8GT"H|۳z\@'8M3AX:hٍqY+d>Sp^v+aLqR\^.x9j19,eL}?ɼ&[m ?cKcf"WmcAM&7c e RjbmoOGIsqr $- OkqN)/D(G,9H|<вYUVl%72|qUb?wj]+J s'-mS3]g:Lӭ9)"v5Iv*mNZ2 6y"+Ż;d'Bı CU,U&}$R:e.Ts0Nք  <բ;'ERߗޅQDs4^| uP5$Ct K𙤹x] iN2x)l1'*a.>bβ5ԑ4\P@BE+p.!M9Ibb(D &3εX%/$zZj`=y8Lki2¤.SCMRnMcj)>T]+iNݳ7}逋I.nlƎ|8-bnU0Z,qə]+ĊTM nG\}Mm_M. ZHkf⼦-BZ-Ź *,e*X鸆{^3T ; sQt01a.cR{mZ ((9lkǸ[s/:?{ <ӿ_σV0fV+vFNwmTFi:ٺO O}tTD< J*Ȧɛ< I'% Z?<:4m -GC(6bš7k~{D$W~q׆5UL@[ T#SW9LnRx݃.a+q_SbJ.(rcJiBs^F*ۢ@74*y>MB8ɤ׽`H+S`JznYmiVb<2wl@oÿq󆍾44FfQɧa> v?졔vgyq+Y8bYlHgnp8vjGL\C^-G}{fh+' Xiȇ7h}g@,2~|!S)R0J%B1僠A1Fy|mh< ˍ3j22"6}i)xlLo};3ED9}s2/>V"Ђ9Zsz#+[_f63|>b9;ep'IA yaz+NI%0OĽ)$31Ali3Hdk v{\/Ux SЈg,.rp[WYf00-qz92Sh>JSn$lNiÌ'ڗ3ժ-<^ zċbNMjGA,*o7Y,%_r %GvO\rDŽ4R S Ld&+(, )ix3~W42m.M &| L/%0k4IK}MMj+y-^b. bcR'RJV=[T%TZn6-7K鷹͵W_#O}ޭqdXݮܬ~:I2džHPiY‡xy 3XnC?#aJj1nYl(Cxbo3P&{nW I_P[fej TA>./\82J|> bUcZϘ|C9OI;[/J"`\,,OnkvhA҉_Wp35F嗲t{S,gɖPfϱ.miM(Os> ŗY/7$Y.ʁSm> Z@,UD֟@ ~ 7"M9tb+0yPq+M6]z_?Al\|}ƙV[8`nD͕g4-SFe҈cKgj4we "bG7sX.E皍cж䡗|'i(,65,fZM١ݛdUG^i9Y9&aTL51J`s:4)/l$|Э Aʬzp^Ҷ&l(WKU'f X&l..3+YEk8 G֋qۜ$*5gYsXڱfs)^gO[ُ^߳,*UD)El)Ā7dNgK284;[4jx ւ]o%y`M#mXêtwN`cq(zQvAn. Yj@jЕK{aɮi uNxSތ S%[Cj Ju3 Ћ$xxb+=-0nbnYޕ RZI[<[߸_umMj]\C!xT;9+YCv1.8z|`KqKe\ٻeGVHDgjj|TinS.8:朐"L<6Sw=-?N@ē9_m4(tfLwYH夔#N<܄~|%ҏɎd9} 7ĩNd`6ͱx,MT`KM*'|5|g*Kۥ\y0(c{s%JN;轹ht-T hxN1Ǫz*n)&1imU *t׋!#JeZ,QsF|,*ȗ@ʐ}SاW$g_{]T i8 ],<spMMnux\* G8zzVSۚ!Z6{6$2ޖ>BQٚF tڔ/:vmdNyIAVGaKаRVۋ"}Hrf~#m_zQv}tPhS;k`Uްd؞XthP:`SBie12ZZ]Y\3(ra @O{GwΊG5˗1%W{ӠFDdr7d'KP\lmmPoK6PT«<ȬrS}WoX V)"Z,aUɩpyiM`@3({I7^)xHUEwMZAO"M1gM?=b 64hrI2=K^X'g"l XSgx撍V9FJ B=Ie7ğܜWo*tA} {}W>Szj`fukq'4I8Nޚn1oeG+$DBc.5:uN#Do31=wW*#6 {,(l_ԬQApnw[8ŧ9U:!h&[9rB728oTh6`H"zJ3ДSbrߚP;a;/5ibBwv@Bcl<烵LyLO5ے2p2Gg%Xa6 fŹN+pzݳΑԐuW9.\ DPO."6bwn?׮p20.t5}5 jݗUǰoРilBi"᭚rY:xE #l/veHx, KNFd΁d pGaˌ |ݚ [~%r[ c<4.bK cd˿%69 Lp$cqp]'?2IR+d{ iS_tpʦݲ{Li1G$n GsZV>ynB:& (?y +a/A ( XHFrN;'gKaO{KqR =c!a=ǽ˓_Wn?VջO|˳Kv[-p*sW&Y 0u&(.ᡕV{BRc´@^ ^DAZLGd׎`ɴjDחgQlʰ*)j)Jz8@R򛻟 50,J蘓Ig *C-p(s}-GxNQ/*<[CD6`>R!y6PIZQwDVxL%j -γt~sP|Lr^k@BA}qRس鶼RkCT(D+GnX Pu>3k2iV[GZA%Lћ Lr2CB./NlS>$N8,]'yPeE>Vsr 1ghJR]d)$7p"M$kČ0 @MA_ %q0B+`&m8FpĮ( )TRwY(ܯj qAfK9m8贏F$ gU~L[sGb;uZb{5"n\L C kېr?6$Br. $c >%#'Cd)GKhM]^p;p;M%eeˇjvFm:c'0Mw %j/FrGK~!+"(Ϥ0rn F ӧe "p(#< ݊-N=aQd1c(hi/70 x+M%)Vm+ 8Ǝf?q,ȜkYk8WPBiO?ʢ%U+#a5awTu%Ra]Lq>D.^#KL )864\!5j\s9l&7<:9ː|*Rޞ\C$Zd8b&/P5nx,nP;zQN5d㏟+ݗ/1蜞P cE[!X+rk$ !h\cbju">7&p%DjZ# hlQzg7e "hZh I 쮺zz+wcgp߇j9T;zжG(mBlkڰi@ߔkRg=7&$M7pViiR̨U3`%qoiƬ'ƒN]>*`T,[[lα(͘R}_['z_=;t{nfSW3>#HR2AK7&Y˱*IvK' iX5 oS-U~^"mhݾj}?|}C VWwl[>ⰺ|oyS, +Q,fS)p]N@}WHARՇ R RFR$nRmL =s۵cPZ[E^̙a(+uF:&쭥v!@G::/wێ9DTs=lzVȔ}ypowcZT(Q g~蕱|ro$їEG43A/H|h,ur1>XgZPrtaK.JڎA C4b{|@nėf&yPg |zk2W aW?1ӱ:h${OϬ0Ob=8IyeOƲ+cd(hknܺK]ECi * ی嗖mG4Ċ-70X =U;Y9Ic hApSE].c"GO%8EJUҶaίSaXս\~؋cԕ0x`ceBV{-|rmgs6#T"%̨gxEqNGO2XY! }+jҍ*:ےgB(,Oj'Ӱ~yX,2hkQL)yd]M!; ,S?[FB[lrUȚtdn¼*wYV}[Q17Rk8:2Z-)]˨OYkYZi̩e+iޯ 퇛s`7 #z$E[W.<<'i\ʱ 󔒬ooj2]ldLss'ZmtgTu9-TM~--;G ;2-R*|?r_nM +$4ȇZ'g?pfϫ#l&RT>6^̽2O"i*ūRf06f+spWo; SLbx[ioƖ_QXKIcAvw7I JdIHz>ZHʲlɲĺu.ngbr*1g:SxdTJ|Զ>:Zi+R4*j;+ +t^f~PH, 7(+S "Va}RЅBUHMMوOϓU]tl& NL3#Y78gjrt.0jWR1W,, e4ֲV'vh(X $o[*+:i4/{d&Y|u2!0/LsY2YLLNlܨjSLaIj|i^$%eISAuTkފ܂fkuTeZ҂*z$Ƭ_wuO dB&NЬdG !a]j-3߳']ӏY.K62\pY+ZlꕬȹQN={&9YL-dmrʇD=|#~aFj$?l8&+Y<JR%z,n! ^'yGXoYf:aY@`7*nI! CJ&+ "$nmƮRYzs7454Y5 b!nu%H)a;S!u"MIK"g;$&m?9W;MvٸWo@8QUÀBcKL~c좙a8aWN>d8yXΓG=[<<W*?/Ucȡk"aɩ)zIG?n%v'lLU=cK* $۔}tW L:.dEL!sžxR({& h8YȊQAg\.IH DsdLv)H-Ցw|MŃpJQ'sHl<|){O8i?BT w)?KFy^, Sw+g阪[ tpװ8{tئao#D"k@>t@>'w> ÀxI hgwBc (@g9k3faO:N8}kf!Sx+" (9ġb׺:a Mɡ8Zz{X{n!Kp@_sq|Mo%u4RMQGqڅ!OVRndR=JS8Ms5CK ʀhkROFs[xZXH70iMes# 2W߮@m@}RCON /W91`c6!4Jʷn˦vPM4ud]WUfl|n.Έiې:C aG=ۻ lT}' DЎZZ(CyZN1yKk) u[%x,G GSv5"JSb3؂f%6 bQ?ng?t4|&,keǵX uI*AtJ-%A?I9OMB{`rC=Ar.kV)@009S(8y㱣偷t_ܺ3͠ű>.q||<4^tE=A7Rt%>|d/= 2峨+1![djZqEh(v#v?q87"QWCNF ݲ6 B`F]rπbm:R.oڐTGU+f]n0;mG 1YZlD;K7P^3{ g#AzJ9YUJDddp&*G\:Md2 W!jehݴ縎Sp˶>'Z AHPGNlkzݖn7XӃ.ZsW*QR^fֈNdE?GKGxjWW_ H]x~z91!uɌJ)krLz}z t\J x͐S?Q t)}q/ N~awhdbPCR 1L29_ w^\ !7X ʑLqьp-Upb>uxj}`-C}a %\gq4=Zeڸ_`c@hDwTL%.cj}9j&=? lG nnӮ]ݕŸ71}95/lK$9BDAYJxߺzZz@Tjv#ʲdF!T5ΩFǕP1!<{|~C*pIHL4Nt(rޕaj6xAZC YbEgEZV-$aSU M+Q J'I&iV`P*[C]0ێ(\3a>pvX+eyOzp}Ydl۰W͂9:Wunz*K?xUٷ g _\ ZV[?t-{H'fn٠-"$"Z$]uMǨߦ8&ӌv4-ecZCh 1.=gs@7 s Jk t,]tz1QöslЋxj-tCS2<;ƾMt{gB:I2-×,*t,'|izLs,:7`uYc@6!.䋓 u#ʪioPOjVg7 (J ͖HCn}l-~g'ajTr~\|U|`lۚjnWߟ?ݾ=ffio֑R!읿%5Yg.~7q+T&:}K _sHO<ީT-r/{ N6Gsv013Dק^^>;/_ݰtHfMCl*W'-)>qBcSrGݐز;=h9sgyC1{S)P3?~ ✹_rG]4{1YIqv{=BU,~pگM=)hZ$F\Kw!mČgt\׳P]8RG%JL<lk;bc8I*j7fƵNkEU<>)> x\]sF}T٤BR8jfeKN|c*ɎJ 0$Ho`b)[w_ 棧J]eBWjN(YE{?&-}ut4:J3+xMj_8=:!{>+uBs'}UčUE|4O2AC?YZcuI|qO]uѦ9 =RFQZbm'`TDhhQUB `IE]SeJ4jZ ITQok"dj o]Yf/jfg>i7&=/L_L]ɤYʴ~*Tg4!|_4yUT}4fV:y%kOxk5Ћ ?> 8֜HԊb~W8KgbBWyEjb(7Jk,#UK3!$[N;Xui4f6W./"mtg^iF:Z]wCt))r+ĺ҆{OHL'4q!<']:oߺ(2&U@P1ˢl2JU!E6*n*}:ދZW9i%z,鈵QA {̒vT%z5YG뮯'dcv?d2)3SSFDԴ)EOnG{m|WEBtYѶ'UX`FN}[zwjSc?S:#*v̂.[:E5q5UtafzȚ:%evYٶ,5L/-ny)0*2ReU4*mlzYzmxNՇ%,Q:*Y4)ze#5LĆE/( ڳJ/ ɠBX+=' .m"eN/$~ku?_o($^BHg]״i3xv29A&2?`j]bdHy= a|^A bBxlEݭ(XFhM7`6-ze58o i҇ETq$ԡEWsR%|Ue;64!qy qHZS@$ utz"s|sߘ x0 1nF#lr"Q쯞ߨ%-ez2x*nhQ{ ֶT-_b_zHu>a Tq]FoAC'ƾF t\/z ь3=o)UȮ^Rc,4y2U@$۱jO1+9QfC 8.dyhZYۮP[SL06LĶ6`"W)y, 2<2Z`CxͬhqB6pjCӊƲi W}R@ؚEŷw~X:ȋ|q EWbx Ӷ#8* Ք$ ),^oW›nЬN}N=>d>rQD1<;NkXW'o?["oNhyp7U\,yN1+I*3ȌlmbKᡗYa9!WJ@RsEm~Me/:DԜAb8gMy!ЎZ>Bń_*Nu_24M QlAy5#.K|~!h I\Wu7ڝZ?v >c`C]E&GzLJizncA$-}f4l %ti3OCSEO]<͵s,`U8bm%]3~x0 )Sy[5cq+صh F@Ww0`Gd9@YP0@L<9<ă~Cn EI"6fJ>k@ mK#*Pg? <Ãæ-*\@ ORQ)%#%Z1,n!JW @EՅEv:)$Ӥ5 ;ZaЛ5@UO]?yP3m~,C^iU^N2Axi}Oo^mXzK> cR䔰5_*?WcgcX&P*ʣ/Ǹ\IG֊0dgkq1 tBȦlç6hSԆA)5م1҂#j<1Ma& mYr<=0+6 "2ޯlx\Zf4F->t1q vڑ%`(* ;C&xNP/2Fβᏸ@lK(zƆYV/C` \5paڰʀ^ljOn' aKA r2k4ἋAoy 6@«:oaɃ6|Z.kqnZLM:7#p2H; RUܠֆ Ɯtp*# 4[ǶR<:M`ychj`j%0qT$?>;cST t+yQ~sZlC;Hۦǒճ@$ޅX5?\WmTi[3k?Dhޛ*BS3n95)[qvSy*Bu[Y%58 :m%)"-cB'ӼEfp.9żN,o$<%Dt]oqrɚKr9A:"i٧\7A+ ^aodC:cZo"a&W &k$\]FYs,H"wYH hEL 4fjeecߒ6coCnN|ʒsƘ,띺vHֲECz V3n9ؙKrEc yF:n4zeq!h(0ҕv 7b62O- .#2Hzₕ*όĽ*@AN<e9FX2NH00n,,&9"ۼ<쎕>8 :^~_Fd!~`D&F h#5!e|CXFrI%Yi m#F/۷EC۵E>}5?D$MBNAͼYoR]Pt+B4c([gH% .i-};Xj"sz~|LB PdgHo;7#ιU\aΙ ^dMf[jۀ8pц=nk۶XR& {ZE@8Zyr)q+f)uԣb/\e>O@md({y ĆrGGB0ιu*hjm{EfwBAD }͸"n~f鬢J6)d}\,={4ѥ=ⳐطGdl:$hq3nFҳX#}}ӺSGü0[=D Lz}`kCHAoC$SR/3'_(ٵ|rɐ <ݳߵi ۮo~藈Jq{ |RJ)FzjrѐU.TkYJzިzlVy mQ/ܡ}!hfó. e.><' I,;A{l{La\Bfn "ۮD! |'6iUhsu- o;Ku~I+,}D PnK9FqUp\d<.};sQ m=E$'h?F 'i\uofhV!xK*,ӫYQ!gA¹%dal PQU9f/I _\ڋ̣;d! )2(ۻ-TH@0OcQ@oRA2Wan nj5&}&|L[pn9C+,MPH;c XIP<,C TNԲ$&zJmf3VO~[54fw [cEzn H 4}4Ci0{nVP۝ !'l'pp߷(A$[X+BQ#|RB4!IxJEN6E&(%>/U|滙D Ly [t%6JRX6WS fٛd^ty0}wn:|"߿[}ch8qTU=({ !^v1qUs{!" +;XfAUHB_o#Dq\%xty3ʢ&BBlðvp:.fkTš ~Ar4 ߼v夜!iԮ%|v7{AYzOU8$@yۢ )^J}h~zuE6P_1y>oYG V¢F5.cceSGWEnú &D\&2eMpp؉kSmpK$a[srvg/IcV)PZ\4Zjɕ6Vf!QGe8[dП-&$&p1Gn(\L3oͪ\ۮf%Qx&Rb#j*alI9J MtEEWC֣xJ[g38f3¹adΝ I{Py fcA ' 9Cj@iQnb6%go?~ժv誶xZ_-RoEi /ˬ%=$X2C:ے(c8H{ s,2=1eb"Ϭ~EYи Z9qmn/NR8."pX&{FH-KעYOv({梅ŏX|V8Wc &H^+e8EFnFljXD`9>3!A @^xCoƷc[6{@`L򱥑I"v;d/mLL[VNko%obfF0KȇUg_T1,X w( ESUt7qhX2`_M<% R5"J~.G&w r)}{:b}98ru y-ÕD"?B{iċq>N(qnldT1O&8XInCqdDhq6H gjN )"DRӽi[\HNs v-IBBN;&+Mҗ^dwZOrQ'owŽ&w-~c{;L=Y̺֫c!;G|DS߭a=s2GL+ b6ng1u"D~i-?KW)cnejۋ9۠{WaRZN_}/( .hQ_ad0T2*X:~صګgVň'H Ht) N{@fƒR{RYPoV _L^ Ud)nE%CAH 1b}#8́Ҫ!_, 4_DIxWob\Tξ H8U|\KiO]bwiY-ڿovIGTZEμy3fxBeIl05+& T[#i#Ô:-XRZ55)뫠/U֧$`|HhM-yۻJUmqS)مo%< 44>Z)ֺeQ 7rm+ 5$"NzOep:Lm~{W6Yx"AAVeX)Y7_m XSDU*x\y|YؽoDh'EɆhhDTyTΚVMJhaIMtv(f5^E)rݬTf6PGvcɪ ;YjQs;+|q~]ZӨEi<>Ε[-9M5{N= !~b,.#\[~>v)O%)FVTZu /g ˢch\jD,m+SiK4[Nx)ӠuZAz?.V?wA5u vn>~xbw1GJ]V^<ތ3q˘vmRK :.Ij (7*!yۇG%ίI9#ur ɳ8nd5fuVڳG\I7bk$>N 45S"X5LxApo+l ]J^ uDɆFvԯO^Y 5qb>˴x:bM|u=_8ܣťuܽ`B%4xKß3Fu%pLqbQ{0PБ >KI}_{oWxbVr" ?dx@nw>{hО&?\F'o0vb9:|g4Q {.6 ~49)EN;(nŧuw~?iE+ӎaFpTGt'Yv=J5bxE:6(Bmɜ]2_f (1,uБlV+gx[fl8rMpRrrngl¦x<xU]O0}ﯸ+Uim[&7cGCVMﻶ@s{U3 .F dJC%rv~%w5zGtYqF#c[ *#,,W#ЮU,TEp [La3V XdƗ ]1r0 BEWYRY()VcŅ0􀂀ah#cthpQraG\vˣ.8A)XfVκD´m0};;mN))兯xQ6\m-7%#Bv-%CAҚ?I( na㥕sB*wq[)DEw B.j9x%FQuҿɇ/pvqr9=NiMd-%# gwcW͊}B\Y"uUڢ#xO8V(AR:Hoe*UJ/]C%xQS0h$REp֩=;t{ie@3CF[z@/?]-sA@|?b>\[]b|nɴAWsNkV"M5M("ܙrg% k|xX[o8~ׯ ڇƁOI/h2."%""wIIv"$6}x߹xe^uWbSVZ⟇H=tbK~E@i+ *ʭEBֹ謲Bd=k駁n`է4u[q~T8ӓU7Z7*rn };W%'ҴtAɍ.KQ n.lM%\]?X-Af#/gTMk:WKͿB/EmX鵪O{Ag) SBfiY=g׿*nU F̖U,sӪ%~35O|{aV4BZ+z"];~!5>荶W[圃岫ཆ䣲P(#\atrщNTr X˲sVzil :V[D:  #\v=\!^4E9x'Qa8Հߝ$pR5pv}7\ӹÇhGѲGlS\[pTm|sqv9L^/v*[D%k'p:9% Y/;:9g&4d% #<' ¬Ue='dVT|#vTLùP IF_&i@$J$*:/ơ"Hsmŷ~ts@bUPnki{RtSZ-u"HpU )jYX:*:_z Ȗ 6a&gCy͕e5]xZUZ3+L}ŏ c\Pԍk0+9 8%ݮFmL-ŜȌu%"D4[f-%6`ZAHP ʗNj6vye¯Ȋ(@W7 +ondՔ4ynk4Z]j~P> DCPFQ^n}+_D.u]uB7|ԞH9Ҟ䫙%ķ|sIьʪrMu-prH5ō2 / Գ0O&Is/ڦ+JYEFm=zU,qAȌODS;|ODO,$:@'J;vO8X| nf CEZ+<4d$e`0]`W!g{S#32Ջܱ" ) \@ X*̥4$Pl/x!2j\(GɬǠ;f<s:Kpj3Wg>qْYϹ+JcIq[Xzk+0ٮiLۻxbdU4nzzz[;^)t?|Ќ5 \}%L6FNc'jEkB]ػu?Nh\ݩ b{~ JV3*\dT `lƒݴoRNitisxߟOg,KРwhш {Ʉ4;{9t`XsuFdݒa}za 1|hA*θaDdMh/\5,۷ =X#'_+lcG#4Y m.ɴ>8.x9UOaA]RT\Nh]% /Y^s!jԐT[kOaFea+D`_v'4nխ%ێaM-A騠lh>XXR6<3Bы Z DJ~]`Lz~uev ރh417L(O}[PAhQ&FpJmJ9/tpH"+2<}Zp_Q/BK0O, Lđ\~mN%QREXw%Ua][o2,+teNh7ka?в2RN>s ri-{_77e=+TK3i6\:<ܳ{-g ʭroAt:{MPkU Fқ3PA|s` ŷ|e,N9c8xm Q~(|B$޿M'S9J?t2HLqKgR`j.VsA++,ɏL\ =U"@G^=abCL$h P*!lYR‡ʿ6V4a^ ]buÚ5BU ww@? 3d%58>xW#cȞÙG 闳ח<}wϾ=gSzd e|-7"p" z(u]ad^uI 廿QB R?Ѭヹo'ld<[@xVr: +0EHJ:ʲ]Mww 8H ;zؒwnXAj4Y5%[7Hʘ (?vbxsqo_šw-(cS}^U֪+)_ݹfr@ߪ76ڥ'Hmu6d庵9He<ȌdU=VsZ*š:lJdixCEkߝ,ʏLQ5 !uf9S4Rpe VXTLeȿ4FӄFrĉVJB>jer jBdHPmį8 :G?GL 1VTGb?VSLOuCm.3xL_TԬ@FfxgIh>'V}[QI͏?\Ժtcre1`44<'|QPjBV18=^ `+_3ũ{&'Nrܤ.pfKyJzay})?>6`RTGdBijS[Z=^gyuv[QŸU)SxxsT!*YpTaH#(㩋sLIx:Wĝ OZfs~/c!mlj`CDt+iHX)#S/Yx߇rc5)DZrA񤾂GAtkF0_W+l1'&,2MoCH;J^"{3b搩w:!MKpnn9QX"}qƸ06\^4s9&6r1T_#@fh WԠq+6?[lMȦVxH.Ƹ63oCYxTM6W d]^$MѢIhR,(q,MM?E{@s9o̼ 't^(5$^6[Eg~W@AcR׀wu܍?7Hv-Ywx/4j[Lv-^!ypSMBfHnkt*/O7V/s kScPA_X#VF((;@{-> WO7PR 3jl =Q3v<~56[B(kZ#ՊCFGMd=d}"@gcASӆ?sk+I)g~G9R46v:*8Ff1q`l}y'H޺+sEJ Yz} c"V ^C|rֆ'΋s`F[xͳ%3!xE>8b*S}fjΝ)QnL[(z h#l2rzA$ޱ%LqW̢`MYט]ĩ c3Yk_>KIp1# F6I2L;D_Thwŏ߿HOH"SؖZΨmblU85O;/ %SJx OۓDvLzG3I /i.y[R+æSd*VNdOS'[hY5xUn8 +5b(r+6@Q2m -U1%mٞNA4H|O7+!贳.D#W& 5(j@VNrhnela%N8]:mLJa#{aI`*Qrz+ ) MBgh|`fpR :Ž uyAzy/ c-0\v2_{Jjkx#CmQm*u}Ӳ7q/c\34 -X{)#ŎS8HfeBnX B40 i"`IOKѧ9eGdu$(_ CPp\`,@#CEi%#8F *ݙ6萔!+輄Y_4)0ӯ kQz瓟]5ӊb"V4jƪXD lwh"SKnCe.)ldmEe~W|l5`h=!%-Zȅ '쮌s"7l4Ն"|go#IW.oC;1@EiGdkeKi F<+cfiȼܦ&x&YARWQ}~j:Gg@l%|owʔ!_'Yb./USlsFHB+|EUqk2h7sd{ao{<<MVx}Tj#1 +9l MS ] eY²4G1Ɩ3;LB{zz[Zrv42)ˉB,#Ŧ$)|RTDHb?Z_RrLJKYDnTp_B#_򖯎3[Hz3sxzY5rpC cd|R| 9һHCϓD@&:Ǒn,ٱo˶:"3chaڶn:㵸V{BA;zͷ,;-mf5rL?]l!+b϶/z"fM\6.֩=׏ i0 TK n֙1󝺠#x\e@TQ6: Jt !Jtww%1tw34 sw1׮W3k(ba@ \y9 P|zl q g(`BBQF4&rA oF(+t ܸ@}CH("vl$tr_3IO]a<#b8sqKIz.fyޅ希Ocbb~uP.s 7漼YAۏz I3ɝpb}GFf+C<Ι&2ǁӚֺ|s?"fbj;g4x=\ěSBlD_^A p8aO~4/ڍj>a +׽UЇvN :fٍ c=da8TSrxCkak9: WE'{:d ^%<āϐ!cy}58O Ad6(?67u ]x3[bp3zjK>m@wkzpϛhkU$@H tC؉Oz-%Z, r5g߆;= ,:,63fRU}z_=c~Q,k/ô`0qư6? 0OD.F@ǹsf/YT(&]|9A)*WOejedL.O^*${Ub9(Q" Ah#p=t~:氾VUP\&ƻumUH~f{xlAAy~Wx𱞒#omǾ=xy%bdV2pUN,C^2n/\fݺP_7u?`#yҋ&Z_GߠZÑA%S03I߳cp<IlXʃ#9է{ؖtn W=P'=|+V7V(u R44\qk8Oҋc,y H(8r2Z}FqCѕ:ћNU;T]Xz{x@wP'l6$ *ii7%Ck1!O/X[/k0 lcA\^'d"oa ΀'9 |!=M4|'ꕙ,7Gx|Bw_Vjw=#Z/fr*OTG $npg}ȩdv\}5lWzvuiYg}tJsv6h,Z=2H#'7dEMk|!9˥ ek/bEd'nݞ9O|_JTR;0z--\4&G.jjđ0qUǚ纅T"g=EsvOpx r%.#fXW ~IaR S0h((2gJx^)[ՍEV+~IDE#!ڽJD3?$hj3=k^͜ym'xBGVF#"/9… T8M’TYK,ӵ}wyT}v֍v^{ 5>YOOP;LT.7I1mf\1O[Nڮti*@kr R;lo*rK o;z ?`_rmizwmR`|^$u?a]3HakUYX? I^ZX봴j1R*G#4 |  4d䲺fQ܍{t拀dHޫx-&v Yuchu|{#n#7S@<דc> B>JvgrE0g*ހј=ۂX@#~g=*>`,^JH4}8T,](U^ h𠡥gL,䕨ߜ.9J-@MHff **(Pg|Bޮ LL)LSTLB!Mb$db|ϫgOKamjjHzܵiίE%r;7 RQ♂.q[[/'. 23yݝζ8_PDX;5`8~ӃO I^ɓ )z^EXl:~T++q(QsKYX,BPrX8޹޿ЛSgyNq46(%S(JZ)(U9:kEzJL2F"Z8Fdg>Kc?*f6q4:)֦t\HYwJcU\ VGOъGe Tg!tKh׹01১OpWC-]%L@KSeKV3Y+zNtOը]mhfd+k>,˨`N˰3bm!E%1eyVEM ø@~l~  w;j,M6!t2^_i5wd̨ARSCV/{A& |_vtu !xBJdbǡ-X?8- ^c X>'QQ-tnzOe(M:d@焠'tw@$Md y<&\W^| +ANOL4h0R1_|f׍O"T 4TL>vƏ }w-ktGUaN5_uU;omYs|l632@L:0SqK䉢*fޣ:qA';5?Ww#҅*O_F|!? '/ܯ"ƪWW6&=sX[jSj.;{$۳y v}=6Үyl <M!88?lo^?< Y_pgƤviߠv} $S"DI^`꩕iA u!٢1J"mWqB 2rvwW5BTol^ǧz~edA/OD0yeFcvccijb5Ycb*N/B6.uIfjJm'_+W($T!uDv!6 tk:ͧYKőa9q]H槨띡yU]O3*9q@%K%}t5ݬcwNoK*\dq)acDr;UЙk1wM 焸1NѪ.Vuڴm9.Ux>_2II%Yyه'`Yo+km㵶))cΨ޲3n:40jD 11(F#N;xdyD/P3jy݆B>uWnEx2'db_Io[~J[O+]M!կo҆OW6j,_&"Z"Ǽz$L75tԤy4g AT]!/?];jfG 0H>a-< ǖOYaw:1ҏ#1 A#}5]N'@  㳹ж<ܴ2Xb% +0[͆*l@#Wx}_g/_ v5d]sNL[fo)p@7>x-M</[?vJu~H> X5ŭCFc/kTJHۮϴQ9?_gۊ4}#Yp}6v-c*S|6 fp0l8:Pjjjc폏$8C([e0,b9tX p0J9 UNeakkÞSZ^O""#MFE[RD}~YM2~U@HJ9#Z8;矃S񴯈%Dh׉kx%C j>|._|) OW),_/v?uuuizrFs`k1.S%!KomQ]%ľ Rd|?mR'RF`IJ+_nDvkt/^A)LڢԔU*19 {\=ѮmFm;ɫz1gt v w-rjRgl7[bƚ,R4on!Ol&#^ Yp(HOV"R'K_vXgJ[2 $ ףb[)jZwIɱU{ɢQYϷjaw,l*G %}U-M2w$#/&(R=NA7ef0.s;~J#1:{^?.:¨>sX ;{s^?\;²<?~E˷"+)9]٩[lmkZn޵'\wJR-#dyn}b$I ;1b0Qua(8o첨 #1٨ӐKM*!#VI$' FnXھR'm2 |6o*9Oǀr|k3 |KrI/Ñw= |:M:Fy ;U?txԣ`2HDPa~n/@T N5=ޅtuurawx{ϩ04{ c®I9Wcn+aOPphMC^Lo3\HESC,!๻ sYe s'Y~8[Df4տ㎕ &("l3c*_VԾko-O.vMK!NG+6ߨ8{O&v"<0896{ؕ=bZ;b,8a[!RЦE@V>{:p? -Z 8ZŲJV}'(fLPg_ZF 3t'LlQ:T7YG՝#M9pCn!Exl;aM;Y=m  O2 v<Ksh0 /ܲ0%Ҵ!MQB/k7n^ ΖV X!G2$ft4sAYs` ;42vLkӴg 6dkkO I:C)=wF!l%i-5V^ݢ>NOZw:npIP0M+^xxG1P ˗i"^'σINvޜ-#]tT=g48yb6ciXy2*z.(H-"..}?0URͽ7;1~QSJ[_ŝ[@"/iL!“}Cٵ/<Ҟoq4LYKYd}zMt= m|@RVUFtcn8`_4cLρViphaA-{Pd^DěxEϒr}rfF>VQ_`t,.hOO+e KLL.U+وky%ܮт)cGzSet3gϺL(є /to_ (_:ps RqŒSZ%)LEy[{M+^ǻVˎ]ʹk(*jl:!LLLa&}1{?f Y0$յ}XQB L/emA9Iq.gQg蛏@;.D}s $p^b ?Ce%[h;}2j^AgF}YlP AT 11\XW&D";4dG)O1=-5:QOq:j4KW1U~Kby>I83Y8IoF=\VZ`vq9^GJ)Wdz ־7uKymnV>-܍1ؘ6`h^1|MV:$Û3/]C_0.>{!ȗl+-;S*y=.ppt57|E>8t( LgHwX:f: MẸX^϶H-(׽}rMtI8^ѰUʿ.t'{.S{g L.5-pE@oo6iov'^kOdo*m`v; ;ǂ}Rve…,-d_}ai"X"Erv)iZvaϛ~7Lybw>"\(AU)0r`|9&=Q fF}0kFA2sSsXR-7X)Z}.S0c澘;Q>8L >|hʥ7^ >=EEE|aQ<ܙ|M͝!*{C"($ JGg_๹_RPu^ !] BxEMJYS jWd^P0e5V <36 kuE >J:!F~tTTrxɩd<8ZnCz%'C!M A/y5*] OiqG$'wg&-IZ6.' Ϯ󟥘 5ODǜ禱ǻb7#Z-{L g#-tTbcl*]GƏҋ\ ˉ;JآJ/l*Bt}LlN |DxS>_Z#yo5J]*9NfJjj| XF/PHY;VdIY"S "{2:1hIT"B-|1 ľ zx9FJUc`SGdyRV0ݞ. L},2HC<1:Ќa!a5/{pz^멸|Xg߿ w6O>vIS9>91 1QL޿&b3sbbcmttCztoؖ,y&gܲ\`#B $ 3nXW=1qeRs\ O6g'*Ӊ|JWrwuY^N~{K\g3ZUMCZ]֛W-$N/-4k?bCH/.zwJ}ސ B^yBu-΅X X "$O.E~Um[4]> Y¼} Ǭ/ /Y:)i^;22z6Dn H)&r[[!)UB8L6 d̿9t&yaG N-r>&ZR4y|:`n[^_ġwe6(EֺTm~rL]]1|ԿݧU>r?B߬i{rju*fj6M:ڷ7kXFv؀o{gVpw\"T'/<'2,$\6²j>-:M$ϱ%}tipcz&jUqE>Ev>Ω}xcջh*<+yx2j k[sOYaBKFcQwt MuZI'&mՑIWovGm6K-XZ89Z"!SnCj vyKKˤ:qHč?s Dk8cWʧ\X&%RkhY .(*hgS*b#1?=hϔGop#5 |Aqj:{ kf)z7&%-=^@d4c(/K=: +& ڥ *WX[Fx&2#f0=|qk@?ݹ'Zh/1晩L{D{>GE?zb~2s.&AOݬ)Z}?\X%^_$Gҽ+s<^%2 qZ?)rc5g,OmT'r'C[bȘF~*Q"ve~jRGIioG[xuާsP$R7O)B|_ `!XюYPyxT8lhvOkV{-TuIo~aXU~QVϙ ؼϊ&K& HRxv rm&08fe3_ ٳ\;)Yj 0甐N+Z) HΫh|.J."̞Z i$5]wx,xH@NHyQ..^"쇕zp1&"lDW/;]J -t>/la'k(MXG|~6ބ4&'&e>mg$KPܑ:H7& #A ] )tX8_NgDn' +EwriMq +R4.QCm]%d)N1 9b[N!j;&Юh^1 d7hs e篓Ԩ^RGosӓ1~/ kfkW1ElV4iU"QF(Z]!-[8FrUB7fjCn w ˧gg32%!Q$œ_邌<tNy{z)w8;Z>͖ϓڢ[ $׻(r?rMLVPLV;{&ueZglH_=ܼK~Zs 3=nl]QC Eeo1cO&\";f :{`P^vxi&AˏzК# ;55q⏫Y7R9FK1xkҭq%,d=//Eb|,[GHϨF2%߿+r.o;r0']š"N2NO\X rNLK.`hj8:DcC%oxCv-|a F]gD7S0%_ET%Ϸ?7O򋦃@1+Ѽaqsxx\L{e >ZH[:[=W%'hc\X8L%{#:gdF\腣C~˺ߚ* ˙JCG`V jI(ld2do2ge t1cQ;FJgo](<QrgجSJ ~՗(I|n&,tnh.p0ǷVsTHvŢe>ӂ8wK 'tшI6 DPzCsװpHRd#ztrnwC^!Kj̪NB3,srfTBYpx6D'4O/dS-7ВSp_xJU%مժ% 1h\%Go"ȩP%餩>MSj~=l"W,}5l{[UqFӺЧ*\Pj yA/q3 Q-R:o[vx'4,D[Gn< n7ޛFCG?ɨwb1UċrE)r-Aog\{E O{MMrݻs$,LoFndA>g9(ܶ3ӓ>߹U4>߾ɤ'%|ؖڹQ~NYuG AQyw=vpZUDMq ,p@n_b`))+= A稙 J=c7[b" t:mA jQ!hQ`OsD @v7%Óig̙޷$@fF[SH!ب(:5GfJ!&5, ))0WoߞXG" #Hpe\!*RXGj[X2qR- C[rr?֙\y~6TF˦1RuΧ!J]j]vGJst{pEĜe78W) 9QQ8e2o"_7(THUS }"6Lowʒ}oy.4J; 'j-VG~};lZv#} `Z*Kl}ԳS4*9ڱ^Ξ*g3 Cn@~KV-p閲XY6H~'L_ߛLUHNa7&ph#2011;''k?L͟~H5wFKB?!/o_}_} vWQ\zȞkTn`) D}Oc?$Q%$$uoOVW=n!o ؙXʹB{6+k{5iY2ﰟ=9y4r7JZR}ah9T% ~W8ۭ/y?JS5"p(r AGs!26'>Y.NVs|y?>[ +/+Wh_Jc 'Ze=*"""01iEEcGlf}Ԭ]RXΏ ad?WW GWE~~cx^E+K\Tdsh$>*69!,(!LFLk!7QPQQI:N7H?Ku~#A0 j43`,t^&HǡMmI%E>!UȍnV<=hrǽM#_ꢢH_?d{Lk @bz(zܒmw+U![.5yip~m[&|;O[3l][9Dw12XrxS@7X{{t3;GU\!?N={uEm> H6Zkf~j&б^fpT}ءJW`.eAhϺ X.F כ{e <Ԁqɦ,KUudL/I\=45;@|{y&)ܜ׭m6֋d.5f"Ϩ38 =N\?Sjҟh߾nc.|.Ff?ot3lͺYE6p_QCH9S@#UZj-:S&&Zپ -C)E=Uej"J=q9[[RWݲ ;MyS_mf+97>&H{ȋ+Am>* %zmI*fW/inĒ뗪M e$ە=u] OGD= 5t]& N;%їY :HHc?zjj)*{;rIE,#tM *s/T#=_w }yYY ߽OIM8;;Zܟ.Tx>UuuqCҚ$2񇇶)ɄԚR`v/b)։f+0M6,E}bcat"{-njU]] 4Ծn$rx[ctS8RDu͇_V1||-dyN3?QCiJaϏ?~Վ&'k'7+[[) ۟\JR#N鮗P(hr4_Fe{I'rs9)hcb7C?=9ĶPhSoȌp(~씍)cJ\EZQ:o tHKw R !tHww4HIww) !%uw曹3{=Z>8yD@q\=t pX ) ֢pOF>O`07c)—.4ש3yz8XRJMKnvr*0K-m'6F[xhb "\lϞZ"kc1( "n`bI/Ē񓊺s,..wO8|wx}QWo526ohWɓ+'NeS-AGFE&]B+dvD ġ9+LS(3R 1aG%ANz!QL*}>cG?6){1#cuf)M+77鵟`ǜ.S͔F x ǽZuڐ *Xq(ڳu:GLژӓ$9YLtϞ}e3ME%%5qW :UnQw1Z"`qqWGVS eK2X' H漂TB! 桋;Ϟ9Os7UwGɦs+y RLnv$RKRffZgz,ww4t_JG:g5cSPPnޒY(!uM4z%Y^$[KR֧8rzö/qBxgoN='et<-~wsz! b .aQ s+Z\\uG> y>8btL) 6!e];$r4K_*gghdl]?I,p$9+&&|[[X|GqwG_f~y܉OL3M\p [$17"[576v%$gu_2lTׇCΞC^JII:\\`e`j|d{kkT| aûkLAI-y03x1jh}[nn^&뚻8/eķayl>o\'俌(x > r/{]qd31!~ jQt\jgXy@なm6fsD!u3ۼI%8 );tA2f}^ʼnIqS+l}܅^gV_xO]e0~Γ,V[ /..NW9ã %%h5|+-5s͎b?3:K$P+;kxM>nG'EQ{S>}Z2!V'}Ҡx{/ˠA3{A٢{ߩKS<3hxjIC d׶Ud.+{yXR8S~C4FS-dZ>FPno!O,Xg#&\4lwSb=yb:% W Zsx+RNKEiO"M|U FBD|/im_s@.& Ցf*wM0a\!hשּׂ_roovʻ-4 8Z=irq맟u0542ZKfb:]6_VEjttt5͟1-aT)# dyQGLTM>OB뭫uʮuC HeLbVG'LQe:@'ZW !>s>"l3CMH]ovHIV) qjNN1*HpppܴGw\ש@raC>{9)p_sbgSLV.V0I_Q?G-N$9'?s2p;)bK3KҐ1: "pNHO&KKUҪ_o-WsP^f+~Ӧ ~7,O$?,6ksdѯ$K&4H ٓ-j-G U5ߔauvVnD!Y .Ȥ*ij A`g!8q>ِݗFӨ !0aZ=U:sxM"2i>_]UK٩GoSUJjq]ldLTluK=1ZzcW$+aG@n~<j48U͛$EPP KO^(bʕLTYbcPYitTOp|D9"7 ǫamuSLp@7|8\ۻ?cu{8!Txsk3k"4lKLTlRA;+*vGϕ{,sJ:?Ĝ((7nu 8ASCt/<љ^.\ŗ;'66Z]ؚН&$PS]aW؃%7@UcwT=/ǽ7zuDUSM9B{oV&xnK O6G'//ȟƲ-,J3ҭ rd(ޏS4co u#,N6z'={[ZR[1p3ROZ īD ĺgBۧ;GFGS 㲃`jRG&]ݢدaɩW2E|&%h%J6WSɫF}{LA{! eXlQ0YXB(FV>63ߕ{aO[ڭ410123dDERT0>*FOJ. !"}3488Ǒ/PBn 'YyyEs"pʗ)Eg֏~U&{)^~3x<8Ԭ)T1B6= #͝bTi!(o+[1ICj8޾I2\?3U11"n6lyȨMЀ@>J j NfV3؟}?]]ݶԷLiZNNdQ #2 z|A9203o.ev$qa #K Ƀ Eem ȗ;iUF`@gdP Z@ϕLHa)0]rI&*"wܟ:ޑce9yZגCS׻8;:P(nNC^~!$Ϟ:]Rvz#-.c$EJ[Y ꦆ%'&xxxH+)}q*S-F.pOG_3Xr:zzB_H[A8 ]Pqy}a_[\ ^ %ˤqnƠ{ Q#lAHhe1=~y`Wb?$&מ[1QcgOt]oan"n_hчg'}49"jE /3;%Ewbi>sk3ch"xr`sl<]Y1,$zVxXrb.7GWT\Ldt2bxPQe4_~}{=m#?U.dr莄>G?˨t[h%7XR|z`v'ozf7Lo(mxBļUPdt?.sg](bgo]ou%9\jZS}N_z<] K=MEG t\81*HRPff~ѝ7e3ago4WWimYe?-uS@9xb_WCe>z315}֑sXczBGL)B*)9eh|U.uM B^9V}8PEXqp3 jMmLPrMj]XF}D$,헛6F1SlofX"55x 2Vc\f%GfO1gUXWt\ rliWϿw[wK>SY쎯o36ٱ؉n=U63 JpnH6fN##ʛkWN)e:LaN& O 6|n|_zOuIN9S̽^JLFPߟO%aeh2o ]N?_8k;!+'g6MHv xraX! BD 2*?J#t077;Zi?x,Rc>{wwDž0U&;Mʘ ^ R*KJҨ³_iDDZAo|d R}St9:}+[OxK.h k| -|d|`![؄#eN" ݹS .Vݜ2k`dd4D6lǏ'zz)\ 6ǙPU,"p+})X8!`sxxHC>6-R@Tg$|)R>ި+FSwc2\`hqz~6 lN; uaϱ;ҷ#CDj6V}o}%SL\, +_Sk999Cմb=e!*8|q`6iǟB-H 7g$NνY)f <뭩%7y}='O9uuu}~ޝT,g|Zн4/pgMgsM,㐐 R6LrYqbb{QHy$AfEhԷ84ۆ&'/_M ycٟaŃu|0").1n p^txQFNgjxIKrL6Pa)բNuyAf^: )ÔªK v|"Aa9j}DJMR~CfJn8Z) vkV*^[efI7ߖK_Ir< z|Km:==Mp.@{n^GBz*(`R"Q `TOl#={{!D"R^N̩ {ZSsd#Ƒ^68ԯ鸁OebۃmfS`xI8aU #8#;Zꏨ {,$bז|^R¬pȬ'#(j̩/ gl,!xoz2'H@Xɟ^l)Frswsdd֥G|p4Ɗw5M(u, .$&u_nb`,Y\|vPRAl S͝Z-&/V48,߂UI˗@Mlpu-8m^`@*,U{)G7%%5]q8M|D2Kb2ZjiӳW}u=V`ޤ]M%סjoS>YT.[LALMA2",nU s-9F2h&1XCg#Weo6W{kKX&uPzfp$MZD$5 UI~^"ٟUy4i44 ND*Ϭӱu$O—>κn}Q¼_PNN^nlnVENλe3qq錦ĄMGIrrz_Ax _㱝P^?9ql{ӷ{(=L28 BQg]tҊ ~YYZ8::202fur <N? n;w|ll |LTOO졙?%0S^^^Q^4踸nnnf #jjNm%7AxI3ee WȦDG6dZö4΃E:DŽGAA!gjm=\IGG{ʔOұS,. ;6I "HhR-=dPx}G2Y8/2TK" YS4'g4oR\ĎDʵyQeo]5rsmPq|n/zA,Rl00+krğ ?v'KRK$dTTKj}ͯVVL~d~Pr+$^;VdU04?>].ܲw߮zSuSSe_J,$>@̀ϱ_dML*/kZO <:9j?.xހ| a]?JIL -n^/?"h=ͭl?j ړ{>XXZqww_9Tq2)B.QSsc{;JLsk,)z3?O⥺:~<8F|H2~xDP^llG)'Mṹ2NNQޯu.x .,SHr<9.z{P8Yƍ3o₡+6!,O6 d윘NʯQS@qp.Ho\N 3v5,d{^W$!6'6-(ٚ:>nVaYfc ++n#T.NQn޴ۡ3:q6 (h\AӉ3Shx77eQ4} O/WfmLl vJG\/D@4D|!kjsN8??jJWV`#`A2[x g Jz8;]H[_+x-;qMʠH ;W2ݰ+Tއk1d*h۱!"+#{w_PW}_>.È[Il;Lnˡialw.{KPE\:!=tF<"d.wz p!E} )Uqn($"r5A'$xg .oJc;;L翌1$>66a_eH<0xjW2Z`]T2st^-v$K/8tPRFkX`>uR3Dz,ٶ؊`#}vO//z:[~>ȝc/7|l?=m`ugaxBBKSj6K8o9b|;Z/2zӑ90K I*,k+-fͨ$F7=,!\!hnO~雃~ruE$(Y_yՠ%Y𭦱##04,F i+S+ YGT4貙G&OJIL_#҉'bפ` o\i"bύ9Aa|K`!\"?}}%jH ⋈5EoG$tHn'rH%_V7g놆K5YhCdQg:{IZ ^3d4@!qoxp4@iB/jiwt[D;BVN33sL`(6-0ԙ1pcy{_oѓD`Dr_Ch>,!>S~W t/X"@hn^ybz?efnRߩB4Tg,**cl:{FFSagK[6m 7KZZqbSE< ɽl. 쀠&rAɉ2:iaj`n5])|Tm:Hf^~x(K BLߴ̨gfW,66s#뛙&wxa)hJ0WAM:uvn,¦  1J ˬFqo^!ސQLݟ/R4^FkeYB5;@9& Ւ=jm֣\P1:M1bGh"竓; HkO0:Rْ6Nw߿Y[AL8 srr܊edd+yM$F}34N6E2?tL5z!pT+Y*+ Y ;''2yDX>H[S7+H4#TH~T=L}P!AOZd򝷇48{z; \Z '*2$)#77*tA,|>wS):ۓ>>&vאfҢ#:IQ_DGG 1n+6fNQZ yutC8#ww'1G9Zd)!"HL2ǚj\E|S?6&%z =Gm֎ٴ@-k t/r+t[Q\8SfA-YǜS1i.+zsBܘRq˯S6 HXxˣh ܘâR~!@A7Aֽ2M/kʕÆr7MWC&kQi'b7&]EY4f~tlبN&W{vY #fkF !#pvnFI൐/}/hm ųUH=)֭Ny>z$ G5XcQk#˖WrYU1a,׀|X"XJ3C$RSzmPQ]]. .%#CQ ZiP*?4]i}=& y3L璣QT]ԦM)Y)Y%^lƦ[ܛsTNsm<6$Q_`ȅ8lGIqsaD.Qsd K.iڏ;tYdLl؂`QK(*y1A,_4'Y vlu,xtRxsaO`@61ޔ& &΀)U o㟞*dOт3 S?Hz>b γꣷݺ4: za 8V';X5Ys0*q:f51<[6x11!q%!!!i|_ !G2yէ7xnuܟH = ^j=EkiD0F&L=vΝ$&c)"2a7~bmL5e~;ӱ+y '2Y](DxгݧË՟XCZQYsG a!b5$8e B[I KjlޡQJg.G'E6"> ZƸa2AʬZ#e*;KQRB  )() <7r#SPPH|3P S-) N`+2 2a *0y{ҮS{)j~d--W̪Mm@Z1~~^Yǟ`E'(zjv-VV,X ,oQH^ U˳rʍ4j Ҡ5==#~8kv' #S_JݮBg 3^՟y2)))y_m _sd z"JniFd^RT.g=t<2ZD7:A?3տL/6Vc2f 1ÿnNc\*iF` 1VT$!s5=_X2.\>{}}T6ɓ͎+7w!eNM Ax Q@EEE*#e,i&d 1 u+2r2N:AQp9 ۺGrVa ]':Df8JF8HS1Z(:9[a(J+佀OLhݷ^s}X{1awanKWj }Ig!?H?B7 K!1c]i<,$BM-UhcՕ'%-j7aZZDoqog/677}YR~3.[my8o׼AHJGǴw|V,>PäA3욛iCKP8 N//2q/'! 26a>L~,"/f^'ЉJ?$HeSk06Ay[:X /쾖dqt^O|,oUlώz[ȆcW P˷+j_}9\>2h=%o cC|cÈSHeÏ,U%}rx׷ƴT=#~WGJ^k@V |5sk{ޱM &=ܜ<\(m]J1L60I/_S4Q4]ںDڗ<@!RffYYcc*aJU8K+@}Or 7ښ&2Z:qtϟDiXBbK#I]R` 쩾ˣAFABN,6!d0 \sw|d36Ր3<ړluRfS/e6>c'A"D> A~=ңK2ğQ$*^ /}Z~MVGgWA_Q"_I~iFjq888̼ ̤`r3jXL1 " n,Q()JƘI}D,($>؏QP/OC b」jv^Ai"Hi{RK'A)J ]zwADzI( Mz$Κ?Zfͬ~ݮNv}h0kڪ1:EymJɩd_GI_*a3*m;~e،#q7CۃKIo: ds6[YdpË#!)Wq:sՖ*AY/MGj*ᅵVh)-tD9D./;L^zU[@O$T`MѤQ>`WڅA^1%ۛ"E[ %ȅ5!҇ WCP]zK&dtIJfSEIǐհ4z3r~zN{w/ kQ&o-9Ę[5x6Xr$o (^kh.55}Pӧ"Utd]ܲ%-m?b >-vK|e^}QęTo%grLy>/CI)Y@B:I]~Ml~+_=*A18,WۥSu\mdH~0~y:ULZTk/˓6 3xM ྊ'..nؾ.//Ć`LFswHaDx!t|*CHp\ e1ZLPt1(Q@9z\6zodpmtsu}﶑)ޥD牣J^6|wQi "w`.J(& 8|hf;D\?ju^YEZ'>T*_?1R !Ľ 8_nL&iQԮ'1/L\FFtvvacYv Rb֢N@^T=Tk)lB68Ѹڸ&埧K ĕ?;Ŧ13am3ڗ <1@6(?{sb +W0|dAwNs-Mu+OE^/<3E3605` Wķ|< UJxb(qSQ ^m@} #"">]~qt`J/); yGѥ@ۑ$ԸXiXtBRv=GR?CC1)?^QkŇٶl飶ϲא|e PGJsՀy" w:n~_MTLL)6ZfFGq=Wk!_;(Y ,H\^,;dI\oK;24/eݜ;#,yA[6c{|b2A/!~璺]|vspR>V9S."R.lX:bu.UmvóTŴcokd!2}s`Q6!r)"S7)1^HFFE-i PD!^V+이1}{w念~"%~*TLON# ;/7sV'W"y@K TC:"t ֦$ {! GuDgFO灑蠝6Gkhym$oU瞨Wa?xOw ]C܌d9 xj8Lry:tphX.fٻguz~ixhiN}2- 福ӯΝ_sWm. |5_kUw~;['ardƦR== js="î1-ѿX "IZ.=0Ʉ' E3SzͰ =' @b  tz~.Q99vnEJ|'*-Y ~,ooiS,yUvp3DǬ?J:-I"s LqVfv{lʀgf,X,@Yk4o ޞ7 WGYE@>ZsU&p ACnDTW]pޚXb%ywuzѴX@pzw;i9/W>mj)Maujp?޳χvMsA\@;0 {Zmk`jW˫f;Qx~7[py=ٱ 5m۳c44č^v Ze8N;,^<%~;"ߋm'#\i?ilGR7 v. jUA8ɧXc/yr5ǁ? FNmJ)QQv \xsu .~&T&ZT.owB&=˺#"P_. ^hb_ @Xscst`_3AhҠYa{ nIrsg^DNMSGI>'JSw5.!پ57r[z7 }vYI=sJ3>hD10'ą9ɺ ,j`")mMUSXR)uGbv] z&b-YWFAohd/5S =DyXWU ܬE0)W}v~䧺|$B0M7^Z`4*dҶV  RPN-L}MXoT$X->7vI) iZ J/=5SbǘKH9z(U>'ӽwg[ķO R3o~s+643(2!3E_%zVX? pư}gpW?=FdZm Ǥ}{1Q#vQa?J4I@Ex =O2jm!EW &ujmmst9{ȋ_f6.8ޔ}Ac4PdډXr|WUzOY~Cxgo`hcႵ_ndYJ?ONj'oD}jQ#|KDvnʢuWH}#PId@5[dSl0i3` u %E<şs7,*&[=6cC0!]db76o m,f+gA ,5!(N?J SB3tQD\/k jYU˛*[#Xy.ˀ-V)w}ȮgJ yf?sSLs≠I@U>Vb@ L&q+…JVHW˛jUd.vs9ʬκaA3g0v9ᔭLkSw1\u47,}xO{jӟ')="aJL̗<4~M1`+G HK6*is)] pJ AD>6U:eșx [bwtvZ%0h?Ph a4}Nr5)jVKD'bm{l.y06690s;WR}->d0XODgj<}03\,xX iE(_ :tE+vY72 ,MYLu{1a:ܪu\#{8Wkrg5ňn0D=~qO,JL+YaYjq>Ėef?QYIvA1>3)bpixnWșr5ۮmFx~B5M: өJ.VXӮ wF d@m+tmUy#ܡ?^V}.06qvGJF­if& *7EiHe-"-x&J& hjhפc"vEtсX2W#C{S_sdĽ!m ̊HOs$u^5w(a^P$HqI㚗~rssn|yBOL]@hM>l>6L^*+B2x ٠8,.-w=¸kx{--o08MRuO?E8S_#Hϙ S/wA\R *%8f8.0R e ~ه9GLSBCcCU-k5*- "`i = NE]q`_G=. kjxQTCӵ*,T52Q jƦkXQ*. |]قdZqn G5 Hw-Bpi=aslVɿFLCSQ?leTR˺3#1,EPmm&n<>)|=.ڗaCXl ޻M lllL?<c~_܈!Ґ¯?4 Fb#%>R1 ,IiwRv~DLlO]J~WWsrbVuy9&O2r WٷJ-;{urz~3.x<p۲VWU} " _\dz[Wg|'w\'`wWoU- no%zTn ]?S1O!$n08pLMM?o}K&m} Uq7԰xI vo|Wg&rN|'5L,_.*4Kf4KxD7TۇҳRvfE5lMvb|.FƦM-wsjO5қʳV JGG,R|(4XUwvL<]iRe7l_ ?+īWʗYR9ҤN5|J# 0)SWq'.KeoSn!&/:w;( {WT($&P:Ҍ3n}uN%t;ZLfx}xv<ڪH]#5CKOH ďV6HLp"d6.)*9ӈQYO$rxZ= zD*#{>^YȨ-V'H ndѴ䰯^@wi %t\WH\141KnWڇgmM=Ug_l}w%{v8!l'2-SC?oD'8,(Ū. Yy"AJiFJ/;h5 Z+p7 ݵm I[hhkw 40Sz]{E. {*QXe|Fb/YI \U b؟\K!6-tz6zR{`>uE7+oLxg>up-,"U~݈=,5x+=g/XkWRod=T[xfw%Xd#&ګEBgbDsg:JfGPd WYAuf RꒈݽB7&$yPl}1)GJhjV@%F#&:r>c܏Q>B m"Ԫ v0p8])&@u-=(Zwx` N7H Dߑ  dJC eyB N rNM AKu<Ͽ]v"vD:Ncx)13"jſW!QK~9w=2EZF-; H@(c5Fkt>=BTb~6! 0*- 镮pژE ;0 #cL#0MgGK)9P7>7}+MQ9i9/a4iGK-,р -0i6&E 5lB [@I 9Tmt!5ͭ3hpn%< ( EQJ3`D@mw(I}N}=ѓ C@!8& p >TB79{vbI[6K<.C \z4pbC ǣiuE B@٧ xxS heD< Jq2 atE@dDv7S0Tuf8 S- +)p ծ)V` [~uQ2]>4V<,ȂwB Q"s~v){83/LA/!$Nzo /ˁ* u̧p!2 `Bazސ#@ 1,WsqjFä9^Ay K@+fx'gZͤí9 P4F 1TTP 34?x\6SQ|]2CK n:KKV;۵l FNis0^U|"1H#Γ7̸n,fߣM VHȌ5#s ջ(!$[1#.V<%EP){=& ^`XMA !h5-W{!#LP fȴC%yn2ie,y7F9ڥW.3'j@ʓܦ)b곚f4xѹ4]>iaYyr^E@Dt=>f@щ/qO( wߔ#7WشWӊo]qŁ M5Rhً%:`~S&nDŽ@, (WMS)^޴gį'Lud >qݍ۳6ifB Wlgɼ.ρMo&*, 7DZO –{(WG [N]|4 'E4`<0@OҔ&': E"{xPEz(䋩q!aɶ"qM7Om/7&s+*is'l43ya[K9キqF.l/q( JӶ>|owy߸FQ:<ukPE{r2s(HTsu1C5R7P)dhJ *5$@ f¤edR䥈񟟘~i;H Uf'hM͹ZkFFEᾩ\ B݉߃m>*Xa`YF?,]HGG|ObנXu9NEZڮ ցyV~0$vBA;yf77XguBjwϋ&&Uܖ_K=;0fF ~E!Abmz Ld{-Q@q}ҋr~(~rEBZ%`'w+#.t(Uvь'>@k*,-\)e0sUzr(E^+\4YfҸ;ՉHo% eW~=zܻj"2T+eK‚!Ev~~9%Xs=<2FIW$(6hnc2ꧣh@/L,^Ժ=UJf.-z_*]9 1eP?C{,[m5e[.H9 ݲ8m*Y$ c8pB˲~h29dc(ƠU%.=TGW'/[K|+Ze4E=17+߮]^eKfί~S  {#i5 nj]r5C6˹Xđ:Tvpao?c=1bQci=aG;}c剉 C8״ļx: >PۜT_Xbz {\H ViӐD8Nz{ΏjHjaR$Ȟ jBnF*E n0b".fO5z45`>m`"Isl3;O 1pO"5JL} ogzv.=iuOb R1wnIM!3o?撉k EåhI4+}>(yD%2FLԽ{t^yP˱öZEd_! o,qRR$Qr+ޭe/} K&=/ -芪J GSJ咊̱8Tj,VHLI$XN6z_L<=(3go7B&nط:_U7h:X:Y'sG zXT7W(~gȰ>-/L[^AA/iHK+!|߄XeKΚIȈMFh{(I_NIy ̀!1ć4Y9&ek :g!Jjq 'Q&+xW@3&Gg_3&'mE$̤s+*Y"h?/l!hz1E|\֘:S'(m ޺ %BZ`cRQ4vǾ`즉EFD6|(e9SCXok'qr}a],pc GL;v™s۽ykX ϡ S~e4wWt{Fo؈s>wt6!Uo> q+cE~$%״O%h<_kX*&y{^ !{͠Zy18|(=mʱօnv3cfq?:D_f+kM,2y8uP;S5fdVuXr.I&A5ja92j3;'1 .BsFjL玿A&ME̔:/N Ajc>q@Mk㾁'wS/EqB3dXMUk%PBZL>#iXi Nw~u$OX~+&Jӝ -oEK>.ĈӤ$Lxz3|c6HJ]Ne7ԁgY\ bbG x.x4YUǴ83n>ݴ I}ug*n5ģpMfGM}I'xb- | j5Y IOJ3DI ͩ <0ISwЪEfuꢬ +~1983 C8kP:dcݶ _q( toiU|>=zid~ڑBqbb Yll,5(e@cm>TqbcY2uɉ/ASRT2˶!T*v&$WƦA:IoV~m&=&<:AH8NjُhCBivXqL}XYs7t_xc85D 5wߊIxSOW*IӠ3^@Cg-!!!ZDfXImw5RP|WO> _(Z]n.gk%ثI71ʩ+ٜ[5tI_wffh99ze g8\&kr%&[s&+4wi?aKa* 2GR`wk:Ⲽ?7wpoE 1{S^C%a[cDDgs{q5-†u`)fpB*a5^4o>tyeYf&jk3%}tqD'.LWG|w&Y8 P+rJYu^9ڀ4槝q9(5l<`rI5 /3r:y7b5q\aE5$Nvn;%)VLIΟ3ʈP'UFB#ZK^屛VIWЛ]*kNޯʹaw+'mtS~*C#L.=OZv] OyӶaF2yNGeE|K v~VxK`3$siԲ.\=:o[Aߟ} &549.gRђV`Z1S9Ppw{X{м_24{s!R*Wᚊǜk1U~NSLE l"Huǖʵb˱k&tDa(NAKd1ѻ[imm&%!QRQqR>"2ˋH'(/8N{BKw`8)RPȵvBUm['T]{g]Ϋ@qǨCCǹƏʒㅆI{(WrNhݚlTc4$pIJ=lQlZfsQ_yH[.tpߣy+ vFzD)z^0g2PI;GϘt*$4ԣV/ 5tk+Y_3,Ƚ[Q!p]ڃRON,X̛s19N'_aq ػLzr8== ^zam_X^5IJ4%WT467o{R¥?ddd~Dp{=bnWڤե৉%&9!ܚG$!c+2Yɍ5hUz{׃3}e~0W_þ[*Z-wǡ$u7(VsX0-G[.GoXħR L ? 1YPNЈ PG C\FSs6D?ҐF3j0}뱜YʘjY+8NW:"4R \+7 L!yyy!ƠaUt jvC':بsp l[0cX !z֟6Uy['(I0UVVVjSd\  s%S8zW##3U%5|4[| Lb^۬HrQyĝ?3&Hq48C@-524-+ƃ^▖Xyl.}۰ݾNy!-epg ,B\D QQA lyAzbMkJECYS:0777#wuIe>kh7kEV0^Nڕ{C+_m5핍Nuh&*/(?? k\wA~~G$'NPl#ڀ&+G"+pUm3[g>KFE=⟥ŋ=Dp \ǧ :4ss@RE\Z4o62ĝ4,*?7;6W塚NcH*NVBgD4ÅluYXM~?273!/{zԷve[;~"# h%dծ/.W& Yh|\`|s؀Y3̚dux~.͛k"BBS_4(]y w7޶})1DS5ȋKIK($2'-2{f7o޴R WhV<͋$76۽rqntm` nb~z dOG/o-gRoKUzmq9}Iym9sktTp3s:kF1WچLf |Uⱚ 꼕U:Q86_Jl0+Իz6 ѢSDТzg]B2 3]vrh".6$,!a(GEa^ ?OTqwh1~f4ֆռ*QS͖P^4ըӻoKKLTen۞D6Mò8x:K۰? Z1^dIjE[iztض4\%3;~xP'Ԋy9Ѣ{Fjڝ}7d=&m'~UfPzÑJX_t8ѵ GɸD[ϩGDIq ġE35@{yOGfy=v1V&a'rƫBWGBȪD:p'r/96(7aˌfݧj7'IN*$qjٸW>?CʳE7 pPK(re c\EKx"c9E;[Oe *{ådX4JMAG`ȨDpNs4WD=2Oq^{GM@d|| >62 m9:n`滙QwZΉ|6e'_T-<'/?OtzG/(D&;:4t)gnP25D-,g J+--+dD>3f:➛(! )!籲/U]ь"w%2}`kO+TdsNgxik{S.!a/Blo7clG,]Mk Vr g>XZ]~챾~RCCCU7G!c t,~X:߃rQ\6^Cf /Mߣʹѧ2clɰ-`[UyY[` q,A_.{Ďx#tA 6A#"0щzymꃃfalNWS6jj&RJCV뉅ش%1-\Fy` ?tB}3itl}P^УN"\J1SfxpBLIHnɬ0M| 9??%|jT7ivuJU#]av.B 02y9:ev0=4?jS<f40 Rޭ~QaCZz_;iC=ecpl#Qq|Z txey]={BJ$1]O~3l[xx: o7QE#pba3z \DZ Ci VC=8OQ;UL&s0!:ޭ 1.0 ]vc&z2_x!}??7vn=Z_J綴7H7*z$gLW AKx Nϋ (V=]o w9II8Wep }[Ϩ\^jJ\w)478\dMq{QQ@؝oBw-+!o.-sVhkc?eǘ%$>.'%Zz&%41| yVG~uFMk .*~eaם̳UG קsJ^CD:^1T8YACRL%rZ[SBpkFF/[:v;i_id<=b*#?~jkNA<<4$:37-Rv&cc)RzPi~oK%# f^G EG^YB(f ffR9^Ȅ&(p]Ӿ%HqO%o U.F KXS]=};ð^HCdkg| [:/SG>1 VK'wOZ8">mwžM 7E\v}2aЖ|%eubPFF]CH/JVK=u W$#..I _ѠWR /4A ~VdRiLeTCZf;-6g :G +ͧ30Pe>,'c6Z5[q%)nrVhoC i޳.0|{=_{"G/Φ*<À~.(S~{uFC.͖]7 7,5Cjq땜UnMʾc0qQ%' bc TBl?=^n6] Ckɠ]-Y?Z w2袯hwgk%2?\?CQcWg[m ,daW64EL]-F&j\as"h;ߘg$}J"JP?܇G&M|Ta+ʢ>'K ^}gRzYD+̥'=7y&IoE5j$$:^Ь9oGhY)$T_Czq1g.l.IB0?/NsOsw|jzڋTYS<8a}Z-x!"*:(<=U֚#e]UcjӀ݄QѲ/Uh~q\VRFU(bԪRjՌ=J J5kS;VbjXG}9וV=_?vXe'+ sٿ>| R*A'ji3Ki.vuvP똛1^z活6C)55E-"#͑Mkzg^fsWwa8*fBĎ 2 8-,`uȦe0_Mtӑut;QBrη{'mYu!0SXfRߋ&{t7s #s߰L#lFOGCCC%rޯ++AwC*/U:#+\2Y慳HM-nFw`J&1R wCW򎎽Ȧggz0MxSYǩ"wg=L2~dEZ~jSmK^爚aӰ/!RD&3>݅nG„:wD i}3# L%%"*<.Pv-S˹zF9< 1$8SO(Hcf:w_ n#k;W{8O[b*aI&2 4`H-A\O뎲#XaU];*kݰ0s49ލx%}" l>7`ƻn~an.cky%AE\3GǞjUz8C[7XXhkjP8÷Fc*JUMvquTKYR#3 Y?wO֟DL[\Z`ooԻHGxw#"c|ET!' c~3m3zXpCϗg |O|%>I*(pDrBvE &tk+M6K)S,;.hhξ(tSh W6'Rg@&UQ3W>]YYЫvO|+ͩv1SRRX{J^zF#۟tQ)Ɛؕ~y|!Oe]K3n&vvvx볲CoN&%677E'\Ca M2i󣔔F`%57?z~\K0:?_)ȼE.66l܁д,@n ǏNBW386_,A!ךڦ?^z96:KP}H]C=_hPĐ#8:: }`Ƥ=<+`ʘ w|pf ,Զj%|lyFX\WdΣm^>$&S&a#">P]@Lw4*2+^$]dRIV 3-7IcpOtD;k]sζ{xw#C;]qgs? a玱}hUϵX! Ϫ"6`x(MkViDdI1b]==>=Px$.>>.fuR\==< >0Ϛ#c]{83ID@@GXF6}~~"ܴ jSUTOπ fJX)C6NMNas'iwZL [gYF)|<D7c'GP#sA"Go{@ygUGdzBq1Z;Rۃ*m-AAylq[nXMM0v# M WZ^>[7pJR = qo95 95xFFfxpJȠ~~~;ۚ?/my?ΌFiwTlYHw͡i闣T> ]@Mm "]dIοb:B|CNOOAPL@xb O&O^̙?23'7Gҋ}w([tu(F puD(!ZCz11fN ۺ;=z]YԴDmwrj]華5:d,V mJ^52*6c'7WhmnBK {̢Ph;𷪼z]V _c5'M@ :|0|͑UNd}DϰO yߧD%ίZE 9u^2$4=QƷh*}7t'4 g](_ شDm40`JLlXxs5٣dJ:m]>}קQ&.i!g@1-s#E& 3i*77w"TøN''KOߩx#M>um<۟;٨Dn:L]IJ]J\7ÊoCz7 6q|&n uUB 1'gB OhuG2-U,c 䌤gKt<#rgHFF\$UXaWT+zIPL҇âCsSCmlĞSNm-D+ DU qu|$⛟ml&듮|ŊRFV̹m +S㴽KRJlY-tA'87 ڌ2]}/3I1a7%n+ӗnq%$/ [R04nnՇjM?F }$>5vc-W=E B^s_g>u}QPBqT8 R;-, hp`]u_MkgX m FpI#MM>y2R ׾_p$^}K!L'GEHWz/ jPT9/#Bl3[G*5^UKK+&Vsj[FLR}?>^q @9уmļŮooxWqc)?Bt/I#X|7^w%s^}6!*^^=~db'׵GZC2$}j70?b-%ܜl> S[3chP{50I?-)yƞT=`@순tXh]b"?V +d=lbvxqCƗ4ΪDcbCr|cPT̩,L4+g9$O j|L80-ST<<07 Kh^u`WZo.!v!G@ }X><#º*IR =' e0ɓ-N.P+VBvRxYX2jM`#" 37X ~IWsv,vs#)Ab IRB4^y *GJ.L0&(utji6(qCMzhOb|l!'J]A=+}yCfhH+SVOLdj0P =Բ*x0)QCΫ@ok:KidUMF|,Z ad6wh NW eIdpqjk>IWM9'l =f=I)x{О&`N6p>[I/O2DE0xwx]ڟvo!ksr 91oC t<.=6I6 UQ2{ LV?P*ms93n!>ajf !7bSu@NG')!h j9CM|ᷛWfFs':[^1U~qm}=`CWd,!mGׯ_\ψֺ.sspr =OIiCe.RH~I%^;JJQtttc!{uLΎ{ehYw5J,V;I55&٢bכI9=%PU$~bCxz9qtы1K%==Q| 4cW6dԫcgǺ 4'Uzo IV:@kKq'~ Rrqu5j,aT m6sM5wXR*b34]k=X4:]YwB?\aAi廀}ԁi՝/2IyU)'r B<1qwrۭd`9S . d}MnbWԤksg9˾JHw;բ UP\Nf;vÿ_^I;'9jlW7=h F'U?|CuC朁LySܰ'!skěP9Q o=#x9qY@v)QMuwĬ[{ȚKJe(@EDf_ Ga0p}>QWX]]|cBf}{C&۸\z]F ,qRx&xHx5Ҽ XOO#F 7t+*4S'} NUMZr~^|'"~mz*Gϼvcw㩝y"1 -͔gQWZZ:Z؟W9`F%1'mwi#+6zUo>>222x |d;#qn(!3AsnmKFPL7Qcm{JUaw{ 1GOM_ !~Z{4w- _VPG%Iuci̡ _6VëzdW/)+FN}kGirËc5pHQ ;__f%fKC.HԻyXLBKƔzYf(yI}B{p[gcQ~KO7W !f|s)އ8_Jsd2e}0DMќt("[1EΜxܲG I wב ~֋#F1%-&%669YuvLVJ/T4vGB$mPwDԴFfXR$aiH]X׉KKӱݺaYW0zS[Ks1\;Oq̔tk JDψ (نwԣC(=Ǡ |a֋@5k2^-,zUk=7̇H:hB9*w^^EEjI=9D=4w Ƿcܐ[vz&<^G0+-Ǯܳ¶J& FAN0}?Pmvz*Bi}py,V[uu]xe0 %?D&|jl;Q#6;џȸO6-@鏚7:͙U_ηD߲7$Z}R+r˝ z)V 'M~}hdp20<ԣ{6`3RZ3RS,چDXeѸ)ݡWc=|wEE8v͞DxFdS,EPv}=f[ J&F,/W0c0V<~wZ.S1"}ڥ8^#L)nƢ>={pAӨxFFFTS"ȮgOR?rlO 4tv. 6R'4֡nh[~ӣsui ݎQJ4(|:}ijڽSjE/t[@gcqXkRl|kZV5x$YAtv6sSn3}7)&S@*;!4tpxgϞEU6?w2 ϟ??)l +m#*ztDe[_n󉂂,VEНUaxnƈpe@g=mu`_QՃiӐ҉LC'eDHgj0}?"l`OZwʴPup[k`;zjh#xI3 ؕG!:}2<^&IΚe4I#wM{.?֢t-,,"Df)/t[ V:GѝTiDŞ:'&: iZs+W (XVsԒ wy`rL,Bil^lINPt]~ū_>E0VX;xqd_;?ja۵j>huF jJLˀ1ͧ%yIiez]zd/ 0rMy;/OxxkǻOSʼ(kthgws"P+oOꇷo(psi)`[;x,ѐO}ݽ#*0IAwՔ#8[tg#3A;[=I[vWVJj{{ͳ?DHͳ;7]2Ocn΅V>ݮTnso3 ߫Wb'= >+FT:VSW kM';HQ#}myˮ l􂢉V%b*C]B6h _f.]. k? 6b=6wW@R*Ce} ~Pxdp5xIώG`̺>3y4r8y/%K?/d4wE_y:fb)/NXL݊tn_[62Q*Ypw.eiR$TL@׵9y8>#QQQ[vPϭ4qv?&O0B)KV]{{G9'Obn٣_5S( ڤAoy X2Rp+D {G|m2C@Se\8PV;nXh%Jx$8jѰZq.gg?gK,I%/n۲&H>֊[!O,ZmgerfXM\4?)B3{_p#`#XșKO\M~ff䐑"Ӽ7<ƶ^%5?xޮ(^GFdҷ YĽ l|#6IX|CZz.@mgg hi)H {/(ij@ S-Rd.HuaO5՗0yH. %^]u$A2TNw?~ܦ^:FJbI$4zGmP+/7QiB"WfP cȇJaclj$bmœ*mM@~M1/ͩI՚3]!'E`۬v0E9[0}ѠҮe`)SӾ`hi#2f5,hHHCz{Ck闫[ڻr3҂N~QM$'Y$=6== Rbv3rӎǠ'=͋{lcc*D3Pل}-u=^\SSML#T0[E w[!բՒ\ɤ3 ~bSRG&kw}3U8l_>Tڞaӥb/4Ð QA_ 1HtI="TcR742nIoU: U5UlE~t* 0D\?]A8Q$J ʫWb쐙u}[`b_MH٭FPI7O b rg$gf x|"nF<y=;nZ/mwlVnXD?ߟLG^WRsG/ՒDmS$<+ 'L踵|m{yw8#tWcbbX >ܿs7,>= `}22v\\qr U_qn,Ξc4rȿjD_n6 !(,U} R˞P7Uo;C:&9 Ī4!} }ͫɼ)hÑƂU-bw$oVyL `= lqoFRlՄ3!q2-nzyJpk퉳sjrxx8k5cH9S0tx?Y2@/{eKѠ.u'-**#S1I}J)j:,vcso?rpnhZr}{Ħ)cEGGC-~_ęyzzOAؤ%<5`XX_\^Xm 3d (E{0^`U]C{I8eg1o-1a1Cr{o$S>QLۑR g{ 8P%sL g [hA&a^uX 3Ctλ.1Bw+c;y7OER4w;,3oUM_6Us0M6&| lHtz" ,QFcY`(A4,2J6J;[w>.lJ5m l:$}c'亵"mW)܀8,@C|UTbcc)i%do=S^̳F.޺@lat\B=ҎK-sܘ{jO{3bD0I·.QnYp>q7G. fzij:y([̧x*!ȝus#*Q??ZG<!9(͵Azl/qҎms0WC.G!7cu;(? $r躥fj(mQǯgȹ ˘^H K;gCW=a;.=<'MFf"eLߣd.ڏIpʛtn~:mwobƷ0յKҎC}NoTQcahusAwvwӚ2 hokm􍎚c*#Ř߮xz YKuGoy`2S)<ꑎC#lO| )[TG eGN)P`tzrRŋwRdR_'9_!FQ߈>H~լ;]TiuB-=e5I ܓ8XcKT8>Ƒ6z67džs)8>k~0wjy%QȏG!QC\246]cKƨSVʆ'}(`x_m+\*o+; Fi+VkX'xȶ΍A)JVD_ {jԫBF)K~U0Q sDI,RV^qn݀g}f`ȣWt!Y 9keqM^Zrq  {32/G["m?C78Ҧ-'  R>߿PFA_G \>Ygbxyf97x$uTL.ЙUSm~s3S;GqOb} !7_KT1XXե"V;|TJBu|fu5]wǨ&9{3'q*{N@9VgyEϯH˼BQC8-D(5p4ynG'w5MLvd_Y{Z;&}ܟr+ ռ{U~ߦ l$Ač ʫt޺~*zen ER~&I$0Y+ML-{{;0xǜ>bN%]A9Ves:`qQpEqtV> Y,Ak;mFP?`~6ER%\sR<&p|%H+,󃘛ߦCZ5v8C x:S*.Pt:"'/y+p8"׀{z֊ 83ҮվW'Г-;KʮJkܳh>j@tu O{=0$ /DjпēwR1-~QS!@Myac ymWa|{a(DУE $Y/=uҋWE_1 x]zT[QuZ@)Z-P݊ۼ~?k]+7yIMr>BeE@ <1< %;{y~ao'UG!$R (zrduA $&E xFT#wpqs;BDՖ~ l StY>KwvRM@8jዏC1B%)<5?IOC7Q K4V4U6y c'䰱qWt ixРK H>LM$k,?+CX:;֖o2 LLLgLM%ЋAAċՂ'3\^%^N*IQYX>q8uvz"-TY,*H͗OLtx>;HK`h`X: " |=aLbǓg<6]^^Ξ_P@Vc86nt ;;vOQ---8$\4llYW}ȵ# aaʇDHBǓy@?/?aTݡ<&c.95~~C^3 >YpL!x,vo$ay6۳%3X lO "Ţ'#__ߊY~.; EEE*i؋{{@6VlZ[S[;WΎYL++(̸ߟ}a``_/ՂFFr@V掳oG܌4lZf(<ׂvPy`zJDl39(>4 ˛`{* 0fj0 M01Ʉ 9nOk/GF~ۇ[Xdl69990(ȬBJaW?;ի;Mwõ67_%b~q8=n8MyXxB1TFOz½Qlam~EKCb&8BWhrT[32_$G{LX< D&?*/ONlX .'t0_ !!+72|My'@h=T6d E=KM%C qWJ^MV~iWlχSQOE߿ 4+!?uD#"؝+3|*D4OHym RI-;;;+7Xypqg?;1]}&ʗp)s5{먟X)z4f1SXQ>Q!Â+'N6hvA'G[&\aɌ!i׿*qz:)n=IGy_ldd1 ޯ?  V<""&LekDlBN@BU*v/}eFnGܧ{G~1aƌ_Hkϰ `yrqA+nPbHVW^R5FUhC)H`t"nJgrkq v-,ZFw'U0p:$BZ {5N]Ce\]~DĸѓIa!P/YN))Nkxm zҿ"4|SIBRYQ:]m ?:tvwwoa!?\ ~{1Ԩ`#8_hVo2նqt[+Uqk Ue:y&1 [$w¸fN1 9 kYگ/sdƒ=v"h} 1Kz.İ7'd.ФY?o]ey"mގtNgV mnmּ*s%q ᔅ _$W8¡a,{ph-J$Gԓ 1 }5ۉk ]H4b]t>~_5WAL@䍷%h2FF&[E9!0rYr9x!gLpam$ ?v@ԀhJf(}{\[֧뀴bkғb]!dM,'uv;aE0` noos3s󵹥>L>dUhPy!#_>LKp{X@fAL.fJpxgs]hY9jBB0RW;4g_kOw- IIlzm.Cux%w-&XʑϯgРEib膂k|X1yk)fًlTk\AוI~<6ݯlPj􄅻U6*|9 Bò}|uMq?fK :/0}yv"of*kہVXM/;N z537HBZqVđtx$5ju{ 'c@+$amn93qAͧ:<̹-%cMFQ]|AlH`%|Wо5QEm#| Ux/6ȕg7L]7 "6kzl)3.1x1JEI{$.(A""/| +zayWMAb^onԭh0[a0.-}͌o޳c`t{~4B!danβNKc権b`c1d"w' |`O/yz57㣫+7 Q ]:GK V:vZy9|5Zm\T:[JM(L7X޶.ŗou0|"ة>P'KFd#}ԣo<ڒ[Τj'rZK  ۣ)5/% ˧H57,FF0VK>f#ϬJoڥU+"RhzD odv9A\}ueȧ5$FT lLG$dnfbLjty߈ H>GA h,7P8<{Y6=p<~W3M ٟO՛qS6& !lRW.gN76;p;w:HWLOtQ(?`unq*07:-.MFhfıLŶyGɖbv12)!1xs`U2)lv'h?df_*![.Wku TUe|\Dzhl{}t<γ0Yo{Y1ScW1fQQQwjFe檒03!&&Uxʺ144CآtY$ϥ4P6GⲾz-}n~pc?먇<]ۘr'SBI8c(͏z*Ĺۥn(K3ʐᢴ"O4kia2 5gki1?*wN7Pn\aR() Ge*fY:ljB>ڟs~Z|KG(z2܏Erˡ:Bz_Bdrt ]]]D2Pu Y6׻b%J]NV~2O PEpWN|y mc'"7d>>a}O<lZdn61ҟ7_ I_IDIKn1owb7T <1d".>|6kj5/|VO:[V F[7z6Y*m.|k0>]Ͱf0!L?_áQӬ3ɓkCxNīOOi-x 8 >Է#!ANhz8'D MzH g㵭2|@EݾlI!JT5}Ͼ{(0 xGVƒ½k}7v3'%vO P!VE}%r455ZωDvboųҵ8g2QaE@90dR}0L$\/65{8š&|DV5T'sjJOhhGG @u !=':8>>>@26w)b.*<} Bf~oDӭ ^ zOn2S%HtM_#|boqf4A> 3;76V !;r|x_BEܹ#39l=x xVWxo[v*<^\k!UJ)ܓ_Шp].uj*.z%SwE4,,NnLn)mibn0hr\ܐQ N~x@ lietlnwcLt?M7bZmRG1i5[UsUW<d>,5.nYp];uf5&=/Tɘ{SĉvCwrU TWX}@ <ߜbTq%蠤Je!6un<ޖ(^!쬡;E w1e`ed%N?:@z< ιĩOYGzaYWIWA 52Bm/WгBգh?X":q= T un~]mnnn|\J P;Xj** NnJ$49S4{hCoO!# (3ÅvuOPݑQ6گ;ĖAl0=+kɗPa+i}OVզoѯȚGSn}šboT[zL! d |Ts^Pi ~m}`|O+ZQfQP:ϟ?f,@^IkP_M1->ؠ|{ ]⸮빉@ _!w[Dl&] 0♅,au CxLA fw J擎M^;"w yv-susvSʅC쇘K >ZpX*:xh&_R[ozAq`R_2M7 ҽ1Q|Q!(q5S I^ӽ|4ۭ܇ORIڲu,Q6rG{ ORl o' ByevKAAKJEyx: 1ؼ} D4.Vb2~ݏC^'tz 4~#7CU0szݷ_ŁFKNfϞAsS+@ ƙTe'fħѦeTDIֻ !es#+7K/שo+`zZ{˘~xlnn:_(ꉀJ3e$VbB#=N6>`aap8+$ aKhbbbá}zQu9{2Vp !oTړ6(%x ;i[3,ͼ<ߌ9N$ޠ) ׶QܓӃBq#6x2nA][qcpm=<=#25ZoTv-/,, G:YL%Ȉ 8ؕ~XeLMUϝ+L>o/V:Z;.:;3RKkɵo@9F| o!cq`" 霛|AJelx󂏼iGSZR% Hkvo#'::jYKN'ojAz6I?wM*KSZ/oG2l:͇q֍CQKY -utщ `7+\ 8WrVFf?8)pW.BA_>Sm#Z jgv_: cglmXqߵ&舖@dn5g ) 2 g:,,hjj:]]ܥZʼntΓe,3KqV?9;kw`/I"n;QkJ;.Nh2۬^2"U{2"yr[ӲXL}ɏ6F?ltHDWnZfmԠ0+%sA:Z'*b7W 1ɚ(cz%:IY[n!#0Sog:0v~ blqSGE$(QMVIϩ0$N63 c\z-GTwm}zHs1[7ku;>| ZWp(M#BǤ*HwR1jWs9^4#5vݜ(J}ja/T1n51dc ݝjs&U<,|ᛎe⽒/HcBPFe D}d_jf*(JVnd6?h5)b@Y}J @/ՎS@$/`SYk+}1;MģQW  CCf{ _wKt`|+eIOx07"2pN:m`t*S+.oڪRfIR Y5^̩B#0jz3LsWƷ` rBO D Kձ4|h :3 h-N^r+Hes&H\ `9$ =ǍV!_0; E0hrs i*D5?h6'Q=˸Қ&<C۟ay#7t6tWu)z&GX\iNḊ;CT)py?i&GWD^Ɍ*&dVAZքjyl N\Wc<fzwSpG R5R{{a[6.`\WTǢ4b>]EcЊ]lYMIpV$[ɒuI$=!4- % :ޞ7b%>4qCMsB˫,?+v!nOI\NA/"&xY]s8}WhHgfmLRhh⯕+@lR<[ҹ+[GttY05__ᜅ,M 4CM`xfbk"$vv6 ,@΁)i$_ ;iMS'"DtEDLB0Af c҃T/Ґ`rl@ @$SI7)Ͷ*5Ṕxn/ jFƒv%ڶG'qBdc))tANhd.8,.HfrA9 τlrjvԊƤn5]"Wk`2&_M1c7"t5}DKe$ɔ+F;fɚHc3a^qH|c:$1*)(!R?yQ"znbe sFH1xӬ 3h GSj@LK} ٔFbA]_~tn͛ۙ8N?vG]^݅BiB֛5/g,8j5BY*'n e,. ]àoߩWˊa4i+o<:t'ax8Axb?Y*H;[d>Y6Βcu{1P꿱V46mc z6z/J |uJYުizC\eĻ?QKfTMEP&eC"P&9?2E/ @s(w3\ݔ'o@`*.Ӷp$:y6ZJw2J iژˬd|)S$AH)рdͺ35]G1q"nu(,TCpI 4TgR'+ D'j.O` =g|G>:tC7N˵٬SlWTi:gKJW'TI*iV _X E|EVG~uz'tuZ*O,zUZv|Bu:'rF h(ni . :=KSu>k8uNjobd">zfmhێmmZ$!9P] [:ڛ%uj>N4ၱUZ}ͣ(%4܈A龒ٗxA>G0 SF LHH[S*ųX 2*(β{C,uv$C.bxtDs@0 UhhmhˁXx]}_R}SEaY4F sO"~eڴp`죒SE9[ , ]i!p֐G\̣$pԻԋy<#SoXe{Ӟ%Lx[x4TA~ջ;#MC! ?jy}\m[$mh9+zwfQ5|T$3y0L ELHL6Ӈ 7I˷9.,w YzÌq%˃} n#F+LE,4E; [&QΊt5R/8p}U18:x}NAcuA^XJC'BctM =-lgיYmc L[nM4/*lZb|gvK{y^N9GȒjO^kjeW vR!SNĺp"iL[]ҟ,(0oRq^H"q/ٖei =\֣Ws['68A@Yt0$ )q;Z !d`W%h ܖHc"T_zΎ{}{ƶ_k4OXndn5<9(dS}#O![ ^RLX/VY0-b66"c2+%̘ᜂvL&#LɊܶ; YPd@ +630P=/ ~X0Og#__ҲDhmUfwcRd+rhBDBYMHe@Y!v ARM^ !zCʃP UK9ĵm<Ӹ-DǕ8q%NKv%^8u%NKg%]~KWrD۫ j"J۝Bri_`SZ bL'OĔfFkp ICz㔖MTƫnK6c_oG{XX *RzY!"FD6PR>% ,qB}2 2`+ͬ,OXE;&ۆG{!ZU0Ì0Ed9ft BuXhp3fϏ[aټ6/V3 j7 7O l8ZAhjT(Mܫ|tƖ)d۳QbI1%ˎ. P2j1:k*4 o9D.﨨.wPd5]*{0Z= > 6;tkx*t/4\[5c\U܌W6(0WcYZ2&f mި%U^o0 ;x=wgy|4+_6ck ]d 9*}4{6LTzK#RfIZR u]AB` dֵ릉4s kZF  ή L\VF'cc3jI|nxUAoEVJ BABEBCuH"%qc%"nwe<1({p/RN?@8rofe{ޛ$ǥa@#oRtXq5+9ÜVd/bU&HsCRXQäHl+IG*ʸ6>\R Ӽ{ mIU@Hm2r}?G7o`䱆!mg\G\Hdyrz֮غ"~9=g׆>l_K OS#۴).*4H'[Z81>Ǒv(65 |X/9!ܛP<~&Ů% 1^߻?6 *;d}q/\fZY_,.'tvk_l&md_瓟XJ,$y5~&>7./ئ*xMQAKA] !@/$yh項 ]bmufE-ܥ̡ѱҩA͎if7$x\4` a p̏9@N"X, XQ_Wec> kkb^/QڃSj= E2! 4^ ڑ ~:8P,|e5^P@ypO/5cPD0E'#+-bl+7cGaY5\ZWsW{i*ߴեref1>iZpBFrVzV_vBj(Z(OkTzy(֗P٢$NPgWrK^:xOO0IN+(ırIjDNj){~'efAў+_#GTthT7ԴڠʵAt`M*rk)"Vple%B3m oXi&T"chC[E[ :8AN+FYMFp]Q>5 Mn20N +wJ۲yv`-h7暨6yVb]O=>!/FpB-t+FXJȕvӒ<'}QBsQ[]%^QGP nU~B|",XWLB9+5[߫}i xdȇV:~^] g xr'ƔrT=;ã|y½᧦O;3>,0$7QJE\mv';h֓44xQn0[KHĽ"Dū UUU2aC,MLm/zvfvf}`8Lp H!4$Br()"4Z$Ql@߿f6v'^ rC8!Ki%iM!ZPpjgrߩWpi :NODXJ+h$E&a0]V*HyYT)%S6F)k<퀨BZ{riE+V7]/< ٶӪR?&0HdR2CY@rAMqMv)bU~Ւ(JYSÄ P+RAlj:pD~t~|7ľ8w x'x{?E0 ]BGLkH3-XD95MٜW7$**Lj*J&u%%`%SD7%e 4"򦤕}LKL#K 0ޭ؟8_w㑷OI6X70kXF뜔beS&߽0Bʾ?he_ `EnOhy kL6Qkh"p 1t n㠎ܒ،-'i]xl3)@ïI8qrN psu![j: &5Q8nlnz~GmDJ%_[K0 %v#$ 3Hk^Qu%p̩oقN JEښlf`gmЭlW]wu{6lqzbv1/Zex+?ocC!FkV_a&RjBxu R#/عz8x5gޠ{7w91XMFMopH16XK\%XMQt[ߎBfzhVsɴ$m=JoZBrX5XATXrA¢ ]t8(_z9.ff:-岇Al]e&Z%Qa!4t=ѺO]N m{Q~܏|wF^NCFN0K`C/m RJ4CL0a#ȃD߾~Fmxu1O0`lŽJP+hq.)6TD 0*t`,1ʁĢRMj=(u8\T}9ɢdcO0%tt GnupҀOdD^Xm*O*8-""099v O\GóPQ-w!{b`G5jIBNYџZZ0Bzv::Ҁ[C?#b,-ۯ߷4uIwG<$H ܸ>1)˖xWߏ7~]!Fڇ\[pAGk;XYЪ{?؅4Q"8ό3g1~V$d0^B2H\;-ʕu~}~ Ϟ>~[J504(,_]іWJ:j>C患4C#saWq!%)ET]\^9;@* A! #ڂպa+ʟwч,#s.wPű!0^Y[InGG;RL`gl2],SqǃG#4egY-( H:\[-*itnraYc[㣴 2&b^9y;}powwl;.g^xnf! EZ (LDlPBpJM +JA-)#QqWj(ܹZXf$/wP{" Roo^NӗtqNRguQ~26ϔF_BݖU"˘$~W:U omuvW"֍MIL4ëBx4]r9iȥmw5t[3LcOk}-Nzp1zOPIҲsD#hDkAgF{b)QimuKs;#oՋܮ+ܓOp% QҩP`ZHmCDM9RZPk]m{@wǸ fgu҉Q `|\$ =.naQ 0r/XX+aЁPHѩ9e,(kHz%NY磽Gg/ccFw|I(a#mXщdCO)k(FO1ن>ҹ*wR@nl 7C QJc; vA;ݽtcS٬? V !̓Lj 9^d>TIY䨲 ЩiR}:۹n+#,u\/-;Rj-LӬ1I5J[Hˬ~gUMElhH5a@ Q:s1Vwnj}oS%B5, ب2 ^8au W ETE1D?uYVJzgP:>%zeQ.V7S@K%M*1{R cR+exFYRFPreJ`b|5Vt xg<iې2 ;#9/qhʸ ELaϝMH[0e3?y.\įӁ o})U5ba_^y^+d#XQi!{R% хW&1WEMh>?32)@t`.iT'A*8J/aZTL-w6^s;d4e:+Du2&69啻R=W˥97(7cKvɘ4{(q]NT-_N:($-Nq.4p]rQ=C|N)pyG0Q'OV <>.Lkؠ| As c+3e [v^+`}OEjHis&Z/ =XDj ovUG?vec,ioԤ{*Aa*o_8|)!nO:U&_μ쩹g4,(n?ܵu ڣݻ檢SCboWxQW ChD-Nag 5͑k8)sh"^{ua#& r8R79sxY_o6d^{æ{X76FS"qI%&"*I7CJeˎmq8A,_ΐ|[NM i,f9رPf+%fsCo 3x\$"5W~?Y}.~r on. &7"i4̜ c!+FWZA&C'gDb%sX@9\9B(Y"XrX 3| *={ACN Hv?)@ύ~\EۓjO<ݿ^\"b}pA?rP XB6E [Tf㘑x Zf'2Finj +TKdpûip7xxu_nr8~g~_.pTϙ"  UX:H:㡈E8W)JW ɤFD& a"F@ ApOWJ~ ß ha=3H&yDLL~7O ʩM}Hh*eQlGoCUjfJ|Y*j=Ӭ2Nr@LS"40*V-U o !CQNaZ٠li$b~6E8ՐI-v g Ro25,钃g'.Gɶ5 LcV筿5Vⶪ;N,iI%5:sqc9qKiqi]I,|D{"s'1٥̅Z\Jd?ʢk2t'6E"^UU=b R2&:os IqYUiamNcpJ4Oq,&h|[ӥXo;g: (pIW1 M&Ч ,F7'y8?DGYJ>UȀannKzg'1؊*!b;e1rH\XqK^e9;m}|`J$uA|lL:y )9U< QRz?<):@L"r{zG%S,>y*c6LDusuęJ*!n0%Vra'W$E~ZsiLDJ\:t(ߎt[Sj;kK7FZĘۇE~׬j+!gFj8z)N'CXZORDsvVLun2~rSk36exEr2ep˩u2eXb'l{eOa ΠWY|ա.TBq R,s#|bR~!z*嘲1Mr^mPOET|EN -d}x2QrqYױ?rX({a\WHʪL=ޟdɣ:ɖA & S@wʵAMz93;g.q)=S,[9TfmeNB{ I)wB/6mj?PTH (w%\%p $8I5UCGd/۸bn\mYri.C)|5^sÝЛ6]lqк\/L!{!>yʞKs S5[S]2H`gGw?ON{w~^hb}VhnsJL/ m~a؝Ƿ]go0..KihPvӵXwp5ߓ=&RUH*4T:I~m#&HRN![4y)}KbICwl6S|Kgc<,6YyZe_P*daZLKGClur(̷Ii@C־LdXup&<"q ^FmWj*e_Ɉǭ,]3%6d!k͞U荓6:w Q上BhnvBKrhP} 箟GrDX 9e)xJ$1gADb/ԦGdD[?Pą !VnYIR| n %\ |WVU6?/Cٹ gS#.>lW67v~n]YEb A$(T"L:Vx;˖OTaӡ XN]v>j9 Z)09soao%‰6\[D~Xmݻ!:h D@\ #5BhP(R>^8A51:Ul2 :TqbiVRe|>-IgkTK-&62S;0`0`R# L HeW<krq]_6L7;DZ?KHmVE3gH,SH1#-JqE6-SS1&(c؁n'eL@oٲ7l9 ϳ Ǐj6]#LaA>x>C@BYD[+1}["v(Q<1IYImܡ T[mI5L,Lη0S/O)B>o!) NjcxŹR< TyHV9_XRao(Z3Ju"g >JEEJ 8gH^Lq(̘$/Jj *Fv1G[sH\䥶rz]A'03 3_94)kR:rёnQQi7Sv-)KRqn,R* %Zpaq'Q,Z"u;#EʳY-wZ+>V:gOśi"uP)"VڹZTT9G rJx;ɓ7w.f)jK2fFs-UV(c)  |E@]YY?5]_D?.Wf=qwz')=a~u*&Fx{"4oB,Kfr~dF]=}]=㹔SR2RAR'aRMKLO-RHKI|YB+1;(dd|.D2D<'Y2"!9e9 9$s\Jĭ3sJSRJs2K2& 8;&+$A,:`3yqjQм".- Rp(J-)-SHF!?MCUC*sr2A:P|:YMR[ph c @Tp&wZ`ǹW݃z0$%*>WY~f H_KMG=LRUpK_꜖880gx}OKAƩ]ڃ, !y(@hwWev-/!}ug q!SW4}-yXNCv5=m Hm!u)]VQx~uP-U!5n bP)}Hk%P[iRCfrS*D:K_,ȩѮ.Pdi^nDޥMǞ\8i}=*K&ݲWy9UA)Zo9LѬ1L>{vXM(P>zFT(ۙ6)ן)-UJLJ6y:iM}.x-6R aFxB\J3t:h`7`jR-$e[zxVQo6~88fTm)AZ:KD(#8^#%rf@W#yw(셣QFpa {S,V4yA<' \14ar Ά;Kʔpfdc, &P DcpYInC^O,!g[( |8 egE68}ȕe亽Ig*3f06: Eτ|q'@c@%j L`X֬t7[^`nF&j*` ? $c' .~.&77b7p~=0^#Lp58$vM@<%0kShEZ! RCF[fE\(5-!fn""Yy/\iGIb B]+P` *t8c[6"YhS2A]𕳆Z46YI)qoDzp:aӻ%(ҜQ67 s(ɒ? _{=^Dn R$Yս0T;ZH Y7T^Nq()@Wn)*NN<ܯpmK& GI=FըAP5zLRÊ*2廁,.ii%(Z1w,*K};5yZP+`fwR.w+*tZȎ5$U9T[Ϥw^yr ]Qpb&BP? !S}B/g]mIO刮>Mԥ\1V.5"wilɲx;}ޣsITj]|\Wʺ#1PkDVoMWutTob 4 򜯍>2A$r[ SU6zށdt0 ٰ*M?u0j_ KKM_=D$W&r>%vo~tGwZmG/faKpA3C5VxWmoHί/#8IдB(9RiOf׷]]sQ-ݙ癗;h_uA;hvp5B$U nJ2'eR|ԯ4n92Ԩ_͋(nіO) At|Fp=SU֥5, 4Ph$\1X.3#,Iokm|6dd-g`3tjLr2+@on s,m(N ,!c>1Q FZpmS `bd| 3N: '|F_~|3na8! xXI[E ;mHʬQ63ELcݴYb J7X [( cC3k*ET3p֖ASDkU;嶈 laUÆv Wg>J4M8A@lq'sU2( ˙)(@$mls&r4.mwo4k2`&.} | BG{ KJ~e0kגΫ^SSH+$F>.+g&eeR/uGo8+anj'cQ\05"t5`f9!p'Tnòh5 ~%-Rw𯫽Ơ]%Q[0sWҲ+sb~_?.h ",fN5?tK3^v~|E52~!Yce8"y[Nڕ[j&ueeN,{C41aSNΩvDǮ^wl xs״Zt=3Œdx< FA rr(y4є_ׅƮbfDC*i_ Ƚ25/ 3%S#8Ə]Cv1۟^sxpC}3쏎z/ߛH| +%5oxUKJAJB'>p%AD,|Bdt3MzzԕWP$xO="֪+{a^vbtry|~u F;wN'4z \w}@C.MD)17(Dr xJ- #eFA2l"Ix5vj'/"ťw6˻M w! "DM",hvLSj 2sP<{}sdqN1oc)f@4J?&%*U61I Qc&7ڻ"୒TyT`TI_4 Q`nX Sɉ ]lfEJ(@1B$ƒJM`Ak}^)FF]s&i lZ2O'ḿAխ ^kAt>[#RM4Y[M9q[}7R :@{gj`јNkZ?86+M\NQ/Uu^A}6CU}(+㵖xG5ܨi]KUV/GMɘw@m5,ri<<]f/꼛J.=Anͩ ˵lʹnZ *z⌦TԝNiz]k39=` *t٭oJCSgꭙ5r]eb_ 8Qȷb Pm}hּGM(:[[džẼ,^j:,xF{)з6Yy1tl]?1Si9x2(xͥDtg2v(*Эm0;Nϱy\!^Sݝh/ZǺMsK*ou.|zUkJ+ϬWjrMkWo;ռ۾;$)ew~{ JroxZo8QX͕C3N۹Qݧ`C8;Pj{$hH±||xt~ӂ| Wa"I/ExEhU)[(cʸ E:VZ *c2>4 aB1+{҅x}ghӆبl2j4yl@D2E>0Q?2jFF;yy#0 ~^E_}?\݌7g(qv!\kHHPXlrJ[$EaDVsf '2&T ˇj7X'cOx0׻ <^_&1z0܍_.p4qBLk1ʉcQhZ$B RhrF"#RfҎ^Ĩz'`8|P`tsyw)|~&)%{mP ыB*aDٌiϾ{IN}kxA]PT/Yb;P#1>o-+#µChJ$&e܄ D;jiՋ)K2vQDqjOM~,SM-lpru(s~Ǫ6F <OZV!6@~4ϟ\Tk0hN8Ÿ!al> ӣWMc=ԅN``RtiY&R> !J\.LthLgv N)1TEJ c P(to ǠFH %'LYep6SbO ]g#.OO,2ދfy>%WlX1IV vؑ#P9W`k]snQg뙟& ~{p%ma0=]lN9VEf/Og ?s˜pdr7G['#oxerύ%Ԩe6x⛙2/ǛP)NĊe&:NPaKd dgpa7bR;(SOi(]h ¾7/@aU(.c,^q/pbpp}^ - pɑM5b{ДqD)Wi9x.g$Jy>F6RR:+K,;VJ;a['ӹ$eBʩf5]KNJ2S;.,\[Jhp,m Zn7rJ72s1ب'NqDEQEZ’ ^~X4Wu0>.422en-6XrCCmgrBG9/U3P/ooSY DUILiesp7Ŭ=KngwoI7_ VͭMë< ]9p{{MfFKF9)u[gՑ,K; ́*YT%Ge\lݹk,"7ex yT31Z$D"e1YrNzXO: 8ҥ*]%^E=S|{T1V;[e3i*Ų>iAڵ"Q9h/ޠQU͔kΩg(t!]ق^m ^`UT?נhX -fB qqO^ŧgA+֧/6lg_똡ҽג, LHazl16˅SMgH|bW8h4w>pQi`83>zFV%#Ԯ hU]Ho%tRSzЋUBc&'z ie,8NP<FBrڂY,ﮈb![n:swڰ% V[I"!mxrL%A,qmřҤɜϻQŤTS-ͯqc"PTR~M},ǭ8mIہ^VS 08epާ^R Ⱥ@.i48 AR? HqǧΩg88tV-VHҊlY)Yv\gg&nS\BMWOa5.D8:%-JX+CDY:>!$׬PEH7VU\gx(Fa/ew >\HS4 w- kRwsXB_uV 'YrF6: p|z_-xTMkA3eH9Lb%aE`F|MzA=Awx<ճ3$8aWo}KRGwgX4^VvݞQ@0! kWEkQ}Pθ>cnCi'?gؾ$Pgm@\gCWZP '~cfeR!(gdۀL K>G#8iO+ipϏ试Zm "?'9#,*٤RHL9N#ExH}8>_O\?%qIFgwg9#}ΙޞN;l|V ?&nRiDըߊv326#P ]&+K:nGI7Q؅7\bI.l͗*SX I'qA7/P&) _!'?du\Ii?S =5BWe?37{uM&lDztF ҴxWmo6_qp>,1<(}H!F\;0DD$Q#:F;n!GsϽ :K= ZAӂ\/E!coVrD26ؾy|_V"e k^U^hn-;)S3yyU /Y?~ r,uB//FV @9l8)Q@,29YY?Jl|6dd3A.2ԗA^{̢Ix4Nfß߱(25(O%Fm(f*kFFi\5S$B%4ð H+ݟhֆ7h%#bd>`zf4M'p;t#]?"@myR:HXC+ҊR#\hJF D. 3Vt9 Zgb KÇmx3܆ðuRQRYp-Q4ooǑEJ6I$ef>\̆b2ߌ5uʈ,IʵTY{<9/o&,YHMƛ"te%V*gLY./VH2*fSrERuǐ>4P"Kw\f2ChuƋD,cI-[.Ұ3/-V(YDnLh|  o t2/CRM\Yp0Q %nͫדY3,% (;$˓T3e sRQ-E(vÜ 83p.,hXjbW2kj8~ηi`|0BeumfĚҨڲˬXqSa\V,c\)|Aa1%1(-do+N-ԝ[Ή%ss,nm¾*:Ƥe*h}"9y3햚7΃CR.9ELn7[4chkx*H*rݠgx?I=Z4('4>a 6N%|5mB95K%©񗀺+;) CwGƁ"^ O['zfޮ׻ʾ?߁רgх.v!vuDiIZOZ@Qo- aONoG9f;$}ێ͛|׍5%˃}7 B"7"{bx-qxej@Ʃ.UE8t eM.zcW."z%"l$6;gNvU_A+AmY|߷?9U#@p/Ѐb$`ѬwHRb2YHQ;bubrw{}sts A ߒlK(ЙFk - $G,[s `&dff (#gWO|t4B o{L>_=>^?ႊwOO7OOW?ݸbRJNf|֎rKDT粇M~KBQzAnq(6ӕ|"T˃P++ddE咃Vw@jSqr3QZA5x~>Rq|K_۷aV\:jvƲ9|ȃ 䜃g AAztԯ_h9!F1} nG|$Gn*] S ATb @EVlK\.7s\%+mcYqSj' nA)|H+g so:bpuA#I MEKru=BE<ߓ!ඈ Ja8>Gժ%61a.C.} r~Ƞs"_P9hnx =ܼ |`X21HbU=6;wyYP+vç8JSp'\s \82h~]*,c~^R O -1mYc)ZhJ $p+U|D,y AP 2zU ,ZM0E5ÑaSƌ:%,`=6& ]+%H̘  Ek ^.Ց2"c^y\*[$lF^EH`vVDϪ]BYgj1BTO,_tr #%ڭ:(QT%[EUaͣ64i+WA;IZNn6E0Gɭ]j[YXLsZ'"21bϣ;rY#Fd T 'YTπΣm7R`F/䨨4(Y0uXE (XTA |!UcM G R$f]W+E\Ԛ&aF) Ƚ6V58|aXAhp\_Yhx^tX 7f8b#"hh3]ȃw7&{cD EyB[QERb89P*;86: 1-8/m_=ªeW*SJJ4L` wT0m*F-m8C Q ^#4S}E[R,8AJ J3R iϘYb7RCfߚ8  M7DCKa)6J2^6 BY(rk^A#Fxkw>IO">9a: ^D|:]V1Ԡ)h:a[PaPsV"bΆD_OOd^^u>02-Cq.3V`A,,ڪ{*Dxf% <^ޢO mcqAW6lBYUuc"KS#vtwQAᏳVE0B_M\`4l:ۙb0CtJ|<泫$HofƖڧ,5V IceUXPwj\0 @*EX8 B1Rp..Z45Mq^'1npw.a  >|?f5|ZCg\JȖ?gc S١RI+N瑑byV3V'YY H:J&U0\ň F]X{j`/1`5x؃85Z mwsvMN(1DT&Dv@?R(x gw kh'Ѹ>;\Hތ;ޖ9\+W=zw%?`{C.}4vK!9VVb:RRv|tzt_EdKz 7JQ~JtiK27dw޷esiuZ o"?9jqCq"TJLb)dWH7I5 l3@s o8UX84:HE;᪱b3l0FT@)ĴJf(2#T`<'S`bّ1+[r%Ïo_6wmݮ+G5ז3:A ~eml֍zΰmfŅ5.-`xO-p#S1}Rrbjm`|`I4nce f!itp{ͺ vn4[_W2~^eڞQ6F;hS5aUhr]p/@U_wKdA޾%7 znz<_@xZRl B?xSnA A!$M6bX1tʄ쬍zxdg'|| gv{7|9s_NrDlqscQ@h>ZTdJQBV{e/{8fAWLQrڱl'" xkRhaox<3?):ԥPPq۲1`#$!`SMW`q1ØhBCzq`D!؞Rx俣j[! ɂMP>'k6B=BӶB#[Z>ZvC5vp&,`:rT; u)Wm=0.;Qbc/UoOC"mR)Cb$`5(ygsRgZnn1Q]Hm;jp U-ՏO-!ۅ]eR&jgƍ<3Bޭ/ 2?8F )tkKwI;ʥja*Kf]=.ݝ\dNj8_ 7t`W2EX(3{kIoXLZc߈(HE*rX^M>;y$2y j}s" q%}5R~WjM\Fb° 8'?2?Q28 tOCܫ\LY( 48]A6y*E!lM CVja+kssyv$#^Zo>L<4RDNE"$?SA57㭖Vf![abiaahDݱ3 ?dnWWr6 &OrAs/liHEv.̀ܔ|s\X%c$W2"jٺkE#Qor0fTn$zƋ y#+Ep >A6)iBEK*P(8T*}IvKQ:xg t']xapbRs92(Ū,tcTo'8%г "w[>|>WYXf?+579uNg HrjNJVoaUĻϔJ)%N"R'fxa )vlʭ­1 Wԅak4 cYLkPϒ+6ȈydBI9#,iH̆(fabQikama<`>-x~l6.ƣ9`x?/S܍7}`(Þ Mndqۜ pd'bhEZK<1U`D ZsmS`lar憘j(.P9 N$Kvp4/cP•ZC9"YP?q2 QɨU`o!ʵyv0%%5 ya uX l ;TYT! "P"2㴱\]Bw0Xm  hV $q&k}g!՘b(pRn)h[0{0VB]E5E'{,4O DHQl*7Szf(P)xa{T7.C->fm޸;W] %t.m11@mh%ynfAHE49rMz'`F{j1CֵӲlDu{O!ǜT84][> ka;^z; FmqfM6W jWKMāwu폽WzԠSc }c'Ug9Ϧ|97yZBp D3c[ XJ'C26[к6J9GH2Cw :yޯW0Z,Ft7~N70fHrQ|,e@0#B,+H@.bZ,AH'T91FhRM#&aq^6߹1mVwb2^淳I6:%r|b.<+#2L_?֦6ȍ]pڵָGC;摈|4x#1yƔ#{@,S+FL@6c?'Nc䇄m545e撳a*JERk`m?")6#6pIQhJNp6meNҲU3_f$Q)(BnuYa%^^IIӇȎXCF9tN*_3ga9?:>=$"V2󢖱'O8~(pwkGe-u@¡ytn^2  a($öb=7 Ƹ$ PF (IYƐ_3O +\VV;?(f0$02t=oyZwe|un?X)<-ٜWexҲک#$muz(i'kx9z 6ZwwfofvfNd7pH XM"7 ^bC֙\i. oA:eJ$~46Z(k\,MAixLr!g@v!"硅H6v+Xh]V,ʫ6&vm1)!bWFj ׆O>MH4Cr +:sR9fIn/ ڣ6:4Kǡ~z#wB_h+c` Ly LIbU5 MgTRmN0?)ZNw24ӭ@Wi8=F.A3ͺ鴀Sg#HVNAU[t@4FBg%FBF|Nʛ:Sm})qa o0J.U |иWP*Uam P[! nEFDύ w8S]"SD%%V8 [݂Q֜Z-Uȸ6B&(peZ[ݝk$\PRW1GrQ; ۗi~N1Qus$ IU!ŊrK-$7bU"zqr X5`udq)W9>wu 0k=(swU"dx41DKȢzR.E$H+?~#xMh#x) ϊI)N81vQ0< >'"y\~ՊO-"?|~E3#(uyB=c/WZ N~! 8]Ggb' ذd@9l8{s"PnT,&v uȕa8rxcr}:n N=YʵWѮvr(d+3e[ X8Io0"Ǡl&(*L+avsept~ #vv;&%_fwory].n>+|-.1]hEn J%ln9o%PEeqb|*È j#4MF#R0ئtFc Ae~})Z`[E;T"LYpe Nt]I7 tcZ J| $IJwϛmf4-fFF `:ٗ+o)/^*ߥ5X 0I</s5׳?Яt U]us!Ն0܊,cݨY 0kQ">BV#[ro.|>. O%m/'NSiYQQdqЮ#Z`xa1MHEQF9%]xw[*WXU>sy=t{?;?e"A Jr.NL,#ffX$2i*"XT*d0tKdF1Œ9[W%8P=$1\nD1+hoūpU+Pk"yI0K+αXH#QrsVB43õIɖr 6P#vnT٥)f>wdVi9m\)ܖچFz0l"#ǵ.J]/vV}:x"jZ)=+E*ގJjkGwԡ dj֟mM}ujzR_Udozklчܣw;֯:>'.(N0D$uK̡UܸR~ۑڧsPsuP3OcIq ep˳+` pO257jBސ/=$g[xD?9iO{y o& eSEvsOJ\#%ZODY#?kB[k7`ڝu[VC'*J% x|]hc`e;YvL4:ޏgxKKUc?:(=#!T z>d܏eqwJ%*utj?z:kmteѬg :Lj>I'UOy6X*SaoA,H0׎:MG/ w"Sj'k={:g) >_AN2ᖑ({f4{4nU@}䮕o⪳usY}mo̒)MU!p[_n2&1;A{u!7`-\ޖD@8&WN6'jCYZp7;[b^< ~deʎDڀQF8QS6$MZyC'Ҧ va7#w=I/`/2)*Fܜϧl'2eܳ^rkGsؗ;U~i&-=i[ -`Zk܆^Oc7wxY_oHϧчW!&A% tr{1,^wofk0)EJwg~;gtݯQ{ttG~*$9O@>pѮ*]f"z:+0|_~ _fB4όk~ u6dy2!O4 O"03) OixyDr['T9e 1yj@$y*KB af!CM Cr;O8gƤ],f,ʂNwp{;Dr!"Cm'K`)J )T,8E/2aDAYL$$75>%:AԂM nOO~oWpv5TV~ZU@8a dQ&KE-pڎhOl1(uu\{*ÏZ4G6x~/}} 5OQaY<<׮79F!N}Sս֧mf0`ʄ3V7;RǡW3^%2Q0^ QlW"sa5w<ڏ,!b4%Zo w TŦ ;=& Ԇ񼃩~Y`p !yEpijY$)|'͈/fV*qK |=T.iK*!ם̢(ôv\2{eT*o0TAd>PO4bIj:X8o[SP{Ψj/ǰ;EwH)Byg~k͠,4;F`+0~pw/.?@{u(sQ@QGf}Ź'#3 exVe4x_}csJ}2w Q;yV4E fƓ,M]m}ۚ%mnNl=ͷܼ E,[xPԚN%|†r[ ϴ̛D}T! /mm4n_iT:pݣop^_8"*xXoF~@> %i)F,`V1kwwݵ1NWKvofw@ky^ zp1B4Ӿ,xF9XS t'ymT@'˟˚AĨH;㒼]dQdg̶H`DӉx+"JVz$+-2 h0|<at1Fx1MwNxz$h|MJ -osĊ ęR 䚈2!D JAArÕ 2001pʹmeZ|E+ɟû{|Ñy DP"/LֿZD`hwrUocFc*HLjڣ6Ӻ@UƘ90i\ֻ x7ٯ&o:|*r]O , γAQR&JYh`A(d4sֵ$ZH$3bl0X۔4B[A0r1,R5L7ORkiŒ:|ݧd4*9P!R@] )KCuz0%ne) zUv[‘߭EEת|vʔj쀪ǚh $L褉`Mi4qLLHEPNl4RCeɯi/CҜ|9 RɋMVF1an)ꊓgJH*mC\0".[(9l5s%!9H Đk?0ZbYJ|sձ]ssK5;{DUJszq*-5 _ӤR{+3rmkai9i_cQ)K- /,fޤbCu;7Sej#o5a沧|UԊ'%{ޏgS:o4K_ 53?}:w*qOYPTɝ g3ז*DC>Jc=Ő.O*f3#7g4 wo5 y'Ru\\cJ*)VA#[MH'Mw5_{i_f2.|ڀ[U~8q?˘Կxr6]_qyQ6rӉ8&䉆HHĄ"hY{p! ^dޖ3Lp`=5K(d$(l/&Vx~xY² >*WaVtu4D7痎ɜ44<)L3n=5:;$<-C" &>4SR6KIC ;bQi_ R _@:V*xۍvfX89.˫(øO*% f |<[|Eu! }iUmIiM5"ɌlBa2A6[2f0Mjg=g 4d "q'0Miw#=CQIS1aJ@#硂`lmvq]B; OL$*{zڪ;r"ˌKW/ ?Ck0\2 cSjg:3"#&fNMMF @")~*?< Z+ʈUސ &G``$i rTܱS[U9a~ s)_Mn!-k@oG9IXqKVRb.>Bһ#EEʌRD90-VիhC}gmØ SG<4G'"qԲR4P߈`Esk^ ]l @^ +^GWR'xJy+ŷk=/1< Z؂adO~) 䙱g#A5S%@gﮓUp,slFlo˪ԨNٓBEۯ Ńo:oxgPuޭb@y&lmqEn |XSTAnD.b2`s5V:!#ՋN&5-x-xd«Tu a\n%(IR%zfizvJm,e`xypTc_۝W0t!nMB- ]G~!|]x[O8=qad]a#F~ADS2Zz~1wIvF9ˍ45Rڠ&SH)d1w I^tY$]MAMn|07.ƚa Hׂk%'.' <9k.eNкw.m&,>B?V(·s~s7۟PQMEґoi;og6[y^A:| sJu;Qog\1ZvPЩ=:OJԥ1o*mb-/5kMB6z3e/ېKt:W(iԋL5iG1I?bIq蕦f_N2JaE8<#jij& 34DOLs)%d-=\S]TߡQCygyk JN}k\6A~P{lA;$ٷ 2ʢNXxrt9657.w 5R QV͔K݅qft-:vʦf|xPWT\_ּ{ϲ/Y^$M<6=üZh U]r*b4\+lb$\v|{f4T_ ~pA f2~6ڒxWmo6_qp>,1<Ȇ~H!.F\0tȤJRQܡ}GR嗤i`={tЂ_ZA#hЂj#:s:y,د8}s>yjTpiE؉'fd.eJ j!QhL a9B7g1*M@pyvAR5O-R`K@0>Řb3DPr3wT(q_a0ZrV_TNύYeaێTiu:zѤ#y\Y܊ &?\QXN,"?3VTR3z\*nH۠̔LI6G"l]_@1&NLnnp?1nFfD_; Ht>+-8&[.̤wI)4,EH#*AAjM& 78^\vqgA ?Lq2;°qD*.tx/ј@qƔ f݃8,ba-CA8+_2ELcP;(yi(>ppЭ}tGhJ( UR_ߌg{%Z2N/ڽ;X0xYmo8_1p?ewP7ɶFsq8BKMD$~3$%Q8^[[9 =:?GqC*2JJ3\]eQb4tZ=@/>?;|]L|a\s{aQ|/ sqq\'P 0K\i!sx; z_u,a6KHChǘD\`ya-TD㛧!v$;4xKc }2!hR-FۧG"'nk Z 7 D9PIJ/ejLq"m n@k\wzr'" nWWWp:<&K|ƗՅ|cH)Hgp6\lkQ䦽fTv2DMb,fGK<\vE#n*+T)^`4([ s^["ORYwTaT9>ی,Apꀈ]|GJI3EVj :(tO[ ZTZ,r>0B-A\Z:6_E} J%-͸Tܴyİ!1**)(v=5!vUT]:?"m@Q>0/0C[=oK etǘޜ6NDH-GK)yc%N`=0*s*Ur}* 9FO+\x#Z0VPڍ̝j"s.=w?B5MYNgkH*Hê9(0VMLEQO[-#3f+ _dC&R JIJ@+؂5cJy Q3gEX\NQA$TwUGnq47(g ``{6Ia%EE5r? A_''+i-,3u| y<<r.l 5#6M'4@8TPEw[|w,j{*_Z~@$v*ŹS &3Ųa"Ge1%~Dxf=`Aj26Ƕ%9e؂*lƥQ`D X+l߻/=Q3ampkO"laXL!!:*ۧ+TD^V4\U 78It'yY u_R$iZǗp^<W|n?o+B *m  yZ?a0k%CPi8R.i9%RW>-9zN=E52_ƹf?K䃐8*:rUb;Χ+e)RkB\GIFkAom=vR#McއRC;SAuI>UaBUfO2*&idGX26fvѕfl1$s$wCNÖ!9mI? {y(Һd4X)s#8E2^qZ2wcE GOEuSJ (ƶ84m"sܲ 6ZkB[Svl/raDji83:OHAWSi%=:2GI,]n#cDY:ۛ@ݚnmsz),e oWg!2v"Pl *Ȃ]H2%*w*_KUG< R_hެymGi[D9b3ތ 5P'Zp{I}i̒v&dkl{Gwg`ZOמ['+`0vC?D}kÄ_}nn{,7Dx1]?RyxXo"=+ݛ iUUݫ$hYXѪFfkS9i\Y5(~ix\|WB4i-Κp˷pǃ\X<1<,î8SW>Ӂ6B%pNiA+j]ē`͞ Q2CpǐDZR$v(]ce*/fsW֦^otӶ':lx;>$ .QdPRs4haEQ0 &jla~hv}%`4kl4h~;0]2 g0tr5u Gx;\uPL5Yj r%f7TWɤ<Ѵd%z:A z- ԠHY7c ꝼ1Nwlq< nMI(C&WO)7Q) fx/?09elPJ U S;͇֠I$b^fs8J091eC I&O5J-ˤfuhUgxz̦U/AFɼ*/y}c\ TVŞ-ńX' "UQZѫ%.j}I+T: }&\a̸fZc#(xJ134X^B5SZEYf`J!9yX P+:&%rGhh-Zb wu{}IKv(QQ\A}%.A7eՠkQ =">sv )]jΐF.L[`Tw7otalS㥲t3r78%Yv"Q~q*rΓ@ܕrv[_g~gn5F(N6VXL?yh!^JdU=l͑*nrv'[왹\vV%&/~-ªh@{!x~BAlKH)۹3%O[PEHk'K=^ rgz(?k/v kmaa3)R)A  lV%U2eR:"W[ n]%UU2GXs*"?i?.VeM=am9П~T54 {'b'AO@xfqosPa!(k>`xaU\<g ,"xWo6 ~_Ak6>܀=pYMC^wObӶP$i6%+]{0,@#i:ӿ:tCSUȈMHdWlj6pw Oy Cϱ$'ct{(DLþ 2f C(|T\dA۩'J`+ȄB! ,#|03"O9%׉t·kF wQvsuy,ڮvW!v'\ XN6')[b ^Jy^2Mȕ|^Fv݀R2h0t0=5NO&h6xr0Gzp3]R(>0 ܤC)bB$JH*ǀG< jY\!O(3b9WM\3mE;L u#RDٍ?\W'ogWG -BS>3&?הJs!ҦpVD <&gynZH*K!)9LaTF>rz PR.,| [/¥.X ԉα[?-+岍).0ݤ}06Lސ=Fn^k#hGGװYϵ6X")6K5 ~Cw=r-;HA.xM vYp[( sQĪ KSzJ;sҙvtfe\fX7qg}CzX Hl|9޹@eFLTl+ju*W3)Em,AFE3tz'wΚw?[w4ܘ! 4Y"_iM t*bQk5OS--UggfkR^u0u%KЬX/&F훈eC J1^6jzOv s'C^h ڝNf$jH^QwZ~ m:< 3 J$/tÂqFW9uDNzKP鬿v>^jJTSHM5V~Td9rO.ı K{?ie45 {Z-bJs'(ߨm,qNn0^^gߪ[ "\Q) ݷ4o"x]s6ݿ>TR݃uFyr/4DB.#H+jv ~IT&"4WƳx|RƂmet Oa]&W]/l?'LS -26B^2XR+t}}e\P$ZDH"Ly7oib!}uzQT6|B !5#P9 &%OB2_e8>[js egj2[ylN9q;Uj8=_{Xh2Bf bx |||T*.W6LV2L H<")b2{6?e]'q{ygwa~{ޱ˛̈́ P_ %6%RD{!j,,aI"Khɪ+VYd HRm-`hb9QK.$4;A.ŒGE>N~2`aE$د\,3t t-[eʈ Ilg*_w*X$˜g${4^2Tv}7iXL5`0^ 2lR^z*bS!<)O|b3xl揟?4 `_H=`km}˸d/OcD1Vq߉%ӫS"V0{Jx FeL5h~[uz:aH o湷EsFO|2T\&l:p D~GhGqg gLm.yR`tnYı!F%G%6a$@+qIT˝EІ`F8Xba%򠲎#yKxۄ>sfԁ b..xg =Z2-7H00Vm`$!AM"%1|spu}{ۡj`A01 l$=y!yg"ّ #, 0T|jPcƿ*PՉʹ;~Dpīʡ2U+den0\ADhP{@L(q[%V3EVb^.^-טEʢp__P)t?"+WجtK*ʫE&Af 5|+0[I& HG/]vclK3dsxp&cz*3-~Ke8fz x [wx{{?7}<"|~4i2`2ǂ+kdPgP*9y7? 7_LZ`_ pyk C&Fyї KNP|[tMsE2oܶZ a v PXz f<sL@֥SjPo1 ;Zqc!%`P^ oho"&a6w2E0tXkb/ސ4a**W&4J+~;VxR0KDKBQ `օ2d ߐHC,D| n ^`ɯ0ږ8zi[a>&Aظd0کm@;MCr $bGL!43LלΐZJM6V l,$r[t܅I}gUeawTiʔ[nxtPˤZVQak9-C-UXБR{LoDQ&!?{\w%0#-׸Y|99mdXdvxW-v?;:Ff^.( 2uh'%es1fp}7`APm62GCjSvF/鏝!⪎~&L&peK<2HzT(G/jd(LO|o *9:y\1q;K* (b?92ϾY@0<"CǴ\{Pn;M{^*,W٘ ;V[6QNmv__wQ>^kP2q"do^c8HCf TD ̰G;z7z4Qy,E$VI- $H lh:K $&k+%]ˇVuݲg,@L=<^[Kk!/D O`Orz~9za8aݗ}?i՞x~[[E3yJC+rG2ivФD' y>0[HCjg n=x՞H03$ n =R ^@Ga~ٽ-RR_#)"<C\85*h!4eܔQ%; y[#P K $&MD|=WҥdITo9H7.kJWhZHj:7;*哨 τՙХ  WфM(@Mr b ;yA|3w_\{t̵+[k?eh 1&ש!8! ?d%XOc;-jS≙챷NVˆ1^ٻogybUX)\Vk?,."xm1K1QS]JP-"RA-*XһwлNuudK/ҋ 'm񪡄LU$`_H 7+ ^NFa< !Seǒm>Bc SpODH$x6Y }K>J+m3ZDCCg~e?2g2,TeQH@'f])a E%$ur-$4dS,*ʃpfR~F;EB%PK)įmvI*}Z)H m S!\MɪX4*ĭFҿRkm"x1Wjjɧ8EFXz6 #oTԝL :R폻g[hVlRs?vb|J"BB#l"{1VfKh'݊r!>oWVC8x&GIax{"pgB,SIFӍ|4C|}]B=㹔SR2R}|]d'G1+L~;D׉ZC?"Cm+`)J))TlqfĪ%8DBLLs0X)/@cx4> n/&1\^h27p|ލ.NO3dJY9o0SN$PD%9R-&j0")0cI-ѠD ɻ`2:? A RE(xD<,,`8y'|J9@ )ρewBAw'23 nb$p0M.0I(oRLLAۏM6T)".i>w=ZJu2| 'j%̙sU\_t?tOo O"1 kaDŽ*uPSx,[|g,4 ǮdHB۰/,v@){.uߺC9+>,xA, y_?kN'hq42feGvyD^r#"BSG@u;_aaCo[;k]=zƨp&kl&;5j,Geȵ2PzlWRePU2"RiTJz-쪍uR{lR[ek=Wpg.IZک"I%HT}H͸.o4WI=i8Y$7b~ Tի$دu9ö4êzfU.tùN|KY>4ip+co!|<ůC8) p[j"sa҅\[pb4*UR B;k#J5ʎSm=Ν9B#x4y6cy=A`8#,2W, n[u_w%gâ+i [Qf{͸\@2nyB0NmT+ā_ Xw3&(7.&@}s\zv5^ -nK**/O/[G3i!(Wk NWYabjS2+lK%CZz4V-x7+m*fL\s\+-9\ns Nn76 [x p0~uu^Û7%*#nۿl x=y~ qo61D˹y}Ϻv`|XF8/oqZV(Pe-l(d(ImKpA<* Y0fX؊ڤgiJՋщT|3/0 :idH@kou?xC;&-h)v~fz$7[/zI܇mN.'2%co` 9c_dF.2HܹC}~ 1b8"tDȦ p<tM+PQm[N(;)[OH-lx.޲FmʸwXi7u:lj͹}:1bISs亟[/+eZ۲z8RY?j)@+Sg$#ԧsy=ka'!|u#3^Z<$6ᩅwז 4D IxVQo6 ~ϯ ҇%Az=piC'CiGcy\7GrN $&)Gt0_^0`!a2 O6rZ.اY>Fd(F{9-'|3?FG>oRr 1Cl5c2XWgNe;ȥR#€ȁm sJLއ #s;Ȥm7 j\ t.)c6PkPg)v ʈXRKHqy:-S1M,QGXne,tUΧjl_op7.\zy1_ϯp5_^.(MaBK!uJ@. Z,EHAj+-c&[aqlw"R$wWz^\Ooz~ _!G9g΄ͯ-Q&i R82{!ga3ܢ[J䱬|CVӡ6"=dR0uNB3>BuT)SobGOIe& f2t4k4#  ] UK^W̆m mq$=ю⣏m,o ۑT>V#p3pPI[?Oq"w {&w k>Cq$f}JZV+4uv.k0j*fJ DR۵@Fh}$ F>e].;Cߠ-Vgi\+s0% 1IśP7diɁۭl"D.7k7M5Ki=(fN9ͭn&V/Fv`58k MEm9 'j,x)^OO;M{= .o^'aevrKΪ2X몣t{^Gנ^2WlzeKj(;W_:ou _xVo6_qpTd@u뤫 lA:IDdQ#(ư}GJrDɚb Kw |#2>"C0d2/%crYHRc8}wSK߹IHrY <"&E_ÙWTZNwpl k|rfUe ;\(5 .0|X9p+2rP :;萡a$2 3ө1֓*ZNb6*]l\lv^V0Z/}}\.VS@Cl䦰Pbp \e.Xp -OJ $UNAj'M&#&;aqgqYCHĈ!nn/b4[/ _-|,AmHh RS\ Hqh`4NO j+&;A[+h(YԽ ?oguͭLK^ ^h¤ʉ#aM}tuZRm'GЮ~;<~<C}o6v>uPZCw?ns=nYC'/]Ⴆ];u:7޴ھxXYo8~/HE؇th6I[٤}iʤxGZ#-o9›_a  m*rygbEtV?@?~O }by PWr)b. O ʰ 3F( ЧG~hDU K, ab^Xb,rda%RPs8T=z$ WUYWLx9>\ƭ̹1?| @b6G;seUdJ +d6RbDżpt::'#r7~t|18:OWN8…zC4Sz!џ)sw D}pus BZ䵲{Veѽ=y/{ѕޥ$Y7#8Fjgd9U,7q?ƥ#(C EC- )ylu 4~lF Lrǰ, JQ̠3ݪx9)0ˢG`:+^] E#jD7J${C2'EnmV`xpG}jy*o''Uk?=1ll!t d>(&7,;$V\fbx2og8۷iCϬ+PpZWK+Y*1<7tщLɷSwͫ@8AW?<=-R^xr98<ġ0d}p6[8ʎI2<5[*wz%5s$h0[T%ѹ_%X:n]q&KtBޏqѮ2_)1 vQxK,pŸ Nb:0~~ _# OZ<<"9 ;=+-doNɛb% H 2iq V&܀ i(0 WCN Cp$;YL|0X.}fK5$NG7wgȱ?%\kP_P(t,GB6E>=#Fdh93K86JL ? *c `tw? Fw=B8upƷp14oaxF7zQ]H?$@6GVow0%PDe9傫 %Th2F#BTfҖ\Dhy%f1 x|c T֢"q:!~i6^$kz\I[Q_Cpí^k_ (ghP@~lCBʞU}m- ~"S[FJ3aS蕑C}-!.?JL↖3o$!La><0ai:kqJp%rPZ֑]e<[h-kͦgì"I4>߮۵V_m ,֖PfS}2N(I؞RGCdؒ9$vHRl(P۝B'EvbO|5dXة XhĂFǘgK3\Y)W? ; WOoxv{f Kt;`s; tryN?:f04͚Lp =baT&;/c+I!| ϼއRpϔLJ7GTCW{]+^+2V.>7~^/=ܺ|\4"~XTETZPja~OXJw`g$XoMv;49;⤖m2A$BLM Xcxl8Í45AXId>c)D^$erOEadm)&mG'li4ouȰԵB61'nR|\fGvP/jjl-b;OoJ;KRq. ef{gƲ; u[k M2+5}<^J.Q;-i8C\)~L#޿F( sWc>zL%Ckb 噲ߑu;M&DK~$BÓ,|5)G]TCfU8>WmMd10*n]2N40==ae[\02 ! #5?Ն}(Q`iu#siGjœU+[F|]%jz(:J[}ea{b>ohôf$;NPV*WT2'KVj[,E֥P (J2 .$ -gC&k8p,p[kCF7\Kۖ7s#Z~,"}7?2O7HxSJ@-RƢKIuVQ]0M&48Mdjڅt^[' 'M*is;um: ) }*:=Y;t:|eE\YGWJ Xii]URd۴ؠ^b&_b7zAb $ >r>7_1@7! BܧwMu0C\YŠ9u2wktJ[` 6Y M&ݞ8YJg PE#r!Q0ظѹ't]vyؒ&Wb1^rMЮi^2}Zp{'\vGWezeSߨef !5/ڞ+XZ0d=ldj: d:b- '3ǁAJ 2R?c֒hPv?F.3u7y):jȧƩQ]lJ>j~A+CGlgNG}+_\]w B ս;W7%{$8z6OܷxWMs6WlyJfḗN*;Ue'Tf,iN2($iw(L/X<}=e)<6B<\B&}(*R,˨L3z-B6)DWg#κ^A$E&g~{&8J12F v0N~bpATmE{U@ [A܂UIP U0QȞԶm ;ksv8,r<݁0 ff8Nn˛>Qdƀƿ XNRVg#\ja) =0jkKX-6(V#0]Fx9]< >oof [,t1{?u)"*ᖈ[H Nɤ` B($99LUCcLXf/\sWrk!--ǏITlxrZ:0E1t~ *C˦U5II)ZRj N VX*"˕ViB#M` p$)jV;| 6rVjDqgٴE4u}YqT1>@v]F҇r $Ȩ͟myZ`GCH}d:qgVMZڨ |;Oq>4Ue_iJuGQhkȍ 5i'Jz+BSo TcUpԔ:VUsGbEúc UbPԇXq4gtO1? j/(с;ޠ\itQ Kk[mоq{Y$0Yf0 mC:BBI߼%|ngͬZ wl햚p(R)KVA^&$QjRJ_nyW9 ^'{F3q4m3 yj8KQJЌAh6-" #︣eVP PT퇴z+]0"Mer!*$2)WT=,ƜZn i:Bug5u P~Yw>׋W))6B^kJ ;:#h4\ AǰxUo0~_q/])YI>,ЮHk+H}$jv>C-d3Rl0 xO4Kn3c $gΗ8镈S,sƅq"b:|K ;[?Ү4vq 67QaA\"B(KT`LS̮ʥT8?.YJ )dd6WJH KLtG3&q%_ZW&hY7"\HPJ'."0M1V@SkNKQu>-W=7c te>E%eY9yW ~fG9JeXasU k7tߐ]to'! 'a˳n ;勉7}0)})]k$6{o1OH3qxcX'D)+N XU QuKou<5x; rHW__3 (5E!R$#UA%5)31OMUOSK/WMN-RPK*(A*hVhNtSNLL6Q*q.-*/*Qx2/SH (WWY2"ZR4d^.8'/bv|IP44U BA.I,JO26JK320J4O$3Y%/( > #  S9,LƆ2*Lddd8y:/Ĭ5R33JLv/K-ydpFq} R@ kY v7F#CqkLag ͅ" Vx;$ShD0FKL9?$5$8*UV "ZR4\&<9q=d~Nع7[2502Mͼtr)K9D3Т"~K"LL&'/ㆩ045h10[ox;$tPh;D&K&Lfd404RԼ̪T[CC#SjUjNxfJIPĚkr!huLF&9'`l(abрhEfe&#ؤ'KD3L.eiՇp/K-i1a Pa>@zش-&Ob_50QL@oNcxTMk0ﯘKm9RNCPJЄ, Iލ}kٻv̼7zYI8m1J576y~NnM.Mމ p}u ~B kCtUoKHSrѨ6)V M @!Ҹ&;J観>VcywwREgAfۭez}Cf GKciN)b"B{+P(tmlvv'eRK8Ӭ9L(Cj8.p=Hc0!@Q1( R2Bg E'H&HB \Yl4?ZurA9(jR!X%2_nQ,' q"w_b:pV4$-&eΎu2$n Yޓs~!z0;XjzLBR;fcUɍo_bB!3 Q g(URƐ)DC_}@54 +Cee|qeLU8Z,W3_|\ &k2a\_˅|? Һħ}jo( qZ/!ax PNG  IHDR00W pHYs tEXtSoftwarewww.inkscape.org< IDAThlTWv?5100q0*pKҰ$tCJ) j7!MT-QCZيlmBZuHMpBjdlc߿f3ޛyo<3vGz~={!Ill_# t @80USLigs.}lX߉b:N3aIQ0(JzHif%KLݻvYaEg&01'ffv Xdm|>ĉk{ュIIIŋl6%577D/fS{׮]q᯷m6Jdu΀!5 nkcœΗ_~y]uueJ.E$;;rHЧ3q11`H̄jf txD_u7X]UUcll,3hl(xWn?s̅7 [: CN Iks\zmʕ޹sg/˲Xn]ˍ7s:3=\o]>Xu!PBݧwRs{t>srrEFGG--=<44VkDl3aphD, -YD@dYV-[6ٹX򼨨gXWzZFTywW^㏗WUU;Ktȑ+OlcTݻlhhXvUUw.INdE_YE`Ϟ=_ !T}~9vU@ p!R n!SB==='srrF6mtS#!I!kB׾Ӓ$ %r|,9˗˺/PgB|J|-'?9kUqB7fOPVOO +y+**|)5`1K"WBS|O(I%?=9֐X}$CJ>yuuuن71k.ddd܋8-]t0%%ezVg8z`q4x Y1666tԩF@ y3x>hQcccQ< .XbE$_IYݑt'x*^~Ze}0p%/[_~=+z_<𥥥m===?imm`˖-D.|.$H] F|k>27ڷoכp$`͚5];viQޮ46=Zo,I:`p{ܶ31)nW._sCPtw젍\@/LwŭVx_#9 gϞXrcՅi3.ɦVDӝlsw53Aˌ`@ЬzСҥKP({oZH:j•r*QKk KTBE K/5vdbb":v*У(vO:C1\_^:Ђ:uW6-_M1p11x eqx=ac9O3Yx >[ϫ=X޽{kc v֭G~C IϾqbNSjGSLSu@vx555u60# 9?KᇘpyΝ;kkk̭r(/[:F%Im[!Pay~ ]@Ԙž17@3Aç'[~J 8eM4SD韄3JHNы0q%n,6"^~08.Kwe<5 Z'q9[[xZT5v7hm-mQG ڗ;bn4/ :yedu$ow 3ձ/f BpW<ǣ[C]Wyjw;KD!Y΂}R`۟եLzzSFڎa·>i?i'7;9Ÿ?QͰcadW2'{2=8'7j6BؕjX!. '%!('sqrd(0A*寽ISj8 {E(6-nNLN(~)R;C@O4$B W69D /F1(Qf>IENDB`iҵxjPNG  IHDR@@iq pHYs[M_ptEXtSoftwarewww.inkscape.org<"IDATxytTU?jIR d%"[ ,,=vCA89 =pqڭglgUl< awR$CP`^U7*ޫ~yw (V?o ɏ0Ot(+OdXyPEAօi+߱68 ȔiFBk@+=p`BcXWfZ׍3-c2`SFMwqppКS׋ccA? @bO5y)Jm۶M~&ڛ`6>/ԩS^}=bыaD-Q19K1o7v}~ \)((OkX+l+Ue!Z[ظᗸ='O.9&񯨹 D^orSSHKjI޷oDZYfimj4`ib쫿7Ū*ķzL+ܠ:;;3.] GP^^\YYy`avS?>Y3r>6f XiWw#uCe ɩBֆ[N;vX5+iiitXKff?Dw"P3Th/r岼⋷s.\B 9sΘ7^ĉ[4#pZ`>Nk5 i+֭5:th^O>x%%%Wy][l_K+,ѵ23ێC/n,* TZ#^~,@{{{|PUDDK=f nm~/f@ F>#&Pz%"cߎ 7oxR2x^JEEEb/<>(**j'HK\#N3D fO M*6*'c?P" .x/++S]]}B /ZtvvvlM= gӋ>f2#;6vQe(F8Nyyy"k׮=o4Ǐ/dI RBɽ/wÀyÊT>!8۷o/'b%%%rJMAqΪ($ r .2ܳ,DqSN ̙3;999]ׯo֊7of(;qѴ= ǵ Y<|g@@ss󨶶Vgĉݱ*YhQ?!+?0] v ]ZQ<,1r@صkW^Nٽ{w!2s~Q,oӦM v51`tE4Cm1572,M Y FHPXXUUU粳×c霘sSO0 vth=j!CY(ehDEh cʔ)߫+Wu攅 ***w+G3%M=GXtvv`۝g֝xٻzp{~1?ڰa,4+j[yZ(++AFzVc3+V\6L~KͿD ׁqjnwFz~C&Rmd /LNo3gNsr{9,}]&\ g!yBxeۆ=SFQ\P6W`tJ$"1c4n^Ⱦ}\.WAff"@PHo<񘇧%bIR]Q7#/~6( s#ze@LX`~~x84 ~=d9/iKw0l$(;] 5Ocx/隳"(."!BJ#ǍדH!G?pMcw 4_΄T7z3fNP\\Up䎨V@ BhresSxrHKd"0>ROF0Q={F& E`9E\I3܇^m_\:Z|3oʣߧ|f|;m{͎.^=:K#޴ht{.[v+AjjPQQѕD {뭷R0}CüɰDX,X1^AM훒p(~Rrgk%M`a #Ho֬YSdj^sȑo?=G E.ŵUг<Jgpl}2!\FRRYYk/*ɓ4]u99($kOw(LBcQрkH72W>A (.4 m!¿{0ꃦl6$D}RSSӘUJ:z<넮'X%s9A쭟G'C![@-a2č7~`ә̸-:C >Zm9 hbsK䕗nӦMq۷_4@ U[G2;^E@olfΝ;<΄!>}!z 3X%LV D-\jU̡000`;W'#$#-#QP`BVDc:>"Դ-BiTI'i4kUeeX\ŋ?X[[;%p`o `_>D헺;%);Sݝ Fd0 {=tO=h2 _,O^{v`*w!Y Ah|4ܔ/ZJCajx'~t:k嘐go7Ό=×%NΑA4fu}.gKfFxŝ2!O5w} б3nR ;OpqKB{SvDը퀸BhvZ'>05Tp:B $A$e!:?€$ +_riOјoV&{I C0gš?YL?z Yx*ON<\qAHF4F_4%{#$8[d9\t}'(ms>b1rbvw8L_qN_NdA캖 zULt׏[y/]i`]ߞ>`;)3+$'RF@H5wd44_ڧȎ_E1j`0h_X "gToA1062F]}N7kv%˱(yyyWQBcE1H#o<(,3!'/s  `"?eRԺ0Q6"@aZ`Ƅ6`rX[rEW!FB?E`CbnIENDB`"/x x5K 0@bn 6Ji H43!ln_g>#nKSϢ`jyn킱8Iiapl$( SxySաg3ݭM3^T߫r".>T:M$\NX8,xZ[s~@g~Q,wdv7DN(CP_] J:-=~;ݺxw\Md%&E7jPIgllɦw+]=׋q_gۺx﷗*?EY]p(bĉBM^.'DcJ~Z\M 7S/Ls}S_CƴPBRRįor7;\*H=Z6}yZG;K>Pm7?,+(*eqtg^v;&Kټn/;U Dզ_|RT G*!E|;c"MR$G)Ef&_Au[ԻnIhKV'Pl^.G.gjN7@[/9vPٖE鷐َf~Wofs#)r3[{4݈sԋqfjKqeH;/Q/t)c8 ̹هz̋ݎ\=$RHH~9+;wũ{٭nWGOGTU@CV9]|]ﶇԌÕA^i!>g"MPbj28g3^GjWxUqy$iIw<@p_z_H(+_6X6\7}ֳz}5Zq2cz[XΛOc{> '3Hx}^ywSn>bWklǓz@Ͷ|{#1g;rwo4zx $CV}ۮfo^Gj+:҃NuM[k=NT[pu=;t48;ծsEX1ͶDVcUiȘRF{]:-jWU:(\8]e2VTiF04?S6{oX@ t~~XoS5a4t(^Ոֽrz8dĒ=ZQ#4HWLXGE4Ui]~TUU>٠d |uaHiw͢cL7,`LE?:v yzj˯n7]\ʤHoۯng I&?&VNYTTVZ%A= F.*<ھ Sk9(ԣ(\ԪcT國,F{<|Q}uT콏rW5?ח4E_b"Nӈ7CڙTO wc[ǎbqᛳO1\癀.y $c/yNZy( GbY(BPgiٿ˗2ּhݘg-Eٛ[rN~-;0! x i%_73||r,qsǐ`&n~y惯|'vYv` fS %ˎu3vIc*[x*% T׻9e<6H#L`N B4 CY#oeб1 - 5+ul1D뿌'@*ޘS{DJkWXy٦ivw{#bQG'TПm*gRV.˼UWI獙Ja+倢yU1l[)w RW֡yHU(YE?E@(=@|]B!AUsUHQbDH߾5EaI:yg:|N~3.%[9n"i4/D5G- 9_#-Mmybs+}XjKZJr|2a0^ 0KDBzkr D8(z|6xEзu:~:X|% ) Di+Q(O)h5NB@I0rDeWd1|" _aH,W8er@79uݵ-'L RbVةGH?1PRy(SE$ g+8$EK2"B E$EE 0 ޳n8)+sP=J2[FO%Eӯ3r8}AiP?}Vl@!CU-+8rOlB ::)N܅u3K5,gwOU@;^8@8lyͅ}^ i>; gN (/1G N `xA=@ }k_j% J<f$M(A@ب=E$s%~%8LRy{<tv2&W֝"S!Up$10/,BkB#Sd|(4a;ZTxʹHBMPtmi=e*( r$xN>Ef[*/rL $(Z"|!r9Rp876IMC(e%e)y`(+$4V.)A=}ӫ؊9qZfWpJȉmr2AG\-NX܃s^v$dp-rhCCut (+=uH:>Ԟ,cVNђSr'3H|Ph(W#=!\g|(HE)5{[*5=dP"HIqvH%H B뀙5C&[YnQqВPҩ|,2+xdq(׎3&x<bz+q9K]/j -&u4$i4w|i="˜gPʏWg+AN97rr椯=ƙ)v67ֱf?afl݌5ȶcam?BNeZژ$Y?6fv0җZxtg\aݩBIq ^Tj߿o'xM]j0u BqJB)a#/a}Ɣ73,m.ί[8Yl,fY.jd9$%XVΔҬߩ@t&KkbSxgjf :QZL9Bm҉m#N ^X{xga 웚;QZ-757r:U dMox d%dplex5N )d/ ,F[D- "Gɦ|f͹8>/d#4cSeGa:ڈM:Pڌ5LA>&%ÄOol^6Ś_;-eun^F vPR"A *Ĝ!uHO$jPiRxՓMO@s,QETpR)E"ZkOQڏ;P7E=ggp6@eCo_Ǖ{/Lԣ&?\9#WZ$\:cFS(y+ɝDIl+uR,nՎ.P'b>t^;ヲz~gi5Z?V>61B~$Žyxx{PNG  IHDR88;sRGB,gAMA a cHRMz&u0`:pQ< pHYs.#.#x?vtIME IDATh{PTǿwK@6ZhI,&uSaX'NӤ:gڤ31M 35iL$ִ4Fi&bU =-cサ(w朽9ZԢ|8)ɶXX.Hd%txx893fOO5w.X@{Iv8*!mEuhPEP]_,@qUmCHVmM$r]4[fÞtP˚he'[\ {} rp $ $ D)RuwA ؿ*жWtzx'Uh;/[ж\Q z7!^g:9AbFf=4`z q]~(r7"iU`,9XÂL:XCOYA50_lI4پn-Zz(?FUq>q6n!#hL&iI N+,5a6>&MkxŁ!A1X3U0fe Dgә"S&gU#Xz0ͫ~:( KʟFt s& Q"E<ҚC#_A,y4@[ᛙᘈpۯPP>ON- +-I55A41K9IWFvK£<-x#" ?Ou~mSZl\ϝXz&?-G \Ɏ z#Jj_UzuSxZDflY>{4x|ی}[po|_e,6Ƶy]^(R)#;>tyYh< G#7b d#YwzV-6 XmQ"JmEui&]u] '/J~>8̫Q74` R*^2 ͙3`{2L w}7I# 2`zckVݖAd< ;‰x`Br Ѳ"Jެ4M& j'q;sgN'%joT\WWms˙-=pMhgNz"=SP:(E.b> $@1e 8=cD8lE-</isIENDB`REx|PNG  IHDR88;sRGB,gAMA a cHRMz&u0`:pQ<bKGD pHYs.#.#x?vtIME IDAThilTmxƳ0f<3q QUD"P"4 ԅЪiDQ*D 1E CBTj46c6l:7x#3>+Hs#s6gsv/iQa:c E"_1Du DWDi͞ᨾYc10T23 !|&ժ[^_3+Cۛv=3dcQ"aDJuY=,k:۩%9.%ҁ،=t|& ,YEe-Xl8;ҔJmjcه\Y`]Ǝ40h55m뚶c\ہi,ָ.g YҿcjHW2RP8HKg7>E5˚7z&CCQ]l+bwLgkQ:oʽ2vJ}yA,.̀c.B BT *3i!sRie|)ߥlf}ܲF?z7؎, eBHCD0xU`Tgxp_?|ե_tg-_gsGC{p!"? !=qhmLz@i(5=n6t|ћǺEg/xDZKR*:oQ$wr} JQ!Uǫ`[}@}oz0&JrD3*zฺ?‚w(KIW:g C=xt^;ŮCey">XqJP29^ؤ;YcpK~Qr*%`$o'-ޤsi(@D.Y$^T~7[)56l >rg5=cOS>CN&YLioDS}M_FhrpZ +"?wDq"ꐈB`Ez.wqb#):$i#Լ^<2aLh/aBY$쩮Pv֢rJ.2 dԪ"cJAGB`$j=~U3++lpm2z/`p7,F\\;gϨ1DduY %ר/v 9p~Vհ2rƐgȎh"YtpI>]Xv4gs6g3ktIENDB`ߴ x{7ol5JZ޵*)bSV{njZj&F7~ܿ#O8<뵞x- E|J$ؘ'3W>:(ˣ tu! yFIGb"8}8r2ϯ?T2(e7|)q_$W!o P \9r c5tϿҿBKelu r@ds庫ETvQ5I5 ѻ*ILZ0)Miҷ>>SSZQX8'<% <;哜.4'J"y U7DK+|B P9hi-KyByF9卜^G_X,D;;F}_:Nв/{n=y05r ! trPm-3 qSq#>nGQ;(YjsslexY6&;b+ct^?yhhhlYiNHP$' y֒Ez&Yge}`:1l}p /`l==]ka6acÞG6IbтkI̓ k׃<̋W䔇J/ϫnC[ RGF޷:R{7)/ 6; 6{@ruLDV뮧h[X[..Z*h %(R0}} Yl۬e^@J^h_Rّ/ezc83NۂݛQlnPã}- Bِ "  Yl݋{Vˈ7*фrG˝ .2tH"d9RD4ڼwt(RQzx\nkjjdT* 01d?Y6u\tcH/]]=Q@*C NÅ*] " UYŀn<ĘOzo$*ӛػ|Kfܻכ,P!7#eBUxG1(`*+slS&Mc?D +p:|3zszyZ 7k5ݘr|^'a'Si+9{FB^rwZYo5,JY?vycA ?^[cYFAAajiEsdjcsmhWV+V6j; Θ<z#$]xqg4$2={eKssn9i,Jөkze;.KlX-?0oM; &F)P#=BRO%QyԖ`(fQ3 ʿvp<*@*LV;!R<:*Qx{kulYe*MFz,ڨUI9e#d_0֖?Eger3GBK(ЭYj.nGMBY=@S^s=1:E|XkQfl}\[h`-p\i$' X k^Yb}t3'끬0SlWӤNyx8V_-i)٣~9܋mW \5ːoT,9$:e3x*>skuhkTBkфSSlr-cz(y*)#~kIi75M`߯ s-߼ IzI]HtMiQߗM%Loze=dEtlE"V ^ uxL2,,,T1Xao0x`8} ]H::Gz*֏YNeT a$䱩leiMFS02道}S>LEux y%EHH^n7kq,vrKd+ګӎ`ue%D_@UIW7t4{ߦiJg!1]aE"ZNlW}*14gZ!=Bں2ЧPȻ ޱy?mohj7iڮdOt w'ޟQe1z˅0ܖ`:JSаt'R)>d _=[mp0 K9% $ȅ"PM8v)kpf^Q!fW_|za64jV˝/P:05i}&kEgg}4?bF$~ VctJ+%ʦ?ЎyIHɈC@tɌx+pb~"|˖@иx_d؟Ȭ@jۋ&R俐#𩷻g2!`OEG$Z眀?S(f:0W]jQGzzt>\/Tk;q[ǐzGM ΀rWohW"q0[`»Nז͹̗[9e]Oa=k!=`IS=Ol(Q۴LxatZʆHJ e[ %QğNdRx[Ådx{"VY'gC.Jeqi59pI$\j*.S<;ҟ ?Ģu!'dB]1#YK|ovp}*t\k״pypDYh0#' rݎ ki#[S$8K`+/MާڕبWX 'oPE(pJta&bf$>mBRC#3]ҭ8!$v}fsM&;]FҝLo/ze'tu!9sl\4ѭ  Դ'Z!Θ1ŝi{pP9CS` @|Ad2zds8Ld{Yr-٬^Rj.޳-P"BytPE,p°){HS ʨs3]A#.aH橁6'M`M'ۖW5g8Cvڟ0Ff#/K;FEIlcHx;xwj佦Jez>a1ҫ7BRp?!cSuxS@<}V<2u> `}K4b=+7iDŜHG;r~·Ee̚9j)*׉LauXV:-o5C#ͪbɧFʪ0,SC-N3 <(Y<2wA`n8f•ct;tZ1.^^Bbp]rxJR4G$JosyG!Weն:O?s`oШ9U +ŗLY4Z#0+QJ )RS ruϱ ꪱ}M3mZ T,kLOkQϰ{JA@ 0&YʟTl%M]5j0Ib~xBνUfٔw̗k/2b^QP|ϐԋRsz{kiOϴ 1 w$ǖp'&Z=!EmgQYګueg.aŗ&d RE'޽ 6HjCIwcTND2B W}4aNr9]Eq1 #X3ڋ~ЯvT lnsu"Px|r/Hr9w9j:8T`*+> hxG HB%.Lp)DK3P6I/3!/$]{ZHoZ~FFLIܼUȯ`~7&*FW+]@^l8B^^v#2W؄ CW;OܥX+2TXBgFuLԜ8]ܩd aB)oP; VRV \BCCOjڛ~#_wa%DĦ !%9Ӈ;'T.H޺CV16X@I[Эr*:7="Fƚ,ǃ,3Ɩ]*-hs}jl<0U݄CJM[qi kj ' H걔k^]4 j ޭO>pdF$=$M`*Lܐ$mD6bb[4 1*lURoxKыmED!c@kf<;8jYC4s e7\PL>L0PqufHSf -eժXa`ƯD1֋nަ5AqnWN8F`k(4,:#E/{MOxtS7׵B.QD@aM?1m[4+"<㨇tHg$\F_9*c%>iCmP6ؖ؁L-6=f\W 0o(C| >ƕmjw Au4:l7 >/Eyv)'%ż\\ﲕu2*_pE_D3)ڮi*5GI1mRŶZqioQÑy\tK?O1<,z _Rg|̋W:ڱ$.W5훩{Pcxr~PX QkմeYk%8m1c47 `/b 9 Yt'h-̴tPM `W(/-#('7@~jҧIJn@}NucM޻?wȿƕ"j)q; ַjd}My+`̶7Y*-jhQV}0޿K!(a׊AΑ@2SID6gطD߇=~˕ǰbP@ KX+l3NaYRJɵ pv5y@*D 4)ߵ5_SU90k2/.9ȠYXtc1H~طtȦЩSӦ{62 4$ .ۣ`hYX)p6xޭv+/"3+`N(ES13VQ؇ x@RSjBɭ0 tm%.`*;{ޟI=?Z(b 3Рj~y.jdMA?i<^+6~|WizBNoC3pL˯r"s4(U =?T{˯?f`(.F2S2׾AC:Vs_jvdžM26VKzlf\LNIݷ%-^t8hn{d(؝7#$)/lyE7勚t*mP{aW<统 i"?7TdIe9bb-KA}domDED/r!{ =\Z4*^Qtv  E0:%"mu.#+y:sW>k&T7ԑMW<<2{2J˸obVo?*Sb|$Dž=J/O‹J2V¾}9:FhUy5IrkL?&R Mk^9WYZY !JRyu-CCRc:v'f:ehY,htW8f~"==;s]P$ڴ ;ݻ b\gn8Uܹ*˓W~8FR}Zq7*':)E~/_'^UfK *ؚl>^:TU9qG+ר73-2.jO]ß78b v'\t"pAhshxo9:>gqz]*At=h@6Y>/bc1sG \tJF9M}OSwTNp ݪZQ \ ~Տd n-V=+уO-v-E %VqxF`#ӗ2QRi(!mPP微6kXXȤj;B@<$dZIO諙 b`_訔|uStv5q@gv5o0ʶwҷ{סa_7)fNKH'-ОZƟ>O( {aEK&I杇D#\ onɳ=#V@t:|MꗔWޡ&o6FJl k]Gp*DN JkJH'&6l̋>߾ >?]c yBoR.=떹]^1k[2[|?tsJzka%uy8Mg$ʍ;N'xz6ҊܪLJȋ'W=lG:K)>"1oi/޻y`v>ѥ҉{gZ8ǃ :4+ ,@ݛLz)WiVA~^B9? ;6?NM_Ez|Y?W8ۭN&t_X/>Ѫ]s n NN}5K:)k"p=3> .r_2s4ww3Y]?m_kjK캹v"pǡ!2"IMUAYn-AB#J)= HylC]ztB g8JXluSC}m=p~;;QHv_Q"9h)HR9 +zȇ/5T%CFjF$U 9->ӽn JLuK-CZ^]#וBvҡ#'I|`v>w(nC#{XDDM%s?WFLB7ƃ /蟷Xw(uڄtǙ%r^#g"O W;̯.=flYd˼{G]6~%s5+u8)]kh;˪{}~,FEuU,dk$%%vRRrdp{bQ*fIA\AY`lT:l|{4d|mnˇ+^~9k&j>nz_@࿝%_rO$T`[3n.c }fxKGGŭ!)9hda2 a#5*j_}uA">_QҦ[# 30E/bݷTMwX_e`Pk$h{={ g\vYn#'R*N]Zwc[22XnJ":r+/N_>xR rGgJDTg>V “[ۆuUK\]չLmbdqI/a66f1]qO>*<,'ݫ"Gr6iꟆCఀt3 I8 {Xg9潧V|?c^F8MXF$$]PTX;^A[&*_=ɓ!KU_.t2}q`]/؏Z+ѿ kLZ}(S%] 3)2ql+~ИEU{~'ѕD#AbU qJqZ!yQ*O07 * "g5,"xPu9~M V`"ϲb#Ѱg瀒 X;nfTOOuP$>l!~ރ+3)SDVIclQVI:cڅnG:oqV9^5CDOHB(O"Ίr˘vқ5W8)eiʊ 2 YCW\2|L HK2J^ED=T=͸KEoLeD0'#'WEKwVviL%@$4瓱fbD8eIax*?c|+,?_9S[8' &5n,MOMV>Y9r'rrjҨ!CBS"ord!ϋ-kոO 8UU?${\jPR%s/Y}D/<}*goj$Wu0SZ*|8KMe_U&/GIFꏥx̢H0эz?{hQYkMmcͺ3 srMr68@U;JYUfѡNʬeow IZ,m)8ǛУ']ш>1I(}oDۿm?Zx@Ӡ(Cw>?jֲ_>}ʂ\ppz\&CyOwUwTO=: V}?2 h1фߴֶ 4sK U^ߪ 8is$|kBc{j'e(`XӿpI2ni=0PsX!Z`Q!!JoKKK2XU#Q{9 {WQT7}& &dsrQ>ty=G> 3Qtu+aGDo9`vF{k?-bC RH{CM8ˉFB۱qJ'NsͰQ:)QMx@x@ U..هFZا0x.(Ȯ|._s ^gtboBY\S=69`QM׉p\9p g|_-Y?m%Q8 ͠9' +A,EB ]^`dІ'/|t7' ;(BQ8ܷXxv;Kf~ؒ^܅8s^!֨wWSRmN+uuUo 'w?AlN9Q )]4aMn=Iم4s{F!z^g95jM/PQl \eK20QnkR6C 扶%.5ZR>rڈYҫr;67'!e>K/kZ.6(d V[BOvW+ B. ~_y-AO}.uwA10`4+t4pӳ#}J>NtssR~T=aF2+o"-.MeUzz`K8n"OnC{L1z緉*:8BU{LF!Z}LS Heҡ-'oɤDLK|5P<  %!a!Ђs+RAkiuͧLD7* ht 2eЎ8Z7\ċp7yRz. T)˸πyfޘyed"߲&~Z7%,*,djd5'V&XZqPLʰ8YGy'D՘aK%\#.wzBe^p76= n_*tC4yTYs>4y#s@[eJV|~Xt wŅÅbp)O'Ѩ}M gEՈ5qv; ܻd'.t+ti12v *GK^?ayGq"؋auq 7y.c %M7M< Hg_w{<5%;^Wu*ϡgz3()&2_qn =}ȍ1ns= ^|}&kr9wiz?#ʇR FHS&&0Aga5%"3|BoBS,FvN__Y:/VVomI[W,WZGf-~xAh࿭AUtjx 'OпDlVFOٝ(vF֐TBCYc= ylԏwA|kjjkU!AGfEHRz>48o~Ӡ<3WPqeiOaMdg\ l]IYa-KyK34bufI4\r_҅(@D~UY? G6A& ''l@9pcZ3 N[_G<~¥#\&m[:aw[U8 qQ n#y*ǐa9;vuݤĥjLbSauf4Dͩ/9=^ WpN:&~l,y\uw0#DsZ8;n5յud^{偑GCx"qٯ~|I@"ɚ&Un02X̝ȚK>M= <}BЌkoV6gisY3ɾօ f*#om]]AF~wrAJ՜#K_Ӱ6s {lVeQϏJ,A¡ώPa%@FѠB)QajAvV~?2%L3_,{=IɃqpigR3)F;K#SQ9 z.E@uZwX8C%vpxJ vfJj4(_®xe)yM]mqTt~ّZZk}L@䆌B{4~t*CuT2s4"B!-pG\bA]|■zߋ\(35F`Vl;]ћI$,%U=7Ū`Jl2N0z|wܾ*'q8y |JE2aA{fvFa@θJeAu ybH֢A] 4 ng/ &>J,/譪ʳeVȲ WmK@ k1g_eo7!]YLTYIO$7סXEҡ'xcf6a?]q}U^/ZrVӻ62FתHVm^#l):UpvtL}[N/hKQk+c"'R)KsW@,(/LRJirAYϧ_njׄy??208 j;nxCt尨KJY&i PZWߨFRa!Ei_J'ͪ[ |X|6w =?sֽ Es>pl~kz5t@"Fs>(^/CR]WkjP?FɆseT^dDޜoL.d jn?s. B^jHuN!\+}bK-e)nʯ~y{MHp3$\Z>р tt9mx z1z+3ǘހHODe\tPfڷĭ2)C2pRb-M|m 1X8/5ԤB&6Q*}۷lËS[O!OL3BTnL|.Lkzۿ)ݏU^8qokI|(J=/%l5<,U܃àG9Ӭed[?LChF)Vd6iHk˾O.oT/C}L|S3R@ƪ!"N}F\~0Yzn_S>Z^,,w+0uX/gq9mhSt7nI[ ilZg46uN1fDZKx·ae.fy}54lo}+/umczU&]rň4X_(V Ż\N 0xRFYoZFS+ j՛Sr"٬\c{$ƂVonf +[_.4<>|bvrϲZɶqƉe*Y}~=%|pK}RU;K$^)~7`Xϕf-Ѹ>bkjXE<*񠦮4{G Hki,Fwqӹn(!Mo'_kYrɭd[D.qoBQ_ɳu4ؗxȼ9˿(/ ! tH7 + W.XJZ]^}̃3ssyjmu=R4QLK21Ͱ~R y/n_ζlˎ]@M,*]7Qrxo;:^NYel+I [1#*pCsEȽv-_Fļm(1p]E0ne/)ot ;ȓwvfο1i$3=;Yg[YͤA:-j b}= 9YY%vT:uZv0a  DGߋ-'ҝ٣++j9pXU>Ѵ_+I~2SԊ5}9KXݥTo3urP7xt5*lz e]&;k.묰!L <ÌFϣe IdR"V砤?W4^ Ux~HqWС ꡰz,m:c3CoSA132m416Cjij>%_i&W6" Jmp@:8RaqnSi> H[uLMeC,3W]75VPH9C0qgbe>h)Rw϶M{*5mʄ[QOak& :Z,}7h9#TH[+R\Zm553Ԯ5Hm8('qœ!SYT:<8RyYlyN?0*n啤>K 2)NohmP+^GTwǎDS_$^9i7 oۆM ۭXߜԴR Yz^3a`-*SpJxǨՉIT1R$-9x&eRY)dmx5g2VkXPjwm{B2oKz,݀-"$d\߱ 齅6s窂iϫr~!ǺUH֙\,қ6q<ͱE1nXƱ y#OVEUnbSA}k)ܠjF3V6ǃKy~'>^PuhIHjQfki9_;VϠSc-*icȔFˆ8Hs_qR� zh0 FTfrȽ7[jK޻fn_N:J:=$SDi2ǔ 4+1 $w+E$)eO_y=Z@+R6yiT޵RȈ+G7VKl_UzHTª*(FP¶8iD?5:kdqZ\~+A+Ȥw󮛔w{IqtvC cH9w9ye ҁ\<4=`YI}$}%CUG m8CMQ٩S'濵޲jS)?0|ø\-ћgv)s0FXa]=gt+{uxBf\,JQ7[bTqi̜vh#fK3{ R33tE9z` lyB&`v+*J ~9'"tKWn8;w1ojy5\#{׾3s1wg9otP@2OdxRB`kdwP}7.5< ]ʼ |0Rhߛ>Tiڿ< ؑ)ngͅf.2f?-~%`$U7, xC&-~OP]fE AaƷ9z-BS93L뮂.q%> {K- /IOX\bb:=fMg5 X噊1*ھnlEjX_-xUSR%wx7$_sH98>X]Uƙt!;(}" [}2mDV쌣+UJ3u _e/T%чNmoG9]3ʩO >-u0>\}Q)MK2AܜB FP]Ðü{V 7}aƻ>]e既~ݠ ԣbj+GɕHe !ж1 dE.]TZˤԈNM OI;kxRָ`uN[ʥ|.@%b9N. HN>P`g@+S*xE5%,siKoU٘"($$?NN7 nWgtV45 0` UWV8r|?TnrRs¸?)7KLpϜuPbS/]d@CMܜ$(T+URYJ4 ^ TMxH/}KH!'Ǘ΍^ XcQ 0W XY.j^=kV Q_gzK LѮ6^tk1"C+in&^7޺\3E|r|A#5k)߃3خy=p$IPv֔Uާ#/ p¢PV3M p)l[jꆻ"{ݟE>?;c~y]ŗT>/\uúmƉOM(2ԧ?o)]9ڿ U`\ŽJ/5U% %a.b{Z[p[+M1SݰpƊBNk/t WCҼ,)c掫}#R" ac?4+^NrxHWlj&GKDy[~ %ڃw3O{?_j; ~KókpO0jmR{E8+k|z4 a$A鳳)߹t8VSX,hdJgPY\WmAz/εnk;fo.߲#XoITId/l8cRNbIblv D qRwz=wCL{POik(&Z?wlyVdX06~p 9&pEq{鸀jD,MKM/;?RgPpʰ$8Tz=.d2O=?bryK3SvZ sX A}#)I'g.ͷK{`wo=/]_ ȕ=}G#,Tl!-OSEtʵ%)+{>s LYs|zs*uQQSm^Nʉ?wM#;2! t[>mi8쬷0ӯD~5sg='[YVBgϼ#UEazQsـNth8QKbk&+ܰbx}cFCGetm٨rxQ?LBGhsU ٳLjS^tѫ"c Qdǿ,Jx Ӕ:4؂oGBՐq`-'V|SsIc>Jm JjL'mS_-^ksV5 :<p].aӾ鄬H?(e1Ry1R<#<LdžZuBi 򜤯{t#W:mS*x~w CZpkM]aE-ݑ}[\QFO}LI@%tIy.<PmȔ DS @ڜ* YcQ}YEbz~+ph_F ^L1{xR.e4}񆚒_߳(ulT);S߱&-| ܍vӊy/:~]/M=z|%n(a&dpk/4N$4dca䇌s i=R 鯦C7L_ܾH[}ѵrԩu}}Ww<\s"b.?dZKI6ö-YTc+X]RpQұz[ݬdu]* qpߧ5"Jlldm ͒," ,ZLjG, NW2_;HJ s$h^W:R zדV ,MS{H4*=.ӯ`-b@ "gd~2y]X OUɏ)@޶ uz4=[w/ov&:u鰡o8AC ?r? @|pgYBe& &CUy=ILE;w~5F6h#yX^$t64M cp}zA{H6nt}@R쾙Q1p3/4ol =x־p_v"Yd\3@(% Z 㜹dճpVP^5$*--< ӟZGy=8xY,jȹ6qٙ QN^6Y}kᣔĝ("/qe7n!+چ[ &<~<10cf7}T2V=Sn%mQ`W~v]),^`IǝW7׉աn30}yȶԪW+0Y¢n]FB23)3Q|/o>@- U`cX?4|8_R'PΒs1Jܛ&mذ׽Y(1v^B̮rN_V0:.C]aʫzB{յ\wHy)5P˓Fƒ0U74qtrZ/_܎-浧={jfN~= 9#yT3RW%0$~OVpd_2߀`c?x2}Nh k-R +>,+PNzR|k6>o6uZqQcؗ"1KCOmk(rx\^<0\cSAu%kaՕáo_=b-#-P ֯&WV>7ϤK{'r걊@LW|ϻ:Gji؏rJT4u\Z|Bp0;@YOo54/3NbJ[1#i/5F B 5o2"v S - nR˓%=R|)$ᩞh'B>eUwG_vWo~3DC ymn߉>/欓F}`}yň}ư'inf'PDVL/*mW,)!gennO!U7a~c*yusL@ OM d\[r(SmۧCX) ^quQ2y]_%g|%V p%UTlj5;bk,D#I>& {l֠Q[Aaa7:l27C8Mw!h˺#4@6J݈c5D Z|G1iL"MKEtjt^`m)ab*\y4ʰ'w-Fh7Sďlis#̫Tr$mѺY5TzedN?|ީ"Rsh}9|+#+/'tpz]lJ_Jy5kMf,Cp'?V3]/ <&AAVs-O ͚6I}~LV5d[u{)uھL'%pD}hن ~SQ Dk=ȑKW7{3*>1pѺ--կ]j],d;HqH`V\Q L~oyN:t{P2~"Jҽiƃ%\vŜ˃Bzjyn$ /e ,>/ x?GD_HI>4)@)0e?)˻A;qq_n8+JUOV% ۱eIpz VUs4$Q a؝ !fZҥi$CKMb?IsWPμS+4,uL ¼Td w@^^k& 9~4OnmmQX(pbq` Z[*_/\gL7Ơ:[*PmONӻ: 47S_16SʈBY򡂋yl)6}c[xoQ-8CEkuo'O;<=.J KOQuoaL(LA3?Q!w3USMu{Ek/! ͡Q xX 3閯K7gqüvj-tc%Ob۔oʮw9>ڸ aQ-pEښ w[cu9ԣkYʁ&ec~* qJ:ºaDkQkM 3f-me@&{&0ʈ6ȪvՏG@t_^.7/QoJf#T@r$taj Y&Qbk(R|({Cc#c&["fXK2eAu tZ.hD8f]>vPo]"BI9ڋn;وEmoY:j?yBzivٛpbO(F(C-aEvٺIFG`efPO%$r=Z"orsafvk[48Hb򭯸 7ѢiQ)Pk>~.w(n1#Fjw>yt\ޙ5޴u99zzYk+?aۡqzSa*;TgztK ITUiG>>>I4KuZЇbA!嗩'wZ@  (97aȳn4N -$v9>L֦әeQƤ'6!ɮ6*mi OɃ9TJ3eGU008dy2jQTTEf #C0Kie mf$^SfrGK[%?)g8 u2}f,Xfo=mZ-t=!M]~sxe9q]$Yzi^⳽p2ՏFC*p 7R:?ɟh[@$WQCEa _7ҳzFםS---c/"*@ > uh*+n(> [+ 4U)x=q}֤AZ4[4n8*`^U0CmNQȍDg;*:΂iM~TEHkИ՟@4'Idr"AO̙ğ3Ly4 g,Z!N;F/ ZZREGBeX")L#ɴ'н,dj/#h8,noXU3@oZ"v,wӦn 8}it݈[4&Qx sl H)vƆd`nZa}mzon6v<0G8OQn(;6YoPb};W8JSee0/T)ڰbZ0rET nĪ,тt.$Bj~XIz:C87 ҧv1aS3)5֧8 drh2H.P.F$O`:(yү%H?5lS+gZ^R&9 {SWJh3o:Vb~ǂ{@zzsgxGދiܞCxVI7eĕhbiy=C0e2ln'P)6r>]{˟aZ瘨E̼BLJ}zYV 0r>LMd,>Mᓔe~1#CH-r +m& YA#ǜ"vs g7{^o w>ꥢsLj=CQ67Cm:^?2KoqSx^g,'84)d \Hy !^OE0,@ans 1&%5=Q [(y`sÎˑsD<jY0̴qhݤtdxƬs~l0Nee%Cb&xo7 4v[]UZ;FHFB(=xtY6d&!1Ag>@q ~hHK77*[>Ъ Fh*dY>|F ' *4""<ϊ;wPZM\gYsolg0|,AvYο߅̔i%&ޢFuwEQsf~_.3:a|/Tua~7w@N+2P7v1)9gMVvfw#qw +w*(ZfGz W? i:]; wS9W_n?Kh|V`m'K?_P1<-@zxZ1b!ϱȱo0<-ly즻n9Weр^|:>_+U+&<:2sAᄂcM`@5/|h]'9d9lԲXV 4ryNQ|ٱFkZ<.JJGȇ3y2$1ŒߚԂ1RG!T>֨.-~<_}WXO7 @dBo6_,}tW7ػ؀3 Bc6 "ilE]̖]N8>q|)+Y:^g#x{HD1?ܱi>ݶ7t  @UdulRdd )ft83F@684Icҏ'D&rǼxlؠQDi :KockȲx\Xa@ AϨ281s +a|KkɔU\4#dxh4B!9kᏘc!` F8y8P^;3h̚ϟE9%t"jMI7!kqXIYB |q,/6ܥ~r7# sB̏ =nx1N>]Z.+* =ȸ}NӻsL.mċBvw4N-!2r%ܚS54>aj42<ȓ#q\uw>;q{{`\mJJ!JdɆQJ5x+UV9`u[ 2P!\PXZ.OmQt^'=yEztJ)eeԅ 2eԉusR@_8P{@L@ŒY!v8jta޺۳(C@Y8(&Hb\4BW&( \T`B`> w\8YeIad }". @y|P8bPG2 X!\$@,yX,5v ;PXb1J0(zRNIJ +&73(5 @4J5VyChrק&F^`MS%-l`xtBQŢQwe"[n:H$ԑJ7EEw(0Е Kh*J`W7xރjy4JpR5`节n!h+ <CD>v_VwnJо#T-(^Lg /R5%TWN]!}>qOGgf(M_[9 r39[|OIy2gޟ6ŵT@.^KcbG m=(k3X}>##N2/8\˞R 뇧yp;P)k њ۶mC[ݜDsڵp>LX~m0^)/gäSVr~隆j>=}[8R}b~J:&,Qhz{!B"g*Xo1JwU. \t2~z~pk_vFo%fݛl)J//A3W\śo̐#v?v,- mu6ɬSٱ >/Te|_[ $$V92lbמs7jGi?u/+]g?Dru5k1Ei3B*rj ӟ8g+apԻͅ6ۡRU߅ 72; npfy3Vⷲ_Sk0,^>Y:*|Z1e7{=\MiV9n$l}>`,4T*NS2'@Sb||6SnD{5~3grge\uF *iXˆCxaʪW Y׭{etXlכxbj6lJ* *ĝt6)CF?v'[#lǬ\*_6TA1M$SXQL5>i_]hSxD*!)*IBN OVIw*ҳϻ'H?rmȋ}/6vhoduU-TnBϊFd 9>>p\pC1ǃb'wr7_0'tjwAyuʧ^F핕|nem(Nq ~yqjW0Ґͫ5~nnOu >s9tbm<_NNNe`<+&$'Gm]JFy屓Myœ.`M0(J@}Y8)fzs##>fע:۫#o>Ur)kW &O5;`W*b6 4ϴm~(T Mgz]%EIAa#}FWD#$*sX WΪ=@^TF$}aFXJB(2na$. ]_X8+Npoh/OF<riiE}qF#%Ɠ~[$ȲY,kLJ' AC݇9K#NHD[D8]^YR m$g$r˸ x+2\"" IBӶtLLY(KD :`Tp z72R_onm((,, 0&[W4UZ"% Z oHRJzKy-`㐷S\WQՖ/>S&mͤų>޷` RAѵ= ƟtnrE69~<Vumim-.>K68ػ놘/5ڗN]sH .| oLzR„? ~~to}K+>uˁCt~tweh.CPnpl)wXKn~!jm ӝ[Fso37v="**JpME30}%" a\]f9>5(6%aD[,Pu%@Zp\R:r㸔d9bx)!\E dBҷFø@8(h[3K&@jVCjiB:D"gdRF𤸒I[ ҴBij!uQȟcc{@/I9%Am#8r8D2I8Ń⍧:jg7E~p;M"i.j8 D%gD[4r @!5lNf+桑I$d%3wz **b޼Oj. @?2vǯ4xWu\S[h CT ixtሁ"!H GI ҭ(Hאǻs{sgztt0jiV.k PW.1QH_S8kC(dǣh#M ,г @v.'8 Um@i8xD.f^K c4j`2yV XқȬe_a s=Cpv:b LFˋ~;AzƅJyt $Q4ZJr qF7%8??:'IʽV04u@5holj0[ia¾]Ўg\$:ZZxKo-ZBG"q,6زE,Dd8]iƵض6pZۙfcc뎷`aPhbCdMrڙ2/o+|ʶ:ke>#(|!oDhw\0ۻ#:D=ųQ+xxz;LMM'$'9Z+՘[xxxl>!ɂ xMKĄIo2ff! ^a -uMLش-F?7BW-}UbOdaaf}<>{PuKPP˂$ӪOb(̕CBa%pTKN׷N B~>aI|ύɪ2̌3@mbb==֬Iy:"%՟m!)k%z~S36fVVƁK\31 } ${c}? )*133V?~o:dmNۀlMx-UeV:& ~^mg8t Z7瞯??XZY5PjꃟIJcx'@+%0 I ?~D:AvS'gg>n}I5AI\䁁v/ǟ W]Wuu/u=\@x<T +}GX (E>\@.zxTf0ljޑ#Ј[_mK#c`ҐvQeHeT;邁ᤝ%?,p]Ү~bI?m+ ?ɰTw ✚jԭ[zXM$B)aUTt%;;͛ +ZMd |ܚ)KQMsM2+{VKO[x<~ll̼-ෳ\hT70PTRb{]> }젼g?઼ԹpW&2C"dRMlmUYt}|jsdKS4ʺd2Ⴣ &[[[P$-WKsUFNo#76ܾs.ckkk9m5www@6QQѶ Q0dmgegffC~ R9lA!,u]>+a7wͭn;da!؋Y:ֱ99 ߨ#>ze ;YexS5;N< c١kl툎Esdd?==100ȕǒ]2w2ۚaYCt ֬I4ke.U݀By@}%Xy`mvK(J#Ҫ4 EP/A&NVn"9RyTT8(>fޢы -('ag LMMo3G8= cnF}ႂf0ӛc%Z%a4( €pЗ<[MNAON*R΁8ڞo "n6dw[5pۍ!{aKbګA앿IHd{J||Q(K&UKen%ox "Q$wBUbOooJArˋF7eAPSb܌2,Do4=]EUu}kk9AvZ)HI9z~+tVVţn#{zx6zPɫB6ն)R*8 ?VzD5Pr)4~pLI#1oqtKEYׂ˻f&&u~+QNgkBzh-ooqZZZs CS{8r8#(TX0*))1PSsoѼD `Ѱ\UUbb7_y]zEP0bserrDmnw *5QDy Z]wI:Y _q yzNwG k:'p}:Jӧvg޺2(,M7)((zVVz6c/aWRdJRBb5G_PW .Ddo|]9AGUSS>_ _ <fǮ`U ,xl۷;Zܳ"wlVG3`b~6A N%uǛ[\\=qcWq rZV+'{ 8v{[NN`Sq;]1>14#,BRjez]ex7{d/H{S% oʲrskNBښxAZZOUm) ,XƲ^b.22SW,B=]?\M<աW%ߥCl6]WceϽZʗLQ Ĝ(NS6bLlg_v\FFF pk0-Y_ߪ. XR*rVI^ =]{@b)X&h;ob|N͐GGސghpRLdqjFs V Vb78xXy8.[QiCY03"ewX*%RUHEBRThW(K*҂uιw]?zz}{yTOg%eDei#e ~q%O 8 fp d2 #&+0/#zb|ؚ?TnB)eZRu&ĻJ;w7c}Th橆,‰nR7䰇 ZwK^9,{p9 ɑTf@U9dۣ7P-j2,OײEJ*wf|H95PiP"iobor"%#bc(WYŤv">(tnomUlnS*>Ɍ {'3V.MVײKk윘jpN+zx'% XX*Z RD "p4:AЩ4 ~Esh  yG9>. iBKmyIs t8z _㛰zY[Ʈ:z;%Sڵ ]姯)O]-> Gqk?8eyד;R{iȔV73cBZxnDs/ER:F>wNI`Twd]}ye:X!s$~nGuɚDA{n R?.aw))&:+uGBЁ~W?xb ۯt~882?XT"]kTdv >&Wݳ[9qW~|qq dBrqR=eM*Ʈ~ʁ.uȻwDw2LU2gKgI${ןk/=5lLE廞y;qb"co}99GTmy+m&_,i|֑zU_*iɡ,İf?0uM=1:隝h:0)3{PtcGi{h-{1ĿK@Bv.?#9tzx+=kls ;a]d|A?P_AMC]Ȑ`x PNG  IHDR@@iqsRGB,gAMA a cHRMz&u0`:pQ<bKGD pHYs  tIME 0`$NIDATx[?D<!(R]Eu4( EFB)BA44zw""]ջD<%I zgƻ>n{+ovvso-kX;76/G$Y#yP23x|'!@4wH (vZC̋}Gںij%"Ml-hїw;.Ļ"}X{^wa&\pa2(՝OͷG93MPxÞ/y m`+-۾pbW`//t!Pxξ0" qylgè:u]s&gXw|5lƪF<0Bi8Mb>;&uq `,~M`2mɮ:8l-M -Ub(}K{9 zb$[_E!e H&\|)uD/*u\*Z]B+a]R4Va`L%~n :J=c )bP x 3ALuE|k69 hN *84 TY Z^`*B̺o&@Y%(1ԑ4_wuSΦϨI!_^ݒ:f;LBz~|=xRnE@ LUH%VBxuC hb'X;mv _g(<@eq>ogd6Õ2v=bOs\IƊ,I1jFwaB\ uP18@0r&{$\-u@Xhg 쀵E3iBo@,4o@V50xap.1Zsh,/x8<=zc>ـuԷ]l1 Bؕ 0(+#1(pbJonh; 4sB\t`E[39yki7e+yw7I`GKbͲVF_!'˯967}m7uhlFWFhĺ4 L!WûMCͽ 9f$>w0`W>_gRRp՝!3.m&}z=lO3n)cT7MǓè0ً6lRG$L:Xp=/q]3o]. Q7Hekr1k0Xa)6 f2L~|A$пIENDB`mx LPNG  IHDR@@iqsRGB,gAMA a cHRMz&u0`:pQ< pHYs  tIME =Xr IDATxkPTgt 6 M "A$1ꘉ; qf$_`RVTC%!V%nmUVM䢣آ3j2$^qѨ/ͥitهO>|<+ݸo+?? |'o{aELBPs,j}XW׈bZ 5!y^B.V&un0hv>F&\xkW鹏$Ajj:kdÆѩ*~5I}}6u{ͅG[ x~N#\bxxp-11?)**⛯7pa\8x!W  p~V#KNFeeeQPP@KK oߞ~$_*1lyg @ N {D_VVF]]]]]X,, ]]]XVJKKBp;Qo9#ip: u?ȉߢh7tM6aXؿ?{\{`8{ֲ`?>轖}pF q,9rJjo&F$\AEOv,coė_7ݻ^x]v@BBvt5⥗^>`b|o9رq8&ػw//"m>1)ш fB}l۶wy={pv;vgϞFpm yFڮqvgi1q[t:9$_@5I8x :9E B[ zlXP馹;wb۪Ux7hmmw{ioEy4(B]ܹSHݻ+!pf~I^<7Ou? Iw^ ԵZϳcrrr۷JF#FJ7ٔsܓstMR'v4o~;HOk#TTT=HSUU###TUUeΝ;:Ʀ( wf㏍uS(^VVVFzz:mMs@GG+oupiݲa] Yf,˔ymNyrdY^έPY`?݉޽`D##CtSw"{M$wA.`k,l۶M r^h ?ȸ/2wnߊwaڵLT }7b$4ˁ՞{V+.~a^֭['P1lV@_`|3#Ãin7h@>ݸSn7}R^ۭn  / jʍ}xXI({1;{X 8&Ɨ?HD||jF.G 8%OJLqIll,F5Z-z.גu:.K(.pL`0}`0t:BN:?>>˩pi >~i p8&BK ȁifҵ骥l" $uGUǎdž]ب7^C珎D;#> eFs8Nbt8].:nFDJUӋ] x-_Q;,츸e!.]2Nu@r FCkkjl64Z- !.,/_!ޘddLCjj6MUL)H,bRlff?JR)D/P|y26=hLF bJQ5XhbyR 17D(MM:fLE)GhjjRLRɟE};Uyx<Μ9uN> @ܰ\UB@A.`LH$-1jkkn@mm-iiV`0bJMSoJ}eEBrxx<+rVY.R5c 晉7R]]5`'+;gZ!!Zd2iA>j{r#Ǐ]]]:eamz4vNEQ[,_d,X|'l'w\6W09D^C޾hx[[bIHX>rĄ$Wdd>v߀O-err^{mQ!+믿$6nYp DvY[> 7p;&WC~~gϞ壏>Z~!Νcy-ef/P2=-ܼ_8<@*hn_㫯7ȑ#Ʊw>-ϡ.DzaӖ\oH,x!Fc"?o!x~駼!x1,3.4`doKxx|-m>f䓖ZևS<?#p嶀TV]Aݿ/#0p+ďw:W+s{1twc`pXc%^a";dz& X JB[ܶVD1lƶmBM$ R(x lX,Foo/SSS N(.]X,rʕU2 _Ecee6ted2,//[lY==@K/>߯_>IMM͚K&8qL&):tT*Ekk+kγm6^|}v9r˗/+W.*gTH+XM6&>}ė/_\snn_{*={ 6a  ]v;w6~A٣%囯Çs5@3`YX,}!ixwD\.Sx۷;vL&m6ض͙3gV]ӧp}|7n HN[!XQKŦO "HT @H 04W5=2!֝3T~UF} x}9\|sاDƃnQ݆/at7:335q2rm崩@)Ɯs"5"Szh6Z$$4F4 xI4! NtXqoH9Wʆ҉Pt8,4f* Ȥ0֢D34< Q@Y ZFR˺kql٩ޱt<}Ot)njO%;H#azUBWHV4CE/"lMU̼浬a:b^(IkOoi`fkP)`=&mzi`X'|IENDB`48\x9PNG  IHDR@@iqsRGB,gAMA a cHRMz&u0`:pQ<bKGD pHYs  tIME .7N IDATx[=HkIr BZ"X +`h# /6QB [ yyjx ,(ODl{̝fe:޹s|gsׯ-P.~(DzcHb[O`,|&OQAB6DeQ[N?~π3I½~W /9e&GH&U.03/ۙJ"P߾p Xqi:^OQkJXK-Z. X|k {|e|aaL&"B6glmm-.//Qxƒ/jjjֲ'D"FD(FGGD"@ qcnnC `crVF*)1>>& }hii-QWW#=|uuݕ=9zzz000ouuXZZr>cee[[[Htuuacc}}}8<<|3ƒN*^{yxaq̠099`0H$GK=l6h;<@eqK0 BFBR@R[wQK6;}T򒴇@QL@,!9<smx= S ` }; |rwbLk8m'}`1yú.O!1}mx=0f[ŎP+/2^UvXg#( /&py?ie; 9;lHP t#B G@ƥl,> ,˝W8, >]HlҺ̐)yUHj&<+WP"iOTY٪0`%SdlRv=IENDB`(D"xXms8ίPW&{3sI!8gdRU*Yo'Vdl0/ [ݏn 0I/h}!OSHX3..qd24;g s(0 )UJ7&2qzJ[mlQHg L 9`Rxh!'1ehR)`46f"R^Jf7 eXhec{hM8dYG"J|5J" @&v-e\``%e#R48p]bEb5Ȟvѭ9`^L5'mM㢡3ٞL`v+z's NmRHb)c 9pxC1LD3UH/ 9u(Dfe\}~VAE=`+%B цf:7=^P~Q&M9ɨA迱:>gt.>i)TdžoOP_0 hq![U*{I%hSR 090$0deLe"=`l8e˺LpA1Ks ^2}PXK HHA( z1H9_C3ABWz]M 5_ ,lW MvB/QN;%1 klr. e3XYQ&0SPVA!LzYlCh, 08ZJ4Ow= 5I%z 䈹^4 A&,41hꙞ=w$ &q6T]Y{yI=$V=ytOȣ;!y"GyNmGvh "򂀺`z}1Xs*qG8UnȄ&aC~؄ǫ&χ9}ȫs}/3ؾ5kG *V}s34mi.=<6EɁzpmcV` =2^/s.2..PC1^n=cnZ;7Z Ջn8[Ƞ~:3wEo%[{bf]?nzF3*]Oh!Psx)B>nNVAݗ>[{~v}j1׬#UK ca4gQ_w /OOdvoM=hm~ UσͶ8QfXCf {SA[:|)k=Ysyx76O%z!Ď`onU([d*Qdn')ɩmwu4Z8/!?|!0?;#kAtgT/U31kUx;rg+FҤĔԢ̜b.Լ ;,ZɕTXT5F!81dV'cdgC~>w=Ji~k Oi<ƿ?H} Ϟ<\6hHgdB[yCo ^HSG!xNI{iO?I0ȳR58@AB5$nD@.cd,kSJx,-^& m\mHX0=ewuuղ y]u.p, |F 韉kb/=>=!g!wq_n&)q(Ik K2'tG5=ꎚ]wjp6&a?vFd0$Gqw'&.C?.CtQazQpp%Dg=d\Ȓ 7.Ax=*ȅ sw?Bvvڱx4_&dna`r* ZP?n=K&0[s7rc0 73KCLԳCawMu*t6`~4&֕/hhwϣVL>= AD/\,z5aB7a'et$^4AGD.B_L"%ÖR/U;2%wsʼ<"KV[n_s^62;`XtXq z탊.KaȡT&*Y)] >.$Ptln/1 WGbGQ0umԮ\+]H<%m{g 8s%SHC?)LEM?՜#72ӹjGt[ȩ174-&lGy^1 ʆ`ƛcFfY;Td-0u!s=)4ٻ&oEt | sŧl : ˍ4D&ҳɿ4F*[2pAYر]I=UjݚڞJw]EzwA؎u cź-)ިYĹo/hJPz;q{Ap,U:.fy=đ^3;Uỷ,@CRPNq ؀ ɉ 0.ES+Ώ|@;7=?;OkM2Nۯ;C};f/rh a_ ^[a/v;qcAgnx;^t^vt|gN Cd +kv^hN=o|ӃvUI<Ȼ ` D߄ n0|=z>Xfy3L}~WCgڎAAؠ {vTqg4C\%u6ެV  jhn#Cs^pER]gX:f?IDRBchT}N ,qjPKEޡ9DuYѴY){g2?][_>doZ2҈yf>;*(Wǐa HԎij?iB0Ɲ}BUcTV%eWkG[椋z%f|kk l|m]N6ۦoIh`w~wEsjSw/B5qrsPxi;IU*EKT\SM8gZo*}Aµ5]83y0>m]pԝ{V+ت~[Ef-}8Y 7? )C>|+fOj*(NC;E&uJWz1L0TE1P<"Kj7re^&ќW*DAQhizRftt7z`xEVFsz%[#i{{d<8=02J82 \Epɓ͔\QpJE}1OIyv봄4Wze#B`H4(dblz~[:RNaxFc AImfL.хCp 9Mn #w#ӵuG= ̦6D8,Ҝ2I4:ߵn%;tp3?CBVmsٙ޽2pp[a+a ˥aRG%9ݖ9cmΰVar)~0f+ ڍT@l%nHO {AmR3:zMs/L$jDEVK=k {i@SJ$ "%xncHd/TjLbLU9;㹃kb f˲YyIl/ sJP VBlF"-hm,o7`*T߆tKZ'Qֲ{i>-^FvƧ&˹Y4rb/ S Q7gf:w`xU3,{Pwg֡)Z.).=5[PK\ceݚNwFz+\2j!K_*j2bd_R.QgzYFD=[75vf߭^kz͞>Yݚktd86F]C^aZ^Ra+#bskG~x8vL儆򖤸ׂU?CͶ*3 !jq`k)o [1Xv~y׶ ~d.,8‡ҙŢꂳ%(WJ^*.ͻŽ6յ2}ޟa ;JbjgӬJz˅bAfpU}2.>&U Z3} -ʝmxĢQyc/םV?]5{t 8/3T -?jRbx=;SA|cQXP2/ ɣqGq 66V9V嗐F3yh?j1@) 4Q)g&F v|DX}nAu=k۔HJi c3?EE1UZ1SbVZJP%T8(_%|pц 4WG`%% r) ՠ 9zjGu%ך.ӰSX^;#[}XzZV\YxHL{lR%Ok .QATl-we_]>fdSwi \Z \Yj LKhV ~0kkG!a'X g}v &w}*WFjCR)?AʑdkRFb#Md`#j`8^ȿKrʵZš%c Ix#OW6yEF?}-މn&u*]a@ɏ߷ Xzxܙ0tE0L8fk2n!B<}ԤV8iF˻R[am?~q?Gs<~EOKR'Pxy"*#eH#+˨T uo@ -mO*ˑV!$J8i2inf;kK$Փ.Q4_xH 1n/ANSIaJ)t:kpVZNb.Uǁ꫹>`i#qYr~E4*ɦV=A3f_" tg)AUL38a&biY3#=0s@nj9Iq҆x1LGd 6 O}s1!zκ^e-JH DɢQdr̊YzƶJ+3+wYXװrrN^ꞬY j>՜^bڅ~W_rNke^W׽2Ye}&b;"fy`fbCor@[7^f!ʳuyiʾw9%滿m_w۲3S^V@x<LtḆxWmoFίq_D.^K[`a6ҪCPu /g*Jdy癙gfvwo;tӜG @$-qX tR΂sx{n0:ZyuyEX9`6Ԇx`\"'9h@C&ʒܤL`qBI:S k0&<-͑~诀[ mjF%D75㌈aP .E<3;ڀPע6ӭSki١a}P;Đ8mc[՜q#-T5;)5#K׺1 [<>2|9#m20YeW uetB]3"Ԇڶ&Z7N›vSeWAl2_!P՞Omf"G3;[D>S=/,j6&u-z羫?{F?6"~jD܈xҜw#/[n(!XztP( y )vc;UWTu?,[uBȂˊ#p^)l sezWM#W<E1T nb̔T2ˡ9E+9loSiDC4J fKV_8(REPijӭdjv3T!˲$+Si:.*Ί-V"~sɊrq. s*Lcö8>$Ѧgh x~\gG%+so=&<U^EJl"eV:~mMu;*8,I$,svtKUoTk1vSZwpy7ٗA ~m?`7/-q:2Ox}JAAZ1 RRx-BDXZq=i'3$7h' oIY979A>>\)iy 2\ZZ y)9 iE % `#FNUA"D<'71G*ϥ\XZlBȆɻxAjRKJ! %Ce&[(:@ NSqմ dKjqIQ~e1.)H,JU@2n#5)4z^A [PR9yZP+h`kl^DACxZbp%wFm$m(I8*9vk{sݍ]t3; {.zNtȻ )|x2A]n'>w6z,dB1a~rK!u)Air<*~?溷u-S=?&͇,sj om5 ^!aLg/ަG1Bk`74tQG4ss d`(OGss|zl3F/i+xޡ'BON'Vis1A{!c* 'Ls0!J+`(*Ml ]sFq%XH-瑒:8^5oX<@ee鐶2L9h;)Lwr:2-w*0g̽Ot0C,[m!ۀ͆*,HrL-Snx!*,ת b׬|q+Dg?fS{4ݪ5y¨5#/~CZAjlz ,K%ǀ}n%]1'0ijj{Wmĝ fqwinZm,c|gU:ƛ'ZxAFĘu5܎А2>X`վ| WUZ]rW6!w99(U?ݫJ]33[L˕]`!}47+@eor=xWmo6_qp>1\}h!F\RY""I%ʖK;^ICz`CXq#d/!q2)'ƾMS8{}|LD*jT0//" i{m~2&s ," siF;*M{ Ts l2iHBC>r `G vg1>9 FXA G:1&o|zdIiNdXN_c6KQkPG!yĈxl RHxY<-fZHhDXVj~vse/arY 77j6] LzAo`WEv1W)l(1*DlQXˊΑZ,F<FhRM# 0̔'~YC~DI _Mo f}NH-2 xZD?" Fzɯ 6Q(eDH̶AJ'};,ko)D͕ %&+dNe~ByVG/=ǢxK80G 7̶㲇:9@U(L]("ȩ\;X:nA*-Q)U_uaD$?;hm9B]F; g bVrV9-f5#罯,]wLJs]/ 3uc)v=. 6{$b xE8 ;/oɧӫpL.~} _ɟGg \0Npf8@3DR>8f C)Oi8 0x^z̓ aF$ e#Ks@G0Q y@ϞSO A]t˃xciBm $0͎F{v䀹x_UPg+*O,w(a0ȧ+fh%`M&Ю2%P3[p1Ol0JLN^vO ClA'&j~3egH+6&plS͒ +t@‰?0I#?_"֢@[q8{yn^ 4[w>R 0;PZ6-8Vn6qr#6`_!BI,ȩg bᰁOVдLO8!]`逃V ^~yĦ~(M-78XOO)p*cha,s%Y,/xBC9`䊾ga>E?,xP:0HZ0q489[5 Z)scQTJ@OEj/hSE2(S0PAX f1tJ ur߽j$d d SSbrEd23ܱ\"EV!9.*K,Lc,@#:cWzL2 '`8?0P0 Y_wO {Q:%px\l!0O03iOR@tF|YhZ;pÑ̺ks *F!.,+Z(Q-Φߠ!Du@igTJ+u]E_Y)1QWɄq`J>rU_Uqx0' #:&%4%˴KU8;1,x!+nwPp=p5ZwaSv:fȤJdK+]jP0ndV,tlݤ"βK,ͯ.c]J]#wA@F.Qgvx 33;ϝC|iY$LH02Y㯃N0XVR֐OeA .L/I͒ucJ0T 4 4ӑ%%'3“6Esºpt6^ %^R=1jYoc*0&E [[>'MJ9Sc˔RP?{$8ͽ@`6SHNaE/(Bʘn$1&m@wc*LdV\d-&.NfbmmfZ/DyNDJgsaa-lCX~'ЩiG,vK7OR_-jxܐM2Έ9m6!;<*~]ir{-j@=R+=l?wEvBqT /cKOʃP k[ו!WJg]gZ`؎cK66b߂JPku栖T%_A͍i Dg+x3fC;DBd ,IkZf>v[|{[*1c mRh`|22$i3{-U);?1HNywoWH9'q6|wѡUbQRc`cX[l>K[Y#VHس5]|EM7oԜ7>Xr|8mF)(jkVK .E}<)g~ԂW CApw{=:`e5ߔ /Hi/PI b#CIs?ɻ+g${,6HY}ctw7,Qyu9@;pqiu$gH y(xXIf[_ |cS lV 4U Y'G="=Ov-ͷ iJ4Vy=ү ;+UTŬm76"!=+Mg[L)0@7>/~&G:|Yméb+(YZT QRf4lz?f+Gkw[JBG 0eacYPKjE߆p$l%ro e5 /ŶxlZl[02ZdF Y>*v̘-&5"M*L=]yזCz\Lrm]P&ͅe'~[g^4ȫ$}糶30FDc@ z}-h$[YȤW1ژhQ{49be]f^hBC =T:#Sx7"?;]e#{B'3ow&4d ˶QNWLLCMb V+2zEc}=}⧦V mNn]1fxlZLsX2,jA~cGΛ%W"dYvAG9LIpK1ElXYi7$iQʆS09VWYWKޚd;)0s\`#.gML~uތn3ֵJ&]ѬzQJ-bOƢ8YY*Qơ] Jd2S@H>d:Z=iK%ku k3&RF:y13ӈ1^,H[UjM[ uXW|B%hQOإk&vqpn֠m ʥ\xw%%}3>ްQ YC{H{Rڮh ${vT窙ΣBhMי`'#&c, `';g}<8CG{^ ^뛫QWP i󶃸J0+`*U3emDg9RshaC-VYwZv Z2&9r$I7~S򸒙kSeJԂ(+k}}B)eRO}eA(6jbI3V"v>IFJ:ks=^YF>t$Ş/KR.ԗqtOz3tM1)>GU<AwВI+EC[cv<*',_@Vw2E=1xTnu 7 D~G QE CiN_\58<^z\ݿ㫟TL)*)ߓ_‡s#rK *ڌ#zeuM&:h`՞.lן?+$/W=`ef9q6Ֆ[kMBltwxko8{~Ŭimñ.lzugkԱ ۹ DBdQ'Rq7|AIVp|B:6Ȧ'TI\! KXb!qh*WVDJ!6[ʐAWm.Jr Bc_麑 8 F}o$R46]{[vTSˆr$@1F Z꒥ܾE[z>eʢ +Yb7 8T#' ,5FK"H-*$vd,% q}1rgB}E s 0o>c*r|ɴ%%EяK#LOv^F<Y^"0"( 9&R. s"/ p[6v/m:ES5ঁjnan%_Κ3\NsH ",-:"+$ҁeD7$4gefcjzҋaZW 111\fE5CAX %(6msPbjzn2$e4ّPY۵-ưи4%aH%G^P+nCcC ƒzBovJS)8JKVK4lV tN3P`Xau&oYSCp >]k9RFBMsQ[Bs|5 3д08a @L)l୩XY R]hBɃ".ɇz~޺A QiM!#8&}E7ʢ~oe.L0L&Hɔsvc_W14O z*0P![J+Yqs6xB^8<*F|Ug0K~Lt2,yne,VF繺wcQ++.D}?3]&$+T&5GQKY4DY.\xXp)8X.ʲƀ'}~ 򎉁HLivD[m&ZvU9Z)I溿Do*W/-jVh4gD"2a[$ 'WCv ]K-fX t)neQ0F´r>3d,&?q 49zt$͞Gvа7RƓ>I8qSJ# px C^"r0) eR Y^ʛ-n)ի]ydM柮|.&KkGN?ǿ}Pr s1Ah䌼(o 4Wj&f&OymȾ&Y6B3W0` kW`nP3;FW >ieֱ&w&eh -(#qvl/E<,ƴb&)󎤾o(Gk$ "!܀-05ZR  jL7d7~G^) }a 5~=~VE*|rvbKƨ!c,o|4n_%}N&N*5 Dz{5>"ͷK[-Dn4)]}ds6N;ձYŖYks@Vt|\ Tl49h]NƓd<<&"ćVJvdw?ק!/oUU喧3B[ *rG6z~Ɠ#UH^[$EKi43G~8$ x]ms6_&ʲ$=v#&ͰHYP-_$[NsfZKbX,,f;_DEd}o4Ǹ?_4Bq uZ ]NיѿL;{rxzE²QE+=hG;'XDKJ,{x6ʸ`x'~>|3B5k|Ftm#w?8>KǮ{^C]ĥ`8B'w6 }A!~?ut<߲vEȟI%۳aaSFdl(4,;'3Nx"ǁ3FK UUh١^ aGw #1A',?Ch`chL5y6G ξͣAF蔅ln|4݅`;kX! lOvݔ![BDU**ٓLeG~ȼdWl4T sM6w4Z 4ZцbF btB=:b1T+}LV^/}ydAY+Y+4J:O$& gWzS*hc׶OL.B\EFv MA?<ΫP6ߡH%N|TtDScdd9 T9E1wvTmGe_]\{rl- 1_Clj}qqq.̑k[[MYg _^r'^2@IL)cg b\VHFMIiyp3zI4rI򛜄^MEz˹Dfv4 lC{Ǻ$%]HCC3AUl(l6L1!]/ˋlxe\ ,_ab޿yMJ0I+Ke$VcBsTv6*+o'Y\-뷅stY#l89^~%:9ds풘#U<1dvnXoNVal$Z6i`%,ǻHmrDs/W@*$'k7OM5M'sFا6IiAXvӀ$cΜզl~IqRqe 'Ycp%ŭ5piRE!EJJYjb',"դK:Uk p6tX\-7p7: zWCIZEd{8P,`C׿-<÷(t~IU+j TWЧT ~fQh<5T[]HﰋnQP,3jk梸j"/.FVP$HKpy9A [jQ➆~pՅ?y߇5Wl_ou.ByfҠ"K8ȳ*j1&,_U%g*LU ¢ibJ$ɍ|=<[Nh9t&Q'G5ffN8jɚ+(n+UXƚ0QZ(gmJ2iRɠ^tǴ!Vv>&`=ѦL .ǶN5=m2zTǓk";DnY32ղ|(~Y:ޘ>ò pϥ&Z@#z[}[PT@eO?}MR25tɉhɪ@OPmrVʱbRrZTޝ$J'K]_1vdX3)XNdޟ@";u ?ӛ l &sNyN:DSY:Mp>[]H1D-NKZpod4)&jax6hEc y1&)JuS-{\l7m䐅)iʉ$ J|ځm\kc7ٔ`G1/f3!xD:8wLM^FFZϪ:B`ɲeVᕒDG yZDv: Q{R5/+3ZWI˜<0Hp{m3[9JELD2$l*l'5WBx‡gB^a\Wq+i(8hJ%ZUrSa2@r<[FL!C0s&:6=q3%DgAxs֧h ڝ,'4"tɉ,zvw)I P;50,\XTqDaY}whh!F!-G; BUi] lj0ŚhC-r|ECo~9hk;`kv(Z(R%9g%:N$J(R_jK.B LZNTlneJ*BH3'L(hPGt}(S3d1.X=u.HIUGu:$Hi>9|zӆCH`hBG -YGSax)}_I$=G- ,.#$NS1^gtAl/Vr^<{\>hMA_?\P&XRLȲ& @PBc =kT$#3~ Ub,2$%m^MfrYT:y,Ű k6l\ D%*B&"GbtAb(;/FGۧrXZEjp;f~JbO T(\&I9͢ D(`VqR(E a,츌3v@%"tDEDSNiLwd%DFb~ُ7-/%, ΀{wuFqqe\W*oELݽLh/,s&q~-e\`xI@{f`B*$弲LquD8}.E&?~Öː$d/x#9w[˥%2}1o.x'%v|vLëDsa`2ZBnv+xl|-3!e/JmOysٽN7w*Aѭ a ;Acc{)y)[ZV322,|OLtNO>Aޟ?aEOB>,{JQ٨oVJ1S|I԰"s[D*;+f4\XvR\=AeypӨ)"$)oZ=8l%{V;0f))LiZ_YZ;Oyv% z8]Bk|ƻsQgsr/%Y[Bʛ1X6JߛXgE[-ɮ x5oBJWW%Þ;wYR5,kp~9Y旈z낸Tkz!J/ meO#\B)@M!UAq߮qde"LCpZBGUoPRZ:tu8)Z:hˉ8 +sF̛/k)Շ[+}j0ٰqkZOΝ4VNRjw֫`Ӫ!oEAoI[g xks۸N$,]i?g7iΕohz$4aIw]|ɊvG }aQ"Ѩ?"}fJ8X9Ђ'J~#xW9/& i@yD֢N)!?y0\I$7̢nHm6\ɀ!\z‎|!!83F#\byKakQbтP7 {Lnx@bFEx4ZVCS;zz} CÐ1 ۧ 1}2N\/ <,U"$f (Y)% 2%{20y|>LuvLuHxP|d:gGD B# DŽd撺jڞ's@\YŠ]=;ScR^dж (gCL62/IgH|<#"s7(@<1e'O|~CUH_{aQ60>xlSmع9yq@KCNH Z| qE@qT8,b!,QFkS;R,XRsvYWHb T*Z렭iG-u՘L!JVo0\m?L ~@HCK`$=u W4D 1(Br[$q7B'>2hL4J-lQsgln,i=DKN(0DEfa $N@n:).  2hάas]Znp^%m3J Hp]O؀?}M1_& -ӡå $ߵ@rՃJS8ڼ҄sx9 牂W^ H~ެ f;}u/A%8DL(:Tbx\T6 h9]QrrR?F*DZ'R-Hfҙ5UɈ#sͅ]٪I+O/D> )#Y-dqYÙ6dO:kP\inٖUl6@ duHV ؝0z5LqY]YHA~v_Ri"޶:2qYL"J2)ޒXq`@>NzY#ۡJbCN8֕2Kb]s]M'dOcNoe{I,To!rLbx!$>._ckgSh@J}{o\ Hj7)Xp'= hPZN}Lp2į!|݁(S2n@ z8-#[&[ KV9gِ(T #g5>-SJ}P4`G I P>;1Kuߺ\<<<S_7xZC^6uĖxnb9?'etEk苔d$} Zɭ ŌBc N8}-'eIi3 %xƦ ~A)o|30$g' 3@D(6S 1A'Rqi 0 ve0=:@)u>Kɮnqb0*-~xlgbuYFKӈ  [*A'VJtdȭ 0Tp99{V&VN ٕ8Xf>+( Jy8kd2~ކP-`UP Z1+޿_jà|x"q3篤b_3|S.-e 2h)dsC##\%*ߊT%e ,fՆVVr} "߅ro8 Kڨ V v$=  T >E\6?/I .D:Zd-θ5}s YoG=yg;G1ME%͍m"V}FVCIP65o̘b泻&"L{پm@CuVD呏Fwn7ehA1vڊUsk۰uRmkc6SvU~ڠ6kIzh#F-'Y_xZ[s8~ϯdz"ڇdIBPa+'b,-Œ|\vu9ObuN!A ߭EAχQ9!5w jm?5 ikLR֝ 6 Ǘɘ$JI3k1KtѿIRS q\x`1 "ᓘ#!m'hKZTzwM-8 [ Z5qzoXj].$;n)J2c 3[W 9΄ۄr(eK d.2^0X..fh4;FhDNof034Ah>N3L䪋 q"410% fDX2%R.E Z{DIpi LH7c.*z F`u}L+NI_ɔ ,cY҈ov6n'bxs͎QBf9 HGPh${wY-4*P᫖?f)UAjk2 !$g'~/䠜٫O(=T`[WřR^?~>ތZߺ&1KD|B%VNq݈b2an) ]bL>ѧ}{Tf,d_lg0E<`-[zE)]v<>3&yDH Ej)u~Y>R6v)6}\߇7vr!/ sպHTr|֖*ڕZMi[5ǵL35r)ŤY=zWW,̃o>MI݄zYnI>I[GA{nKkr3!q^<(?ZkKE.kKPxVW<5]k*0}RN}0xC#C-y '{QQb7q~T{+?y; [|!*q͚=Jku'<|tؑ '8SƟ{Pdk([K匢u$fbN,˒Kڼq~\BĻo>f-n!?4v8/BzsY}$A<H*ͧ)sNPwLw܊Jky3?eGx!`'0eߺxWms8ί&e!i IpL?yd[MJr _b;9vˣk[=5zvq" $>(L很+mކ X~ NONX Jayj=\ f0d| b~L|W:J XF/5[b+X-p!Q14&Xu1} W>9J`|1 0XVֱ:6Mv {QzО81z[,xDI&LbHC?#!dJ,Hj`d^k +ð 2¡w`4awFNǀ܍柦9gd>`8\߮?7e( 4̤iJk.,E撊ϖxB(Q1"\3eJDl4ѩE\ݝ{or1yW( 8h]|լ,}`\mLUJi<(˿닊Dd:x)D#Cd=s??x47Yh*zC0|cKGD~q󦱀(SjԤb=3ky(̾ [ZQ)t}EFf5]L}麙z; Nc*4+"Va$[KU[&Qtn`ֈj{&ɚ0.C?ᾩ/t2PFR0Ǩc?BVz%W'c!':}9n5pZ]L e}I8]`Z]ᤳ,cs/2&;nr#Zǵg+k5]#1MÒDfgF"]y%rRopQQLJ9zs )^K{ MDu4wRʛ[fVWJa'j,`e'smOi!1$~[7+%.[lO;,se/zM+\lxXXU_[M,o '') '"o #pІ C IbL9#w^iz,5 t?˫~%N3Vv_3I<0 +_3pKhx;%C~B,syNX/{Os8mX̓ 㴃x=sH篘K!]ٛd]qp嶮!PYx0!͌F apRF3=5=7#''<J_=͊hqyNK5yiۛ7fɇ(Sh Y#S]|'-O'~礁ģ vq-ɍ{O(%˄B~BpG)CE7wCKUSNZ@zYVmaێY'i?ƢU$!1ߥh]F;<wE³4BWEhܘb7?IcL5I`j Ȼ|N~/r@N/g>zO_YP wGhHJ:atR08Jɂz`hl(E4aDdA?A& ?uSVToh:^02aC D<#47 8Qޤv8Lɓ ;`(5aR̈‹E+!$[d4PX$ԡ:yw2dN RY7`(J!ex!X ٳ>0ij])S*J܍v^*3졁oZ{qF/hSS kSi'ysV,T }S6_#E)|[1p "'|~ܔbAyN џûp,'m:,EiX5J/dHXrH pRhн9Y_E|I3r@ÙM@v% m'v?cT5p7UF-g|R0n"Ԇ  v;mkB&ca V98IJ$ [`,8B"'"_)AbiEE`-Oi/^S`t?=~6[fw'/)ּw/Z5V`s߻h4sIs 9@B+5$Yox*95m) 7HV,xk٘Cz(;lva]hzw(<6 [I"GM2nFSabf{̕d^ʜ yZ6f{nD^E$o4^`cJ dE8$W#XjϨ\7$->aH.H(I"xhʾ4 2LX%4Bs8nG 'Be⃱H辡8ղ^ߔ!R\nɇPC-)ԴPG&I!%_ oѓJ'Ciìk`)Ql*V!ס9ſ=1S&Um,K6wI~3(ê]4M:!2@tWh/F\>@V8n]nȨ%Yu4b4I!RQc lm )[5',t8z+rbz7+Eu+6;ܪ8H5,`ߛ*R)hb,ƙE6*Zh `6ÝRҲXՄkk14 0@YM%gYO6g'0ͅ7nͭmo|0 UTOEIE߸xUt O! ,X6TЬ)wu΁b"*F ~}t\>?wO!iud4IaOF"vj:2IR%e\luU ?)_zaz[H'mcWB9vlGs79,4Ye.KtW^n/DCaMa"b5;?d?x:lg>0_g뜝:b+_)$ixy55t\(;$dt3JSV%Ms4E-Ql* U!դrԔ^cf>Z?pSEݲiؾp[JbUp(sRޓ98ddML$neoc:bi,Dh]nDb:MI 2+Dx4U癔wzpRh]Vo(L 2 7_8}3b@&,KVV E\8y^6/ˬ_b.4Z<"[ZSS7Ĝjlϋ+ OFUMdžʭVD vj yV$aZz>Qm,:/%R֝B`FbV Am$M5x~ Ժ"z4k""E5ĢSC7`q1Eku81ՉYdxCegK|4qV6ƻB"LfO&qh7!dԩ,Wnm6T5^ۃ u J&򲤖a U|ͦ4+i}mor+N,ض%N>E"?Q'ZdBC̀;_2ۀFAp]M2cp۪IxYKVAaI(lzN"}u!/$"1Hz29En 6HdZm:BTg*m6-l/ڰ>6 I1Ohu_Rn[9-HXslG,[,}1bfղu"-ǬDJvM+/^[/W&,2/%!$;)nOd<=5~Bwp&/69p룕ϮߵfEFJUf:V'CN*1ljkխyXu*|FKǞ$0fꨕ4[Vc^+MNKY}滳uS U {Q"TݧnuHQTbDw;gB%kW2^[i_^;R.cxp[v(x%vyjNCUnk7gz L!/zHUHU%̨͋ǰ];l4miڻ"DT-EmlA6\` gxUBq?2ZW,DHIXP˂&YQRƮ黲в Zt-b! uG,Pv`kFѽ[tʚi,k]Ryj<|_oɶۖY'b %_hu z_dF|];!Wc?˷!N;^`·Oڗ6juqc"pWoB1*IU<蒿NJWɣqq\эk4_啍enJ|QDknǯ|}{O qհw8(pTc%]+!nڝZY?oCa)w4oɠjHJG A8,XNֆ- `݋f;_ Z$>{QCPan{;BpSej$f[k^:'< #|2;LīEizz}z5ǻݸd7|۰:5}*5Syݩ~VAoL*ٷz4(A%YfkBDL`]~1w0+=5jYYyZ:Z tCwƯQѨ XHIp5L/w{00ToPv6Y氏ޑoGZQp[˟k#6]3oOC%xn9nz Ӡ(koG`*K&![f{!{󾼭=ɲxZ[S;~WtTl9qŁ6!y3e<4418(.ݭ1ۿ;[ꑫ G`(ӭ˕.?8<7D,>1[~[f96.9M&D<<,Yq,/?Ӈ\i!x3x ǴO`ͶHHChO!O BNcF wOC ՅzeLφf3`VځTad|qu=:E$Z̈́ηR(ds3f Rq3$(aD샖 aHh<35ᱫ Pe,Cx?}"r?}vt=_M.n/dz5FՅ|S( UmyMt"锇b!B>]ӫۯ1^Hx$nM88+9|wL鴩Oiͥ["i,ܰ-8:uW41OnUpw=O;c5'Q$Nmn3,eb4⏨疥J<$@VsY{Tn<I%+70(v`Zܣٚm,4%[hWz-A/O.DK=nAL zI_-\^Oˤ^5 hgJʱpl]_#qc*%1̏"&"+'&F نSn3 vʱTo=d R6ߤRH2UP笎u 8jXصy1ٹWb`64^7S`:b F_Ɲ1#E-O6V.K"p@32YJ|QQD eX) ü{F~=B 7c f˨ XӠ`µ<>\q㜄hFSIKUZylcMk%9E Iu+n{lxY]mҴvPͩ+6rj (pO#S^W#cOt1]S|kc^+ZyN8jϊ]ɽP{Wtњ3 c ౠbݕ3a FQGBɵpvrT 8g%8֡8WM dvT$n{p 3R gj",{QQr{^9[Jz`F-̖\A8z|}/50AjDP6jU;Lwf{gg#Ը=jC_@Tk[6\gk6*UѨE>VWrQN-V*Xb~sFܒ6+| \RyaQ}b\qN#\,NVp^4eɒGņ~}O],tA[!@j^t%|=u[۽ gx(r hLEMTZr8iϐߣU` 0#ŗyMS/9'&PRNuxFؔC[5SbMm3)2 <vbŖmLR$M(Qf⒒ OֹQ'G:ǯ&7B"y#T豅2*^|w8/.һnxCBv ?7۝ҍ%a>όfڽ(Qn%WX x>8 !u KbkhrQj>0,ɬ`LԞl(.)^ѢqGQ:YbD\Uk}0X8ˑB])~ݕT٬ͣMzC YӼ̓8Uű)n2Tw*tD)k:&¾&ηD,DϠy37Vq}j6ۃdٔ?wJj-hdG34nE&VURDVc"$:vq)4d)v]*>|)Db&q.JKٳrWFT=cǓo(ft.zVOfyBk璛LxiIg烎:Y۷p xşGj9xSjAF &JzLK\Tra 1+Ūwa3,3g1x |řnmչ:|9ٷGF:fK/}1ޥ;M.fR ~smE1Pj^#CzFEVh(pپVWkwݻE&SfE>&dS$+2O3i1N;X4EP^(ZXVA|Xm@[mƅ;2X`dbSm\_,pI!,-<~h FʃSKzGɭ?(G c×)  iD5rxS=oAt@(r~ & (EJ2lSֻ=9i)%6BI 5Cq Z<͛ݻ|aCJ+~xo`o30/$cؽyu7cmˣJ Tpm;Y02]cbO4v$$[4%E$a: yҘA8Д"/-=J {;\MU/g84 } _~m;ZNx;gw+(E0VWiǞ0KYHC2KƏM)XB;ܷ?_wcp]jf^2uWٙ,lL. m%B ib V}*ErmocM7D{u<%{n0O2"IŲjM t:Г2+wWOW<lj+&KxuRMK1-a"b~UA7 lwn$ꩠ'oJ"IVm.eޛ7וb}_KU^\SJI%0i-WHu8C&O`*>(T QJ)㬧 \LP$e}/U's?y}Ryі~+6\h 4r vAbLe uL70gJ8~>iv` -UBRٙ4(meW3e<+pt=k ~MAWR[h ܅`3\5 8ȋc}`_EC2AxeRn04Hⱀ"LYT4Y M5  $YNbǎlVW3`o`㤐s|=_}c ^ ܿxCsԢ["8+ТR8cn  c{ UKKz@s۾D PpJL{BTS ZPuB(TJ2. x%S^#]QZj-:2\28N.26YQs̪ZgaҮe_JK d[8.qX8! XPY%iIݍ$J;:`՚+V,Iw}:xx+EZN.쿭6 A*X Uе>4UTu"âO ׈ΉW4CW(pL6&̃9oOixSr>3H,C@V~l$6jś٥ ju>$u{|v͏qK&۶xkoI;* t:ŗ1΢8Vt{ 3 z溛`nzzރ|1]类n =njՠ?y/:17=IXj׋@k›~^eIhܘٌue3(?{!i8Y&CP164ˮtRyB$vb+wаQ ipF!v뱵jucziX*3" ;y\m^jVovq! SMQbqL)?.Q5J33p $ pM x+ J֕\ig3X,@!O'0t2`OtПztp9p: 604akIL|c c9"IC]0X/L\qE.U(OdVIaτFɔ V mdv+}Oh@2e< v1 j7 pgba2}߅ ;"e$#iw<􂍏jwղfb]YbqM䗔gBRS]&5w}6!عdzz6gc97l7h7Lq3v:]ff]t ut0|k.V[al@&Nb8r= BJ6R iڥ \i諲bb:pWiGC˱8pF B5m- 欵lVUKX_#0R Wst3_VF$ ̥XE6*%A2חc<) IָIi }{X5@2\U'3R iRN Y׿ft6y'=|ЛF>~!B]ǜ 7XG ɐm2?tD7V ft7Sv{"( F4@v%>fQhNR{1aVfsE(F/H|%B L",Z'J+(8} G@oQs#,p{q]M3y!)nSZݢD9 Ift56P#~X׏, zBeӱVaRbf[p"C53+J&bO~&Ahfiאַ|DwZʐTk!1^1#{[S]Dhb~#^6WBߏ˷"i#sEգЉ`Uwb􏼖^2`$:7m=+K/o:Ҙ4hRm8q}3f ^"{{E+V*? ¼ZmҊs%+& 5!J72T3qaa*r6qN'x.d+mTɡYSc2D1UV3yיqjʳPj6H0g@0>0&d0l0G8v!1#]QԪ h%J*I֝V_< ND$$U jVVQdD5뼬=7d.1`^Xze;b:)E&D@NʋWnKgUӊ%R5,J(!)nX5FdRZ$g !;zI!9(Pxnh\|l/,]< bT<\mS4pYKM@;Sq3Mmܳk\4F4zE)~T˕+mhUf͓]H&olHgu/J 4|tT M3{*xV6TH|WS^=(e¢W"Oғ45MZ4^7D(M)8+O2Ѕ70eL^T3ELJ!;x!~||CkGK,GjnW9sv:\˭ekyQyb^;`l+cZ~,Uj]F$5uT-ྡV577Ц;oHXc?3v๊9.-.$K*t7;"0.ܵs;u,`fڟL_#uQLQWPҤoV̿ ܓOL9=KW$e$Pۅe靎p>[ Kpn`@Z+QVxmP|# /z@e߻0{)ijꚞ&zSodԎY[ͺf.'.>;?j!Ɓ<=#t`oRGi*guWk<ƩŌ:ōDggI3&}!R>r3f^Wk>~菝ոwwGzO n-##JaiA,zi4)C^(Cܷ%ObI:?{MT&ڝg);{-^ JEoU+=7_3Gޜᕧr3@BAy33] ]5ȃ82xXmoۺ_q~XbE/0 :u\]͊aPiHFRq9^(I8=Eݏ?:'ЇH92E20NdcnRixo~zg( ȸ N\0ܜuᑟJ%]_O*!"yfa\ʕ27ß69Ad A. #` !/ MˊT<fcT(CRaȵa273c v4nCfJROg|5}W'nk  ]Q֨gʶ Dq\34*aD @l mXZ?4߀.c9+zar7eq{wrlJSXc!Ng9 AZdUCcL< :MXwן:xb9&۫&p%_(H cy$5y+Av xgkMrr2=)~?L;i \;ZFqfevXbGw"s)s2,֍~(Ew$6J``E7-B{<燆!]CEä80 %9H<[)*{N{m Z!զrgY7 ރ,[T"nl0&Î3ːvE(e 6Cеa#93\lIyZvy6ռ#-45ݥ԰MImUHuNwER1V2#< ب1ÆԤy\T*RjK e=[B̰rQט0~E\c~3I>cv ^, z.bJ޳Z9ije43IB[8mp0xlHS0Vp+Ihnǻg. [_)OCX(^_g~hHLɒWsǂ*VP$ )uRߕ.`FH*/&imSq,quņi_#?+9)gWX6K>Y'f"-/8qT)0+zf7jW%O9\~iOyF+uMO"\9Mkgm5p:!\r~;Lo5Q}Åw`SĄ4ks-ߦލD$`[0"; DbŒyպoZfZZUw0rŭQHDt#v"V{DĚE)ga4"`ő;Y)m}I3"Qz#|܎p{޸w3_פ3D~ }#tQrQ!aXHmw?[Z3Jf ipn) 7b"jmoK=״b%6KpgXL+Ωh?jt7%'L?BD,7g!](,rbH?Aq-܊`{' *0BM@ H ނ38?H<؝WC >(z*pS0g4iA_c1r*696kiļS͘|="}&o`<׿C7)Ց [gP_DXkf98=;tЖv2t 8fM|q2Y WA&yqYl{2F6q<3ފFKKљ>rDGtX8M` ߨx"bn9I`(p%KYZ_色20E&x°^ʯѵ?l)h19qb ɓ.*‰ 4Y9rP0sh\̣8Wʓ y`+LGN(DŽ2}D|k"*$P%{07ٚq,vX~=~(Mp{Y6N!^H;N7Pj= *l.LEŧ2o byK@S#+F< y9S`ʭ,A.-MUΩJu ,ITO~taDI}.E: B~ߊVM^s1!Ʉ#f}C^C|] ~jcbxa,+8F2g? #;}c>4ze!(,;4;Wx@bn]#PX[Gߴ73*!ҝd[tF|¨zk'cЁM+c!)Ue]=tG7 u}ϤP&Ξ~2 bzMr!4!y&0Y{A"tgiypAQ"y> dU;qZ܍p9@E KUrCUX,q;o| T`µzz\kqndi:7*|,l7o$=RI^bc1/+Ld1Dp[OfōzEjha;vu3x Xe?^ NhQu[*I]64M~o~z| %gvX_m`+on{0 ȷ{BrN_?VϨQDU*Z^^?+VR>HKWXswƃ*ZhTjUS+S(74[ZClZ2.Hoͨgj2oB3]+4h1AA5;?;PCMT v#b쌣MDO@wS|`CT㵯`yy ;b1n7%z\K: _IKKTkqJ^WfҨjY\T@<ڐ`nݰʭMk++ $DR@?#𧎻>o~H^: |',yYvȳW9oA:J$UNAmׄrXw[nݹGXK_wji>>ݓO%'q0-߱<?]柣+?Oy륋25tK.h]S|7tќy!{tZYe?hnًlxTu"m<( 7ote_g4yO24%=hFWz6]2I:gEӘрpKJÏ_i$&/H:EdMVކIN֌>ݧiN˜*B/) %#tW#{NH\2z777]sME/pw18s8#.2F2ulg̀Ȼ!IFEF/O,x!,7^FM< gȼ424d0 /|tLhLFt0;~% .C Q4rPj0OK,~8}ZX{ J5bIi .)D0rT%hs-]6p6f`$Qm7<$vu t1%_.q/i텣^IAaMygp#4~' UdGe= zW4ze^\I<& hz(w75fy+3[sX0_ =IMz_DHA5]lZ9qowE35G0 G#+k1ĩ|bNN]pIwY5kcNN@諒u Jʷ s4 p* P? Qi;ɩXw簌m` gJAqk/ZS.Iv*Y=&K†"6 ު/$l{)Р _Ц 4EB  $?m62gii HLDb똗etBme/e$2FiNTx" %QlDat|фs%2x;|:Ot9Taގ'L!/wSPo¹Ҹsф[)sS2 3" &4*sgr&s@X d*]rbVL|8:bx%}Ȏ8&XV4_&=7c=赍o?69uJ"W|U"B$rgH7Vp`l8àXSI$k,0_(JKCɼiiܯK`/S]oʙZY0=K@K"LacXt\@uF1<a#(0ttYi)jbզ*OJv3T"%m& פI50wYryhYa]$mv)ۣ[+֠[.ʿy|XRz$̰! gƻF[52Vj7-iE6LS! u/V둌`X Af/4=B5['chƖ}1J6Q;$li:{isZcO9&fĻߺb6BMᔷ!aٲ>G78seu|?Q𒨔Ѡ!MlYV$9w>9'd&/զ[K K:D: *R3p1qcc~c(p{zV+==/Z*%κh^=í1He$X9J`oC!7?;[gX \6/ϙ,g`&("cz71ud(T5G$ly)97VĝEn| j9.ȢApGۛ1ՅjML|l@Ai5w#g\4iC*L:7ܼj~XSE5Q8`njtg|C-9jζ+Ia¶\r{ADci\OqhQN DRgŭ*]$(s[D{(DcBkeOƒ}=R!c egNguu店ˉs?8hFjC/-׷%(ͽŗlLt8fP$ucL< 48o| isv('fT\!`ѻd*:;!s/wEyA&es.L=N9VWWݰpXő,zM ,!6RGڨ _@36(vlSPu}MuVYXn@LStAx9pK$ ϴZє/,1*vqSO'OKx};Oy^OdQUr CJLS|2郷ΓvHp8wK!`c&1 e11pu*R{" [jжq= =1[uԪ&giўKprv/_L'䌊[Q[f8J# $og(}v$.{iYjbWSՊfٔ8ڲ*X⪕&0"H3JQ "TqH/\T3LLMxYmsFίت/#38Ʉl3!"4htMNN4 IةIL{쳧;nzѮ4Ō 诤@<c>_HͿ˂<^})kyz M~3!h0 0a>>A'r=+Mxℋ[$C/r),5BB0U +ExXFwC˅;CL.P feEpNz!esx1n7u[d@+ţ祔H,?r7D8<#+XXG+!!J> 5lQgX'e'ĥ\9'oCi `Y-_IJ'Nc[myh ̺T1H.`#ۚd3R!^<.Jx`9*eFI[R6GL쏹dZj|c3;ckr1y&V{w@{֊ȻeȞ_w& ufR ='x a!8{3=n۸oU'm%$"=ԔHfesGw4<9A5K2? 2ݖXOgX7BH&:WO0Ĩ=C81PƗ_Ǹa|!v< vw Z:5LJ?FIP8g u[ԞsH2NVi3ʝ,kߕvdMND1ًrw.7*VLR\iQHួ|;y0OKo j-Y)蒔w y0͆K dj4$.tGŚsYoW3mEjP cT(_+9D_eoh^"ÎE$0-zGJ`=w`:Nw7paEt*S/f]ny3w}ԟ)Cݬͧ~ܳ?KF xks66Gkze_d$)GRRNAAR#{:Lb }9 ibJXȓ.ّBzVin0XV@`t~zxq ILd 5 R( ƀgH0(<+8bXi|R"EƋb6;˂tNr!Ntytz{{z}wy1$7~}$׿_/{`9͐@!+i$6Ba%JyJC6a!L Vdʗ4K"lr\L х 0^D:ƣ()Fk ~јkv*X518rLB%|/FûW8msV́oMG1dğ`J4nVY2˺d_%t7C^&,ߌʡURW Brjzzju}%zơWR3Hg)QmN<9_X@49IJ{r#7htv;l'D.T!$Z7Rk49Sa!q}bD)496yA GݡفB_BR}j 1njڀ+~x`Q j>ӬgbD"_/Gdʟ%-nD=E3.uuo OO7j$Yǝ6̚b[Z^s;=}} {M s6od"7 8ҀS(z*r9糙gb # ]9t34F?`MSmiK>f=\M;G ^ۛ| Xg|jM{D_o;ݎnGOKc耺4©Bum3SEP5Cmz:uLc>Ѐ/)O|Mskk]~v60>tLu*IV] 56o*YekdzcӚIcd9t?$_LGA:ʑmblAC8þssl.mZ^P[$@#`K=aK>#guCMJ>_9ak IDz<^L& ^ȎTQaic`&yJeNP$Jl%8c"\F$^Ռ&5:W5oZKi+؜k5Y{!.>sVSJ!\ߝ^^_܎n>Vܚ f銙Լ cl@6uĊ#߾flc8MA|ӈ e2"¿pkJ^$|B)g "!>oUx f:1C#_%7}@`}[1ڼA'/>,`mJvk6x BE= N M+ E//=GNhD1^B0Y^`l AUҮ#HE72"6\55qJi"&/P[иXEl[Jjƽ*Yަ_a8/fҹ~ [Bfls ?|6 Xhpj0[xGp-w66NUMnt/@“ԨJ1#/Bx/vy槜oCsgtgػ> o&-kwHD0va-&WdQ8!xY]LV b`5Il@)$N Y ,8vk*%*VUJ}nЇlSZ/o}zy=ž~sϹ'GxѷۤBQu*&{#:+wb!/5 RCRZ,f(2RD iY&j낔JN>9!**[~OHI9+VQXﱴ(_}Mv&ҹ,aL:aF=$\sj. O'^TDb0 [.'+%q ~:?N[`>߈3ՒOykyn?P;s`>yh~Eu0 |\skXz8co&/,jVljk3ulj5c.嫷v՞/~Yُ{*~=߈?*]rݬ/J:]/-m:CJ)֧0V]Hd‚q8v3+˵Ϥw^&: ddDxr_^tZO(:[6Dy'!l1sb  Mf~>[+N?uXyNKyٝ\_\`:oS 7f(H>uOw+| 'l},cCkՖMCK_maU _nA"WU9-zl ZNNзx.+4KԞS ւ0yXeH7Z{s[KnRjne.g/plWF뢫X1 1orxĽe!ET:6A=dˉ8d>?,n"7CGK8^ I|QSML.uJBXRs&T MiCahs}SȒP TQYb(luZv fGyp`ifWw+ɅYnq.وC!C-IOD9,Oi{t7#CBFq04Urn^lB# jU__!Kj'+V{|_>v8se{TQ2Iq+7]S' F%Դ`A}w /be"bMvk?%z]}=nBMD_ #oEsֳCW/8=zXDj&LijYHj@*eoH+vETo6w 4O^SS2ftSdֈek|уySF[KĖd+C@tOl'V¥NUjja,|}w<:˥:.=mEdv̴{vI&5S^RSq ͢(X(hh8u^\uM\at&k|`Y_@SogʩrpMUN3~$)]Cy"[Tt-5BE1c(K6f|U˵xCrtȷJiexjJZ Y&c831Q/BË"Q,QxL__ p{1߱*uf)=> 䧅1:.?9쐼g=i|-t1^Lnǣ˛v|3b:\q5(#6c@‚+€µ5%#K▆@`'N\ gGqt=f#n2xw=/0z @/ 9\zx-aU9ya5݈ ^6qݻPCRCt=zACs'{J~)]#D3 Jr 6ޚ޳|"R7V!ұ%D@]Ox;j=׿Gەꯄ@pN@}s?xWrx$+9h5hf}_X,E5r`} "`֨")[0?j#q<6%- -5GXج`"O+&S@?v L(q N$OBA )z/>dbk+`_ { 8o 1,qJ:9<5pSrZc9b(i !ťa@;؏p@nQb}D˥ ]l`5f:R46oOnkYDdکKTi]?W{5L6}01Z8TsUސ"nvz&`TLqK32Ϡvm܅Z B99ٶ95$ >kV#٭rvZQ?/,/]yFpeylm7Tm{4Z=3$͉}ZBh"&Ϫ> iO]Y(/VJBbl r/C0K9IN#&⁴umը6F7|l6ނc(xYT IFVFrם#+ v*VxC/ō7YKv Ɇh[.g] h(5>݄Tp 7vX2>ORrm.JŖedE 9;|?;c_T>&vE"rw DbK&e*0ʇeZpU~; nZɁAB1Z4enݓdi$FF/Ir7wl!w~ě.3S:䯣xS@ +fv\ :(dtG96N˰Z\U8I'ni~CbAeR<]ʢWv8 loQ7IVKj;J& {GcckۖK_3]V1t xu՘bn2-SZJ]Adm\ָLR^m[\ZDh)$Vr6Ye5={>B4k3#0eN[ǜ$O}BR!,w`L2蚬-tm]g/>fkrc}}~.K'pnnBWl;ɵ)Cffwo{-=V;*ڇ 燹\6}x{2oY82=xV]oF}W\%Q*CBmEP}=3T^$K*[$<>s???` Tu}$uy0*I_ͺ K~*Se Z40տIݢ'7ʳ\$K%AUDRi)$w&t8`^~` ("a( |X:PHD+yZ1z `~ גN+{Ol=m?k⬿ ff1nx,2 Y)Cj%1bK<3m@$xoSE2c&Rܑa? D~n̈́A>w<0[C^G`5]K b+1} QuCɖ(U$I+J$~BS"(rI-&SrG_DBt#0 Ex V(Y!d]:_Q1e&LjxuO ?gs9YI>Mz4N]QYtq#BYe;gGp6XD*nJgC[zԽndKRM!t]EIj]_咻i 'en4t-zm'h>|SQF?x*:]Ep=F|nh 5?}%vx੫G9N|3ʪ X%w3TwTfZhj0 !{I!Σp;x*MjMcdј D±1xO0SgјℕZ(+KWVAv RȒ<,Znu3T 6HavJJ+GKFW*e`mõuz0yAưSRGaU1΢HٚRuiOX\SYD|bʸZV pbc C2WFLc 2W3)Lc)e =S˜)ˆhdBz0` Sp@)t#8¤g% ȫDeH% ^81uqDޭ*%S,4d_v[]PgƛqG̛ {'[3>m{sLۿ'%۲q`Ju/~0es~Xv6+y! ׵' Zo:23Wno0hOK*9GR|'mPHg|9P|k@މSȫ;&/_† $x"tg+F,Ң" .ʤĢ0'71/1=(ɕTXTWPZ T՝)+IHLJ,.2`={>$tI~~Nvf&WIbQzjI|r~nAfNj|~AIf~^P XUbl0}N>`7(T;:z;9ƻx*A}2=}B]r0pHG?`נ0 J"}}=yQ')]:ٚMv<%K^E(`N&7pHTe;e[Y LP))8/,./8|T8{x"tc+F&Ĕ̤ĢJ݂Ģ"OgTQҚ\ Aa!@дT +3/3c2/P'Og CI594(/$>?453V_OOi4Zhʘ]KoQN? x=6gI~{>%&Xq .ݨ4ț7?KkgF4$;b3юl`Gi0HCd#^Rr--WF ț9cl5.ĎmޑdQEO>8t/ [}J^x$M`؆66;;D/xvmvX_ozI O7^툽{t؋B[̐m^hRzQzMl0L;Ԯ&?IB __GSj<N h?폆Fo q*@!3O1 T=>m!qDtƂC@(Z3|²*(qqtDW8yns N-mmp^-ދjvl3pBđhi fji1p= ~%z*9 eLPҭ&- 1tEAo*f6PJ-4.9a&  ]wtaÑ.Tҙgi\+^iL9>K 0(C[h+9=Yis}fX-Cj~o'Mm\>;!{*m*+Ѽq 7.ḷ^y7ξq30lؾϜkaټnj4DI}x;P`$& 6ic]ံ$чӄpE 8$I-̥4*^邛[=aT=ߋwA܆i+LpFK  $9HatBCnlAhG}A"5%'a!5i*?JJql;KSS|N(0G"\xݣEzbI7{h,S2۳ERd'k`.fޥ®(Ec2$7n&!ZҀ˽$WoA%ieLK*h^TLyOQbdz/" FIn+ Dd,lmgbFdA +.8V fQrBc+d?lRHB3fy``F4%7[`zo_O!6,3W~- h (KX+\;"dmH^)ǧ3ITKDy2DS 4Eщ)*()ygm#z>{IhgJޗZ3jRr/я_Q穣 ?euPS!ju0C #c< x.E2e S;in]y2;9JT|@|",%J{hn`9Fkާ LdLLgc=z0!%7OI;^y"άd \UE ȓlOOK{ySQˁ9D2|ETHn]NE^wㅠ<1J{d ZcXS>xʙ̐9CX)6r/''"3c6ǬN:lu`b՟.w1/%吟n,S+B-i'uqXxI-dhЂ5U! z9XU#+ՔN7Uמ@3#oFU[ 30ZǢ*H9wlKHz s-ʯCk0So@/?X_N5^M{qo2F^ëq?#48A_Fe0^PK#>;pޠwU0'zJhyYkVdJa/TYOjFO{f?&StdU`dҹ%}~_/'m)r]'dR}ԓ$q=.eo^Yw9[d|'yxD,.<)A.zJT8Q&ł m.[b{Wd׋*1,tn=׷ 2ð0#^sXiDafP,cT en4gKhi@z!: :X. }e鈀Tr"Ϗ76*L]?"(KGk^JUl痫?"7?S.ZuY9tJY[F.l.2*ov|55[v<wq^{k c!]PM_9[w[XY$ϒWF~`qTgW{f:^wxJ&ꕪPV0<@JܿxWmo6_qp?,1H~hF\e0tHFRq}GR%YQ@wOA~^?@^/xJK5Hi-ƲX+L}/p!/ bQɽ{^Q8=mo-/7)i2UN&M,dJʴ$5B .Rۿ~2tus/5Q*D6^k{Rt,Ұ4R.¨D<6DdO` 4&׭׹AI- Y맰JIywR$v?O  xsFw{c'ĵ[&|f"/j% c&5h߾}Us}'%7~ɴ3؋ğ/2u& /^\-cƥ)MH|5Жh`(ZޙX{4L锬)c׃?bI$l:Pq(Y$2J)SX<g-wC;?[ux/pDp $ы,Wwww-Qۊa^ub1: hg'䞸1P3pHwP".3?7IͲ;7fYOV!0Iܐ#;j"w/1yq3"!9ϻ.Hϛ`)N GQ)ۈRYIJc3ʝS2niG$OqKS phgnY|B;;C/XMa.j㟼>ũ}AO&nJA`/̡4N( h|vރh`0 /JZ8L)y:ףsۯ{}v8C;IYAt3sZF @T7~v(}:DEΩ'.t^`,@TmH{eĹ RdaE̟8`3 IXS]#)e ;q,@=YrVFZe4NpxJ\ƬeuOw'|ȟ}I`}azȭJŖ#]cٗVA\AvJ'XI O92%63dU~'']N^-YVk s98^t;CgKb37NI. >NJW#1[̒{6d;(j6e4}Y//K!(E<(c Ψ]ӫȣRΧ8J07\lM0Kp_4| M{ܬV̰@YX0AԹ[.1s6D9 2PѬ^hxHv8P|@ rP}%PQ͉EikOSj% Sx)ǐB xzײu4ӌ}ɥ_IZX 6zt]P0<|P?XJfIh[rߜ%}P҇bۣQg8v1NkH%tV/S}ȟ&%6I&J5K6\sa PX]c%}D_gwK5= ҍd@86&C)|2? rT;' w?8? J'v` Oii9ʨ(d I^5ȟ!n l^&@˧ᝄ=+]j? -}3bOvI*N eY4i0[hbW,y)Z=S ]`WH1qG|liShU;7}fdCܰy7^;Cp:qmGvz雴)ؗg\Hfэt߫.=\ɞHf +8C~Zd H+#XWpvbjHo6> E9cjdOM1jI ǝ9\M{>eZ p0:cTjGUbo=w,$ct*&?]v "w  몹dv{*j}T~CcɫSp|x I!־N OQ𘵬O.y;w,:'oEdXSn\nΰ{w ُd[~X͘ rxZEomBm7qf [5 ʘd m>OȂK @L{ncf)|T +64pF(Oɐ/_G%_ V ũ~e;iONK'Wi[YE˵]spmP&!\x%Cfn H1g9كx\QA^ k>$YPկNNJL6:gA Px)`G&"12pIlH ͤ <)K5g/Ϣ"H++NARR081`8*KТW5 Ix5C/*oJIŲ!IwYD҆B1JL)dt7[9!rP;2RI+xA0mb|.8:\~x .`P ;~'Kb- 8Z2 jB;$=Cd DDTldHy,e{?x$94B :]HEpS̥%RW*<քjSʊ?$-ЄZr;VjoIҀ|偒f¦uϲ`ӊ=evYqaxyV }ʆdyj6"n5Y(9Ԕ+GXs+~K*T)ETEN~&A08lLa*8}٘e~>W!!I )(<8.-8ecN];<o1 ޸k_VԊV"v&AIٰkM-?-Z.|MXO(.}6,3 7y2"G YYly9.:!.CE_7 ]OaXl#c':SPRٯͪYmmߊnJ;HأH^/frz ՆEJpJBDZzwi@LGt۝7Er/, SM+Rf"Lԋi zVЗܒ xҁg;zv% ;B:>{%iؤ/Ljjp .|QmKZ盼_ɫBu#tv祠2y[B@S DQ* &1ro =6ϲ%d=e,tJ(9@*!T yyF3V6d\3M<3iF:sRPـ<&ISHbY̗J\K=x˾w((R'J7{CT  H4ii%hڂh@1 ( ,.vxy>۸YT{ el$^>$\$œED`W$LW(P(rJIQ/3m+퓇@Y+m#-擇G:$ZY1Fs2x4ޔo_#W762iv1MrjJ[-*P):[j-dQp"(ם맶96jooG=5?hŤ>v!-ZF2lh(;Xl S׽,]8 gt[QJ Jf5.5DY]*|AY&:cӭW/WF6^zBni0v ]5hZ|mNUVYG:2z՛#Mtǽ+Sqazz`۽!D0t,;ptth̐8;DVXDxymU>NSov[Nٷ7+;ZĄ\(Uf?G lsv& ίntu]_*LDKGr~$Z`ҳc&KbҢ#ۨ3i0!9쨞yA(TDqӱ:H7`b|ڊk/0bGnJUAH%U5(Aa@[źt_ҁ׵!nC7ޫ!wߔ}?kj8yo45v!ɽYI @W;Y C2j굺Qxdp?gV70v}"z_IȌ!ʉODR2ҽw[rgE6Gi&rb/|mlB>=kZI> q`{\}듀)}<YlU~oNVs\JQ;ģjzY駞p2]Z:`P=kdf07 }!]e+$NHk#_PzgB u򂤴An+ٽ?Łнݪ򜏔 k[1rȐ ߔU*˝O?р3s"ɏиjVċu%.=uK zѶxN"DIruwkqVzȎߖPyN:?%@,{X[$/>#+Ui?9k\jCuM}H((;" "ROo>EgF׫-X: 7;NŔAPqt傃].DCai]P1/3:RT e0ND_BAY{8N||%Avۙ U) 4/pˬYk wsڨ˫ʔT[a׀L)\}9b]15=W"͊Z}'t/X e^J_N-$GgqLKT3>S(!L(!xe; h_F  ZC+d8*LJRQ%O( T'@"syW?0I izyjmF(f2z0cqWA2="e:Ftevt+MtJ8mDC}2I+e*[")W<`cH {?ΗLƙJX`Kv"rsg5}YBws.ɧpK1)ٮC>ʱuԦjj +QȏzvGz)[:,iTլo*HVY|#3Dg-a&(+0FOOv1VM Ѧ{xHv A , ;MC\1*܋ri2Mz2l]:^G.ui) ˢ⸋='ɠsUpқ =R6,c W`͸")܉L*Rβ"gDy wla@sf`؈`Xq#BsEDWEI1P"Ӧ0+`Կf%qCYqd ys6 ⊏wJ$+C.0NOV"y7/R&:ܖuߢkGhoO0 Ƛ,0l;ȤIʑ`Y4 ߯^ CrI 'f+c0v3kXd::=]^קhx0 wr(b!ڙ-`8Y8Io0h4[8 n d,tELň<?=a:]/.W p3_#\<@3A]-8o$H,EeI|*Fh 1)aE&ObKۻC0{z /k>!wiPPEġxL,a4×mO1H:lRE5Eߠka&{zk:MȢ5,L^ eXuC}霤/Q@>DLZ4QR2Raذ"y6c z (}3!WEĮD( DTmĵvr`b$cg+Z{ dKT\%HS)ZTr kئW,-z N$< ៾nBB)KSH剪.<npm"C@ !j(wVϊ-b‚ %@XoOE| ԺҝI1괬ilw֖+ HV?7_nC`} ]vd17WfUYbo%}o@e-;*ͬR. }> \\]:_ikZ~M+YeG=o~qmeZs2Pgp4P89'Xb!=e p!* {p£|3cS?95Qp6α{q{X:WJʒoseCŧ\!|d,a ";hkiA0@p{1ga#GVcD:8:64K@ vQkT;%蹨Lz/RpT1F g:;sхn*e'G!(UpӅ}wi3+lPLʎءv/6٦+}}>x.m7L6zmBew߂=mSM=l9w_mCt f5-ӌG,9j 2,DTZ5TEf(Zl])(Ǎ :Ts?%}W [*-T͎}]pMGF WʿV5݉/4жcH} C54$u9Z:vU bQz'".j=ԱJۖyu0_Bp<ԈkSη#ڧ6)un([탛SSU>}g?)Ư~ AjqɊX!gIO$?XO Pti;iӾ_W}Y:?:pBט38(? ')7xJQ1t3h9-B,52D I".839ȝ1s#wݮEvC"Gi*-&}9..^ĮUصu 0tg[PmDh6+OϡBZ'Zu\n7ZG!bϰ xZx 8.u]D5I'2u)Ԣ2Щ#%##?O Cw,($/(rp._,_ͽ7ZT\_V;*z'xq<9DfoVA?Ȋ}(f`W)ra_QxkoH{~EOc"sY>\',Fe|c^w;Lv~cp#-])\N'8_AH AIpHz4~H劋L >̯t>'oڃvC0HĈi#i"Ћ]6N;2Z}rHA)(摘CGqG`\GS_4 :. TtQkW}l:Ce7Tp;\Y#:C5, c? AiȑΑM]&8oѲ .M '<; C27g6g r7{<]o2鍦ˆƣ`:z/i0귁p-Nf TI|7TbCѢe. ,=I"b&Lʄ_2aPE.PoA䅩weFc!&X eOYTX\Nm9=]&LsJ"[rtɑOQNhgQ]Vp +4)wg p~8ӕTHAqBRCch%J1|d&]gNcN̓w+"]tg#$1) !?<~r8WkwÔ0*Jh<U1sAyNE@vppik4l# `t;:׃ 3'糅#.c^Ө#p9N#7zv$ɗƚ8jd ͖6O6m/a\u2}LjIx8Dmꧪ&PQ~~}SEe5տ8RMͦߍ)G’Ch,NSo~Y}˾r.ya3 q_P\2m%;:$))J\^?`K2}zƩH;Adc^bi<41MxJ&5wctnF&I\N>k>?1nADh,Cn;.q $ KBo3Rص!.<<$d&@֠guhʀ}((KeaBV$*!0'3LZMS!_tIrդ%v&u4w覌2 AEg+h N.;xQ5/|(hqS["[ޤQ݃F0' ]_-h@g֙,TѼf øn ߷5 Ճg,8w媫_;7ŒRQzR)vՕe&o~L&#,w?5l7q-V_'ki}O@BqHnhQUeQ24E*&sUf>}%]LPsLֶϴaA epEh(r |v׈l2#4*~-/4k׍:> EE"o/Ƒ89]; vEX=('pa΃0-EW$F $#vT(Iǰ ÿjv/+{Qe rVA~ \Ć_U"^ksB&.;ʹw[~J9YY'{7}ؓT$5W\(85  Q8R*Qzې :R))9rVrV+z]: =E`}x1^hu`-Uvi vf+7[ e L9Tiua׻[{Xŏj>A_ЇoاTIiR/jP09, 8L /A{3& 80bléO/b8FXYʽi>SunCcvmL-}g[nZ&D~ TEUƉE^#W%BZ?1xgC>utĤ8g,ve͂o㴺s N~>n2Fvv}Uŵ+s/ iן_):^(G#1wk2⌬ߜęXddfVhˋ6CZ*ⱖt2j'4B~{Dkm%id=uoh=%bvH_Ρƾ&5X69u{a)1QB*\X/> ?}YxVo"7~>:$= Q NU2ށn7t I"37fI~~IZIӂ|^TBZԽ*WFUnp .>]w(DY-CNUX`gԊC`2;@ǂ4A%39 dq 6BI}SgЎ[ب lRY 03҂պ,6q"Ja|ja3p%7fcҹL{gSz$z2>ǃ,w%4]lg ʳ`5( lrZX!W]0jikdX-ml71 F6|FyͿN8LLa8\n`0UEqԎ)\)1u!6RX)DM*BX'ԒAz-;Rt` YtJZ'bIKHn:5M['%$KΒUs! f0^أ-!`lT[T[ z4'"wї!s^q>S u2K$iظiB`u؅[u7R~R*':h6Ѫ%ˌv? msjlPz9u;$sU_Wݔ$g#nipkFR}5Q%LAVɁFuMn3V5j [=88 x<5L 3UQq ]*0]Vt*+Uf<Ζ\㭙YRZMm8K(c^0sz^kny/.r: tŞ6鰻l@Ģyɮx6HFqiڒYMzo;q\6`:yݴGH}~s9α]GnWH^jc"Y-Rdo +=yyw^uM@w őؠ3^|X&?\N}gCc'"wIׅAN<}۸McuXЮi`7Mht!\1]_dCI?!Q>y=%MM9 R$$)pcX  ھCM6 E? "MwԎNћ$ ^oumFm7={`<ňkߣqL"ԍCȱL;D^Gڒ)EnU#`nD"M4ImI?#Yφy?8?32r8N?ePࡷa..fj$NRR]S{M:F[7)%ܭ {T NNt}K0n?:#],s#p'&?~&/ N)N Jn0wq/Z?IOg%u<;2$G'3MQ'ubFl7`ZG#4n[CDW$Jd>$'NCD- 8׷=rFI 4` hVS-LPxjl SԆ p* zXla)Mxv2$p3EZP>!f"vn Zü FDt14q@{[~ed/ "&bq6z%P3dƎv .w6p>SBX zJC;ԁQV v ҍ$J}R(VԇPڔʨXڊiRQt!(؂襑>LEQBЌ!ռt*9wgЇ(Q1\h݇8,o # znd]fl>4O$a #F 0:DF9e)c x0jB R&pi\Iٙ'΋"2tΚ3ڃÔ&Qp4 )jc7H1"CIrsGZ,Z\Akԙp6!^aburvX6̻3a ja6FĴ*~zD$)D-*g̉L2&t4~@m?ÖڿBχγ4XE~櫙W1%[LC2iJ.gA+&\`]]:YZ0:̺cfC9E1rP$y@܆A9'?c})TEvLFUm~ PP^1_(tG$_RĭPV)qo t nFKt&M$yׯ9=\ JI4h7nGx(7,goiһp Je;4(:%e*\HRa<9ŵ/Byd3_h]ȯU*+@hf8@9&3w SȖ&Gp1BT?Y[h@?K73k0N~S5ƲNȫ!PדU3,Rc H]sr7yir"ij6o{7:3* I.9/_FG0yU2b+,鐰Cfj:x#HXE6ÃL>,JOTȓPSUq>>uٟtv;_+ YO5\O/q&KLKD)jqS&4z1r[FSO҇Hʨ\l]Ux)X3Đ`SE0өְķ0e!ZZ.Tbj=2 Nn'jei8XOlڿ;&Y p=d5BUs+LZ=@)YuR>ELF2Mcw}5`w.~Pު򯆊CPOW+Ȏis?e$GaW׬%U)af\8ukz UtWEW^MEAJMH5W"܂坵UYjAz8g>Ǒq LorJJY'Ғ'~:W;*3j#XxXɔed*:(Y̧ Bڴ/!Y!?aI eR'Pfv7f3V=ݑ;)VU fkI ͸CS-</ ^BLHe&5b[5ȍ)?NS\F3st0|N;./)"<3ͿpǦ̆*T$=>`6cUpvB* 767OoW_[C09b;Ih+TgB8}Ng=ȡL>Rc$Ha{GIliNt K҆/=xQk0VuTku&?RH j2[Q'ӟco~Jg e_vO3AtO+&,S+MWڂcS{~BDX&FdSUaOp!6`]ra{^?~HhG(N(ǫ3zf]Ԛ\3̝WؐGⷀW>j~7x"QC;.w4~zd|lWs\=~kߏ~^_瓱u1^5IBZ`T5SWur{E  MM7&t/) <(+wm^Q`H/D!j\KR@ E߽D<e^Vq~ ;_IOe{{h1p qR"ƺJ1nV Q~y,p>DlBWӨtTy\6t<eI,<#zvCKE !g7Z1%^~vM$Aںm"LqpP~M9:EuŅXH'VOPW`ݺ5A}۳t{cBrC%M%T)I&/(W Zl`},mI "llm}2gq'(5иЂXIGZeD5:#t4dҐ23aZLB4 ӄ7wƩUJhv6ђy* & x,ZsbP.T8;&cl =QEBcWXe"a\hiA <op\7lpiYOq 顾6ɣ@1-2Դ;D %=Mko_s`1X?Y`3#Պ3:A6e@)ֺءA\#|&Jd:[&]&՟ySLF: 7{.h^ 2t 2ښ]sE]BÁ=AV9 d~?n5/32-Yv@ٕ'6)zW^[_[eS %Wá),A rD9p/VS\M %DhKr6@2׾ J >q⌣h.ahP!b4Q6;>!5JD{$k>ͭlwALF2X빠Jm,+G}FCpc'1;$LX՛_!+r^ .+L3Q Ұi|l\W߾y3CdfChSR} -Lo`( .X~Y<,_73ƥsZtx5ɚL`~=+|d\V҅Qq!oZ>}Ό=|k yδ\.'(!N7S!0ͧ?:e*0+{fO+ 5!?@΃: xis۸V$l&$Ȓia( 8H }n{.!QlD" 1zXt=c0;Yck]1}:뗯.֮!lc#Լb߿wuKC> uO%`kzh1%u%j h m;1J.A+|0F`z;ݸP:W#6tK#cA:CrtlFm'V]#AN6P,F|"čCȱL7(0&rc_Ǝ0pI$& 2G' 7O Ki4/7 hx֟GCxzïsxf <6(@KELn5'q^a qG(%tJ `pd;1iO@DJK&]7J$) pk"xf"oYs0x=1]'(d y-5X3 ;e%8'\0FYށK=Xi$y9M#/(N `lp`YƖj2Kưl&hBoEQ 42F}|&2۪]-+]2.T1s1ymDt:@Lj?ևQu{y(W8_oVѷ,=anc"@v<5ΡFI#&R 9M2zsy˒9~4y߹T>&ٺe&D ATs ^m=]O`BcM)]RX VHAy(Ms#EA+Nl>s5Cp^ZzNf] DNlBjL>/@w/e`l[)U+xsb+ϬvVsZi 3Qvv}HQ)jAz0Zٺ*<E6)˺Ue2>h>EڅYBZ A6CK^,nv5y)*V"HvXTb/.q!ˋ( MX+`YFaލ*bخe&6j!^7 䭷$(^ ԃ'%}[q+וzIr NV<9тnˋrkejGkߕ=pͱ5ٞ(g-({[3ih®l8`Idcy;!ϮkKp_e QY +R]5#d;g=7ڴn%68kT vlѯϠfy W}h6<;m|2\Nˆv^o4kbg:ïXDRL /.j*w;D̓mf"1=($ A//f%<{h"=wQ@q LlT8\zyrL '@:r_ӃNA}:c>]:x*C2px YQ0(8)oxsEZ;E>\q["+IϦtnjr`ͯGw:__-'y'T34h1?r֝Ffp|6-ʥubKϨSR'9k/?,s<<7!G%$\2 C?t=U;2t4H(hh=c μ=xLFY)S*aN`?1M S&\7Ɯs+&+?%ֱO!4̇J>KxWzž?$ 8^ )6.+쬳fTdV2Qխ(JPU@E<-OJ%j5}(ث&W nPa_NI&qcR*M<9QTG}.`I3/-'*ֽ;@rBBj~?P<4mZ?RE$b>kcU O[8z;jV$TQg+x}yq1ZXDT-*~~jf&%um4=o(uӨ. W~Ao$"ܩ)K&f5] R\%ʣ~=!3-e32{ܑj봪%>UfF(JsH]ڏ-4VxZ~mۧ;Ꮲ:%'ĎJQ͓1?R)ަߡ<*^GltXLjuA7bӿASV[֩qicQ )]˺ъҿءrŨۅ)}zjo Gֹy>- >@᧠_ ItruIv_zA!iHșZ;Xh?#w)Dy)Nz9k'ʻlo%˱ي˭A;-r:1 D lrjNiobZs84[춲N9KDC;V Oy#G :GIfG9{,}ݜ+{?Dłnm(+SN˥9j X`cs:>]D;#j_[~KcTdHȒݥ.%*G'f-\LorÛN:u&=a=5pjxZ{sOu'  n:sc'6i`t:BH l-svWJ HrIq=:y>ު ̣d<ې-.Ruj%X6hHwox6MgYo-9_^R31uI@C,)iL|aĸO6OH驣- qDԆn|U1w(Y3|4&iaMPwBb -R :muӖ6yhyj]u/;Q$;n}F b-ȱg g ) C&h)qY$B6ED>P\>9jHwtD.ڣD&1v`H.;! D @LPRW64'œ+:lP_+F d=:]@`&'W`gwd̎T'048O1 2IMDGh0pre8dlH;,4G JOfJ&@2pI8X`F˶acQ2QcD0{F9Q<F4;5g(nt54QC dD8R6 ~<ݛd_ r#XOd&̙wM98 TUXF SiU|Nj]([ANz폝GSOaؖ~z(ٓ aʪȠyrₙ[u',18ڃ0u)w;%X] majRHB>]'- SUQ-)AZH06MMeO2yҳ۟xpmu:{E?< j2ZB='ݐ:6AWNF uwqI2+HTO%:vu_J 0_ ^{G)5}`u['7C0ܪn77IeKJ {;֕-Zu6 ~jDU=+gغN>5hO.j*fRhԵ訹S)濔ϫ PA!D CZg31KR{J^nH#QIP Z.fYC2,ĮUss2f[rLvYdՋ9`CU Oqle2eN]  AcoHR̟sx]"V [ybXZ0FP \*>bmXŊ`ͭZfzx>#K )R?<1J ggl30<!FyxR6 5FxM._Ġ eX[x*ȷ'1c$]r%Z7*{=*Ǘ_d :9^+TR~x1_q2ܓڲRVT(=`ILddR"Γw7ovn$)aIxY>+3 YPQfJIdPO/Vfm{^ʇJۃeU]yisw'g$5$y!ؘ1:YGx6`[Ow[߇?qA'yP{i!K`qˣ壬y=c '3]$='seYǚ1 .qۇ\6U9 S5f}'?XsxCJ 8)@9A*TuW:D{RdJ*ҲWnd}bCeՃAsiӾb Q@&=Q#`&]^N`h_CidB&\k ~͡B͐9BN՗bŸ6M)3띕k\A~J1,r 'lgw#WGS'Su*³ټ:v70Mt>keU~d/PL|{f׆L2%FǶIfsLs.-#gR;wUAʲrpNFTb@eǞ U%ދ̊-/eB(!ߔmNտ&I y=8|3lf55WL-*﹣ˈr1`~>[Vd%c+ВO/R`–j(6d(0 W!:E9 }nȃI:+<HoCPJEj WKk$b~"` 9QO./o-Q+[jQ'tG;wx+ 7/ \ݧ gZ} l_^3`h*y)c{⫘R!2Sxx;D2)y4)Ww6xUQkP!9ֵku֢Cqtna N%m\LJr1ǨO> Ig {W|oҴMl̼%|(?z)>ѱ$W||)QดJY#(9)fhp ~=EǮ$.+\TkqGn*Qޏ-ŸQ:c۝ )AZ VU,P՚B<8DS^ۜ"FLӨi=PV4e՗~("!$0;~ʡR[ 8 EX(>R#?cYnt+AJXC`jkr;0&>#(_߄+s^5~ /m e_ـ 5,Ii{^)!"Z== !eNh*6ܪ]įhz:7n[ǁD7dMe' `Z$m`: \N<Ix0/WH;+뫾Cpm4쌳.:/b)8]pGϢ ċgcNHz Nзs3:jBLLnSDsh:=IࣨŪ k~nrq_E _*pt:J[>A%Ϣk&m4\tϵ}ӕ :3 n1,R,N0ГN%RȇA@]]3YsvKǝ`wk֬k\loyxUKoQNR(5憦Gm16RP6F'SLΌ3hc;7xԅ]rOqm\x3@i8>E07g3|2c`OaqE#xwU9~tsmKON` A GL` ~ȃj J Mz.iJ-B]kU|5qP A!p] :R0Җa 3 * d,F+&@ҔpyH'dTd%NE:9m/jلcF(NPC[GsӿN1tpc5v`d,C0x͐4Rs. ! 5!ѬXja1w=/ff6؅\n%wͮ + 1tD][ Y3 wzFkTL2Zɸj)pJ%-5)AF!/rSroScEy;<ƮKEd$ZwH$S"0 cJ D 60KS@U@%5!PSBjc#|O!x=ks8+0Grr,Mٗ82ZGI4I?@.YDdGHǔn5&!C)^7F0`tM0#܋ɒƀ񽹗 }ի󂑿:Sݹ1>[w8߅{IП=+&q8 #\`I8ʎ&-`zx;o]%kH8$2V2Y,C߅S0gg7ttEiS~K`!&(_2ҥ="]F96wDޡ@l2# Bop"Ʉɤ-Dџ8I l() +'3G=D+ L!ǙI<8?ټu2`ABVTD `GtxM61LO:@0!QK^I؋aG( Ł"83Q1`G:KưT,G(Q}mA*!@sEO%13yZ J\e U\6 \ɸF*HZ! o nC#LDf X0Uyu'pUW15QԞe#bq2 '`XrQKA"4RFŠ_V["Vcef?&RvfqfEF,J9ā{$Hh#AAڞ[WI=l#ߧ2.:XCm23%=Y p>A9Rr0J1&2%KL&8DS 1RLv}@{0(E#nb x0v<j$N#e?u\`Nb6_r]YcN޾JM*gc#$)]1+c0ႝ-v3,Ǖ3&COd;$bhJȪs :P4'?qgVoY؀.im Y b,/Pl0ph"f@~@C< L9 ;k3rhohނ:dsMONy&3Ǽ jo// eUMn"5U_޶"ަ(䄓cñ/u]*1qsRHB۸B,hK#+ň"b`֤zl%ht?aʅbeJkO \8c1 ޥɷ_#,>TZ2PQ 9h HQ厎P?L _8\SDZb[ $8} ]rD Śj^+M&'# Fax4JaFz_y,v5k?W^{= E6Oq^+D+',ǍJP5i41{wuG/o;FMqzHg4rH`0[GQ54[@NOË˳LWÏݏWןv ':pP\T;z+_(ή/N]lѪSyE"?\Lej # sDv?>A>v*g 5--޺]mop/̴ȉ7XlOQqH e27ioDJ͈QY bĢ6Oݮ_)-䖃w7TOج%lhgJF*0fwlA@V0: l8_jT"25C*x[*ft o4'o DZ-m`21x}<A$g=sR8]d w)W' Xr4S-]=7 c[ Ӛ&-]TNT0q9|뛨Ԋ !:W٭2cEY#raE)VՎă7VA|TfͦSe.{ʆ>m,@cÖSe풴iVDUneIuu$K5Z'?] ?vo~wPJ*x<W-`2XkAPL1N^( pi%r]H'h‰$\(ԀeU_2 P=6G [&ITdWPQ2fho01 r–a g?skwM*y&9=!r{- QvIi"4] 4%)#1 \BL-&c[-R !~MRI$bgOR)'L|G֮Y D<$s8>?Xdt4jJ7ՙlCcXHB U'8l<0 l[!#qlK?|TO])kHT%]PтR^S,f'\ 7d3s7@w5ޭM5֗:4lGyh0ݔ ބNrQ`k$ Trr?D,tqNG7̎g؍[WW&7A-׏u6yOY':ODpZ&+ a+ TVL>B@_t5| VQe1k A+u 3ުQiN|yTbφw`<У"jJ6]PUI/H\ZE}l) ^E1i8ҎL%𿔻ƒR,04㷍fq)[^0Z`OhHojl.?*\kgiSJԿjHлZF%5_laZX@7uNeiFGx>lޤ ! /9K.wXh\\bJF0AaLyzAR7(BsŸo[vH/ݗҀ^]hƒDD'YOk߽|I]czw&WS0:t@7S5o刏D\kIR+k;n^+go'\Z|eI@Vϝ1q; ?Qi;_3!M@"_'F`q|҈-pLxW2lc$(U*RM y MvH%Ǔ Wb0cE%iPP27׎!}+f.6op%KPo$7"π"w2xV|'25w=돇s~DьT$(_ Ԍ0j/R((b]HܘԇbٯO Bv#ۼR1srL:􉹱xZ ; A2ѤwK%1;&v(A-43]Jɜ_b)k ~qigowGSb4.]krAv{1ٖ͠6'`=tcC<(QV0t\:Df^|sg! ̓Ą)n2Q#܍4[jd)Z@T2wFMQkYk޺YrY-6[unςnU ߤW-yY!=HE%`akVE+9STf'64ou;"Ɓrm65{*ed/ wXrV پtܴhԶi Ȁd| Bva)؎@/~݋_U;+ Ƶ;6v~qޱ|~&߶|e]_ƑBn]o9ۄR YQJ fE;d_$Ȓ& !MA"܌Ž{{|BTZJAeK^(a0)/43_N4d^*qX*leq00^=_/qopNOgADs98˳2LQ}ˉWrej.hZmƋ#8+ bקƲxz( 2'B,ejLbDYQʼnq P*yEM ˬ]%g* IxeĹ '89 GAȏoX,'Be,9[_q)i):֞}V Jg 0 \9Q:Q]҃ oB'PZmqJv5R^۹` UlJ3Į /]ADpZb+"DLy*Hݱ.9vvvN7.cl g.0Bm&E(?eL kBM v2򙫒'yC/x lKu};: P|Vpa5pn#wt58f1rz $YE8NSǿ=v`m̎:ķM^TWq>.Nқ8m[HMU(˿.nbuimo~ZCtl7fIp$qoe]կ+}P}DŞ"@A8N;Rppӌd` 0g#%78TcgN ֠Z *هiMUˣS8kTXp@ 6v7x;X6ĉ?ا8(˃,{ Ec5LU"ʽ~bpgUKaeAHSEVz8'7λptqϺqq-_ #? Ć3OŧDaߠ&"cJgLb#WIk|Za;pѐ2]8旮RIha`tt.` 9 K QSH|er.)Pk,x4B(BM{ Bbz+sQX;=-EEI ;;f2Z-``u[!M%#)ZrIyK\q [*   @[dʈ9VࠞfQz۪ *maJ;{ZsKna:̺SNJ35sN@Zq֖Sy1T@>)Hvr@ɴi]N/i3plDB& ;ZiG8Cg05yJX3BCfݘ agl ѐ‚wg7w1GS!lDtaSOÉ0.5W,s)10x)ŌųJHǘ!?xQ=`h*0~ i2+C bK3s {.( |#|9h'[QNSGAu/a_eAV6bU7`aARت]5/ԀJA[khAF3Q&di-;v,6LrEP0a%r՞]d׫|[H[aP459}Zv-}ݙ HuНXYH}%]$qvs:} >',RVfcƾ{ UzHrdyM1ÝL&ɾt *,DmCYSDk~\F wei=lc;Gʓ| [QNɧtz,VLsʼBע XaËa/hk-ui`}3v~N~##t} 4 7yuDX:Hk_55ځ,uP^(Wj*31K(-+;F%`$}(쐎sof㋝mÂ; bgi&>*`0Εf~R)CPǥ X ByӤ.(_F9l~خj&=H;rݩ6͐dl*M"p7__:,nC/Z8-LA~SO~7wVLc]f4Ih;jk|!}Fk7́rDxƉEhp,(SW<*\j%O6rp6T*xn7+8hLOmV>$؝\*xFɗGuӱcEȞ b]IP+/wjU]єή3mŗ)k&jI>Cr7菭"~"3B9q̨wtD6o Xz^?E4 _> ~,=HzyCZ5GҌʎS峬lcog][yz]2]y{?X`!&Ih;[>RErD"ڣwPAs$q?I? ~zCU*wb?jC߻x7yt`E^zS}^kI/aYz7 ,z7ɕZ+M:*ګve 3N2M v {ٮivB4e! YI#.+)i4(2Z YՖ{͑QY5Iy)ٍX2yVRIm= Jƴ|zѢ" ʹBethٵ'BOx4VIV&:gCtIFӣ4y ;-%~sUR[:[g)P ;vGOq$ ytO(^Eo6_dCūtZ"zlqrGDI5Dxpd} xKr}YD4~8c|gm/C&9EcZԚHKָmـCF܉*' B/q+fm1T>O{5oePU]IJ޾=7 aFʒ~$Fm{/{SZV'|$- ~}wE'kD~|LCV35H s-|׃P?UmV f/R;ۚQiRƹhBinWĨ02#_g4o|Q[S``R۵h'5>,Td_SR5m)s j'eJn-w[w!uH'oῬƑ(]%w&Xگ\Y|CAXOm:KwS $]tq^[;Zp cNB!߰eYW 3Z5(h߃Vx6myZDG[e)cԵm%j:J6[}iABJרk3 ' r|:p^ {߱UiŏGsBQ D~0VaV<~:B>|ȗ >ͼĽ*Ws3<66b?4WF]-_G%PK>3ᵯPC[XPal`k5\YEvG1oJ{QhQi]Y bd^ x]s6ݿ%G=$LmgcwuZ]A~| #^f. c$\1rQd-#o&bdipQlyBtKB.I3D1ľ,Qed˕'2Fe8\Ry'|*3WRFdٌvrpz:>a|KhtttC t)Io/p9"1_  x~,?OdE`9}"!\l@>^nf#Drq1}{ry7y¯7fz5" 㰯@LE<%c<%)/|X ]2`[!pD"&~*@ڗTG p ԟj=W4fxDz&_'~%=Pcq(PޢJDDRF jr{ p5et+Z{+ l$A6j"zW6dI*'z/a!^:fXugߍz|z!nкb.dN&W"~1 tBb両 *YezdCU n38sGdSLc 4F ,˱F& -\ImľbtHօ*iQ6&r.FE[@څ6"o:ȭR@ ҥZ& f+Q!> QbQ&IECkolaB*Ua L(ݨQ&~v7tY?:E{)J34ZYDsֿ;•Z[c΢1t0*w%PTBIMοz< _?eWdpAƪ40rqhNLVb*`=gN`\0 3cUêИmgp!.v&&m1V{%{ KHsZ*]>9AS!Ņ4(|I@%Hs-F%#r2^*^\5RU1v-uV7W_{"M U6#(nq aɦZ2X]LLY./Z8VbRfLmJzyxcvXaQ@hZAn@|dC_r`|nZNx{ 4rZDa!nGsNw.XYR@efIVQ=2c]M )p;Cy٧` H,csstaˋ͘@JZD@4f\c04# 9/h!3nQjH^u XI,L[ =hbӔ rg gvZmAt]7-ְUbaiE'L=bX&2[>V0Z`cXyZf7p씷^2IߣYk5s| =5`zKP_u.cZŚ]CnngTc|ϺxPmLd4@Zۑv;X8?ȑkU4THnX+SئlNyI/F^S~-fNMd Bwonj3 acN]C B(YM~L:۽񘤺@ Θl QAsp~uhFx-vTP?ubdMnaekLu"ə5Ch{A-hsꊍPIBNH4mUyM&Ujz,ƠDeoYMXUB+=`3w1Q~i;Z,:z0!U@b!v%5&*(D6S۩F\ZFKѩs/Zn't/'k-UzZ;}F'#8YsYr;[|s6S^7KU?~7eqŨږ==rri?wjW nfw=q2Ѣw4S=~x [N\-|\< PB{ T#Y*m4IfHqՠE(5\5LR$,y̯!uV<= y5q5YҧeU$՚jw䖻4 7Hrw/WR|A$tI i -WoxM/@:ĸovmя3npq3w~*MeDX *S:rLJs4zXRzULJsuejл]Xk-rIq]ʽBD5h*i3pGTfc<2\+UeGsrQBk=6*)#ٍҼ Eیp&2e i5U-5 -J]ٿ\O_i򳝈[4kGE&Q\0u(+֘ 3/*/~KcqO߮jD篟03% ĽFԋ{#cCl10@9< ˧ unlJmრ~̌(G:N(| Ocun5p/t-s>U'嫤ETuVǹ+s\JMm*I^v]f!r ©{yѢ'eGL48&s/Ȧ5"cTKs{Pڏxw0_Ef4g|VyMqx]ms8_UI%˙}7Ql'Znmi(XH._xw߯$EʲY~Hdlt7O7x8DovupEa4K|uE}xJ IzUA:?@_qFa?y8,s;T22@=Rgݡ$C2HwYXrdQy&d0/p^8 XTŨ7F=nr}~="D>|}\]M7g \NOo/{4|z:BiF$6CJP]c,H*"AxYzK-b8[9i (\W[\ý c?*h~C] 㢸Oq>^F|/?eEʽ"'~{38BIF ,@KX <5*^.[(Icn)#/c{4l@F׈dg3If ab}й Y*"QrItDtoqMB?]@z/NհWI5UH.p#-.V܅q3 GbWΌP?>޾5@L򻨹E.ׯ R&[M7p,kK,VAڻ p *!c>}qe&[]jEX2ճIrZ"׿4'z;.>&9GqFJYTP/{E,LO^Bk& (">848 E#b!@*ШYy$_+I3Y;QNE+vJj6ZqE!t#;;z *b j =)59SlYR!//t$ ZS䖻n(g lǍ2h <5drjCH ..Pau hU.ۚo¡swh%*r7]#B+*SoɆM]̋{_іԎow,4;+$#{|dX;DPI d::R<* n #IW^P"2E_CQFn=`\Rqͅ2#\+(jWに?ԱP)KyRt IFBW2 3kU: 6 Go TƈDCF>sƨ3lB>q{: uFx ˨bwՀVMzߋL܎,h J%|_y.xeFѽOAv&Iq2Z*}-No\ F.[XseP1wPuPTw+o1b5f@8 x=׹yް,mYnz?tH=NzAː_[zD!$ yȳ́'sl"]vJZo_D {f Ĉ%\?EEl0B9P 4'@8UYL6{ *0#(S9j[u+7{뿻]]]^PF$ "p@ȵ+pyQj9r㛩XZaFl yӒM$x{hk64LYJgX^̪Fen-4e탴3ʳEcxݗW|75M>tF7 m񷑠›6H%IB T57VQXš2>dT7ܲ>hI>È::lit6"3uFV6ۻYU;~ `ȒX`X)Kؿ?zvr9OϮfeG.#TNϮnR"*Ip  Yo pMQT7U pQؑ/ERQ-x#u\AOߚծNbb->5/MjnPGHj|D= `ݣ$FKEߛCucl.*Myk@ {LʩzdWjqVJNՔlp;#(IH+e$"ȅeRN txbV2x,,[M=jS^X}! 7f* |lz3:N]uJ+6p0F[֎?*Fڵ*fUbfqJ&P&*RоTվTs?XXjм,(BYGkJ"E4 Ћǫ_znid5 @wRHyFӳw>7$˜ng,g ~e96a!ˤ(:L̫< "g;h/ #?JzFRk2^s%!fA;#oݞ C9Y#K(ݫ=ҁnqŔapneT:h sZ\Ӳ3 DA6aQF%=F^UkygZMFy~ކ=D1\[Gdw9Ӄ 9[6?+pcH&hA0Isçy誸P}=.\l3lRꎕ1jGak E~~!̼љ :2GbѦ6gA6oڊHsV}6~\Mϧna?0>n%UQ_)ocys? 'ƓX:s/aT-v6QY #eH"HZ546,E>dnX:&Iqz{!AƤ)k^WJW뼙 _:ݸW,9ʂ뛈[O!8%LbrTcH&Y焖|+.୏秣j?dF%TӤT=wݒl42f^v~@Flf}2mA;*ǬlVl*s3vбA;q&FmYbJvFk]/m"c\#r+2"s)l gB2صO8S|+e1jM rJ5`1+U-ЉPjtSWO#<(g&vb8ІuSV^(PP@"Ra+٨ZS/w1uOY%R'J+l ͢Oc'o;8 }}Z_Qz2#{@f zQJ &cg>%7f MtF5ǖS4f Vq\RW=#.N|fHp19LVT|0f }"ο:o\̩6'6^;Tsh? SFaJ>]81)$R Y3KEnClq ;(CMt7'zu0 Kն-tPt*in>ϯPguş}ىl]҉l>LH7>($kiCBؒOj]fFeL_԰[q0'~`MPS\I|䑚Mxk.#q}[c'8 2pQ/DxXߏ8~QR{ޞJe>'"9)Eo&? .Ubo@kyn v.c!Z믵NF NF.t+xpL$Т*~H򪾌j]oo&2De1TEd-h ^_A'6)vZ$B~ 1q zRa#2ۧg_ :p `AϫAҹyͦ/2}m^ϳhx3޼$ŊG`T6؂HQ( hbaƜf#TX=waD:#+ DPLa4mût4ϣ<F7Sp2~?zM/i4~$h`J)b \lː\ST,+EAf--r-p٫#x#Bi`?}|ww?L_ar  OO *XA@_rm2AUX6AJHC&^|ߜ /{>yrMvǔ\exZI<ȁ&qj6z*sϫ)ei(8XMY8pyfZ#\&N3Sa9T~2N=?,dULtr9##%l$om+*Y$H0oW|łsr7Z kOv BFVx7R;Ma'찆ooYҌs:=aS'GǜpV}$z{Ӄ\(7* (9u++Ng(0K@HdW0 K}Bi= S(:,e?6DMòsT]HNāIO- kˣefg3rVoV9c˂,1XYBc,@g}cetYV cT;l,p.edU-u$yASbV&$Jaȧ&‚; GT$i,u.ƅŌ5K zrJQsxSG[@RWiB3_TϨ}7EwiW:jlRr Q ]n8v(2pj-9Ζg7Yc/a³mBFjCDYթ;URyܲ+/?\9z Q#ja[<6pgxp}/,/'{{pCO xks6NSɑ,ǹ8[VM#q; M E$e]>dI?2=rXkvu(fOH ʮ+q,ܾ#~E XL8OCÄ08<;,Y2x$}y'DHD=K0>jͩiV{ֹv@q{4ID\;{ tz$@K?m8$K;ƅx l`l춇;%aw@ Ո\Ũ/λn~&ɻyPX.C?rd2\JuRj0 IqH6aOipK#8"!,-@xl;MQkgjq[}]:a>%e:YpAV2j?;6ى$ ilzlut,^Z$kQwZ2-i 0ICNO'kܘm '_-4:IyIDH 4U~wANB7?Re%8 Dl$O=` PjG=k 4 g2V!c0Y ~C tضPOh{ qnEkO6QMMNGQtoH̦%ÿQz/=>ÔOj-:R>6!)HrR~DnEoab+9:Z2 X[{.M3fXޣ#ؔ9x ZgɍY%g{-C?:(RCjB>rJXɉ;[>L ~9aE;*_#C T)&{NDZYSM+ CC [nd/-3]"e[썵[F*p &`q q`_BxI@D75 $D a[wG P)$Hߞ=8d+bKŭ`6@qO(&%sYsF`0.,beԪ^\;;| } /AZ~!PqzKcǪ4@X˞uYgWQ vXj'UtR'paWo{6)>%0X[J p ӗ[LKKt؁EWs.~0 Op="g\6*:r8)}'güNWn JBa0`qK88h=(Bס}g!1?ut'k/{}v |e.4*'|CGƯ%?" D}| -HH P(/3S0ܵ]ךa%L1vl L%_ ¢Z z]IF6ώec92gRN%泤,?=;Eg<[zKfdܰ/P,ݭ/GHƑX@Uk9[,WR^e( *&%l"fUl1jbŻw둔.98S'Qq%2Qf5O~nTPYld U~em 8H?|if7֢\хPMg^6O:3MYIO2SymN:7i Dv<~=f8@=oCUYqG7(lKX6XwIhcSshS6]D--2XeHv}8ErYzȦt%D/鉢@Lj@S\$' y? caY\2 |J3$VyqQ-us4H(Ϸ ck: M ^}6\+ëY^`MʊKTM.;}RБ*H]C4Uʟ{׸ˮ2mVt f1IS9pZy*/In79V[2N{>RvaFƂ$wBRԦtRlsMG9O +^u;A2!hD3j3^(澘rX_ya`zJ˯} iC8xWTdC)?>WfZv UKvd5ٴҵ*YlFZ3P!ƚ@#hDoELvf< IېœKmAK y.[liŶ5w,_\ &!ßY{z;i7ɔ۝|z':@X^Def>NT(d,N3*Ĥ} XN]]dxLF$HE^ߎGqx H,҆7O?7L(+]dNzAzݿY^޷{).#;X`{䨤C0Hi8a8h(6mCN+CXO=AiJ?wca5 ,9Az2L8)8k }2Bd3-݀~#Ahc? p:6=q1Hhk+4vJE*!"[ ?4oqe7v`- K<1,睺l T| t.`fw'5Ln:!* t$nZH4Z5Xԣ?3oo&W=<VS(³7r 6_3xW[o6~8pU5Q7)bE1 -Q6aZH*1;ۉ ;7*ЫNJ:uM$/rrc|y^<3}Z(ʒ>餥e}3f3KbyI362R,̨*2 IRON苴N^3ؘVbCT9DPBL{*KOԬJDJZ+vj-=af^]N&e$kޗ4IuOo{\2^ ƃ.<%>Z:GVQ)hg% .OH.ؑKMP,6rυD\)SJ%͝"RڕrR3VJyQ\l(i=S9rN'h8LOg B>CHuILVp2)cɟ蝱:S?4Acsjco ЮROӵ^.z:@; VhV}dvm{cR-1h]>,N`6udlҚ11B~h? 7ζ\ò8ߑVv"{p Q]V֏YK*-u)/,2r;3 ؠAY .Nue`E> %d3‘xA 8RE5nH%XY6^wftɜG]C}{ڙ}@GSF Lol⼃Gpq493Eh:Z*+_^h2Vj^"QBFTީ,؏1`( Fbe8]#>#^"3(6v`wQ#c2IZ1z(?b6qTO茏Lht_1VƛkWSi4^$}AQwρ,rOm]Dk^"_77zJZJI5^:痰Ghݬ{ :[o j.=V / _^q׫kkmR<nb_Z999EͽTuԇ!qӳ JwNMXmpVꀦ0l#-F@ϣ)[c-4)`B6~1?J2t5-cbuew8BvY\#<#(B"ugT4,1v=N*}3f6.)_g{^sO^Z"L|w揰!ɽYӓZ P_#囇VZxms8{~@3wmЖ)\vfklDIn'ɲ^YzvK}Psi6OP}#K>,U?^=&d`W/ޜ'yABZ8AνaNq_4cpspؓHZG` +χ?I 'z>Gp*6.8x#bXG+Hx m[<JUˆ̃⼣xfnDD/[ѷfi{v;av^4mbJQ& p;}D ()z'ț'SI#Ѽhy 'pV8YUJ ɒ0_Qo$u uqjԣ#` =D2{>'ί #pEDr,律& 4Y`Qo &r7һlj+2 sL" ѧ ևdO4%Uzp_a,v7?{8zXPa&0qU bPI)u0Ocǩߟ$ JL ),IY*r2I-rH{L,&bKrSr+vM&t H1b'$]s g WP +^,\2/F}?ÐZgjnГ#u@k\G@xdP = !A}拵Ԙ1[8Ø֣_T.Ґ<|d`+ 'cq!QnJ]f Ha=({b^]͒x Ib,RSvHK '.=)2d<4]z^|xE|ZqF# 0k`;xT)Pl}IPLZ^j8q!<;6Lm%vuWގ1%j8Ⅾ&X! GIbr̾QAEhTU'XK*%TYPIOz*BKY)9h gy{Qܹ2J3`\<|#ͪH<1Ly/)nL3hvra)+P1[IơeQ Tq#̎ʨ2HߢUhL"$ NT/$zѷ`7VCk!=z@8VGPEdn@գduovҮn>ͿaB o(;;T!6%>jXI^&Q yJWZT,ũ/_ݱ_NY{zGMQ 3R?q&xYmoHίrR Rͥ*M*Hsd-ϻ*f`MҪ:Te=;/<;CM84۵vY&1”r^rx |!շ N:ӂy,`y4E 9~"1gu!]!q1 >tZ`(z8S*6<%ـ%D&@C $0  ؁ϊ@dB@lWUho[ n0F5Rw"mK;UQá'8?$cU9bKC1"60?OxH͒ag{nTocwÓJAQ*">߈ qLH¬4w|$i`?A$ Shr ۇ|zlG$;pֈC&=kKQl0T&1㧞[vYjbE8N5t&wot)Y&#H;1l !,H龬A $Z3 L|5!?' - bWVfs*llnj`PbgMw91݊ѯ„zfnnw1k%))#'ӊCO-_?N>_GoUJٞuՐyh6vBJ$5aXYѠxܭ$^y>Ǎ'ƶv>aӫ~nޞ_nZ1ƒ@-|wal$sLŽA">,͝ݔϡrbS"~KWj.hR; =#q4T>ȐQ+QR#|fXT,j0u^TD)mKz>MeYa{A<1VhOkO5бe,}UvBh6m ɖUqBnBV \s{vq'Zgc\zʹEZNϕZ{4u ,-I) //V FAl8N>Ep3`t[z로M{'Մ|=s'yZ90!TG=rm%T@q2SIx'ܠd*u8?`"Hj=P?"N-$V*"blbb8P&Ofv2erjXYNɣ| (k#V[j܀rfU{5O9ZZy+t@ |2֑N9&<ʤ!5aK3MP3^&fpfpzPrwon*k8qK'gꍵ- vo{]^BWUb ;hF M"7DΑH{xV]K%׻6DRP۶&(ٙ]zDmj(h`A- Gv'rqWfA>A &'|.9ߞCMNĉh!WGJZo"#,\c,.:do"Aq v@gJ}oa<]BT]z"2U3؄cRKUrU8` 1_Y)R2gp4 a?\QgIR"H)H(P $a?F C_Ѩ/Bgz>o6pfMu*Ln2dqADf<,`D!tzRaoX/UbUL^TX8eI%n[3O@W2G,e r{ -m& :zgA?6nbT¦f+,'u2ӛ.Cl5{%ay6*6b[N;mX}Pxhʮx}t+vU2fcLHEDA]?V fZͷ7:qX!:Av헱t\eL0dzw6HVaq z3nJ%;$)4y8ɎvdGR~S'ZR+2"]f)tEoAagվ2 #txmo۸{~ ٩bNqh.uwF]gv0DDeɓؾ6})Q"%Jq]1p(F}a{}xzB}prUbK ίb_r<&-wԗkNq~2ݤO]ăe! 6#pp\IXD1 贎'짟g&\@2Xa5d>u'0>01spv'DpXE|nV#mѴ'vw GqC8gI#vg8ӈ7rWe4Zr"x4f/0N@9_ 7܇.Ł|~0OQ;kzW|{ 7xcAv>d8&$ې I/K'E҂ҙ7$ "XhNc.8)s*7jP\Ig_bрfwcRNL/tÀKDpD\Tb6 !D)2MH=W}@dNf7k$ CEh)ql{:`G47"#܉4hVQH1aa|{x aYbsReseGÜYF ^GdE^9LO [ZJ< ?f7s\9|d{جیzF.wT~8zp%6UF dbW4ƥ++m*{ _az|9ʿKDB7B+yd@Y2;3Ѓѫx)lOD2 `1I & #S;+RdH52%iHb-8(hmiNp]Co:13ИmbI*Rx^ƈJXU4׎TkC;݆w, YØhh~!]bpjԣ/Z5$Az”泽"xi SSwj]kY,S9"sL2K3Į1fS ?/wl)9 c!{؀&QlT$lK*f ̰0k{l_e@6 ȿxW3j\2u+ GwC<]r"'T!*Q6'!qg&,周'58GČL<.ōmZk>I6&^7TrBxk'm Y8 ߇43Y}(>aLEB|EcL D ~r9\-Yr ę`/{6_ҕk˫7^WosR@Xb jpT9Pަ-!/`)75<>+n+ %KQ輳\5[LIꎝ)\9oY6ZPg2#(0I>+/Gf׬Jhs;+ ,OGR4Klz\K^93#8Xf^*3+ayf 'gL88 ,d cXHS +?E؁v;HHk 6u[H%)l6MxE;gdubG߮a~u'Sm⑲yW gNE36y^3EHҏQHFb2/8j~DG&g:Uh~8m`{{`FuO־el)z[qfr}Vё{ ]'7@֔ YC@G<[1?3# -Ewc78W%T%^">Ivx ]Y@Ɏ03^%/Z6ʍ\6ܣar@˻XfmJC*$B.g LeP޳iUڛ;;[uF ըעq)).?$Ϛt~DaDUveRW, 7&Jn?A]7+ƪD1diVZ^> ;kH8hο&ps[B0*P>ٓХ* Dԛ c !;Q;,ɪT5? FO^ôj!~w/WLe 4N͟54֠aYFZb>{v‰Iu} $_a>/{QKnj-Fw(In0Yυ7$eBF.r׸tB_qHpg* 2"1ө[KWM/}0Vaר;ٗî=FAh:\7H# bu&ݡZ.=9qÊ+VJuC4"m}WdIHL5/דɱxGw>5\u9\hƈy*=nh4a{]6Buvzx'/36/{>C@c]1hZ|{2-~/744n~DYֶ2q-μвsܙ$@vC~˻-ɽ8u^!y ;}wj?y OjXR)/ϲj2r ]4v",|{(iT|jmQiFwN#wV<β+Q QH5xjl:S؜b tPA:5 d׵u/;Zo͖:mv;)&ё- p+,\̜"Eriۓny+]r7y =Qr;0.ةټU? VbM4%/(1pd'yA &w=;4BAdBUlL)~h= KjZ@46TJ9y-p#RRb& OL~ëo?S+]6]܇vұ'x=ks8r+NŖ3KRrwUI>U*5!19H/ !9'Z"ݍ=n]?h ~F~^̗ix3ᯏῢ0 s3oo1]r~N򱳳#g hd|?M YhvK[b,[8"d0CtN1l^ctS>J #" Q2"/@O|= m?Io".;8;xr~uO =2])x9ȻCIwyߥa7=%K1 YE,L[n@̋+tz}8: _Or_^_\KӋs't|O=ry 3 `8v$a es쇓'So F7-Nc2#4,`K3`D,̽>2:x؏U/ |8g{˧,87EX}'I-yo%g_ ]\_]8;DAd!8@ViФnoc$>(~EwLKT^d>MYmTlGxF6fD1ٚٝ+ګb/R[i\4"_§i C9UV7BH~ AT(Otg$ϓ `u5t${>pl4KE\e-R&4Wr;,d,X.s:HrwqP6%zߤxȋas\08Zm_bX:KY @~| l9sp\GzNjxK(?>?*~cd, %DQ֎W?4fbGIÔ&[#"i4,:l@]'֢PNI 6* UȀ)ga?$l#W㫫/ggwHmR3զ&h)HAS!rz4F/0A) QF(s;pҨ|!`c" :›؋ӧݫkeŧ~_m"/b?lIG^ r5iT\IPTtpφv>#h0QjoRb~-2=ܱ1*u/ mIc+'--t0AjVPvw88@nVC9p}ň`"Ɗ-ų&8D !(_..8Wk JJνRRFj$s}'LCiDĕ=ԟӍ$h0eM#fU 'h^/۩ۢO/mDHh׋2J _CO+Ah0 d'S4O>EKbGc0 )ڼѫr"&i"ՍrEzϊ͑?hRH_SWLvEa[zG@MaJY0ޑ$O~rsAyVPekc&NLz*sɔ͘F~3`1C7,nl~ڡ-p# ፓ[ )aѡ P9t/SX742$CJ Q"eNhP'A* ),D&YV&уHqxA(B $#.Ha%J1H|#iGV, Czn0+*g:RKd"':O)$ _ǪX`J>37aommذĕQ2䦟<hIf9syʎ=m!vd@MVTw}KnvWTfԕi8U͗(6خ(Gh |$d=LyǕ`P7&v ȥ?ݞJ!k6p;:=(%'i3c=n1" PhuBf!lGʹl28{1J[]'h-j3:1f.}0U0g~Ga.ytTw|Z3}Nc qٝ@ 2Kx%P f6zL#~Dsԯ^]hrj!4hC(g?/jYB_7mťݵRaxrQ@ 4M 5Mݢfc} k*3,LhN5s= m"5:q M "&Y%}%IZl`q*suP߷[+nis3;K|/B;,kFb!E $S<1;9uCP,~+_') GuIJwʁ&OWwh''<M ?Yڧ9շDj2F/ёURtď}F!$jgDOiӃ]tFZaD`8a싖 `d1"̏x3yե&]^?sw!tYUrtQ1PY׍(+:HJ@؍֞aFEث%^f:f^\`&҈Gw2C'7!=b@V>3A&G1HEi埤j:ngk0ҫC~?衹H)wbHveB˜ tcvLlmVE0,&[$Ԍf¿}qķulD&y%AƉwYI?#fIyC-SjKyn]'sEm˄M#x,pd?`oL7D~ve,BVf1o1ʞzf!1v٩Y֡XAZ?UQwEˀ2.;\Bum A^-'8 $lпOK vT/yL`4<SQte-[whePp߿c! 2 Is64g mF0ݒ tlsΰ[D +1ğ%^"3$lEre6#s۝~<*_TcR_VIG?$k y5[A4BIХP빢~.8}=a2L{{kIzkG$Cs٨mys%Tfr6ET w2>Te #s QWp.HVMںS.GJ6MQEI ǵIJ-bGfbyPjtDu uVQA#Ȝ^̅{<[]+&6hA/%ᄎRJ%-(m:Jz5MWa'7EwDOb.Zqy3еS+?DD}yE_EaW"$?;tʀ#pX%2 sJ1TT4;/j]:|#eN8Ќ*`eNmC=x6%QoaG&of2Z 2ڠQ^ff&x,ٶpFS}*M|nBRL[̎Q luEm*> ڮdzӏ/Nv|;9WۓnV&Շ.W19^ݢ)c=5mOx(?eCAѷ8CBr@F5Jnv^~# N蟃V+d^=䫕b.Jh/H,{x4X)~/aN7ڵ]Q.\ZuUSю6N+׀tmhIwT3y2eDN"dzL<қ~tҪ1&i)e$kScRf ٔyA\ʦ8vTK?5l>2슋L1 &3{E^'=Be j0O]) C7nLca1Z2}8S ,öK?!e)NfY-~+4 gܲ$S/"Zk vAGqm[lIG[#rƆr[8ϡu. F _oN1TA[bl6ICVr&Ԭ71x;Yx=F(|!h_ZصѲ-=ءsOg]5Gy^W5ܘBHw3NniRM2\ ޒ=Rsj˂J^4A=]aAfW9VIrC CQ0\oOS:*[szuԾ*AnmGL ЁU6(@j4G}V"cWb"OGNFW'Dҟ+zUriiWLWC] ԋ@W4&P, {!P>tfQV[LYNJȕ~ XWlEP:<嗓1Lo!Kc?/k2eƘ9Y ec'U>%H[_Mp#ɾZ;a7McؐDFO GqF!#f>SpARJ9Cҁ]4+@m/a:Xŗ7Ԇ;\mCpgU2":D3[wM\zU̽8صnN̑nmpm7}c#ym&JP\B!E2g=bpFXq-M\\WpS{W;OIyBH+LjC 57ɕYC|V67=Wb{d,k-yn`VM>,%r9g]d:=}2^hᙪ)b.gt^w݊6bW,g|(EL-]~H0kvo! "꘢ cAQo_`vnrx1E W{ǵe&u' D>1=)Q`b=cxO%0{Xr'WIXFijD5zON r(#Lq@!{qooH+^K9+zpXaא{a&i1}kAu'q"kV>9j V24}RMx2ueOpG(;b#Pz"ː;v&JծY_hi^:7 &̏ ]5sJ̆]Xv?ST] t#"S Puw=bGdD`sDvgsrmV\T=7a-`̷M4[B3cAbM gܼ^fX:3­T ,RJ:G WEN 3/C1mHKx@U{;i̳M sm4Zv6My4ީaϋ*`X+"6,E;2,ŧ%Wqɕ7v l;jmcpm*1; J0Y)jҼfg۝q3 N5KAzf } !q'Tv2<l?>quh@J=eCcz*q$!XF*-q [e*xaם9urw72:Z~m&MW]='O NZ^qtUPC#Gߋ~2(lmHlG'x{.ۼ5l D;J"짴)~F`)W>4'" s"vhpz)3R)"3o).^ uT9+`}S1^ [L;ƞ*N-~t_z#Mg V|o}id `|~Mx^k Up6}ʋ22Z˵+ָ1'*Ð#"GAyK*J5}K;lvK[\yo@tZ9GHR՞ԕ"WFW+mzXJ/S̲QY RM^CD]ܭIsEQj턔tĶ~,DUA- )DyAi^+_]n[M/j*?r-}x{J+^<?sy*xM9_sw+JHXSߨWͮ6~XG'X14m?N&v[lˏ,ˊQWQ"z&\pEȵ/I,^;6氪/O$A|vk_DE[ 2]a| uڔa;7%ќUK.hek.HPekU7΢ȢW_mKcWCyʙw}Q7*x}膵+_k_oR2[Zy)LS,Z B3M 4t%T רIz\P$fd*g %$޸_LTہj^^'2ȹ_4`G3&քh[íHõJ.Is!?@4 i囹UUyvy~|?n+𽻱RRœHY*'h;c$,+"+"6 g#vŻrlj3@V"Rdb8 k$ ]ܱf\z{1ef-Z/_yFqgL ,Nl,1s 8.N:،aRnGo)fy$E$Sn&)ɒF$U"Э^(T-2SC nEu4rp}$uHPi 1>=졓DW&~kӶ״f]C7ezwCCo ū_#:R#㐨#n5( w4,% !/uHύ2R?:kTG"TIN'9|$;Dtet]YOP wpgzcF4 vۭ̐u,]70}ߕGO ^To}xZ]LRP31>ILn+'-cu"9hHT5cV:7.*Fq/ɗ^aJ=Ti=I()U-lv[+ ;oYz'"J08\{UFu04R荘BN| Ugs;tB ( Tx}2 2xW]L[eR8:_+-?dfldipsN d#%b…^DMLLܦɌɲqƨWd]yYg@˅9~Ol~-o^y?LQdO0dK]%yl³Bw<_"2BԹ3h^dgchyUdr\$J]Wt)8UGܯt\Z{i.#{ |RQIE^d(NbF%Ux@h~pR&dAàaVfzE:{Ņmh ŽMBҜ(Xa9!$xm:^66̬n/Ӭ%9ٻɳqt3;|#3/n z*\*UrVú{e[by nKUOyiQ$艴XF)ҭvqK;~&D^dSӃpjB?J%y!:ݙ";zGmC6SFnm&WX.&b8 w]6#ᅊ;Ք8׏M@cU)a٫=H^SQPYȎv,*-唘`Yv{BP5X>4X3:ջe\>M,r?ߗs$,3{% dlot–F,!Y@aX1VaaQf#4$p]A<,|qҋ'zJ_ Btj`<;tL3wjćZr3K.7To=j쭽^'O?Se#o-F]z:Tfݡ3+V;5h]EluZΑRRrybr>=LB&JIZ"N"b8F#W$'0dNH;?ɥENNpIBmp><>}4y8Rt)v*"<μ?d&Ǔ)Wi- >xNL)=91ߣeV=l*X?6'!@f9%SϓD!=T񎅝fa5)K!+1H N"73EҮ b+v>Fg}x?o/n* 2۔KF]JAΧp7Ō-Gм)RwI1ٛCX^B2ͤף)QDiu̲>H7g1|t̽׽^XT#۱:2rĸ1NBQmJ9C\4N£}v-`mܻ|TW y3dL&hx M0C&42>qb|$ r$T" P V&N2F pJc 1={ã`Ӝq<)?U_e[{Z8uS$ \pm֤Vn j$V0j>=E_"]4/Zz6&K$EEVJ "I5hi~XODIw$ W!%,koSu\}[[M4XYyui"Qaj3, Ec[~x/X¼jH~`n8#G "٠(bӺlxksHEIn>K*ƻTXWJ%SVR33z`s  z==yͽLrqE,Axo' :}yA=!1v)?ΓɄXnn-߂`zIG]d ?ElA:.' B>^CGwMҹ?`q@%!,C:K`EBБX,Lp!00n5rV͛^ 7{ݓN9B厱8܀"G3A>=gA<"UD [9h4fE; S:a=p\tGGp Q3OyA>u .CÈKlRJ2zb0 R␸tF]͟'Μ<"AH%IcdpxtIRA.N7^2EK?]k\7&\t\bmJf'paqnB0~c䎈Yq|v=If2@ ݋c#gtZpIHA l륄Q\dĤ)߅*<5)q='|Pj؋E\ 앷dvyEǽ$ӚP~ YO'Y0>#\R@H3`510^I`G t?dl7"q9ÈB;Gp>9(s$IyĹ""e+H/A<?H _hǃ%aHj*_ qM( Yͯܪ9Ex߄Z,Ӆp}Okݾۚ*%E5%bۣs)\i8zG(mT;Q?`x"vr8J)3g3`3^VY 9 WDX0sN#N i1p2ԕ|ZFaF.URgeĎwZ"Ɩ,LFy |wƕ*9DֽCb2L6%yUiYb<ԾiA])BTO\b5$֣2љCъ*6u+-&SX罼?cekDf>qT..Koj=I|/p/k[Wx胜306<*V@6BXǭ>5)|b¶KWY8 ++,B4'S[0W঍nvE0ve+}X)[ lUNKn^lT7XQ.)ps\}ǣy>=6(oJFTa$"ΥdHp.rZNzݓO$n4t[ځi(Hc1q\fAp +'G+LUxlQK9߃ TœYEOcǰ/j빭2$L" (=#]Z G188ؔfU\twY17)C{C%Dbx2bOs&-xG.sR{mq︙w/[{zA%a\P(SkfIdwMM7ʔ mXVO=,%G inc;^.;L 秗j=Mϰ3J u Ti0 KƂWd^"c!ԎFѨ;j> Gq3;S8I 8 EL >{Jn#s$L"MXpן.Nla]8D`/H L}ɤ*EwvnPJg5k~(킋5L}z2?%wEAiИEEtCdnXO ' Xl.g%oJ')K_jϥ%+ҡaGm>CK % AB!,Xmj D陗)X.Y6Ш<&|t88}x˝H$U*?Dۛ+!VE,V X"$JyPd;A) U҈V[f- _~ mx7%`&( jMdDwndtbR_= }H/s]$) x`%tr\XɛV@;Wr(Y-50xrqJTT r+ Y/24/}V6S,Yk:19+|U KJS)ԍV)ʑG?m&(hmѱ8ݶGa1N|ڕ PGԹYD1Vfsa>0/7X/h˳UԵUnr{%ȭ?GG&,5t6'Pz$t+hC ,{]$U2TܧYxe1a2'QSЗg7dVnZO֎hk-6VHVtWQdQNV4 :Z5lMD'*-ṨFf"2bB#9p0lATih,A2<+X(:EFێRE~b@Z`Kҳa^=CQQ[O3&NH0MV`1Z'^QP=XD;"#X_*Z`YQۉ NbT .:f69˪{ zX\lâlĜ^LDv~͗*mSպ׫NJn{m/x5^M;Y}Ś(Sy6;Wg 0_=I8JN]\õY΢}KUnuN[.G,Rc ^޳['+ eVXfD 9RGIOEodʏfLdt $U1NtF୙Jk h Gc&la,N3)$m(M&ku8+hP5v.i"PםC/?bNnL.` rKxK.Xƴ5:oݚNG;y|9qb{0c:z=Q:̵3-]VfMv{?p 6zI֮2'muK4ȃԊr/f֥g1,e-yx=NCOl[5UʔC<eg`"jŵ/3KU:<:a++Ƈẅ́ {_OX.uz&7㡣 x<O2'lÊpqz0\dq]4cv6"_[fZwQB{h8H"KWײ]bg\Xr+e]n&FAb{RsʥU=\e綝ƧJn^33~ crTǣ_tU?*.>)fe9J.,+@FYn%.wMI?|bu$ -MF\ PYSˤx. ־ʿRFvT j+e%!'QzAv]0W"WbH2է˻?@I+6VڳS '|}e:mB>&M~H,[jdGS&BE(}Kvl᨞ YWx1-" E3ѨS|T"wꐬ::C~TOktC>%ON)ܬ*t*#>*CtE;- բ^bZnl ]PWHir{o<'Q٧ΰ{#;&gkoSݔ}&W4<$\lrUOF]5>Ke'CxO41qĉNx=ks6+DX]ynqf4+RWW -Bk(RKR~\vuA<də+[$h4Fut@l,)I8M3{4WI0_d@:}`"?z4 |`ooio7Aq"?ɇ$u]O3@oc/ Qzt͑5p7 Rx 4 I987'~ 7g"Pf MM@Nu.99=ekʒZL@Gmo9` UiZuG0 '5am *`:gg=f~-~>UkN0Ƭ9z,5/mHc^[{Y(܄>9\ ~R-o 4kFx3"ղ9SgIX53ʅVg^LkKig6Y=`8aUDh @^D2Fw>ZSSޅ0֦*楔9h)(}n?2N,MVs,wBa}PQq8)0C)8$[ws`!90ZOY_\8Q&Iika:]"oazRkDMD/SK)>K>A6!NЖz`  Dž)H+7WtFO)My ӌ`4 sM̜S]W&po=m5ߠVqMnRnD@WCO7B[{s9'S/OKi<3^_\ %}pڲu|WN`*V.۬=שg_$٨1sMq_h`dfc9oH ;DA6;LEUR[cf g%)6)m2 !PiFΉIuakLFdg<xm/3r㢞 HU>EbAK^=fRmETgTKUk'MA0/Ash2S=@+GOVSAW{]{ߏ;>+1?;$GQ_tQK]L%0ӄJ-Qu?x'b/lR]j5D/K[t-sG0亯W*h:epOghruhrsv5<~_T/W':D0/̈́/ s%Ml-*n)-оHL7Q^˒Y(m6?4}F7*_Y3\1͓A-30kW}M'E]]Z< o2T gxDפ{ mEh%aE:jV9KHjy՘-n a).`4暔m zՕpㄍb1s:&ob*N 5 ]2yb*~hˌ s[+4cdgC"М[0r6v7хcMR*D\˝qVn>+ztlipε=I]ܖhLm6 F`Y3k֑H+ᏺPvI q'Lhqrbf/|^0;& ɑ'#_È|2XM̰36$@9 _t_:()&.4jjQȉmŻ*ٱ9oF  rē#I_{R"x(( _Q F FN\]]^^@k%]~3M s4l :JdFf@\$?X C'+=]^M~L[3-б|j֎VJ,Fbstcgo&VtާpqGF=M(E*b&\S_pЭ&5ly!j^P$QhU՛ݿs4fnB}][CO7׮ /9,A;bUՍX$J-7[ o*ΜK9")+xSbw<8UY~A!MW+erviY&*ŵ*.B䵴LmTL5NsBqLn=T /#5M(e=\X۝i mG1Yb\&9N uDʮ^zϫظJIiѓYot L4(ՉxӔ_=Zs5{c YXTmjFXeaUXsGUwSoc-c@L]K16zIJ@n<14`FuvqQѠ&byE$^>02żbpYYeI0 Pk-*VV{jqxIj1\־Zf-JBz;Roe5HD3{לŚLףɤ)i=݀uBZ|શSbTI0VJs F b.ZX:Y d8&^% uGxQU"k&*:v81vݼje7u[Ϲ[J^܊CT ƉTy\UqXFDWnhHZU:#o<hla,FuKii*9įC6QOhFc:̚=߫BpX;l:hًua =#SV1.-{[rv*2T^9#Q|hV222c׌ߔ(vj^Gw+op\I7K>F(2Y*O:4b ѥ%W8JwyʱT$b@:1fо1vR/8[y^UMݗ,h=c[ZՖe`LڌX87'FWGl8?9%&ڋr2(w0Ӥ^{ЭWė!d<qr&}-oC¤nݗY|ڋg+VXu/VjW<kB2=>@*aDX( v&k)f$A$ǰTYsQŴK ,9XU.?O-G%rL#~߾(rvK#y,.<ΟӖHzFooJ , v8_Q .uځ]58\<K`vd=.Ycuq5;f5uP4jPSF;{TPO,X^rr&rS8}{ \ۢY䥚au)+n.6\A?W~ "qtXtnB$ 2xn=(D||?w kN^ױve###5TYڐ L-NhJ.hzfЈ]66mPV3^PMEM-#}C66 ŒѺo(99 (M,\k|,pLg&'TU?l q58%+-]_ %_Fr *h]2{ӰM]V fI& ]-._Xbj~-nb䳃HcOZ139UULQT=oʦߖbCÎDj:G&[̝{/d^Q SGm >Dt1OG̃_0|VPh9So [M46J%e2n+ZO枑8`HWm+c0Z!&3S`U7 V~tBb索&*K8W0Q ]} Z[33^{wgO$$ͻ5#N/lMy2Ϋ2NT7꟩B?OLBk 2~|@G`S+E? Vݷ85R=;^-F,T>qB2όb;] -,P`0?Aڎ*\ߢsq [qb$Yk,ϥkq$rSb g5D?|z-ZLOQЊ[0cE ȋopX&hLİU?Ǒ@70Mh䈛gwCxxƪO:z;-=k7CQKZN.%<\tWK΂ThCF+.shS40ڕ,t:hXիlJI;o O`O2Nwx/+U&e_2>G}AZrEZ,v)ތ4G-*:JES~4DBhM6fu)VrJNs%rJ(ɈkDץq̮uy_Ҿ/E#8{Z1ފ3;$)[r\&(/lc>XLzA%dB̐SB S hzd&Fy=[4EʝHWCRtB/|لǑ#Qɦ\j>}9F5pff\b}kioVUQ]nލ.8<(i#%9 =?-[-{s&8:1|3 gˍ-vDLaw 2OvW?}m(3/<>۹e\Kږ"p*uX DÄ/Fcc8~)++dDd!0];vSJU4\\\ԨJ8v,1@V`niKUh r|l<*GǪB'?y;&ijʖDF#[SoFNr>: K=lv|.7 iC$;2RtAs[D:6;^&3b۲2GCbgN`j 7qF_a,CL+{j[z7T|~S-9CdW6YYS9]jN_j<ٴE^Be%U]wx՛!lQ8d"digId,K}SCS咜--:>jy Ri)`5n7%᭻-Kįj4.P&ֱ1rJ/>:8X|!cvtH "rR+jJ3$ z> g_xEƖ&'[pvn}"E#tIevxmX`_mX4.@ nA *[<6lKZ[zRUg-ڒG/D,^ث4 ?į㼠tn*ZCPm G\䬵&B]}k?i Dn*:/Lx}!Exo>ۃQt$)QI`I85#kjfA<"t~"fK>Eb*Z<,&G(48 :TD#*\NҒ*r!€nML!:)*WrN ēo+(Y4DI΄n*R F_74> Qn)P#Km;i`i jxB әZۦcl@$C%t%U MWMS[wRfXMd]9*/$^. BffH үv. LW8 Bғ#IG:tCKh!޲3i >]: e*m[EP:~F*) Z'CD['3(­p}lܛUE;W'r5 b<8=`( ~pMER<=W%\ⶽUeصv߸LrMZ Nz]ϘDLW#aoP$ =+j9t׮Ȑ+ sCd?Fԧ"g%S6ot{̨C?.)3 9:].^Kq/Ro&)D<,Far@߂zz}:xC":q x2Uqh[q c>X4द]ENI6i&c tM\,il/]pF\-4w[76FO0U>DVvڟ%KrΠ]Ou<\z–$q\Zxdw!? V]F-HY3EFsI,)U7MMbhѢlY$hI䡝#%,&EYӭuޤ_"x[^]>"߮9م?v|\?\.FY(b!V?EzTS+O^jyEiy,a0gx r8a :~V{)|/otu]Ze? HlRAJ`'G*gu"gcK1 8D ^@]_??>0OMYE>Sx8exjVwmid k.TBT]^/`< mzεq93dC= Ϙ^o]ؤ&RKWI{"Ldsǃ:;czK`ڷHzVZfLkt`Q!z'''GS>ܽw~o%%^ɦq[!q4m\7Q4i y)mq.qh>^/Hr4195MFe#ItAQDgW7_킯ox8lo-1ʠqثcZ,  kԧ1@@ehT%nmi4s,TBFK.^!q@S4zlg { JwI')qVQIG?'v8!{pwƮm];\k X7k _#6=8s hHL!jwЃSGk}LKTMj]Q='.' |:/x( -!N#gkrb>\AÞ^良xnj=\y@M=L"u5tUŨ JmTGwX?L/_T̑(+.C ^#7&`N/'+#!%E*d5Չ ;Vbp;}~WxFu5!E}c}X䝣w+EO9TQxkZǴLzB,kA~AiL" Z\\ʙy9) J`lzEyzv?N.<j-nl|f^fdCDK m"[ eյ\'$fij*TOeERa;1i@7j@V}3|RbfmԁaⓏwN^2AS ITMk.Ii ~>> 's#l`'R4 ]bB7O\\I45'575ZS59ut$c&j !_bJx8M()64+.^\Z$h5 "Kb( yrXoЀɱ cdA=|KB ,&?tU B]u$1͒L!))x߈4 Jkbp5iE%@s1҆SpcpkP"3!2(a,zR P)laWqwTvQjIiQXa=7B y) ig%+L4yĶxWmo6_q@v%>4MQ7Q:#Ev`Y-T Eْ!m~Lxs;ӆ4:vic rB,Q<ኁT>WA9(υ"}2R4;/'`-wyU$ߟlƜ0jՏ"FȈ,ly2G*b0H+wl2E}  4VuJ*E! rP}f!KBJc$!5Wcpgt P<Ģ*2#RYN:"[vBNvFswLj؜&12O3vA`8` "`1S3xAZ#5s.UgVCHY@sk7lRr;~=f0 ]oz|1 8 ҅vClyAXLY|6 $똷}l`m!b*s^9G>D8k JUf R,H$'Ht;8O(q%}}['a3NrE8k;m<=+3!⊇KZjb6cbZ?JDgTG>T)Uz.т&_`=;twNǴtL9u*:b<}g"1v0[epĠn6K]J},ABo4ۋ 4EvTj@\#&Sg3'zS+ !$I9_dyv_IrY7S z64]כ=kY3"E4U*Iq(wl]}A5OM2lZ¥k=v ĞG 3:&#ejt< ?\ն,nՋ`RXx!(ݥ:?KB^kCm(ybfvur#(=|t.%Ep6[` q5j,bcq;?/{K$Ԙ2i7;*D2}9Шt*oDD e| ?bh규C&7Sbqu o[?_Y=*7nW#}9Z<`u=mwcsk" 9o ?_~ ߥ`Ӹ-Az}/<xYmo9ίIwRtj.QiB[T icv`uYomow]o%MrgE3/ϋkُ5zv3ɤ+˕_h-8yv;|X@Dc\sOeXjF%r.,pyq!YqGҁBp}M"8KGSl 4kBCsyd@u6¬>K-\ Hw~B^otY"mWe/Hto4gc 5(9 ]lE((g6 f$IQˆp-}a'Qb2P"pԟpvC i<f0d|9'cn8Gs!~)dJ%vq^H:˘-ѹ-W!jWkH5 L 0LEz"t~:*L驵Xnwu^b4d9\l\gѠk W>s@,}Df.)?@o!ePId3Wc@KLY~W@3< yF;-s%\ VM&IQ(v 0ҔilSsM̂Z l%ѶA/`>quOl4ʌlQ'í^ $#G> 1h\`HNB>?A+[t.ݾ%n_3h7yrWkzOM3DaPeA`''딿MPV>oX'7YĉN;5P쬥85FۃRCrő]$57+=18?jswLjs?PelgV܊SYjǶЁ r=|I:oKY֢9Ȩ9P hƸo¤%w|SurW *ӰZE蒞U6S|~H-{j,[y{t|h F_R  ?mȺ?.O Y㞕z[тudVӪf݆f4 R̻T\1Zn4>{L(IJ_lJhXv[`T[#!il=5 $Vu@'KP)xzu;R{}IOX,%F iVX[HO % >ղч(b҆P%u^wN;rîfq3\~Zۿ9l07]G,y9ᬙ+V+ˤ(&6p<:ו+9".(ps_1I镪XɛJII}-.a$ ӒV"%Ep1)5WvJdk@VfSH+]T7щ@ȍBF\J**{Bo3n XN N?[HʅXW+KUe3IlS˘ZNPpA(YD;ߜU_YʻoJws5 .A5X]*\N;'ӟy$ >0/+Ũtcu {} -Xbсn^u(!]aT-(AwpluC 6/N!ߡ:&NI.\Ai쟎{!M$ZQtw"*xC11B h$INjpg[$նEim-,viv-3I^Tmf:8?/%7Xyk4Udx5,A볞y lv8;M^s)E)c(./0!9͋J6ʍ?-/n8{h(A0 tx}TgBpR FscDԷ ޳{薊)FR ~w#W[n1D={bT>",.vP >>h7~#^ UD3ʞިTs>Gg#a~q#T.crfus 9A&~#'&c종 :*#[O$QA5P6/U@s{E_~ppHCؾђGRi1%`Lmn#vLȦa|n/˖͹mѴ~q7c9p vBv9}C ˈPO(&tiGqIL#2^PC`?XDfjCj6ѯ7w#j07xzo& .?[I(6`a;dBX?]Sg"hNbt )[}poLgڻzdG߹xB|>\ZwÞu7^_tB^Ԧp߬`ALUÂUE?mNqۘ&5q9@-Dvmc|Eq !g\} V_bdz#G90‡5~A mGIF& 0CC, pz )cY'10, V M[.MSh`4eH5#ƘS3-(62/9ůGaSrRq*ҚT{= 0 Dwte*3H`A2{\Xư%DDZmsc$r=&m#b!)}[y-eX lf]W_(d5ėf{ES;DֲihsW.B13o~ Qz=AB9Jo?V[nl+/9l&v yt]˱=/pjgq0CuYwL :stUǵ~H]fz]:, I?cn@&a݅.jDP6oua~}HRR ɢI?=Fa ^ ~ lK>9$RIѱ9ؼZWavTK7*< -9d1D6(X g]|VnU"ϒ^V2πcE$/z nn(IxQ~Q'RTdZ&kf}XӾ][ȤJTP4kqhhg6k_U=:DYkUb/VCI>ӏP+bB/H4e#"*Zn{kYfm˨w<ەF: b!ڨ$[ɞḼ+6=S2P7 tZQ-k\5EW"YcHrQ~cG)yצ[pI''{@5 G֛aw]&,b( LNf pS^K&ɟܚG[H? f_t!ͅNĩ)ob$aC/IZT$Ag5aZai4wD2d+o$wHm͓~`GʁCN)$J4o/nK{*KcfHq{D>/"QZW=\kkzYp(/qmlF=ф Lߛ vj`A\"52SO =vJE$ouL!L{~؀*1Cx "$⡣ۻ^%C 9$/GRr T\]TDE\~ut$%8zJO_t {)@7˚,oݳ-y;yMy[i=ۄz+IurK",!W{ 9-t +LKDtO;8}ѼN)F¸6Ml5n,]k[nﮮ6n9a_$͙`ɋ]Y mYS2wXY9w/uhrg-wh8]fZn,s:dD6g 6'N*U>Uy*rL01cJ"}ȥAnn4 Mʣu-Q4cO m-y af}yMGy$IXNL~#%($[1]k `g<35c#I6.~v&t%Y6Zar.RX~'hz7;ڨ6V-Z-65Vwf2`/=n$ۏK +f#ogӤ&h,X7Ӫ-<<(I\+Crux!#)/X)l0xxr*!U?/UFʣ50/Չ^]Q)bb,<ڌL٩oc@l>\\yt^|}3~YMq)a5zWN_m'mGYOA!ܻ+~yWwD< 9Ľ:[:#N }dNC)!u3N xR  fxD1s(,w<, 1nVO|o]ϛY^_x=s6@fR'ɒyϹdJYv{NGS Eꑔ_l)^9"bX,]bow%]% |ghy7/up/䇹%҄Ƥ}~wo5AoQtNN| U8霒å(iQHC ;h AG+pIdP' ~2%~Hh |7(9G@ *uKp$+7Ht \ o^/;O>`,Z\M[1ꞸKs#QLܛBY!wM$uz$UjL+ܐNdCN'irtx~~xz1O99:;=\LN;rx309= ~e#4}$%1M)5P8Jɒz›{CMtKFD4^ 4g&~Ua\ދ^wW +7{s0c)4_>r- ӾhbVJҙw|" j+?~zԆa8V0:M XPQ6CTFr,Zy)qENJ~{AFGg3s ЀBj%AKw2P'D{d3) F8M‰D MYZg 91Es*V1c3 E&t )@{(]?$#яӕ@tafHࠃI =T-hJC{OTvf}F`9_Rmp'}ا`n,BDfAu|85eKb`O08 {hN/ON Fhe#eWU0brgduz2HqYBIMYG|d4 9^xx~I{Mʁ)6]8IUҸ:袯ġEp;S0{3a(*x0(Sv(@JT3E獍ٰ`YG> #?389`;9]@wHX fw57& WQӔCq gQք] ͽ30cqt1mñ2fPi/Fa=Ȟ6 +ߛ!U3\(kp=e@D37CWdZ+f]ó}Ï3D¤B#yjpi*A ܔ?dk ɺ*s}R,spJV$_K f-.1l(!oSw HS@41+h9]ˆ؟()#ܽOf]tf]i{Bf }+L/>@-a?B9n|_*YslSk'xzO.3.Hd 5{"nϏn|.X335mD^}v:EFk0bcրARmW܊N;c.R/~P}B)a܀ F:8}È%.8o*[Ϋɢ,~Uc\? D+C?U"KKN>^|pޞq|~19:<镵͐ UQYD^e {L|ɁVQW0 4̾N}?LOmt%d5qjtUp Lj5yY2y7%{5k7H2NZmDDK: Ak`(xAzjHBT䗵`yi[9e@j =&;{{ uH"2œ;BC]oDHQ򅣪z> w )ȋh2ny ;eu=)r/,JW# BHh b:9<7-l rnc΅2^Uط֝2*GLswJyIjU9FΎ ܁ҙQ,Uw{ - ɟu[7gh]E\rKۨޜ ۭ́HLKԱǙ5bsCoh*eJѣI`sTid(h M_lehͼ ?"Fe:KHd *(b\bLMiu. vȻѾT=',&}"lXZKEqn44blU2/Ԅs_Bu11*qLҪvg2c1Tޑi5sfEcm f7/ 1]DtMyh!Čf,k_eMT2¥w䪲FumQj#N0tM$Y = b+GXZn~.)ecss{;7O4?TFO&o17*S}- #ܥU'o^׎%wT'`Tx.᜺3ӻ9w~Kfe߹*k<$6BBl<ɚM{Pji98I2kݴkG,=^iR'QbY9k/(\<4|+ӏ>oxK@(I8|wi=vvc-2` 0H:<؍D+y.ׯoF=BL>GHuEWӅuW_ L.ۓAyߟ`hGC DyǓ։f S͔njAnZk};Oͤc<e$q-3o-BYVc +;l[a,D r?)#.K ;ӄr5oφv;od#;RU1VNRJVZAڶ@ D bW lP%pn%HP3R*!€:>SłngBዯK'~#-e3\6 e[kqt&=49_'PΏipOU3*x?(ۚL=Lq-.6X٧n*>NX[nP x.Ì4\5߿ʷ ve(wN9* m )OnyZmWS]ؕ78#-hփjȝKi<R%ݸ~$oVrx-S) Z[%eIH︱b4ڊ7_"O_F5yK_"Odk>\A,NN7hN/ ۰gxN=i49u1b -)`0~}?F)ܜ/hOV CbmO1T 4C]Xž\|j֛5}@ L0rVB?XYq/j_FH$_|SYET\ć++ӟ|=*֊ضV׮5?BzMƩ=YVˇ+PY3I (7RWLoqūŷW`ޟT.PQc&Ll wI?*~R(yq.$* sF.;W#<%)pr$n Iя\r)'C,//Rq:`K[h{L=$l_Q #oL-C5&LD f1ɱ?y8u+n^f}͐F=Rze+P0A',t fX4mZPaI 斲_CY 2!An^RIDjGڲD; èɲ$ߣJPhn+RGMbCk(T^FF  ד qgq{\l7`}on7q:GRxTftl~aYuMh!]Ǜ#0*PpBV:F D@!֥ 8>F1e(]*`C~*h(ámv~y[.0DFV@ }Ld3[p$<%I_GaQ&Ā*9qaW;5(zk7x=ks6+_ő,;sYM6[3Q#JrRW,Z$P (Yxv*GͣfS?h`ƞOɌ K1BV> 7&L~!>9i$4"O5m z3h vqq&4,L)i!OW^F԰®xdޓ L"Ї6'z7x[/qD/ 7GxP%8wՊM$ǧGG AɑGv>EqL" /^w kwoIwQxm%^08'nD'wH4I`j@VtmױOϗW뵺NO.{{t.iu#tuB]0G8CT[R qAt荽!L-, %F̈i4b\a777aEy@G;;C1Hu1Q)$X/k7G!L2Nݑ!Ar8 Gd]`2fW-Y4&;(2BHoWd{J>S:Wj2 Ƌ4݆;07|3KaCbVyz'AhhA~as&΍OJ^y 6PwM0A`_Sҽ0 !V8O"e 73, _޳I zTuqX=9pNg8\F$w$3ZJӱo8,~pWQw( U2w\w78B 2ANp% ~l(;)bȯ*,'RO6ؕTX>w .A*]tNa#b9H?0Rj v f\mYED"K7(gFmEK^-83}ƜʸJnj Ȋ- ~Rdyo&QS/]ff6FQK?ClalNw },8d({X2T4roky'BL/]tp*P77*ӄwl/+\6ZUz #`p1 d'˔hXoyQ@Nvz%X3I̔%,d'QxenA;!TfS6;M*FcjēƵG Wi ~q.e9kw`F.$rGqx|xCIvGGdpy~Y8?%iGߘtda V?Owt ݽr:mh[n\'Ku#skLn?Ƌ=E*Mp띇C新TIAgK0 <hqҙחT9d7o /;,J&Z%'4+R8EjY[ːqDoZ%pbduXHXYI0{t\L^5Y fdU6UhZ] rbahU0 ݊7bD.Q]N+O`L#WV]B<)mt`Bhz9)^[G38Fy-RSMvc\UYտ1n?6.oӸr3J MZYךmsU&Xf*Vif?;!iTiX<è~JWX%rI|"&d*Ʒ^2 vCD`fvm§ OU kٰ"~dbVnو݅fcomu;uۙagaYl"D?v_Lȷ(*67SdD[ k k5=舼_c:Y-Xr#GnBB8"VAĊ {Z֦^\rְrǦeh h%2">uC;7)rӗcvӅ8$9ڨu_΢!KN+UXjcXd6S'=>l7*38RV8%AR~nK4TV4R*zC;94R]46R>=Ta`KeU&idh}Ĝs18Ny,9MpFx9޿o~ȧ\r<۽e/v߹jp~u/_$_U ;g;4P3\=iOeIQQ:ַsĭ#!-E.=v:8ɋR=eM*/*7n3K+̀7g5c׫9MId0Bp>&t8jYtYxmSzE茑((@i7ջTUҊs l f㥵RB@)urB7}u9SIU9W81-͚Χb۞pz q1v P֒sA=x5/qRcLNsHgcdٝ~gRe /Osk;R<Psu:Km;g۫܎mҍ57j+FE4N4vFSBU~6,?먩1i6^U ynjHRb FjH%lJc38%bg<|`w!8`ox-;Me^1n5tMy{;#):Axz*49.oԼ,ς;DK--L\|2Wp& >%iZM]<eo}RbtoiͶ^Lx*E?jrgRxKczFq":m9je;o_칿.s@/"+ډ ^_ Q2!f=ckWxƗIaȵ "rUŝx我8XRjV3=4OHȼZUef-N4]S_OӗNf wR7rT n-lNy[Dq0t%yc Rrx}`fzc1'4--dhUUxV'8?WNz2szZZ Ɓ:z<e x/WSxueV>HT:9YfMaOLݦJjHfVl҉()d`Dn 9!IWڜl@Ýq^NcdÜK1aev;iXL@AN2߿lg|0 _%U-cMĥ봅YE4>^L.0Ii;nd9~s#bG&rEta%' 8—XԦ=n gXr<5ym8)=1Ⱥ#|[]iOy|#ia!E^c8u{Vb5ؖ߼eJ׳1\' zKNlx[mo8_*vmi^ ٢X,ѶYIr\~7HIvtnÙћh>u(YRmjOM@#]CqUa,ƻ4ȃhe,^;7ńdy,b0,[&:Mf6ai2x{?Gxnd>·j6s5I @̀n31S)K, UVn4#qA؄&ݜ>%p+a> JY/8= v?zqʸxD}sgc~:_3t?%ԯP8 QuQr1B!||]m q"m &@z9څNAsyPЃc/tS=hnb#u!8Bĵpv&pT:9vC. <1EHUUPIpV~QADkQ+b "0{V1ݵ|]߇5Z^q5ʨK'A2qS8т4~7Y'-_т?iע4 &!8,_< y#\5c35=Y(>~1BU%uoWyŅF_^d~nœrўcV6"$94Xx@%̡䜡phemԃ%Jv?2VK=)]ox&XV)N{DN$=TEm}.!IvuX">ׇ04v!OX͕0Q#%Gr䆔>15! S1xiz R%77$$=dQJomJGe(&u g*WR-@(W:u ZKrԒO;sTSS1zξ,ԼHEr$rr#.:YܰI{U anɠ͒ |`j8Xvxr7(h QT!Y5J20G?u~ijB>R%UKJu.?'$ħ:ؿz{,#[=[G&YlKb46(s0gϜajn'䤐qDf춑θeSz_R|Pd_ǛM!ti`6?lgCJ,I7!P|!޲䬼 /$)}/1 e{uTͯ/l.jc۝F(gW 0Ón$0l6;9A %٫eh2qYivq6z[3{{\7\q8DsreFK:I!b|QJn/mÑtGZQz BM[^V V&FԄ'Ò5- >҇DڗilY"X|b"oAktW~tWtE`S^*:"oWOE-ujMSN*?T!fd̈́& NW?/]PM)W=rMj9J{jܾ x׳Z!J $9qOdhn[nMF)!Q7Fc4*9v}inD:MǛVL"KIMG~}h\V@aB~mJ~&EΠtʯ88Sj2݂"KqF0}E=l9ҍգrTU2LaVYHR~⧑-UcKF@a%U^\*UNB%p[y jǤR^:\R.U[r!+BjB AAH/WsS`y\ ffeJ)KWwReveCCpi >Q)6G炢@Uj :/+IäW^"LBExlu@=}FP0c|7^fhb_lݠ%'):[b&oWt)~o֗8O~Rpw}X!ĝKW)jr' 'K1"N=ߝ;n^2j^޲5[PJւ\MEgeLղ{YH_xks6q$$?WSVRO;gennX$)GBv >$ډiH,b&y>dskmksslAHIF4yBX|)ǧ#@Z^n A1yҔ&u%G#m{LyF16NO"GdO)9]HJ4`m>es2soI8p)Ɯ,7(Tg .A )qu}}s=LB n.Pf\F!MS̃7 9OgÓ_<%g .X rd(J (H3IRS/M B8"1MfA*M@ф,.J|B[kk "/~iuOa0)R߽1r%@:L<?)rrX-r%w2s88&9^&rL9|"G:+Gh |̷l1O:bp &fi2xfr<5iYk0!X&lv 6+iV6%ϓ0[i,ǫ  FJY N%Pܞ*ꝓR| "mL“9m+I*ݷ/,®3tmߦb \|83e5VCWm8BLvٴcDҾ̸c"zi?,Ila e8Q!xaSD(L'`WrwjgHM\Bt0ZP ɶp2J? ! s"Ӝ?$ "Pk.ښKٯc[.r)I&nŠPà1Zᛁ9᜿ms:v>{WҤVv-|$' |7w/2=3hN`e<"1PF0+.&B 8/ a+SJN 1vr߫ƈi6Idn ѣG6#y d$|[ PUpICD 4q͎lM Ev{JBbӈ:Yy5Bk.:-,]84+U-d<.Lȉ9岝]2芾٥:5ҕ {"lSU\k0&jg,Q*l⧚ơqaX=o\' |.nWh`Ueq%qBiYw2؝b) VHB+o{Ŷef<Ni`5q%ͷ(@ԕqbjQ})X_6'z,m*}{ {1B-*ǹIW\T$1`,p: R|[9L>N9g)MKY)M_Ҁ?p[Ϙ^@:\b=Vf\)YUmjv3Edi|l5yadw2YmV7uvyH'|?Tob$v,iFv~|n"cC/,L\ t%s],C<5V:tO ob3"e]C! >l:k1cgslA-8SW ڃ녰 p& Z;rm[.9p|8ihr `ev۴8M_8Ѻ;FcX@L\*?ꣴ=XkȶȧcmZBqiH݅7O BN7?[^*\8쌠7:Qod0$W:ag0uGp9Eoܻ|OՅtn3 MTIf\o#B^R&6SEskA`E`M Idp8tEC+C9Pv634CiCS+ '⳹|+MlyNnc[kLώѕc#L32.Мd0w2㟄5:~3d>dD2#>(zC:\ ؎ =(l#4ano9hTl:̙!|?|8 Bw!Y7l:>g^%.1yPr?W$\z$xbZ6J ǚ'p BO05mH Xd*o,+ʲY}̕XUv16[Vx~AP)Tif`ص+UxZbd#43+@[W,yxT%eAʐdX&"!8բЋqވvۢ;RO3e>0!:5thOm]糶3=aW8~!*PZqbC|K)C@e:bAfm9gGs->/z=*_tgzVg]`Ze-K=IfE$.,+ڝ[S D0>Qc4eH*]W.bHG7Nwԯ9DԷzL+jDjRtYYd;uY:ORfU$یڔB֤L:[h_G0/&Eq[!D/orQkdU;nVy$Q+-蓲/g}Y)r+/.%Z#9v|YgɈZk';7*ce1J*g4.wg0([{hxVs_a= |[XS7CՀր#5~*}oK_~K4eY4nnTP+lQ`J3ғ -V vB#3u4y;Ln"tl|_V){B-Kx_$B?{}#E'IzMMZj#zu?֜fvvG7'|> '`!'Cӻ[+Yh@:_:c{ʤ/VT[+3,X”Pk7 9~#kHAEWH.Bsd\wH-L]G`ۂB]oA)FA;s Eճ\<[ψz,jEEXZCA Ⓧ <<,^4Բ),K"$ ߸H4]\_ǒ7sK2%L ^Щ- {YgP+=Ӝ &if/r:7Rryɒ}[A]{&8~!r[n9oL[YAbPVpc7OˡyЙu$`ƨ$Nkj&8Dp ѝgr[HUXm*g"c1''__ ^PI?BHZ=\4Bph )cΨk1~ίUC!QȯYlH/!'%j/>t0r޳IJ}6ZqGx[ms6_fRɧHNrmM'(r*qӛDA n/$7Iw`}vXCS]}><<@) b?/{/ͅ|:PkG/_s(B1ᄡeI˛7`+> g0Ywj&QL2 1'a~LK}!0@/[G.;LSq,I܅Kw(Zr4(GJ&rH  /\D>ŁGЊPiI_ p,0tHʎ©aa jjam+dv{s، |9%e 8 #&BQAYp*VIfB`tb@l@T QwXC'awؔD#t :Q:uG݋>7b{3*RcΦ`I, D|g8$-TOp'i;Q1v!bEx& u{VA1ư3|}4L1ǒx 9=x%_(K¤ V@AznkGҢf}.G??26/6QSpH ‰*' c+T$\Ϛ1|Is+ד{ O6yΩך4 I:7e6囿x5!ֿ쿍fn'\[2W7韐?%s9r9|yӐ=dZOOy[2Ё$ clr9QdW##Y{S]`ʿo bZ {je f40"wWg32q= 0N&N.ejŒji龙a|N!J4,?>NR_WOoAӫcaSq}/po#v*0LR+l;0Z?:2VPh`s394S\ ZJ۝sG?&+q7$Ad-@χ2\}Lʝܜ 1lv^ Np?<س<)E_1 ۛjTߦDg?B~es?6ڋD ߅8{xN44>`&EdNJ+Cx"F$OsvhWy6(I&Me>q-jВ:W/zEd& [w{j@L/=Qiz2c=@m [xoVܸ/ej)'$A_T ykYfd + )e 5X5U;g^8`n񮸑[IkW"W> = 7,#Wuܤ@Ej茴Ӄ1 ' l# atJvƓs.yOM 2Iwj7'yTO`ܓ:-vM_pRVn {IU*g=_݋;pF~=>>ĒӖD[au -ʷL&\l:quGձFj'*>w2UwzCRQLW"Ag'ՑtHूN oFu9WexQ.%\k*LՊ([KCH*GՑDRqmק[}lؾM{As`b;XZnw4Է |Bb.+U*(Amu,?x] OLՉNbڄBu9pjcv_2/IxksHńsqW%l,^]]i`BjFl=I3zq[<==J=[zч9q1 1%Zv3bMHKƟ.Tuj-^dcCTtβfn-oG_^B!{P[blLCJ|4Q/J팃Z E B` ;8`xWKlhMRQPß2ۈLd,znڂڦ.Z\G[E?js1 " ȱ@k"{bc>xFEQ֠fF( 4bbm}P=BQGQL=ngCt1_vAP}/BI(LmAܗ$;dN`[DC8BWrR pdEP/up79n4guY&$.Nm[;;E-;wcG?BlͱgG/ %К:֤?{}Bù 5sf2XD!#L^a68Rˆg =W<χ60!j͗ B{O-ƁR<Rd0Ô=͝Usd퇷<\, Q]bǵCeyF#0|C 8+v -)4sFKْH鳃5䠥x\ J%ԁ+TrSK,",58uq7 >p%ڠ# _ce$2cYM48IDQZ3 Yvxl6P&cx͉i#ʝOf%~DkX!C' I*j dvo+e8TbYcp9 %Z̈S2,bX=JԒS#6^[&8Mr)@Ȩ2Z{3]cϕB"T#LVK|Hí<3Lw*7UʤiyVu?ɠ O<s}82k.6[-BZiDčWmclv]ߑX5d؟b&jq 8'(_"7hWPʔs>]D``b %F7Owd8 U{KLJ*8Wk8{Eعرi"Qkt۳R[:\Et$LǟF)Dy,Q LB~T* !eԕ> 42v[A1A ^!)GUY՞ZּHA KqSH/qۇTspޘ(@jH}3V}8?=5fr 3&gTУ//1dj?^Oj[vS_C.i[S ^RifªUwի~Qea$'׏vl?},MKV Qn/zϫι󖄺",n26UrpQnoQGVy̴ޝn۞g=@ҳ5zxA=EvB{Z4_%6s@yI3kwDm_sU˯B//rrJ']a:z5l2P!>ytb2F $UJ";( ~,sSQ¾嫓 zKD)EnZ.20#)J/RqyfE٪+'&a1Wo8DiG`>)m<ó8i)g+x+ Ј/Ɲغhx߹?W#3}+)^y7ꌥȾ@]=w2/;G)Q9[||%I3ܬkoP]hBvfΰ{w~x2Wc4 Hol/j IJ`+HoH} rjh?9.x8K4l#G| >3r49NW:\Y'ȺFfyv[Ҙ& |VgYy6oQ)0io^P͢UùM _4-=[ U>]lf3ِ[BaO/2DԅM ]z4<@ن JBxdXGMDQ>8t aR4Zq@Vz:zjm&6Yh.j =cdPc1 |El=YF> ,Bs ǽ`рTqlSC e$JׄYˮ90BO-N&ݑ50LO7`oWf @|avt!\[O".R [s\$FS8/p`υ4جb?b1~VF,\~>We'}%\H~hry΅t'ӫ]& #~Oyh(=kp4|rҫ +'σųnn/Q& L守 ~?NhBE/&2ڂx8'8Sꍇ-gt4#)2ޖ23KŌrn!ä68H},LHeqGrC"D'xM B\@]7Au|UK%l)kԙ})yX7wL(XhPTg9~W![X #}~DKsh5 ES|ulP໷z B* 95VhZtr_u܉NTl#<:8KQq2[~ uqE `HӊZ ٢zibDL%6>pǂFsp#[r2U&deߩ#Vןxd0Av;|"  N?Sɧ"ۍK8*;9TE&0š?1_YE(1ΔU4eld-W8;bw2%6f4wzIHVMIXcnOj*-(?N{OsrzI0&$]5ⳏY~qhIZvRfK ,V-% i%>-48X4lFZ>iS%c˧ڨDz[/VivVrU%MDם~ beϦJfxj=xK2LuMD1GшXiX*iF0WmLDLσ+ gIB#֡ZV?)ʣ)R${E8 CiϿT ,vivT8cEktg&'ӘG6Pecg{df<1dTحm {2Rԧz)74~Ӭ"= XTpqrt pVFut^:8 7y΃9iaj矁`7vr ˗,U7$C!ydz#ԋUOg~;_Hk7y~_]҄.F#2kݠ˿_h C]^A.})E8ٌC~IG!Ke-XE 2wW$2H)SfD=gwC8Ja!`D̅.h6$n&eY/ˮ˰F8ˋ^;E0iJ? ?َVč#$QBiB]!p!i4ɖnBO-2`?H\ ^A|fH^_&gWU}$?ȯ@. t6TCaqҘz`jtN)Fw4 aF$OqIS@p`gnzb /uRyo?)=vgi6GQDYE"1!%_.AϹ.{j 9V1 EEXkFڤ@ ZۀYIg4A:OPL:at.zQ"Btr?ZL!7$N=!_)~/ ꓌Y50[97l(= )ʼKnA^FU9݂(dy_D{g̳o> (D\f'4$ p@)*>)oldNYJ)| [t!# أrh8FCGml|]?@`TsC̉A}hڨz%hhF-HϠWg`62 &X$ePqஐЮ\r{  ċ:Od`y^7Io JI%? A'n#@B/vsy["gt;ڣ?˫~M]Ӡ ~< aZQl<(22uHB}4Lր8$c)F0nG9k#Nj';9#X=PDuh%ߺ97`F/3<.J}^߁7ägnJzG`쬝x{CsyaZI(Lڡ:U*[S*ڦ"l\9U7^\UZ>̗]Ťڙ8%`-ҲePe),zHR2Ͻ)f:1eQl lS}`I d<2Q[0%%B 38SK=7"A5ML!?!%R[iIBE|'l SWF]Lݕm'QJ%m$h1gT0ĺpf]03Նbim!i*F;u*Fb _:d{M$sAz=JoNgZkSX G(Ch'y/vڜ悓}$iUTc(.]+S~ͅ)hb`c C&Ԣ;*EӰz6Tď7+;9%gnp%1xUz`fy]޵sX#!6j7)a քॊ9 ={ĉϞY!ԩ\gϊWyJĺJ& ́rOޙЈ$v [km^fêBٛ;qڝo0oVNV|5nl~I8Ä2 jov7Fr*CٹehbkoT&g(;N}rWdD7t⌎ڙ Y8zسm3aګ.)\9v1M9i!gho(j:z7<*m/^H(̓dB~^K+JTM܌iEBxsޱkP@OT2Mh[ER{P\Еي{8pSz c\Eri+h| %}JM,2Ϯv: B:\p7Iї3b0.tI~r 31*C4dT* !%`[C땆.9G ^JQ1pE>m+$Wa0DQ&XD-"laK7a8}Z0e}W_ș#$Se'L4\`PG\\NV"0Z 6w9.CP4`x:9&'-$)sQ4ӵAz PgRo]Cn>*ROi00zW_wQc miŋ:8o?aߕa?S-"7P*KhU-uͣėf4 mJ m'h޴S8`~Gaz8yub~(6[rs@^)fT߸Je~fscuf(:e3k}_[AW4YzeOªBOEާ1j˥-yAH, (ňrGO!8em-,"*(e%{f)0|*&U᭲o*/**Vt51_[[yMntyأwK%>4JRD/oѕ}.B;G<=E&-՜E< t| [] Q>L”^R]M_N4t$OUL󣌆+V~TشFO˴MO[ጵ榼ba'E-~~"E[ңrՑ| ]˵ց !v'[Ώmn@mz` -pSQ.ժ1UlV[ڥ3KƤBs2'doZ1sGD% [f;`F/n^uGQO;j◑;F+ؘc!*}H}-~x>Q,, U=ĝbQ yQxK?@+=X~wOAyoOWW~7ycy(= \ekdh F` ACpl/[mvQ0&P2S 3@ސs:ES?:BJ ~)4 ]uEqr?~?ZrN;o. :R.jr\jk}xߝ0D 粊K5@(vDb:3Vẙd\ԿAd6qҦ+w Æ6=Q\#NZj4okСAcyET jcёzE9D~ySgH36ڊWWF뿾1M絋Ql+*fi)a!ݡR?F˅)ҺxUmO0_q*Vm6-kQӂ5Hvi}4@;Ƥ2%ywgG6N8Y6NH(J81vwvV؆p9\0Ԩu[}Ob>G'LfdJpxV yBc$%]BpC*4kQBd[ MA$\CޅB)g"D(I*ű75F l LtbL?nYutݡw6y\[DZW|,'B6'?SVTb$3z\*n”L6 {(MJM8ߵ x6dM GhF]@J]l&ĨʛB]9|C MbD%("Qe\+`daRqLu$.K6{\iWU47K"GG4P*t($I0lOI\>/ (iX@EOeuV)3 rehx'ֲK#(6c2.iQclF?@˞V 4ֶo5{Ǖ\۾`{5Th#‡v!`Yˉw՟j~FXJN?#Tq!P̽5D1Ev' ϓzO`ILX#s{ \L8Oҟ:r$M;Zvn׽wk($i oAv/A|o|bޔx(ѴiBw~>Y%L٤B$rb0KiB|:>n-.)iHX;9MJSd0hB:r/ASF~PJgucGYܞ]C4| x쥤'q)X3NVYt=$̛νX0C(ة)_t_Ӓ&B> 2d.:A6e83qG|/BS~ g}cL0z!F."$1)xi{4 ݀[ހݾ~q?}0:?S2A-@A`"UFkk%PZ"c?M=ig w)Zipt/GeQ:sP*ӕU77FAiԞrꭒp8'|/*i\>8CS(4\cZgMx&ob+ХBtNy {ôHbm*]`47ګ c8~`L0%jD\e57$$sW4 eRQj^9+ۥXZe~[_LޕЉ;U6WU2f,z 0w"L`)-ԅ.jO.@dq;KoÿKbOTFlT=Qfj1jajW.T26YMqKX>g" cEpF JArqr"FP4#sakuXJ+/dPdSLPGb"/#dMxO ~L`"k9 P =c~XlWát+ eۏNi)4zB[ y]Hc:vrrw&vlFݵefB: 3xmؓ+y?(fDn`ZG(O'|.32*[{X^1ιlgԌa[p\6Komk*pŝOz#7t\ 1L;EshJ0l$;nE<Aźmgܭ3o1]ZfC<^,m'Sv2eg˦ Uu7ؐ*o-qrwo=05ϙoV53 Rv!}IRE͇`}ӨɆt@>Bȼ>9ڭD*]lz؎}wdoaQ/Eb `zwGge#K +ښ7 R21'ľR^岿l5dggd;zS3KA@4Ϗ#6KP:8NU3#Y>?.=wj7+CPߚiP?[29-G7 ]R2J{ޯvl[C,bnS-]ي3 pM;а8`Lfo(@HJX*ͲS;(n)[W%8wc{YZ+V1UK9zt+W-ˋ+LOscgnqݛVa0%1 '0Wt"dQcz!ZjW$ QUh]Ѵ[= n7()# _{x  /}FKL<J$`ą.pvDn"&<~}p\..öF:fobe8F߂D@ss.Q!wahKB2" &5dF8ph K27@>jwi(O5v.zy=k]uOv0 RhJͅth;hKgg'CaOF :!?1A[N$ >Ӆ#1YW rM(;# Û oN&h/ YF-ʥ|(`6)Â(] %jjdW q$un?f#05BusX.㜵My\g\s5A4~MB_)6^8¢{ޏ/,(u 0*Xa9塁S/s+u0:+Y2de;8~  [9MYh=SڐD:1Y9P|F#iO D5ockuaPc)Z'-)-㺭Nuv(Y>!d&dT4H5%XN#X"g6[/ݙ]ͨC3<. X(%gx.DQEx.5 qxNa} ڃNs>.>zONr'^E%DzF4cZ%R*Ph:|aTO$(\ݼ%3hN&`մ qR 9#DC@}֊΂Dvald٥Ot )%<Ώ$1Zږ Ri'Vc 3(GBArsa`hu R+mTW\e9{_cSX YU5,,e .*ݱm2qV__ J "NZ iVUFf;E]sӲ EZx)0H88+XXZ; xlgaų5k Q`YYƮ }C_sKDtZP6f{\i1)=սlPis3UrRd\7@ZkX&MJfU@ӵGlaY SbAu)ivBQGmEuVT$b[T")b},ab.YUFۢDbj wϰƽ{̈`ù6, /e>MAQWi^ ʙMP7(/Ě c߾籙Zܭ .ub+V!۔ȝDW^եE- zyf~`5LTp4Av0HɎktQ1c2YD(0gnVb>wzZ? Uc(X)7=Y|bnFL[kiv =t,6pƶs]P? ’Sإ|`?__>_ $6|MW0u&GFRSWve\?v 3t e.Ԫpmg{A.~?cHͲd-IDtmHL*e7uR muEou VYo9Zh'*)26 Y" [)A<+k_Tru[/£^lW}]sho)cu""ubQ)(?.hk )V 3z.Z\ޚk YT*ggmjEo¯n*۪z'+ɺQY/gZfĴztEb2$9v޽fUѮ?mJMCL&ʦkUՅ>+DVRlmS?.H 7S双c#\ Z-Q2JO\C5_T`7 LpBm"PAF[7"-3mSZ%2C+'H&3X"*/DBw ɢDި+Ra:+m4HsGmظk -Vn{N#&趦ȐM{+E3hNoo} u^g-qх_nbjݺLydC2g[eB-P.Ed'iiO jvD^m\^kpVIKbK1hU{AGG5T/ޒf1G`n훹vF2McBOn"ji%(pg8r'^wOIKf/h媲fL_ܷ/1g ,>h]}',8SJ 8L#\~Q;W`Wsp[ul{:)V|GU7Vn}NmO6m.sAR̛~}NNF̍ݔŌm=yoKr}o.]dP0Kߕ:㶭m't9AhH5N {wڏe)`xkoۺ{~9v^CrS4I^Ѭi,i'}PDIqEq껌K: Kq|U8N N HPLυVkZ5Dŭnc5c{,IH;~ 4\:>="AD,b0&xqYTḧ́"`4 *>=` :"  Yvr'Wa.Y+}h:>P`*DmXiShlIĝ;  Y14' .Jr!_z ]cW4f-ٜ å8`wn0aq(,MؔthvF6W^O`k:eL a4n+{7(#"#Ft䖱Ѐ'*ߡX0ף2ͳr&2+3+Epޥ+)D7gp81'͜IG+0!E]PŀMwCAN;I)]PSVj|I3#BB>cKXg^K^q@}-Q=:W1 faSA\EI&*)7=͌%: #ߐ f֢q&CO$VSm%c2*ˢ4c^Os$'ʋ5y2I]y"񱷌(*_$X΁)A9~ &Z!bշ2e8RƵ4_|Tɾ1u$o!!Hsb#pmx|,29e&##1$@&o4ݾMJqn2H,G?.2*KE'n>RS0JNUo*\!}wK'u︊eEqSNՈMվˉ"4)Szae<ePۆNbs6ZoWT ynTUcO8 ^0Aun'|@SϿ`ZӲg\P2 $3,P)ȽfbBbs 955*P碶gn6h 5~J=%%}7lS 1l!֨FcdOGVBcЁO$2٣IGѯL t&mjRk<}{r{MXmb+g1VM%ե>S|e?gǽ[ T/jm(RϮ/Eem}&5x4@Ns6=ْ]* 3l `V5O>Tk;=6S{671e]9gY %VTn ZRKvLO!Eȅr*6+&ʹU}gXyԆWY;WY2P7fueKXрMnI~wocrM!c'K6%m [+ E,k-vn>H8nxS ?[,+7eLiD@-iWU%8#Ѐx oK`‘+!:g9;VZq@,l~otϡ_ϸa>`TY?|S5bVґIO"%[MhFAԹ4aĿn7-xj@cfRK_W\~{8jHCF6ǹ‰!;Hp}bj߇NՍsݿn_/pCǂ x%p"i;eE[z{^ K(P\5*p(en65ZAkhܱ d(:cq̝YlZ2רJW{[mRWj*k(1{['魶|ObitOg(MKz rQ=&dV MRbO,'>S!GGQM#H_x #iT 8,3ZɢȾ%tT?^|t?κqc1{ɮ&TDz\\5CkF -ber f^F5u=!o"ڔ$XpÈ {tJ(ELlf>rݐ<^%$(̹`Nߝ{N}9iC!pxsP5fd ?{Z`O] FkM <&dɱɝJc].vW!{ggd8|F}6#~]nb4{ I%EŤq%\)@Y6i0k& b{. 6#i0|qD]R=- / a4piDwaJ BN"6n]q #ߣKFp) N¹9P^r%GjjSm;_K:iw0jE$!1OŰ`) cB1>"ƫ^h$y i-e@21w7n!bB.GሜgIo8srO6C8+_9|;|xF#`pz$9"'8\c|z^jPB!Jn•#)`؆ k=qXH4 EVKp4Ku6rKL47[A --jrMjXhqx0uq`i@$/~-1Z*p#M1(oNT+,KTn53q(BHlT( 1!Q.6MpKCN[`0PWތ/UlC-w%Xv-T#_N~ uZiFLD+&P{o7^SkƗ!}CM $F򠓼Rڰ4lt&%Yw=%uc8%ԉdH6 ct$uhhCc}`QlQ0 !h>bإ,75$xs\H2&<Ca%lx[4ͦZdFzfi+41S:w/rmj'*6ET؂\rզYZ(mА əsLVCXJ/{6ͬxZ2b.cl1L<Vv2vhҨ @5VySq6d"+T_3?#)iE;Wɇ`TwM Vu5d*<>q*$0*Sw%eay9-bk5(W+08 !?zlh d"]N]o S6~2bml6@꘧V E)~_))`;[˶ط4B?4лѓ܊{Cp{XC.H`FOqo9\囡-R b"+c6 DxRmO&=[@t /\L&Ás6TO\B^cΔ- \7,ߜ1\v/@Sf^̛- =K^g$޼L4R4aA*l 0J6kl{j㪒oi0YB1#"L&y4%! DS;DG490Zclc-d1+sȊ&pט*,)L*r)fU$LcWZ)Iʹ0)f`Eݰle󋋯!\0P jO^qw { uko쌇Ӯs퓯^O$cMjo5{O/p hG KdCxS_XϘ)yMj0YڊI4Gy6P?3~+~36$7" e̠cmĮ0ѻV$GGbJ leؠᵉLp~`Z~*2?ypfO%X HjߊR`X&X Ű5zuan73ߡ˷$քZ%),Hxj[?˺LVkŢns󳣖~NI;W*B82P;B8V|E\#%,"0a$OJE ,uj ɿw φb#2bv"!νEcAdXީU Q[z8 D%E5N  @Q;-bcK~A_Aco4HkkeNuLa7n-Gy f&neȮlmVXJ"?%N57Y<)oޟp'N?|w0aez7{rvD`ad]s-^Uyו>RWuYv7HfYEyR!n̓ Wd}`j(#_ƒG@Vvde)SmXjs7':7+k+ IJBs"iy= cݖTevl=j"Sc,?%8 }ЌUQ+nW閐V;U7*]5޵55E[T2ݥ´MwJDf8V*VWױz}yp뛫;gW7Wo^ӏꧫ&Joh}m;!Xo:FZ6_ւXQTtժMEn6B-bSiGLGꦮۢU>j|KP?EѣG)cm~^ꍮZqzQB7iQ ̶_m) ؟n:c bWjY2=M;3CDE}Ighn'jO?̺U]ϫ:j۶O HoIvASZɚx[yڶܿے1ENJS h~{1ey[[:`0}z<LvLNg0ο])ЃwtAħY5={6xdN[ %-͛ mM mj/;"m4RۖM{kpɥNO9Ė"6_:`~(J]}O4UKЅYb@83J&/3h)\c wZ/@km]d,A8AC ?ֿy$ K7d$O:@IZF`ŪkDr!7GO'} {5툏逪ZCZ=gb*uG,V#i"8o_N3N55or~={iĿ|,"ۨ4I]Q.8D"k;X5 **;>+ oVL_Ewy$Ё{f8P/'|ŪuS8 nBF[DS%_wic#>9޶c#ع677nkO)SU/{;"ޖdKvϊLYJ!Ȍ2`pNniΔH[P/N\&zsu{{֑E +Xq%c%۽q%hKCJ,hkH 9d9>'LǑv@7w|ݿr`ƃ qmUo=KܤEcGnj 8I? 8sE&%'ZwEޮ~h;W>K&Z"kLm͌|,f&EZ:ɻ"ӡZ+rk\O.K6r_If]wd 21ޒ(v& ?5a w؛8DѿM6<)5&T> #&~qK(E2~y|i  &bO ?m?;B2JU]x<%eTіM~۰k> 'R8(@W[!92,il]t8kӑ$ N=v @yT]Uw"-2|kiV0~w@p5*>It<UH|uF#Qb#>\74wK} )f8{l"z4S"8c\ҒA3ǎ"iXctL!6u\춽 p]7{܋-f ڭ%/AAEC jw fdV1*i5.g%^1`Y˗V]6P{IڹሐZ˦ވB0Njx)\u3 /#26N2'3`cӺ'1զE2ajְ&c%ZKre<ꇗcm>Y.̰W;#cMTl 9v*5,5hlɋf1# 9W4W- Či=n#]erÙ8>%MUDkWa﯎o'~3V#oόZ+ Bxj㎓>\`vBm(/V^OZDJV}n.=n y)4pzoJeS'0Dx2 `}gTH>J\[ xt;e#>@ZmsAfP}E}6z}f/Ԟ\ {wH }20HbIHc)ΰ$f2suqp}w8D RS\ Ғ#ΚGh6pakw !4ofqR.H "a\˸t4q4Vn!+$<`l"M~ɟ:]E(ƊPI mb*6ād `߰*v syMY舚 gqYӢl@"E2ԁ=.C*I;OSMVĶFMx1F4}3?;}/JX1Gd}k^~C"ČO7{T* iNx,+Nry9<6wClG±vkEݸ Β؅R@n>Les4s "L^V*Cib'e~BaWVjG  ' خR+ؿ n|.zleo"طE ~(|(*G͵d_N(h$o NN{"B|bӿPB;5mWgD-ov.]tр4cl5h u!\c$[$HdW: eɛtBWҦ*g^I0+rЏ Utj6,M+ `LDIQ`pk$M,sv(z?ڡ΄gX)II$sD~6BpܰHm6~ߎ਄'6Oե/ SW,  `5dD .|`uuV,)dS9꬜Mr?*Ժȴ[XAnZɢo8btJVOؠASqKp=ÏBqGtfj::62vB*e4K 70U|OiYlmyéJ0V*9ے8/׎p7\;4EZvgMjtßVU-ma~凑}>L5e*7ĝuAhOv-,)xBx[^(Wڪ nI-|n4*=4Bl8ÍJϴɚbꉗ#fteYT%]^~"hz(K9Ϫ X` TtrJbU G*0-depa[97] 79 PJo?hB`{mw[A"@i_̈2#`DqY.q&͂W*|@aכnUSydžM;2O޻Gg^tbƪAseX.v>!헅<&zK6e fŶ38mMy+>@{$! Bbt7y_6ͳ73kxb4@Qg5Уyt≊o45]ٺ^ *‰h0$%8pe)Q!ݗ4u[gu9pKOj6ݎJZˀk{]?hX[gͅ"30%MC7#D$ߎp7d4dT2x$A,OĢp\@8J~)*\*3I]}ߕ oKŽnF̎CopY5,!I$@nX$ sʧd =r#%n z ϓ绔 Zh@~mPPa͛JjǤ87jpLH7[:wi`.l)j.,>8YL'}rHD0iA;7H 6s[2-6}BHO ÑrJaSNxW)MC} (,VTxó ]0?)z|:cdHJ :_U6|S|n{f_w2 O@m6_^ ZmCP>'_igUZXZqhVY99٭]9<rHɺV˶&uDMO2MhpS?m#!nySӅĽ)6E^#xڳ Orh45pYh zRʼnL t`N1K*ĀQ24Z<9őf˫c -d!x}lo}cf` ꋣH;Ϛ%_4:kiF6x#?]J^i|,钐`pD0F;x ɗB?g^z8@4|쀭wٵIqոFrE.!c"7 O$BEL1h^r`6e-]ِ sY 2 E\c 9!{v{\q@/.Y2Q`٭dX7o;jnO9c+Ʋh 1(|} h@lG; p%Aj[#2+zn3z~ZAIҢDO,}XbmaC3dB"1Iѳ/AR:RSa6cŽ\!#oxZms"7ίUŰ&`[zqB-lRfF ƾuKH~yjЪs}ު DJM.=Puk~<X,3PKMwV.%a8E9."2𐇚q,_vr0vT`NՎ]9 T8hs Dn8_.P3-ri/G( v'iB`*=SjZժɴPN[Ziw0ve(u-P#9VJ`SqNJ %ipVLrbHI,UX?4;M:C cg6Uo:>F>Ag>.o,@5A=ې Ш-+&EӂM9L.\ED[\(P.ԪT!_zKg{|8,-ݜOMF$;bww`xOW෪U>u"xYͿf>?kHN2EScNT6ߚAu'*W r7;jnMx$gFn{DHVx8b>Ɖ2'5kcŎf`;ڄ4+Z1jJ ګG`RXl6Lz#0% f ᜊ =tḋ?M<_ojmǹH9ׁ43s"O4Eف%Z@8^)z99 VήtKsQ 7Q )ɽ_;ȟ R60_=5 U_ Qa٣LNQR6[[ϲS[.rJAf41NxCdG$t=68~2>Bkwf} r :?H>ْAqwd׿y O 9ᴇ;q8[t/gIfnaw6޾t|Hn J|/pee:q?&j.k:\Xkd)iXs7}'2Rldkrzr23:U#mYY*ťЫ~12WvQn7RbUcY*Qw8v; JVۈy]q2쎌Pߢat(Bz3@aIn= nx_:;t:;`fuz\&bݚ!w^L$9"A;?ƙ͡9Y>&汚1e]4}v{o]23V$j>iݢrC-\MͮVHh(I* ‚ثfg/t!RY]\nܘ#3F!.4-<6P Qޘj-i]ۯ݋ٗⲛX|<ZZThZkk"9|jG\*-d_6S͙p@Ld-f-9inM~YXv`1ic-N͢e\+SKjk-[G$kNX,ȕbAϣg%_[*D7C_VxBm{:&8UZp&ddJtw|5hEN5lހx3 <+,j `kυ4i ۅ.t%p#7 Z7bH_Fn3#.ԇ)a[.]_C+ N۹Vn ΍'}!ZMz7W]?׿9~&Somq&^=Ypտx[a*}$J$H\hӯ1Ϛ7=@aK)x}kwFwLi)ZcgF^gFDgd)W7;wHA%q2[^HˉyQ]]]~K^ׇ>ydw%DqHndF>E]]A:rrlFy'W0썮wjP9)M7h&y8&d m!9#t,҄I lW[;/E:'`A <6wpV(!t: (komHEc'D/H}]ɓ^@Փ˟9><yI9Gv C3nI* ]"ķYTDU餸 GyEya L ʂl]-䲋|<|O>]\O/ys~r~ޑ_NvI~»Y#0#De8x CI@g(D#Zr5BrބY#"0F9Ni8FEPGθ'(1L?-0'oGK^ >\>]^5Ip1LllnI `uy ΋(~8p&.ڜ fMzg> .031Md>pj>""y"rdW| Ù^؆U3*bK`m8J/Hə\Cc>*6Vyͱ6`e)L]4RlЂg/e$Ms; ڤsB㜄qP|VfZ~Sb@k_>0.0!9BX]2Kg,朁c:<~ (#<>AqxȆdlD,qw5OoR)EIra~J-m \ Gmz6$HB]ђx9+2 FlLN1C(DG>Em6ՄC_aBs kV׶z©@0/=k=6.y18ݒNkXר $˞`B!V҂՜$#㜃hk5,d+:ae]R&zhZ rda;-.#+(baާcPyB^_ N꽋z,pΠp0孫>bc'yfJDxΙ (-Mq@ OPѝw Vz)꡴AЂ)$Ḻ`Q*/*Nf?`39VQqY6 F սY;n+^6*'k@*LE<T맟-U75A8& F@xVS<Z{8.G}gpS BM|&t򂥩-Z)1F{bx%KuH74Sa8" #@cK1? Nf~1]:<*m[ D}ԆlĀZI&!5~$ IjF.5 'pb5Xs GeS#FLCpȰAFlvXߓѢ:v9h-"A NaNe/}~l>{y0 B)<ΕrU+eQo+}:llUgD{W Xt6X+mKYl3i10Uc0EtǮf^t5&3ʰj`ܤѸ Hᄇ1ek(fo6BfSÅ݁ntu.7v,džBX~Rg_/R.VG2@hr*,qq]VEc=N+): "dhܰ*TW]UJ$pkۏRz?oH}.A~v˥sWo\N B ՂA_M `u8 (i{ZIǬQuǗg'BdOw4Cř%7=8{"{ ZV;:M?^qz<;~ $(tyڥw:ә P6 W@oFF>yM-/]nhEaPmK#ŭ /eI (B?Z,^._.^' 4AUS5oq%D@Ȇ摟k>= js~e`h}E:yz+ѰP_gqYY('r>M#^3wU;?^#[Bg:7l֍ 9fn־R|>1uF?8mJ&Pv( +vz \/ŷCr\ Sa}]o|~<9{{q/:xsz~ٞv쭋ƲR hF 3`ow98Cr")zRԔ`Yg;Bq\HLđ7 `y=#S7!1x (HXO g јvoף6`@,`4\\嚟&WQT3΂m;͢W`kQl{[֚Ʀ:9oR= 95<Y /La, 4Cx7 G<¥`K7fzWJ-tAO%lR9Nrc pkL(p\]2G+NHAH,0һ#]m3&, ;yEH*~GB+:^r޳>cRGXibJxzYPE@_KT=TXDɲSTc"% ^-;%( :?ڰs?E\=Kc6(_zV}-lJeP`)AC&{Y>N$oC[*+ŲFYB^Ũ bl%8 /o--}m4/߱d*XC-V~A_L{zI/m=x֯H4<#p םUiXyUS%}ؐ}vN H"^yXKP`~#K+[yQŸzSX9@:~BO, oO 2]6]Xaڼ๊tQ%z^%,#,I?x08qnԆޏޓw:Ӓ¤SkW·fGz*@y WJ.Y0H"m( &x47v ێ3\%eC;=2Xa[5 2xpq7'oVu5P\ QHNUW0k#t%w mٻM:G[a^bޟ`Ls z-a34 oij`uސ0Mrm U`%|=7ZS*} vy+"8vIfrנnR`[LcUBVH5b)zG`ľhy|X3mdntK> >>Waa B2+pɒݬ7Ԇʒ RE D'тSߛ_Wzs'Ig1k*6Lzޑs^]5ZpJu[dڜXw^ Zrn^֘e݌FH(itߘ$U9MJ-#yQ7A<BTU]v|X}.zle.&W#f!tHeUK*h.^-]mAs A^m2o1b.n]mb ѼݴK=gt44aβגt_HE7][n٭3uj7)~é_^+]$/ ZAٍ2nټ㒴vڤٰ"]P_Ǘ'ggYH+S㏃!C'{=B]<@Koaud%X[G95kXiuoXY 6kvF4H{*=Wư|G化kqRN-s<]nH:S:3')AS DEc0MtMxAaXh Q5NO${= ]\Qg)hLYO6P:Pnl;O oKn\}ght$9Lb*#^Swkm sh'S5F9↪.#j)`B4*@ӎy0}jft!VKٛa3frʵNi5r1:[{zE]+č0\P9ˤ~iPyZ l᥵tVE&y^-l_j.y<{MT3-02"mEQ;ɮ{sAbtlx@EvGZw;&w2b;ȸp[vEt|'r`L*:<&/aV+wx/L6?򏰟#X8 y9"{"N ɦL/*Rh{nbv"%i8%>EDGZE4cʬ>jo%J"X>F#Ǹo `٭(6hI$J*DNՁi-'y\ZOsMJ _NJ5K"҆=Oss/$IL}ul0E(GWŗ<9yf/K}| uZ(xeR?8y?o(:JT '!Cno>yɐ!"(˜chPJ1J6 \^/o~V$,{(GZ;i߽f%R7uxn90: Ɂ1OT9(x+nؗdU.wgZ dPw_e! L; R`8XZYe}imڙl[u1H4µsN-F_;E0o'pʧN8X'"_jXrs ckr KKƪb~ 2n^k&u[=J2{4ɸܾ-T;ޝƵA=Mnb#K2ӲD1s!\%@פ0O$=lMj__D/)17N WeNh:8^vXE-ohz()~F9.}ʊ;LE_ܘk q+tAOƍd! ~O~k2N!_jx-KU#ӏ+ukڌ QSЃY> 0o~YqXO% ̈"}ZB Pe@_-aM,I*٢ :JLB1j7 ,B*Ey5=os&a^ӥQ"3}'74*;fn5Vz F4υXu1Rym%<_zċf" KӚ>ۣyJ0%G?Od:POmo=x\~,տp\i''_:"9}2a6L 6@g ȬgZ-Z@B |WIۯ%T_\e__ɮvYܗˆ> gVrUv*p/@=J%F5\dvET5>=Q1vfXE^f""qW+VK7mlj~=dIlւM3~\%)=^^XL {'#\,SfyVi"ck3cË?h7d tUC@`z^xU 2^z;(@7gޣ+#9g] y^p< fr×T[ӆ:Z(iYLTLo?*S[HȒ|uiZC,&2,3~SR϶'c"RxH}}T BhU Ք]vDAsxz~Ot _1ފji6bm=rцi(U2Jk9m5k{lT- Zb&[)I0ݕjL96"[K䵞~b7v׻7wYiҝb4 JI.3ͳL3J;ПJ;ʴRYj>\5i-NTo.fjng&<>.-]o4c3'L@m,?I@U X 4r VWY |ɚ߭[) .;:3K1d K*%}@mɕm cy[OZUa~%@nj2ex#7yz%!4OlQ v|e^|36kq[~ o7G* {QJ_ux( cI*soF#qo۞=߂̾ 2{'#1mb,HN@1qK2_@[qQyJT {w>Wó~o "1y*xD& 3+b}EI{˿h"q4s`9~!$;KޕhN&d{@2#O/3 yP7.yßw+Q9D9>xe담\~U$+؝jcx^JE~UċWNadあۓw'>-~(f@s!*SE;2QZTSƅxVծW\7, [M@kY,g@/~'Z!x7YA9:HJRO竨Haueƀf '1ɴMi_ dVCbX_>.IҢ2 |絋6*l&'t]}X+k70>G=$IC҇rE(KoRH_GhOzh ,'|͌7MZw#GQ;XR_\98jJnwL o@wZXGw˚BwKbqa 7Ϯ5uNzS|ZSBZt*Q]tjyZոWEÔWq2ƨR%>Y@)x=w6fy%W#9u&]'QZ&vv)ټPlko_@Ks^K$0 3`MCwno?"?&?&3oD#xȞ"/RR Q!?_?<И>_\Y {h?aBdFzE!`ěƉ}BZXx} ќL  0>( GQ?b(]]D RUΒݛǰFn%o^;. h܏ wx b]ޥb|^vHM/f'i_S`?^H9:{L^uOޝG3rrJ^::?:9_o䧣WB\@GR1 ȟ#Zx9.)iBȌS?!M1 z){6_p0^=֞^Bwse8<ݡ4LwFј&f$CeٍNwP+ͣ)ytl0|w|v~@Ļb߆<,f/-80qYjw t|i])^L}g8I250Aj)M!o,.>HL/3ެXd'yEG .[Ip$gT%=(%Û`~wÔU<d$"$Ԕeo~ڏ9*q"Kӿŀ.,dibH:1yG7~10GaX(3QY͋*g=ZWC"0AWeH 8 a덌|؛ ,]y1q~ChbzҩduÂ7PSC<ïޒY".ri_Q&BCquJʻu7Jk5 h@64* ',./MNA@*.]E`{(& 6%ƛi4 @ =cE7g9:UJk*?ZVd;:*tA7b:BƳ)^;aJ`1t` q{`P6藨?zNYmo.pKY Id*"@W=1ͬ6Ҕ֫ҍ?Zk_N(GW-# 22cKn2>@ ̤94MTpxy $fEQ<m&w QFS—n+a/}]q1 .2>Ss4}D>Q'4V<)D9@3Z/e NŞY /TPF.H'EqTDKSٚuI`5aت@#h&a)]qϭٲ/^f\Շ*tnVSX͢a@ڨ`[ \A-l]urAaV1gLtrWGnјmk vOZ\G8%GI :z.)S\r,L%նQV[pOn`B,]̡fXK0j9xMEX1)'̜1W|j߶Āoԫ3JQVY{PI˽K ;|%5FmlZ lgvƙW3\%;~җa% Ft9,\ bU4hZ? aiɂ. eFfvsl'~=~{@}:8;OMo|rYl}0|1jL>MOogW'h7K ;9TȕߦBHo hJNkmFyW_^w;Lt+T'<Үg]S?3K*n0k {]W9^0Xlv^(lO$P84::r| w sqPV_ L*@fmVbS6?:3E- ؆ּ lE/.ȳ5nfX#3`S%(F'i!κ>e%XF1`_=|ͯ)Zngߙ&}G-Æ<{$W`AtyIÑѨhʁhП7op mtN?; EO%_T*E`3r(j/6֖1^36ҹ7p4-㪦&-+ (Ua_^d])ckZ>r]UOy/9AS6/^bbhq4lslz,_{Ptwm>ڻPFJUW?- a>ψꝓCP٦:hb:+ck#5TU:ELelV/3و#*tj񘱝3ebyWEҸCMɨ]r;a\3#UK)w qd9X.5r 3'K(ץ0WuA{+W&bjU}ZWm@N gc1;$`c/H :[ĔmN̪®֮EAp/ S޼GjY1ـիL{D"cd j k{d_wP\ $rn%N;04Ys7@iO.=yJ-zq|Bv r VMf˞fcklߝEzL Zlg숙wWq zs\oy=ovvD_dOHoDY1^dvo4B.['ޭ?egLp7mD\$C4OHD̓Y]c:G]S/^ďh EwwhhK!E^ĸQbG,hE/NZh]^ _N'Mn2p '615QK!d<`M@<{E #ʘN,#0ɫ\}AOsͽ X4s0 O%s0@i/&ULK#qrA55bs] \`Y1(R$p(CxF$ah5FL]Ki$k .8ۼ@e ]/I0d8pG:I3G:%(c1T=+^.14ꅄawMr㧣+5ży ߟto×?pzվk#ڽ:'1>T6yr~~^so:?V; p9ep j5HhlL'M87t4c FnFe?Ec2FC`Fyw1C(^D]^/le+r 5QgXhkz\9g3b'JB%Ie _aD:N:JyRz?4gu_)vj1Y`gawd$'2)Ա/9e5㧁WO?%b5yfu*7˷i q'_jdMaD5Ks oGTf$zqX9m5G<LqTHXo(q:7{q=^(sr6jE)j?Zn@p'n9/k-]ݭB_k;ݼ M(9\6~s3])%[f1yT4_A& L@c9Fg>#ϊ2L|&`KA1YuCl=ݑ=CFac=F='d=݌I.?WDav}!&i&)l̦sk},외vi+X ,r!X HRx`F{Wɩd:m3 ̬G> bu}lB%O$ 4&yie3o$<:/~7_(_7c"<&cx)V+vB*K(E6/:e/^ :{,{ٯvEI@:H:ךQR'lg-2;~= ^'1boԾL4Jr%,|J~9}aC$0yc<-1Q}s叮a1I3/iL^ʮ(co0/x<>ec~OK |=On[BC݋bq QJnǗCy>5w,dw[@כvgqF(HvNrE sEc\m??>'[[>\<^Jy֋w'w?///яy,rb8 ܐ6LAYw1oFw/,,)QWp h5?Y׻<  g[gXywjNᆚHq,Ћ,P:pCի,*FjrD0[UY8'Qqcq Ϩ&۞תgu߁L?LknYF7,x:D_c[4[6;Jx$Z .9%x5w1Kԍ=RtRǚ4b#`~%qq0?wUc 8',{Y8.ѣh:-i i,{Ջ.pi-{a艌LlQ'Kl55c O[7RNuo嗅֌{ߊl~c}]L7Y8cT<^Qtժ!t)(O>[8)ϴp[I &L$tShBB92o fSH )mor^kj%9"dZ3) 'ޥq4@BQT_EYiepE.,ÑF&Q.wt|%wF}_hBwóE0[BY4&Q;2l.k ckJC`׋Ȝ2|Z-9jCa`~|GZ#߶q*)'Bҏ1Xs}/2i4'Ŧ"6VܩvIXcY8l 1 pL x ,L$0>< _ُ. *W*ngBlQZ!%-tȷ'LF[G[4Ő.>WMR![| T=1KEdLWg>~\Y-3PuͶl\*]$]bZZ[v%T [ɍqk=g >gyc(m.6DDŽH;9f,1߃!Ħ\ lWSްe&ت!,pF `];M#(S 4KUw6{-ڡ3t\adCSr,pY\ a ){$ۖK26[ҕEL.<뱖Iu=]:[DXAf_)h>0Ȗ^K%|s-_`%9RZ\A7l$-qR[Ȳ@76dg6d򛇘 MLU!8XUC{Q7gG-3.4C;cx.}ـU@+Bպ_>z#y|6 ;9B@mzl#v?$; 2;dDOyMر?T}r; q&Krl7`TY3eƚ"Ϧ(J #X)X0hJZh4}tE`e%)f L4?.I<߆wlpPxh{IQ*,rڮT [i=x37qDg2-z$ݑ@1Avߕ[!e11 [5CT3{\h.qdYҟF"&"G"|ՅhR. v_*Ujs []ʣ򱌆FZџ41:s zGN ۼqImɐ=3_,JIZxQE^QYC FE򫚥]I5KLMGQMy2Cn,S7%[^뺫s]Vf|V;)bh9lq6lDZ@8׀Q*y~)e19vс9#o[(7>?Wt{qMBPSMVr0QTtossJͽ0)% _f >d ^DYH%:*%g]Z>/ w%6aXFֵ:g×gTԛV'v~s$7Dž@ˮ-cYɑ\SK>خ` ,xQCGpdx V0cFe<%eF+jه NWe0߫w yC#CWW~l@!Clpl".*d+Y DˆCb\ޭĐ(dZ )g0F4jEF&-F]ƫex/|3SɜOޭ"v6҈8b@G /oRYJr4Te\B1)=}j[UB3a|'ĘD}^Le4"09%Dʁq$Wp\xrq @AF3Ьij\Tsd,gݞ3_ ޞzk!:x R56T)y>,mwcː=\g7l3RlR-G>2q&.ؒ)eY=a*rʖ/ӣ׿ 5<9ߝd3N~4>_2bFX&5^Xdޓ'~&`Pzg'S y6Bx\@T퐝\iՓ4)hm6H 2U  *XJU #~w37e(9$pqTwo|FZQKΒlX\e'Ʋ{t{:QAFLMЗL"ʋvMiǭ.p֭oq>j9G}Y\gK6UwO^1#֠~HN؀ِ Z<sRS%}>-K|!߁ji{#j+5hغV߀lT u͓FPTkK](U%V׸?u~gPO,n-1\T`zy]HGYQ SܳXY6ſ7҈lɯJ7b~u܍2 T5O*m776)!*~}lvM}sdY\ WLY { %:Uj: cʜ#OjԸQ5Xd/됅>θ҂ÌlulF:v~ssI!qǒ<,R>A4I 6[I.euZmf^Vd3 ,-{d-Úy k$_鎡{#|mkyσ{n`=wrorIn'9͝lWWWy40#ճLO̅z.%:1OtUFD3?~cRKt dMe8q]B3wt*o,\hed-Eb;Tﵳ\&?vQj~DmA NL+p^0,q}'D[ feRg,yziBGPStTUC-Ԡ. fIb*uH /i2#u͜x sO0Yr]پ@'ʦ<>h!s^ 9k",Sx<_ɫJ1C8S˲QZ'e@?ixKNx>XV`uiVw3%X2EcQ1=0|E[PoN_ZkǙ9\qsqP^y39v5V B) mmfYO#6qfOt ѽC/- '*sDhq t*'yh|V!\6Vqj?R^/l1J-viz{r{Ÿ .pڛ[j"i#ЊhTɱ{i9f_]*pKcAڥK1:| !0>Jy!7~=.5lxxMkSCєk43=[S m+06oGwhgΰu6I_3j ^"IIv6(0S,aN0K~_~uX97"a\="#PdACc6_}啊|yso8_1UXH?]_|5\w:K,Ee*W2N4uaezWu\Zf\EGZi 1A=lŝ[ 9 w9 e@23׽MҔH_%Pͮr[r{kު[jP5eC:{d90$6KF/`B T‘6S58ˆ& /^Uoٶ )+ZV}aR> 1%llau u^Jw9pbPr}XQ9ܳ^h[{^o6g5ulLCouD%t:K\rV;ƻ]l*xrg^]I%>}`Iv9H=D\,Km13"Cl,g`Ma&DF&w,ckPpQ]ǹRx]Շys!73P94xc&[~>yzJy?NRs Ӊ’ʳ떨ӻ%͹?-anYާiY{Qt}`^Fc £&W4ۣ>Ai2.sT\uU(t{3Ɋ$cr yzQw9}?k;GtE׏Xچv'PF}v12e':Y:D6Ғin>9]*IIϑgnS/PlۯŶ,Xz%(b; dsOOԐy]Ik3i;▽;㖺a[Fh7Ο!][HІ2nGcDE J=3\m@9ߞ:Kn :LB]b:5 :RX. .Es=m>$6zj)[F|sqM2oE$Q'ka`:Н"%s?ŨD90ģLe9a*@'`NLg^OnM;Ru E K/#GBzCleTH/A+5\gМF+<]2^yt=}B`FL9 dyKzq \ j* 6m'%tJg̩Jkє88UR!9<1k>_%Č Lc-WmfW܎LGoHXxx[ӳ ~Rc6/fݖ?2TaNs^k-v8fdpKzr7A̧, #Yp^x36Gq,ɩZ(Ô-g Kc8Gӄl.W`K@%p2䝅3$z$|zzjDm#M_^vb(yE+h⎁N}baya. K`n8%Onԋ!0E܀CE{ڈ{ݵv_uo7}vnfO92=%܆$BARoM`s#xbTi N-Mh.k6, :겷7; [c)yg8f4~N9n̛xU1 |Iq (ͱ's(Nn4// zAR KC@Tq,qcO3H|z}4 #b e7ap'ds3&)ɏx8a^G_iM?kf'GjF3^7AKӽ{?bȝ/3J{-66v'<6{yriq|N%;\Wa$ fsKl%RsXKVe ][ãut$huwτM@P:r,Uszy;npp~X3cb+Lޛo{g0i͠>o{?f8h4(ɇTpIG$9/ƀLa~I?f¼%銅+!v?qNfVc'a|$m1M!8gS`` 󧿴p|1d3–\Z>|h5wLvչbGŮo~ZKl`p3 7 (A\z({܉m-A>A ,Y/\,M `q㼻kWu l^;77=U3^?cw N d[ bei@ICCYM`yu[Z>ž9@WoCf쭋mH9,%VL<* )JzBO yܳ۳ Y!Hgkqm Ʃ'GkOEkhEm!HI=Ub0z!0c3CyzMT(KMƸAtft"<zxw:e/sc {bC3CXNڂ rK8+ ,])sU.V2@NNxV1`Jl߾aD1D\|Qw B9JS@j^oYlqecUDZjhJՊ4h˄d+/Bج} &,M(a`v 2vx &4<0ç;n^c$s`'Xё1V)p6>m)[hW. u@?Kk$z+à$n_h N+!03`+6.4@r8렌1A]qKs`ko}Rk!J(kO&2(7la^( }xmsžC:>IW 7ua$QFODLK,N3wZƗx%\K1囕ZLਰ^@.WR䫼|n"F+3םF%BE_s Am/d*/"b ';@3PM`@'<_cz[)х/IÊ`[ Yߧ\v Fg&7{0fn˰|5L[Dm~w:1Ѵp(Yѣ 7x "KXn!.5h!AqFѰs8 ǠZ )%J;x6g6iHɡ]gWuCB { Gᰥͺ%!te!Pd@pFi1Ԧx1]krh:Pkӿ\iiD B/'?(,lN=RftBbLQ3uB_NMj"k*^belBճSo]LҘSsńj嵇ܪ7pZFzMDMـ7*lssAuJvԙh)_s>j#zgtB5NDD$A)%5bMr;yP_ժ b!%Hpw6"/L@4d 0vf~ l-۸#(^ z}TEQ@ȲJI9nX0!Z[%iv/QEl x\sCM1JJYGUE=S WlW0x0bi2W Tw|#4Ep t%v:Cd+}2=W hr;p3h e!nFO'E(NtzYQPu"Z#R+n)EP[Du7\]򁬱@/9YF)"kЌ[ c_Vu*-Y ǒ47U6dڐ\ݔ~tF 4ҹhlTѕ,|bt9mea.(C !מYk~4/㢅vR{ box`#8[pPP(x1GWsY>`ɴ yX^ͷ*EvvJ֣5;T^qkrGr҉b&6$ewA<y؊q"i(THUlmm\m޲Z?b>~9ǭP`1UcOݴWM eʵf̅^ Q?PI_e!ӡɦ?{F*Ѹw1Ztki()4?yϰ҉)~ݤ0MVT:؀Ne:r΢v[| hIƀ. hQ:д-:ZkT&FP転@2=FfКˀ(9tUQ>+OPK6=@%ۀR;i5 [ZD ֕,\o hS>J;Clz--z jsTv2&RD!m%kgюޯ 胒#@Yi'+y+>k7@_P-rUW%먠oJQAT_{bI}@+5n!tK@yM@w ` =`C-2HQrXU[ 5KzOLΥ&KŵIbkBz]3+xv\/Y?ocͭ7ڿ/˝qIݥOۗ ^|׋_AD~\1?6psb{rwoQ=:;7 +P xYs6ݿNɕ$ybӮ&H١)XSx3"Aд +E68c(klsX`>a+; " AqR t'']X .k8lf/ L BkyN!Lť\'[TR .wHEI?.I+&V;F^F9{2qo!+&F% )(Y"e`$!DEdxZT`jyKnt^<0)=Obr,WQ+"_@`IW2-vȧVФ#s.)4T鴥]9u FyFƣx<{zӑ}8O Oӱv߮ev!v!Kj#2eIAl$șeɩ j\3O/VDk۷Qz(m9$rE*|~XRUvzRbWhR@:P#O,X>w{pSkr5ЉǶ +"SQyȕ )-,슭eM$"U^+rgˮnR%h^9c25yE~;GX7UWGđ0ޛyMY)4$`lCsYRxhp`s N:ȉ_n}_y^W>4K1-{WZ~{ِXqaT$~5 (t(w@!C!`ṱqxqݝtMTY(>gVv'c'quoI؋ jTٮچRb3*a"HS>rhiY$MbEަ zS "BxKG<[mY-P!zJ z~rdj lyٺ.72nKxs Qv- +8d4BUb>d2nt`K8XU2=sتKW& OUeŇut՝OSd]͑D0m`3# gr~({N뷿ѓPbFwh+Jݤ*r$d$n뉚dt[-6EN]'iR<o ˷C)mL؅CsiHUU0uȘw\g%%[>H:K^a"!t& ?~^\ %frz1KH 0Ąv}c-} =#MXe̋cQ7Bh]Yʈ޷'7o8$@"um8ۛe-Twmܐd)tT@ dt5. [O&TBfuK _nEwρ$=bΗHۥh]t~Et{2qgHVK;|;jf͒4luIMWp7Z܈`/$5wمj9r۴A.>ho@E6u1KHyfĆ8aoHޫ}.>xVCD{UxJ~thȪ{}*RU V^VE)a=N>&ޚ4xOÕkbbLd\ꉳUO+W!M-RP/gM!NQ{lC$#ֿĉFٰiTKQKfOXZb %v0H%3rGYI'֊,rJ3ՔW:Kuo%]O5[rM\xH|_]PH`v/x )o+sgEDӣ';6M XujF8WQ^UW9QğW*X>#nֺt!Gdk]Y{m~O4v;4s=osze8T Y&孏ҁҁ|I^q| eD$uS_PmvJe:5r/r9Ohe0-A*E  :.&sբǾC2>xI O#z04C/Ε.i ֿceh@U3)yǽ.nVxZs8_\=$7S$dZ ٹy[*Jr5>lll dkȭ_Z)ms=^t h9 < 7fqw|>7.RI@o~]Ґ1A84E2l-pxc O"AH1@/|opAY:?@S4FZAlX+oT^yI,FUR/ \j>0X 6{l'OZR]OKa| 7ѴQHN~O(6(P[-8w)לJ-l.' &Br:KdAa|<̋ћ`ڀO`(σ7FnAo~n .CcVbRJhM )0gF$ΩK `OG" _QT JOҺhIۿ5e#7f ]Y~Ƚ2186(tp_1g9 s<Ӿ0> !MV\eLP@= Q PKeDҰ;c,F,pKvN,*whB?R f{4ėÕH+ G|MdfYdkW>$Rip5士 x aP<ʉTWtyZT͚yBvk.ha h-\z Tfa(Y՝yx{t yzXۿ} OKx Lp-}ms!9'y/s} –! BqCM4Mzm3&mj_N0t`zI>[mr_a{Q]`\TVׅ̎l'lch+.Lb2Qj^qױ_#wjp}dN`aaq}dgUm p!Oee}&;s@f`*=t|&!jJiT1b8Cv0u'_'_t;b`tW FܣA8NÎF(__s]@~8eg7A+?tj[|U^r|R5:I*=Xʹvd+G(1ۜ.w,(PN@ST4]|Y6p0Xƍmĩ2WWY> o맋ik:}&J?Ώ:a>d4nqj*f^tH\7clQ1\Ef+;FBR7`~©FfͥTS-s'r/h %]|hZzƝjѱ0HڤCJ7u}M')$pVÈzu@5rodf2vWvkL%7~x=ksF+f:ERl+E'ȔW`I)a9%V"陞=t;:B{q8 ,:CxN^ CN@::<p>GA$h#dq{tӖ^ |~ ;?͐ JZD#Mt:i_$ u..^m^b(3H| wYG݇ٔaP:{#|(#L;jAg,'-^w>ոfA$EȟC-sߣ8A$ ]'aF6Jqv'3 , oa}@Z̏Ao@nzW?_\_U;@Ewջï7럵QxS qW#o К0iy0 H& I)H"̓.L3 ȣ]7a4-F0NW~+enx{FYП@h8IPZ`Y<4ȻQ0ݜ_z׃w\xU+͒a"h9h||JN)5z^j(DGb^nծE:0[xa݄lMh`>q6+C;)-x*ٙPU IS48ĜNlJq1؊~pp 1)1?JWH#4 {0"^nAjNj-\amPgChXK\tI)gtIa`??gxgWՂv b`b+3KesEw!3ubU4RL`0Fj,NӦҘ9|Ą9tv>w^!|jC=66ӱ6fȵY6Y5J}ě^ފO˱=~ljQ48 Mb-; |vDצG\{')u{͔J'3?˙觅GE4CSS|/-.V@ux&qI3D>o2v9HlfO`$J9MNqڥU+Yfd*PrVIAPMqPUT]7^CEM<-ezc<'N4ڴEV#h9̈́^G8;Gkzo۫_^x#u/{AqdETz /0fJ4^$Y2XП + C3GmcQJ$2Pj`."+Gufy-`b ShB߆4wFYq²5ݴ-9Phr$Ƭ6lg0tYlv5)S6kk1fī7b ֞zFں@ݵQBеh15FiEY&&S+5賜8R$krL~TRq-.g $КCDbF8s$8H!OO))eۀXI]GDVʤD?-:šԐ Y3+ע8pI0ݔb3fiҒ 9pYff"1ᆏ;vzG%\ui2M- /DuMRXvt?X\ܻ!Iܬ_ɬr!8 ;ojz*85Cs#]IevTB1pBDϭ פGPƥx! z_ףѼ0Oo9>.`#yp čFO qHh[pJ:$9>f=:`!+1cũL\,qQfy]#Հn-kcCo*qpq 7W܊Q8h#ȈUPBhƔV! TBIO:[*r$YqOEi AdL?8?9svqx#kb*bilH沎mKQKN5J. ꢠ`>G1C|Cd /c屚 1֦Xm^d)9vH 1I46a"(RQvhv5OLPl"zHL:,; <׃qP@6uw/7r6or(1nxnjl#e P|J P_r8X9x:xDSqry8H5rxWX.=%QAü $ِZ"oؒ"=%S\xi"xK2Co@9mϠ ^RWOl; *vœs"d$X[wʆaYpu90T֬n_RϞt'Đނ#^t=k'lumUc{kU0b~Z} əu3],>Ѹ+ố".Lx`nk̖sfўoP*OFIkf|sO~0s=@w8;9 4-f$([ ꮋ2Nz<(H6r]EWؔ>91e-0U4ŴVNE<aa 72= !7tFr}wހ+.u¾{ɽR-W2[; 6ړTU;^ kfEO|qj_#fΙ3񷸫qbʱG+炮@y̓Kp ԋ^A}IO@[7}OƗs7z[PoI0=q4_`]{1Ao0Uj[z٘K{aPJ7E˨2՛ NI̮ݬ뺎k\(NNT)yGΑ{%15о#K|^IءnxnzrW^oEl۪,\Hf(Pya̠`˖Xb=t ;$kq'uM#>MjrA~üA&G`.~{S ~>?yW?tu'ʹst;t/qp}g7=ʋب˸kKE6pE/ީU:ZAٴf?jpQR>Uux_-Ǥ"s7"[c3d@sIOR:]>T";c_/ț6ߋãOП /(^54\3X;&QedC@&ЕCW0)aS) ,RVs?*ҔJ&HUMzfaL"IRWY+ƣRd7b0R j3 )S}+i1R6?懗NnxPE^f9%VE=6RGזBrc<# ׸ڊ^7o(<Ȗ\&5ҨEn/*ajE^e(G3ZC!cDٌ.;"Clʾ11gkA!eb~sC#`<$#{EH5tSi]v`>O3+Ucrd{aH8llřCa #v~]6CEX짤-yW/JPiab>wEwGQ N;*<-yj}1]\h~2] <#WH?GH"m@3p%v<e^ NtK[ vYœ .vܺ6SvM=s,ŏ߳BdOF|a[0豭WW@1 J \EVMeUZ;^ޠۿ|Ev0P!/OW@oн"Ka+*I;ɷm.?Oav}W R\P\>3!J38кZ땀l7cQؖ;*mvKzbcd+lAjFf#t%l~͛oaAJGaLTS̛DG[^ø,XlmWa4E+<ܬl.-Tqn!:Z0$ %v!4z~ÜG~WUS!%9umdɇ\jr*Ъ+PK@j^Z\ɸ*910q…V ֑Յw𣧂p!F=h.ȬK"1jFRf@ o}rR}G 9GCDr/YUϞF<-Z fb'KK~.Y v`iM.ju^3¨hv(Wٛl#uw&K.&oapA9+/+ѥ#q8諪 4HܪU)/֗!~j&/XVNs+`Ry_M̥`1qk`ؼ)A +:5nJaI }FXH҆ h/ɥ\L-Μm{Z60HpB/fs?JQB,h(@g{i.x}m:}a'Ve/qcAʃuR.6>k,ʶ[*;˔J+_zR=Qtº ?,bBZ`Dsjp)gϯ.8iRF3XސOes}%NxL&"BR}O];PM1"oLIWgc׼o(u^Nk{g?Ua2POu|=:Qy+[IS:?/ZWֵ !ۡ򑭲I- ϴPs@դ#M-#0L fn02I898ϳ :GxFؠ$/`<f r-%Lk#CnBPefNEmp /y:1vOKχ-ui6#f!r6%(8qFLL߇ J8=HbgXW.gF$'_.\_o@:ss @S1o`-(M/u 6bTW&E9s[x'JzdFIjS:p'xuv+|r|W;E3(89t WZ .E Z<$Ym  ц`D\`.YY? 97 w}E`2&ן,.hR-ӎ7Yʵ*B lR*J RqaJBٲZ.L'3FyaŇ0d,;o܄kw;ddMfh?p0&^Å~sJnƜ@X y,"Fjٲ`KK AZhJF IZfȑ (X@=.(2wCJP&?pG w3TH8M2[Ͻx Ui$70ElLU\V6%Yq~nD&LY^s^wfMΉn *5{"ri~R,iQ,B_?^ǿ0ZBkp\Hxf0ةyV'lͰY4y  \sJ|~x:|oxR> C;;)ihpma8V!oxRԘd)O|w`>D$DM; a~]~lS\qf]JnFUQn^qS`Sߧĕc'ҚHqqX]' s;,4qK ǦjlM{[RU vplCpSY}g ~M5#l; HG6^=`hԾ,Rd,Tym՘Ō>743FHsC\CQ|@&Xq+cZ};}ԓ*=_EA%u[Q3A:Pg~"JrXR*gFZ↋rZLOCkxޙ +"`c(!Ky\o7'o0u:^Fhwx m{,$ɭ@`'t(qDّA)ߘ(Z$5+ 7ĸDy#ڐ=.x5هoa"]>_OG(2Q#aR<'MGԵmh|z6 [`xII14EcHW995vaFb׾EM ػiDYDWЊ(B 1PH'T{gW07}u֞elnO,q|]a9 (/_0 Iƛ,]o ̖QSj<: Q%3 @ײAMFy{8ʾf(n|6ian1%KBwO}uW`!r9,P1h9d&ǣ:5C&?;_BxUQoF~W>S1"d-`Vvw]U;6&JYBO6 OFm-W+FA女"I0{7;fcȳ,Os _Qi.2~`[1E;L5װT"xɔ,B(Tej!Q<bee3ʗZ7JJPdhwޑ*5(+\IILYBKҞBqóX)$k*7-Ҩfy2l\߂Oga̛;h;#|q- DeIR?䥇-kq%F|#+Kr $oTՎ$5ɋ%;n"gM {=!>aҫE[Р6\ AfGݙ=4aʳ-Y).疔eN vo{Vpp&g27NLRb噖KOWy+=hȀ*Xp.>~~g錃П-c =a,5 ֣1й-Ftk@sQZo9gQ+ݯȈ @0^M115?f"ݱ-l-3Kӥm3{4<7>F5B)}Dv: {޻%#ɋSSlW1$?Ҡз|/:_=_yO &=>Rh?3Tg,ʗ7.'N2\5ݺRmoZƠ4o^cD_]f&u:6#zxKMU HMuR/kxKMU HMuT/jdxIPNG  IHDR@@% bKGDIDATh՚[oE:HCNNlp-M+Ԫ~Tp$hA9\ !v|HlvC%w7nbv7TMf}+yOTM:|w ^;pd^rqp*}|jcWl ?X*~7NTM'F!jj'cg2ܬcozF8ٹiriI]޾^Ndm{nnD\#3;{s!|ێ^FEڶNT'TuWG镨כ,,fm˙ΐ*UϽučpB7FTCu JlW\Y['#ygӫ:D ^ɕe;w2A '+esYJQR ^z%*:)O,V%-WCT* Jw{ӫ!5kbb#ӡW\եѢW\xuuqp X("J>`L%hSEDLՁ{1QzSopӻQZ(t, z7 FޭKAIu"H ,Iz¯*!EKGnzoY(p@D1r;Az E!*=$/)zB<~,5MvڔLD Gѣ2Żfډ=_hhfv t=0|j 7I_29R]C=Z ~nxוr AEwG_n|S:n-47!HHoR(HY[GwUSu $gW>DsLJR4 M5%'csAf[mߣdBO3kk.xo;hj^ۻsdz@';{M|J_~R޽q fK9IENDB`6]x PNG  IHDR@@% bKGDIDATh՚[oEߘ?9v<323/!%{B$JC$VxƓ8r0Hᢺ=ƎGuxVU:1J_=|f^g3Ϥe%1ӂM'ċ/QKergg?rx{~kk˳7o @6H>eŋJx1kt-g =b:;Hq#KCq́ކӻ8Po,?ۻwD8\?׉ /U.6N#~)b0ɅJm<ʧf?˝;;2y0QϽ]r$Q}m0(s]W3=HҡAȃ;;@AQ1ҙ]ՁwX>MoO&=+Coҙ2ʟ=~)cjqlG'-&@m0."j>|TBeu(/PES-{pTB DonzP6'U$:j- S0}?R*#%+ w~\bV ,Ťh{_(aZܞ[WKohLףeR*F)F>"=y<= V1ܞ V=f Oopy+ Л=AFB%!"T.iR }t.Ԝ-TRz\iKIc%T|zQ*}R`-#}ne;JH缾*%=J -GVZlz׉$T|z$@I58]Bz< $.+wkkkZ8Y*D7ީS0x,W^P0y"=i 畷Z` Kn=@+o%`JoV[-z入Gy!<ۭ՗j- KCJH߻};+oT6 Fop@U W땷M jQ zweh#^eD+/fz;$I]m땷[M a8*rO2} Bgs>L40`+Ϗg%I76?uW^i" sG BɈ֍$w.X|e0O3O޻M 4ڞ~s{dv Coң$gڰ^k$A`De=V/^_on}vdFKp!H1˩֓#ܺuӏyK'rm4IENDB`ԳhxLSHMH-,)sS> ` xʱ0@ѝxyn.& 2AB#RҖ^a9+p=%  ߇nsQNqJ U/|7/a 6- OgGq٥"yЕWza6VT KSռmtFiY(I5xPK)MIUQpOK-J,I ,.M+QPJN,Q(I-.)OI,I4]xPK)MIUQr2K|SJuJRKSKs"z 9%MgշxPM+U,IURPP,IU0TQKD3qi 0xhxsPNG  IHDRPxbKGDAIDATXWoU33n)]"-l"jD#Dm 'cbbol|4cbT$ƈpr1m¶eanΒDݧ9}7g$PT!P-Dϫ⛐wё&xA A P(H}(grOl5!M C_Dm?y3"d>5?jE= k"`rzT6W&C5ZP 3 WF" :xZj$uMP2пHX:! ,Qj,Lc2ȃO5FƂnXypF$$)ĂfxʹVFԐAzjHْFږnZ[UGl zsjŴq1uTAZ^ª׃+ F1ޝpd6:0/ֶҌܱPdL%H.3ACPH,J"me,wtp?k\90zoצiÞo*TV2y~/^}u GT,ag3vȚGNe*ꏤC?9exAg3IJqwσ.k=WK7tfiE.eF_gas }9޷/c}oㅅP}o:$(w#g f;w:z~!{:BXmɥ9cz{X(fqKg5޿,Q'ҍq[z}݉uRwl:9Y֞Y{9H__i5EჂ C걦;D'2?sԀuĊE1\3rdO45&01+{.3)ŊlڵLR.6Mzb1S ]iT-6uRZ\UlZ}Zi+pGF]؜]զ#̏c7AS ڥUIZZw.0H俩P(L/ 9]DK!%'J@;YADn2v ؽ1& MY aӍ :NF4 vkIQżli`i涍{Q6TJLۺao6F JRS%Ҵx 76$KBȲ=ooxhθ=).2Q ui50 FkLLt _]Vڟn.XPJqj׆ņk^)+AV2x9a_84=sbwo:×{4G{D+?t7IlF!Ӂ>M|_~~Tץn+p?`9 2q&ya m9u=BZV{ͤRORI#U.X"8_)]mj,hi$@y*k劋ٖ%vRax4mmY6;, Ѽh q, O Jw5,z1T+tOCE?'1IDpOL "@M^PP7 oPpCDBĠ?l֨2IENDB`&oxPNG  IHDRPxbKGDIDATXWilTU}oЅil$bI0E%! l&cD" X$E@EB"(`TP kRJh ݦ돷 H]}D@DFDC!,$H ~7rnY >w) 1 ۘlB&~qLP8$8vLך!EkcL.=~Y,A D:$ 2B(!ttB\hh Bv0WfBr,Ȩ(9Ӑt&)@!# ڍ'XJ-}<'dntT$D4=ͩ[<7X-퍭__K8 %ӆZZ,DAL4mziZH^[.bZk:WTV{cWCLS^k',z iɁ(c4v fI`hmP]SMj!uf,B\DM @@nT]C78fM+6xSn–3_<9jEע}]<)ZaВ!}^YSd!&:'Ԃ;$ݞo3!>PJ9b[l(L;ۼw$*)*mֱ7$k9W +wwO4W8:ALU^=5ܒ?ŭn|p \24mW 6 є\1}9wm[ȷ rb_ef/btrR3M9G^>lBzϬ &kI\az;fDEL#]%+Qe'lRG֟Tbx`zt,mỻpcZQi™KtZo<~OツҦv>UŒV 4/ZD{y\ǑSlnl,31FJD",VB9YhnlT7'U߼ADJT"_k DQ֎jHO;z<tB)v_f1M02(.-(ܲjU<&- Ez8YDĝviY'H&!w OCk,{Ҿugh4Oͧoth%5a"" *m\oc~]3'n= vT[biW^d %s˻O ̞|5*P^؄QW}޺ˆ8q4x7179V.Wy .3εtti2kX`H }).Au購Zf9*LӰ3/fن 9:?7³qWw.-jDˡG WݿhJa5;SE^D oʚJ`A<8jmP@t7o ѵ,u\&s6.uZw/4ey-;.gr,حL($4\YQe"ՖXĂ+ ELY.aY'QV# UvV0004HOncbbb``s;o?! >}lɫ7hihϿEyNKמ`j8?jWL`e``sƙ *r}a``sqQ[J A99e27kB23sH 6i 3~o"-B LF:r.?:w!rzf``XvY{Z;4)>yǑm@e+wݧ>jH#++ˋNw?]?sX3?s ?~*=-5oiIbVNK֞螾P)9̌ɑ66{4Rڸ·ᅡ< I `|3=?afbba6h۬nHF=<0cO&C3tIENDB`?wqxPNG  IHDRPxbKGDIDATXW]lW=knvn\;I4Q6BPU> oE"UT@6׭J_^;;Rwws^&  I?`W5HY? .B>/.4 |{?$hB R ܆B%`(se uaDVӆ綳 S6,^XO |:Qmh7TF!pqh;\ٽG,jf%QXtTK2":E,@ P* яHX$+j,72/OMA+ʗ϶۟ S iHHR>i;-H!+%) մcEHQF14T0A~bibz>j9,^o'Rq! w*h1wmZu`lM ݱɍKOdn $GY0\C̮' A!Ah9(moɱp&s.`xr͹ӏ;\ܲ ;Fꃍ=]>PVzDg2}~vԚ689ܽj_EW%gq4+漀[K[_|W?`IqlʵLRnLzҫS^e!cT-?kN U zsm$61m6f'Yw-NU\iݖ؟:]Ֆ#6j毦󑛠)ҪΤ ,ZpUq^T(L!}/L>KV/'m"O!%J8Phѝ/'rȥdS"ZiM` Y6[:H2F9!^^ctV$АʗZXJ VỚdW/J,mUkBH19R4,%]T!M[Ŕ-fVNbh`JԄea깔3 %l˜°$^rDjkG~glzg8Sݙ 0Xf}aֹg7zo}`RNƳ z2RLU]: (ؑw^Ա_Τ/J#g3)K<{j iijT`^/~?{v>iHt`@.~jx/pve| qojr\Fxxygw^]s\Fx-f]z%I^RB\T`>5%5:/XjZQ; J肪ð}δbve!jaGz/XVg!jx= ;aADO\qG\2l;`*jzyLNAHqA@H ;/*. wj7K(P0ס!DBDjoh0IENDB`uWwxPNG  IHDRPxbKGD4IDATXWklTE>g-]B J PQTBTd(>P,! EABD0/ P*﷥ȣ@+iK;wI}D_3;3swsv~ _XHL!rd[Y7A1P@$@"$ ADk:@`֪ `ٍ_{kdf\w 1Xml+@_DyT7gNׇ挡>ed4Q`ðeG9G$5H L/@q$AXp6) ȱ>Zr4ٙ,G+KZ̏c,S"嫷z"HEBo'xNfR fCfD0VŠ6?%. [-$,]dkaq7a _>fЅ8Zn. eaÉcA պؑ QB`0 bXA$բHT\}PD4$^/eo{%#,fL9ESA0'٣*+s ˎ"wYFQpHs-WU]/&ʱ==9( 䱵c?x ˂+9흭_kѓ23{tofFcᖁ^Ow߁^iɡGP)VTGOC 2lc=ftlK_l$ͤ NUц&[ku]~dm(\=ĪuoJx7Rc!?W))'D3:^^NJ+w^w/UI:]0~\邍MMjzuLmZ!{Ÿ|{WI}sūsui+{[ɾi:ڋ5Jwe `쎲LƍTi7)).sCpB ի^tuESȔ[xM)ΖnT?;x:kC;kCHvG* iƎb\5O;|$ ؾ16jqk &9 lUW`X䝾dsUID0ʟӡCa]x* ( cGJi9УKTvk9}hW.[r5ap%+AfF6X_ªi[4Ӌz\$4nw?+hZvVG%pxڻu@`3ⱓҲ }zDGθVsQbCElؽRуpbCLk$zBjeMקfe\oRٯWdFf$еc=^A:⣑Ţ\8ٓ:}!ghᙂf]zE\+{m.X4% Wg?0ẴakYz62%n&OqEe&w=HQpѬe5fwۡ@ Hĸ"16=*`x:R1)]͇t?4qpL/^#Ks E@hZ۸jcΚ1NK#_fl-f""$B(\`I%f~,X@Q6 [c!ņ9jE Pyh։> Yւ;,n+ V,X jەnkX`#E\JtY།Kf ȢϿ ]戨 jSvBD Z/HXDˍ1r01!}CBb}&@IENDB`3JxWPNG  IHDRPxbKGD]IDATXWmL[U> ^hRUXdKLh25N8b ..cEh,FfiD_,ӍYXIR]Ki{{sQeۖ }|q8g::vםdkDtˍU+ aߡST5mN44Dޙ 2 , Kˠ#o$Yz\ cI#I:* 0&]U$I2"q?v+vפ=qEkB(`OBgSQAw>|[ًBGFpeܶ>"HDB #~*W!r>AQ.߬]F^"mi:rYsXD!=\I-x)a JǪΞޔ4]_IENDB`Z M! x sb``p b $lɱRlI. Ap品"՞.!o';5(׶74_f~V}k>blsA=rμJlr7wB%WgmvMdEŪ+Oj XMAͩ5pRyub3<]\9%4FL2x sb``p F $;6)$ow `@.!ogx5+z&_Zn廦\T;"`y68䟷8q(` ` "´Ҵk 1&ֈ /e;`nWa#ņ{e{qkﭺ'QꞆK;3W8rNMS7H(S8OvE>#'tg;ƆBqliɹ_WXf3OaT#/_:&Eтzx sb``p @$E^RlI. Ap品".!SE>$%)&=OLRl9{[&:530If[:CCÔ NelB^f1xsJh`Z*V x sb``p @$E^RlI. Ap品".!So= pXe'I/`QX١f~f1_bd kӍ}4;Y۾/'*nуǎcR OXK91xsJhax sb``p b $lɱRlI. Ap品"v.! d ME< )G؄w)d9<6Ҕt%"ᠺg$ F 応&1xsJh")x sb``p F $;6)$ow `@.!oor1 8\Zrq*'73Jsv̝x>`y=ŽOb[GDN|$_U>Hai̽ۏ _ϳ,@ij|ɏ#mİ浿*V2tljt#:&^9xcPNG  IHDRPغbKGDQIDATXOhe?M9-2/h40lC"2'caYċ<z|Rh Po`WAH:\0cKmØĸ5矖_ 4J6lDfGvoT:7^PM~RV /.oG2NoN!] 9Xc5 s20p)訂w< GN6 )Yu@ׁsR|yl'b'~a841}V 4hРAU LɈn'IENDB`R3xLPNG  IHDRPWbKGDhIDATX햭KA &~p6Ѧ(l^m6M/LA$pA fAA_9ܭ坙gv@ LՕtIےF+UIt'䞁wקǵ)8LMt76 $ݹ|مgȸ20WI{%J:4VK˒*/T[ IJ>$e\ KlfXoT$"s1'I\mf,෋i,`-{jߺefy3Og=^7W%q, \7KAW|-ēfV& Lg]y@k9.`IENDB`#x sb``p <@$7_.RlI. Ap品"z.!S [!!!qeIGMEyX.D v/;ٺΕ˞EfK @ ~.$ʻx sb``p <@$7_.RlI. Ap品".!S쯔dPyKuohݪo39 8D_[:WWx}W2xsJh&]"x"PNG  IHDRwSiCCPICC profile(};H@ƿjT !CuıVBЪyMGc⬫ >@]%1>fUѬ鶙I%\~UabBR,cN_.β9Ԃ8MAXeX֕= #}e4HaK!@FTa#NN '}#_$L r, ZũI/)z_c|;N>Wz_k7:Z.;\OdJ}S׼8}U88Jpwoir>߮b pHYs.#.#x?vtIME.DtEXtCommentCreated with GIMPW IDATcph8tIENDB`Oݾ"x/PNG  IHDRZPBbKGDIDATXߪ@LbET/ *utd]D!R!Pb@8sS{jXp%ah4`8Z[1f^(Pؑ#58!d2 Naum۞[Ώ;vA<pq!^$UU%Ir]cj ][y"ݝft(  |ܱ:BjZ7v]ڛ~W}y3 ~~h%wcl6,꺞G,k( R(yʒ%qqa!4 $c0 nJQ@V;>۶]UU!rLmVqz-"ByJǦiZ-IqDzo x71ƏqO<+QhIENDB`x{PNG  IHDRKlbKGD0IDAT8SJ@n+ 6B'. 8:{+ o?!6rXyrŕٽbaML472{ 2`@0(dYo ι3D|/i~vyQ-+p&JzQ.%4;ޥYoZH49?=>;9x!%exʋJ&BZu */*#μqZ\袈pvuBRJ&qr"=^?FKsZT(,Jf=2Ƭc}ߚ眷Eܝ?ckв,9C(>y%^jIENDB`69$xKPNG  IHDRKlbKGDiIDAT8͓n@Ǐ+wKOIh;0!q"}} > w1:Х`Pz[w|?Wp Z#t]XuQDQ^Ey+rR)) 0#JCc|!j\ji al1cfr^WUREۏiu`Suc$iYT= L3L!Щ?gȕYp'ɤO9ۖ+3PXFp!mUePuOi4YV{,{e 䗧iNY|e}|QA3 *0z.݂v^cIENDB`&EȏxzPNG  IHDRKlbKGD:IDAT8͓=0m CH>鑕$' is 蓂#D,P\ =vdo݈&KWf`>BsZ0z[!!A~Q}R(<)#]@lwPꦩg'4]P,[,$1\tM K%,HJ00k;9/'X c4,Z !z<7¶JJeY0lnAe?|:UE%@LBk:}Y,>cj5 F7Mo*p8r^A|jPIENDB`B"x%PNG  IHDRZPBbKGDIDATX혿KqRI̥+jjp)ZAȱI B)p !C$i;:J_ ɕDuw{/{~xxi3w@0X:::afg\Z6OOLLpw(ZCJH`ah  Z-\. Y?:"{{Ύ|zxxx'F%D <VyNO"Qs{ pxH2hY,B|TJ 15.iO#\]cc1Jc~,?af7i=Fi.:mKnn:ZMQL+@I*E*#N'ŧxyX^N=aeEj5h% tt"LN*꯯DlnvcˑHP, FI:!擓$ecPxЧq=IENDB`VܼxMn@ Em@@]d XKcG0ߔ{~ :z3PR_arpJr<^I\5eXJc_f6xb´m0cRIXmL]ok}֧}{|xx}y_5]y@ǐKs ٦KXgS [-#Ω07@rixTnF}+H5R ԇϦ46v6Oh x3CU{q7n>sϜ{ιWA¤ŏډFtp4arwxR s^ܢpT sFչ(QB9|@mp῅p%A* AzC!Tm.KAݡ͑'#XNhN/qA0W' qĒMI &=Ĕ|C>kTj;haFbZlzIRKe%aqU9q|_>i&y2X-Y˄CM)'jH1fXzd:,VK=juZPŒ4$"Fr{8”8ehm N>4#p鿻g+dUtZ՚|Vsmtu5NdI]?N^\˳)<6Gzud|2>Αj|2UEI>>"3Ho4^_WSgu~yM}#|% <4&`Q"3P߀ŧWq.''c|ޘ^^_6W:E H\*i+Q>Nfb"pg3-( ~K9b{k;͚ƶUVoj5hDв= ޽k ,fY̆8 },*!)nCuΖQQ8rLcvXK]x ߏsosO V@1` .Դٲyy\Q YU"L\5` B-Y[Wz>lfLf`5t  0"zQw`jJT qn*UٮnA+mLމlְ"ق^E&;ѻZնS\Y"[̣早%5ʵC.y^ i;!0`ElD6Deuuf,*Y\C51hd$\;s* FvX-W\fm;TJ^a,ˉ5#OdE`^A!ϠYKU )6?757pVshAgfxrR ر̜2YG6^Jr#Qz&]: dUJGCOSpkƀ3A/Im_+:,l쮬6RBӳj#Fx{j*[Ϫ%Zѩl (җo s'\-؁l[uj1!ǃ*Qlz(0=" -3؁e],OԼ`eR/z!d#lW77cpG%dA$}Cb{Mh%} ilQS%22aԮq@rI5U/eGsr s1+:G+.t !D` V~Bt&+Z:Z6NĤL dL~#2ovC?( r7 }]JP=H #*fg,B8TKp xE,fsL \ 7}&I)9o G;e]:ju@ޫ Ԝ8<5B83#$W.45&ε'$Bu@3pIu@hm % V'o$ ]XЊE3kKTXBlY9G"{ `TPV?Q@1\8ۦa#Se- >\ I*<ܭLކ&᛬;\$DCty P& %}!Ԗhy3I%(ltՒmЉb#1-RakVl+dyd!FyʸÝc^rU.q;Hz|/{O.;^9qUL;Opx85ߩ YZC3W064ۗy3ߥMTmydqYHCV%ǁkժ ?B02Q,s\`NKt /H+~KW:+#bbݔְ(l-0Ú:yV5JbIHWe|ָ: (+neT?D.9)7hO@<Ɖ2>ۡ;#q_<.UOLHdeRnZ Hip2W[jyMBՆR: 6n1CKm ۡjF9+SJ8juU@]'HH̥GKFPWԍTP}$a$!#3H<ר>J6&oIW,5]c;Y 9u fM_ZqC>%5Fz!Zc JTW^bCyXBϛ/\(.)XKӌ/^ħ@YL;/H/շAפsz^%VX)mǭ^ۥMOE+n>`41; BW9}erl)c$u QM0coLAGbZدoPB@". 1kZW~115sUb8o $ؤ ԨM&\ݑN,!<̬-}30.k :aO zW /Žr1Ct a=D<;>6#|u/rz>8mϑIlYH}ǁ&9 PYTP=N)NJFg [r#rN9cY4!CY SE KRaFa*k=/8ގ&t7ꢄ\/K3D*̖!Fӄ>, G6 5+WdqiX&5QՆуYAYP \0T B7,*zb-Uu=R6,cN @@@)5\(<ٝ; m+-vpfV=ZtG at՟)pJ,䏋CJL iqh?!X7:dC:9feR' k9ؙB;-=(@`6OG1%\|NgOW65wtdTAYUyCw>.`Pz5z:7tLUQ6n*6|5ܵ<{!4R!f4h7VƪLMʝ7]Vnb+=[f0VŎ8]GЧ2 ga]ZS~ ,٥_#Z h d& `#j$0;elg8@'B `ᣪqs:FP1 Q}o07%O_V=T_*eLYXm,/eo> >skWY_*T:[ Moӣ=jt^`C2 <^cNe~s-d.Ss;AMMɔK2d``4g L'w]uxtro=TSAAO y s$OAN T8G#?wek9qaM f܅nYS`];3=n;~3jjJGwwMF_[ XCluUe:}KIT3Em{5Hz {"3N*avd /.e\8 ;j`3~- <5AɈL`l]8gGH'vYiO;tcuk,ޠиd^NgS+*X`Qgrݓ4ٮ!Tz7&khlI(ws;61RefWHx<ǬV:7@ǁ.㝽=FR n8&{vC]pwGP`//v"aС7 \BK}%_cOLd~ χsp4b~k0>ya~:gBw[1 6qF5IY?lX Ѵ5K>)+.4K#8SJI\ŗf7c9dI6p(`6]NMe>鏅ζ??[xɗzr_wI wӐGBbWXupGe7\'YҔ|ŢҙS ,F,bh1E ^` 8> N} $t:dG4N($vU 9$R2I(K\:N7 ]JntPlYR;IBڐ8@zdgsvv6.V  ¬[ 3BJd5F8@ GC^2 u.@Z3M\Phb#( #Y8 1=# ׼_g?ٸ\di{q_/7hMzo]{^Z?I Qu i^:8.uwiiXN '̙!RRd58P0.gd$%u`OGڣ&~+L>,#p姦}R0DTRXY#y() hȆR U% 4~L[WlHum~6VS'|.N =y.KfE,%Y7)w^5v֠8amjT%̸VNUiHZS;&zp#'~BSٙQL4 : C:/W=[ՀGdw6l|e셇kEk{Cu'1Mj'YJTIGHe:C eV[Y2;&$VfMȋUoBV&qΐ ?6. LvB RHkdT7&R;iZtl+աj 松էw1muK="ksb)50d0>kŸ.Y_z%{ӑ/uu֐m8&]";AZcJ5I4+>v& 8#xKܴﲪj]zg37f6Ȗ2hI׳)8+YS!`Nшy˜*m`8׹@euUa@R]*  p- p }c90iaƎۙəQV*I#ޣ5;%k. 9^qn_Z!+ݒDO}@șW*ZžLWBȮu9!JRC`2InrM.Q&B0LKā/-ߖIp+0qzTtnry)4ycYE}i[B |Hm |?a~G9JV4 @L22c. r_uZP8V$kl !t(oA,2C]pr,M7t e<:•lt;%)G#̭?Cva%+PA|89lUcNyԂ%F62g $./HF]z@&垮,|aDbsIɤ2[-k} Lt)壼|$˅3|[9IBP0Џubम3GleS`ɛei@9J&Q~ȸrd[Fgjh%d\Aa](uipy{\!}@S;w"!W~O7X0FO7q'@@!f?mQ߫% .Wj@$ Ia@w=qKXM[Iž@GLot<2Q_3OM2J{a#= mZLH~oY>2SiTy/J/5IiC­TFe XE2y*?  ,^TBU1s낹SYjDG{?&\YptgW%3I3`J.ha-nҏ$b9ع tj̝ZZ(jS0}&6#O 9x3I$z&Ga۫xb&2+lq:8$jLj;y'˯gtܑp={r\M}:1roJg$鬮KFiF%%u/ozYw5۝VylZN˄7UE YXxi(2ubpHVRa h-8e<@~T/4h;?;GxC'q$/9IqgX`mBq>{\:>a#K|Hił/8!;!!.@ 1m{p63zJokʰ ^o͇cy\Ȳ~{^!4)1eQ-Qfّ1tcOɖ˳e9*a)WOY.G9'LRT,_9͸(NvfA'䘲=Ҏ ~vÃn7=vf>Gݻ'+G[PALU ftC~IpHO ?dRq cD1ǺX]u^X؀xXo6ߟbXb8RaðCma; BKDDT]þHIˆy>0{`ЃE!`Js奸CK)p׭B,W_:axc@JBO""!3BgJ<Agc7`+L*r`M!0h呵\UFIW)\EzO^\GX6gY| aV5 ~UUL^ ]d-](yWl2Î (`'R ;TdLR!6M+ĆFпU9hJ{NnE-DK?/7\zJeCk a{% wkV;z%6s5K5G9Sk](J$56 8 O7<( f~K{azzwAN/oZjS򜀖y ר`!~;C{㽆 ꟞JaFh:L0L(NŲz_k.) 3M>m[YomeNNx[ hUSgƶ@#|CJP2e8xb%aw2Cɲ吔MVW7+-j2^.nffOa4r\ EͅL| Wznk&Vd GT, ?MW).HM&6¸{D1z.<K| +9ru~һf&ZK=[59Y?cn!} +|OW'hԁkQ=jk0IJB@e-"ibP-AxZ8]nf^Ɍs}sTn:]buGo?jhW2^%d2o[8X$FTMr>?LR[tѣF]m|ۢ Ryy:\c%>mQOjskj(kXLsD?0zg%m@--XHn_`9gE=TeӧFobN\P(]TVU[ч,6rO=m*nJ,s1r[P!q@]+%qXjC%>8 TL@Py3k  -_'vtvxtOsօORDG|<9ݧӐHqY$יּZֺ.B!!s%$v5Ţf村M9~|tŶ ;^Zn`P#}gwOt_Jw8. X/\Rı2Tǚl?gY$34W_9ܔ u@n~fy~p`Îкڧz`{2~ &*xr۸_,eK;}jmĕLP$$qM, q$@XApzH;"=") ]=_yq_by]rvr7vA7.hJ;r3Ӿ6,G/%q#E<e'lE q=#6yG,#r?!NȡNA<vH3(2¹=0DċIG}VG%x\ D2I$;?>ﻜ~.C1/;] XBe$)p; ny {]Xߧ Mxݔ"?X7LXNN d4퐟F f_`< fB.oWf ^=y3_ل(6T#a z"hq,O4#td :`.j|!㣣?n|P?7UGya0^iR@Is7<e gB7}(s=F|dMLr [šoln!^6>\8|Ox,&O4Tnl |"ЬO1 xJ颀ء@hq?uF^㛋i0E2 _>cܺv!E1ܔT}zYB4,Nxn" ;cTp#ଅqSt?t-j{%K$37M!-xm@1ϒ3@=,Bَ0VzNdMMķ E:]&A0j<\l'am t)3M_0iGq@Jr>eqsS`;B`)2C&Qכfc 0yU|R9.4VE^30JV)YJmE %=㹁 Y1Rj_kz䴗$Ts+ŬǧI[2Uj(k{`Nx C-VFFwJ1) - 2&K[^bA5,x Xr_t)DMUw:0GSFhiθu 3-yQMrwR 6& :QDI@J`:pY١[|ؚ00ҸaԓВ2'N@,l"Xv\l)LB*[ ^&~:7`4Nז+˽U_PW Fޥt$\@3uem%ndvBD(@PX dr3I 1lO9өyR:?OaS|x>\ծSYٵ!8,?Alz ]fo0RNw+8Ł*N>r#5ʄTЀL! OR0:ȪIPKm5].? rIm mK9^L2`O-nYT7iJ#K:Bj1em,cE4MTmz$C? ?zSw\xJX"N:mY)k-DTRc4ZYLf_4dFuW"b E̓:otQzfM5*}ʼ"陀ˇzMz":S{nq /J6qkަ!2Kº6Ҧ,6ѳx@(ksz+kJ0$s/Qm;mb2\.l/% PH䗼Hg_Vwڣ#0҂ҶP*&-jn&_= % :Sn;t%%RR.46^eS^u^?p05bw;0n;욞Ԃ_ )[.vКv㫷d,sT[ ',S#βyQè9\.xKjڕ -Rtnwܚ=-+"@Bgy@w^jZ+n^Վ͠u]7'i&49bO+HJ׋Cy?W֗p6+`t\Cg::ͯHwHwv+I9:^huŒ?uxWn"G}+J!XXC$2,2Ƒi3S0-7ӳ=(ʿ{3\"KHM_N:ub؃~zְkAR!HAF~)U~N҅O? eRɢ[ tժc$Y̵?^DT2b eFHh9||hW[%Clu k\;(- 2D %E l˼ e_+ ;A$At\a7@xmVCtr=qu1Wh-VJC[1JDL<؀6 Viif1|n# 2L*32.]C$9Gsh>irhpbr?0}Hr|- {@4%Kmؠԁ-0Kk+~AGPYK! Z:ґ_lhغKXB=MFhE_uA2S[t-OT"d w~bh6 4뜀?Q8ӭ@Еc^5PDwg:̧ $KGz A0l=3a$t Bl眞=N\0K!Uip M(\DwhY ǐ'uuIP >[pٟWfҹTK (KD|JNB Ʉ:lLHeTꭂ' y3kl\0;߲OĮT>:J`[jJz{$ 2r`l$\ 7"vZl:r6y++$:hWTR_ƭ_FWT@eQh6MHn+w27^Ed]\k6vs81JLn7߼?kO)N|.K@0ĿXIK`}DC:ž? ']n8|LJp0agcvt6:Ng#:a q>Rx$ Hg  6DG,OqHS K_rIEQoo~+w2RFӘ'[}hzARLvA_`þ9R5]J"JU{,i?qa!_% 7<r%pPX ]1Ǭs90K:1PX&]j v+P@gJxNDt2 FSց,j"couVkI /Ѧ@Aȗ™%!P4Ykt(Y+@Y6Bzֱ>3!(\LE/gHtj!e4 b5B::sW(h|i(jy(2 s,@A8RFwHn-.Y}WP-S*-cʡÓ!7hs_D]5埱*2 [٘z: 4v2s3D@:92y[=1#JTE%&G0 ӄ&30B+h@IBft~zڥX+~k@qB~C DE#@;Ï -Lq.UT*Asف j*M~؟lm10,\D-EzEK@:e n!ű<L>9)BD6jF@NxkJ}Umj-72҉M.TBXu$~ʁdq^&ѺmRh!6Qb2l_dȱ)D EC `cuY]&zTۏ_"^N J63czUjhik0)zީ at'•O,v 0,ӏyY}u^A/m.kt*II; 3[ųvQ6c/Eҷn2:ǶP\48gLf#3쫄l!].$M \|K/JJ''qn?DKUQmOTTdz|1РGg1" le1v2^l"^Q}U7aҒI2}s"\MoZ| bsi,N+9P1ܮa:~<\4wio-[s Lɠ&_6MU73pcj7阥\;jGOx|6u@ClV-2@FA )gs¸%.xW7 Cjg+/V+#Np%A%jC>`>Co耊uk\:ҽ:=ڞ8Óu8Ʈաq[Ď\ԴeQ>DڃDp @8qWO=I*}$27ڔtdp87ٟ1MPe# ([R@VSHvdD~XMfkґp45erա.sy C-쩠œdp<^9<66RS&BG^l'Wp5lta:L~q@7͛!٧u,(χ) _*>k=u))̗RTSjڅ[Ie_كyPyM3X+4mtM[G`g ڀ6nnYI&8 TQꃪʊI'<= YѓtxBgLCYރ3&x}KƊ2*e}W)ya~D:U lBt&>1ձ,JBѹ٤E9.U2s2 "@Lf!2?^E="7'Ӕ+ә mQEe;;y 0&<lfGbEtmW,"M{X-)ԻJGVtRAZ#: <`<ߕ=6s8} k]%t#CAr+QgditbHSd)*W!( 4#L@=2EdZ+Ct ϑ)!)*R&Uڱ ؄v_]:yi ENT%['^F [NLfúPn`# 6`Ue 9fY,0* wkm:X嬁%A 1S`3.zL˂ ? >3۩CAE6l"+h!d2]hm:h,KTI^W߬rqR?͞2; ` hL۷u ^lxZyx+ƞF]Yn[Gj,t*hbLp-o*@we9RC~b,f&(6)=@Tk@|e3䘰_=rk{<tNn@UiqqXF3$$fhƉ2Q8g︰sѧ~^p2U,0 3.~:Fk%-vOԒ }K<Prhǯ|r!$C!X$PͧcRqa.!N%:; %hL{ dF!}*TSOPrH1g@@26R[s*!"TaT?"5e(f(^$4j3+ԤJ;R}ϫm.SG3IʳDb}]RҪ'i:&>-<.jgh6#=cT+*ƮԀs8+Ί-&gzd|+G4pXmض[nXIg=U zߢYzޙSͼ6#Xt" 궑4( ZHb2; +TtOsug8ooPklx`9]+![0;s~26`26 a8`o<^#s 7նQT5e avi{E3l8u^9G-Q?n&v7-xոNf M;d8xoFϫfhǍ@䀒(cgV|ِ5w-gA. WؖZȎcM;-"Įn!T(j.bjΑSDoCgT$ h꛸]jeBVZ,om`*!h>1 )xu)`șُL8iۉUd=]C`QGYmSr2h%yue`}p2 \İ̑Hf*6a˩g%U 'j>u=]i#c >66?ނFaOX|L;ʂ!nq8XՔ;r|r>q}Ҳ{zFQQj|@y*`{Y˙uS(>ͅWt]&$>("^ƞ )I^mf,@yxڝl4,[$BFQ?KfBu(>4 A ~t''Uwq ٚmՊSRtN'ڟ~~m~7~遹3(W!f>^/nŀo7y.7xŒ1o0 w [xힶ]/Ql&"EǗ_9qt:u=RI3xk8XW *\k(6 % رh#t蕬E5yR:E*JiO\ׂ}E5d$HaM@5P{ȫ[iQz aҝ55AzisN]Xv56IrEMՋ(4;[K?:GS5+ɕY"xY3z{>SY|7<$ha~05P%qԒ枧bѣ%1㰹Y?ZAڅU t;PY^6}\yAټ4FBGYlmaXwlmaker-0.8/.git/objects/info/0000755000175100017510000000000015203543557015667 5ustar runnerrunnerwlmaker-0.8/.git/description0000755000175100017510000000011115203543557015545 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8/.git/index0000644000175100017510000007266115203543557014351 0ustar runnerrunnerDIRC?jo8jo8cNDAPP{`f,.github/workflows/build-for-debian-forky.ymljo8ѵjo8ѵd ?x ~7 {V4.github/workflows/build-for-debian-trixie-wlr019.ymljo8ѵjo8ѵe z;Ty\4.github/workflows/build-for-debian-trixie-wlr020.ymljo8ѵjo8ѵf`ڗV`liCUg-.github/workflows/build-for-debian-trixie.ymljo8ѵjo8ѵg,T7N^2(.github/workflows/build-for-fedora41.ymljo8ѵjo8ѵh9EE;}Ŕl 5'.github/workflows/build-for-freebsd.ymljo8ѵjo8ѵiS =mPYaȦnL3.github/workflows/build-for-opensuse-tumbleweed.ymljo8ѵjo8ѵjǞ]ۦ钱L 蓜%.github/workflows/package-release.ymljo8 *jo8 *k 2v+M}"@ +!.github/workflows/publish-doc.ymljo8 *jo8 *l|: g,Q .gitignorejo8 *jo8 *m \&WKk67dhs& T .gitmodulesjo8 *jo8 *ny*Ojޘ0-VoCMakeLists.txtjo8 *jo8 *ov50d)&T CODE_OF_CONDUCT.mdjo8 *jo8 *pфS+2HCONTRIBUTING.mdjo8 *jo8 *q,]zJ>BL EZH5LICENSEjo8 *jo8 *r* =buImTQNOTICEjo8 *jo8 *s*0BXvjU0|6 README.mdjo8 *jo8 *t]?Tוh+K;U RELEASE.mdjo8 *jo8 *u ?D9c:ud@eSTYLE.mdjo8 *jo8 *wU-XTemplates/TEMPLATE.cjo8/jo8/xCKo׾U?Templates/TEMPLATE.hjo8/jo8/z 3.MɰvOZ(tapps/CMakeLists.txtjo8jo8{7m X1_bapps/example_toplevel.cjo8jo8} }Rʹ46[eaˁ apps/libwlclient/CMakeLists.txtjo8jo8~3Zkآz͝9E apps/libwlclient/client.cjo8jo82"7 Zl|jy&k%apps/libwlclient/dblbuf.cjo8jo8 QIXE0L)<apps/libwlclient/dblbuf.hjo8jo8,nCٰwx0?(apps/libwlclient/icon.cjo8jo8 zi);俌X|apps/libwlclient/icon.hjo8jo87G:@eQ: dapps/libwlclient/libwlclient.hjo8jo8K!\2g©_S[:tapps/libwlclient/xdg_toplevel.cjo8jo8 #@~s;Q&Iapps/libwlclient/xdg_toplevel.hjo8jo8B O,`QL ,^apps/primitives/CMakeLists.txtjo9سjo9س `A-C)e6$=apps/primitives/primitives.cjo9سjo9سvsu&x6!apps/primitives/primitives.hjo9سjo9س*05nŨ4b؛JTE!apps/primitives/segment_display.cjo9سjo9س kA[MIW\m^ !apps/primitives/segment_display.hjo9سjo9س|f+xx;Κ5%Z\)apps/primitives/segment_display_16x24.pngjo9سjo9سb>tγ +.V'apps/primitives/segment_display_6x8.pngjo9سjo9س_6pfn#M͒(apps/primitives/segment_display_7x10.pngjo9سjo9س?|9|yʕ?B&apps/primitives/segment_display_test.cjo9jo9M0&Ul'n aapps/wlm_graph_shared.cjo9jo9 = ~)Kf4$n dapps/wlm_graph_shared.hjo9jo9 Yv1-=̥1KX00apps/wlmbattery.cjo9jo9 2 $][G2&eapps/wlmclock.cjo9jo9 2_b8*$T8apps/wlmcpugraph.cjo9jo9 &$08kbnNo}apps/wlmeyes.cjo9jo9()5cg|"HbMuSAapps/wlmmemgraph.cjo9jo96ݲʧ oSI!z.apps/wlmnetgraph.cjo9jo9.g&mvnqJbuild-cscope-index.shjo9jo9 3odXMݙ*Wcmake/EmbedBinary.cmakejo98jo98 K\\jc=O+--~cmake/WaylandProtocol.cmakejo98jo98&P }x?Oɿcmake/embed.cmake.injo98jo98,_&#QZye3=gcmake/embed_binary.c.injo98jo98՟Qr0r=DU0)\@ݛcmake/embed_binary.h.injo98jo98œqTb} C]f dependenciesjo9+jo9+R߅ MUo'A, doc/BUILD.mdjo9.jo9. C? h[XLKP?doc/CMakeLists.txtjo9.jo9.\#'N/L]doc/Doxyfile.injo9.jo9.= doc/RUN.mdjo9.jo9. 4 F)a=YAdoc/commandline.mdjo9.jo9. ~n/Z9Mo, doc/config.mdjo9$jo9$! N8F9FOdoc/manual.cssjo9$jo9$"߶@+̲ doc/manual.mdjo9$jo9$#NEz{=8HShrWdoc/protocols.mdjo9$jo9$$oA<ǒiV/|Vdoc/root_menu.mdjo9$jo9$% P'{XlN93)a"doc/wlmaker-default-screenshot.pngjo9$jo9$&-~ZoE{kR&doc/wlmaker-header.pngjo9$jo9$( D5h?[w#etc/CMakeLists.txtjo90jo90)9c\DAR{!sHetc/Config.plistjo90jo90*l}L)}}#6Betc/ExampleConfig.plistjo90jo90+))nHaG fetc/HomeConfig.plistjo90jo90,*HqƐp Tetc/RootMenu.plistjo90jo90-J'^_dڑ."Yetc/RootMenuDebian.plist.injo90jo90.0o}Dೆ$ netc/State.plistjo93Ujo93U0LRj\>cYexamples/CMakeLists.txtjo93Ujo93U15.&}lś+M nexamples/README.mdjo93Ujo93U2_qTa(J>{# Kexamples/gtk-layer-shelljo97jo975 xtiZ@%,yinclude/backend/backend.hjo98)jo98)6 XFK$H~ p;include/backend/output.hjo98)jo98)7"a!Ldbv ;ginclude/backend/output_config.hjo98)jo98)8 dYx^6w$ArFjw| include/backend/output_manager.hjo98)jo98): y0g9Mx%zkinclude/toolkit/base.hjo9;njo9;n; ]> include/toolkit/bordered.hjo9;njo9;n< sAP;{ycFinclude/toolkit/box.hjo9;njo9;n= EP?x%т include/toolkit/buffer.hjo9;njo9;n> g] Isb+include/toolkit/button.hjo9;njo9;n? [^Bw7$1<&9&>include/toolkit/container.hjo9;njo9;n@ 6=(ѓ1mʸoFinclude/toolkit/dock.hjo9;njo9;nACnaŎy< 4include/toolkit/element.hjo9;njo9;nB /(hS_-~include/toolkit/fsm.hjo9;njo9;nC f Y^ (gsɾA,yinclude/toolkit/gfxbuf.hjo9;njo9;nD뼌M6$Qinclude/toolkit/image.hjo9;njo9;nE o[+T.a5Q include/toolkit/input.hjo9B,jo9B,FEdjӐ8ainclude/toolkit/layer.hjo9B,jo9B,G߉ ׭eC'sXinclude/toolkit/menu.hjo9B,jo9B,HrӸnBj~uVRinclude/toolkit/menu_item.hjo9B,jo9B,I+ױiQ3Y[bNy include/toolkit/output_tracker.hjo9B,jo9B,Ju6Rw5$ý~finclude/toolkit/style.hjo9B,jo9B,Rqc YGinclude/toolkit/surface.hjo9B,jo9B,S@30Q |@#Minclude/toolkit/test.hjo9B,jo9B,TLQ=i;<<`\dinclude/toolkit/tile.hjo9B,jo9B,U7֡Ih%']!iLB⇿ include/toolkit/titlebar_title.hjo9B,jo9B,X7Bkx'g lZinclude/toolkit/toolkit.hjo9B,jo9B,Yo_x3\+MÇainclude/toolkit/util.hjo9B,jo9B,Z8%2#]=VDieinclude/toolkit/window.hjo9Qڂjo9Qڂ[#b^ZLORiNAuinclude/toolkit/workspace.hjo9Qڂjo9Qڂ\WUk O1&&Ͽ@iwyu-mappings.impjo9Qڂjo9Qڂ]5/L1D/9Zlibcairo-fontconfig.suppjo9Qڂjo9Qڂ^G$RNV;share/CMakeLists.txtjo9Zjo9Zg 985i8"y̸share/Themes/Debian.plistjo9[jo9[h A8Sr fC盼2Ucپshare/Themes/DebianDouble.plistjo9[jo9[i B]H(W=9؏9[,ugshare/Themes/DebianLarger.plistjo9[jo9[j @ o"NZP6iR {share/Themes/Default.plistjo9[jo9[k1ȀIudh&share/org.wlmaker.wlmaker.metainfo.xmljo9[jo9[l S/3J,k 9y<share/wlmaker-48x48.pngjo9[jo9[mgc%Oyvx9hlzshare/wlmaker-64x64.pngjo9[jo9[nfR')pA^=~sq3#share/wlmaker.wlmbattery.desktop.injo9[jo9[qÞOH-ǥ{6]bd!share/wlmaker.wlmclock.desktop.injo9[jo9[rtܻc9rc$share/wlmaker.wlmcpugraph.desktop.injo9[jo9[svة=6Cƥhjy share/wlmaker.wlmeyes.desktop.injo9[jo9[t $Vy(HF$share/wlmaker.wlmmemgraph.desktop.injo9[jo9[u2mx'lhZ$share/wlmaker.wlmnetgraph.desktop.injo9ajo9ax'Ƒ)Nyc sw,share/wlmaker/icons/README.mdjo9djo9dy{zym^$ jX7an$share/wlmaker/icons/chrome-56x56.pngjo9djo9dz|n)1&share/wlmaker/icons/chromium-56x56.pngjo9djo9d{mDYk7S0N }Z"share/wlmaker/icons/clip-56x56.pngjo9djo9d| !H#Q_HbC AD 20s%share/wlmaker/icons/firefox-56x56.pngjo9djo9d} Fh0]&share/wlmaker/icons/terminal-56x56.pngjo9djo9d~ %*8[5QAR_^share/wlmbattery-64x64.pngjo9djo9d$G/Τ{6) 8זOshare/wlmclock-64x64.pngjo9djo9d `%Dmp3W~share/wlmcpugraph-64x64.pngjo9djo9d He>T!FΫ =share/wlmeyes-64x64.pngjo9djo9du~3Z,>/ZLshare/wlmmemgraph-64x64.pngjo9djo9dƺo;dM6/`share/wlmnetgraph-64x64.pngjo9djo9dNkp YȠWL\AplR|src/CMakeLists.txtjo9m(jo9m(𓣲Rᾨ@$B src/action.cjo9m(jo9m(aVF*_Jh] src/action.hjo9m(jo9m(,濬afyBBxlPbsrc/action_item.cjo9m(jo9m( \}Bf'*'=`-9src/action_item.hjo9p_.jo9p_.A,CyjaBsrc/backend/CMakeLists.txtjo9sjo9sn`1)~i7O<src/backend/backend.cjo9sjo9s.0,6?NDsrc/backend/output.cjo9sjo9soq~g'뻮0mL>MYsrc/backend/output_config.cjo9sjo9sO& 9dH}je؉src/backend/output_manager.cjo9sjo9s%qٮ<.A,csrc/background.cjo9sjo9sz(녅s \f*src/background.hjo9sjo9s1(q]ZQ@;C%src/backtrace.cjo9sjo9s41‡w3H & src/clip.cjo9sjo9s sGt#S src/clip.hjo9sjo9s-ּP?&ŝJ >6 src/config.cjo9sjo9sduZ/p/7BJM src/config.hjo9jo9GH.?TODGFg;k src/corner.cjo9jo9,{퀜~E src/dock.cjo9jo9{n8[۔قw src/dock.hjo9jo9O-6"L7Du8$ src/files.cjo9jo9 o݀IZ:W8sr src/files.hjo9jo9Qa4bqٜGv*H7src/icon_manager.cjo9jo9JJvO,}ksrc/icon_manager.hjo9jo9;gu@xk) src/idle.cjo9jo9 9&/*@S src/idle.hjo9jo9J4;Ҹsrc/toolkit/buffer.cjo9hjo9h;[QԩZޕ#%A7src/toolkit/button.cjo9hjo9h8!TV{9.%I7P9src/toolkit/container.cjo9hjo9h6 2ĒoDCxQsrc/toolkit/dock.cjo9hjo9h{bn V 5$T~jݶsrc/toolkit/element.cjo9hjo9hM,61Yö8F @src/toolkit/fsm.cjo9hjo9hht_9 Pd%]`src/toolkit/gfxbuf.cjo9hjo9hjQ&:src/toolkit/image.cjo9hjo9hXI|NuYsrc/toolkit/input.cjo9hjo9hF<1Ϭ 5wjL[Ӟ#<src/toolkit/layer.cjo9jo9j!\T290-%UcI͐src/toolkit/menu.cjo9jo9*F[-?#ͧjsrc/toolkit/menu_item.cjo9jo9?\T5 hJka, lsrc/toolkit/output_tracker.cjo9jo9F;}Wjn v^J?S3src/toolkit/panel.cjo9jo9 Xo2vLm%Dsrc/toolkit/popup.cjo9jo9@Erۑfsrc/toolkit/primitives.cjo9jo96OQCKhZ5 src/toolkit/rectangle.cjo9jo97֯O3V~TI-src/toolkit/resizebar.cjo9jo92Eo7cP"Wtests/data/menu-generate.plistjo9ljo9l6᧬E5! ׇ*e^Vtests/data/menu-include.plistjo9ljo9l7oD`?ҽ,tests/data/toolkit/menu_item_highlighted.pngjo9jo9 Hʽ;:FuR1tests/data/toolkit/menu_item_submenu_disabled.pngjo9jo9H>]iNNES>j1tests/data/toolkit/primitive_close_icon_large.pngjo9jo9&؇/hͳzg.:Ͻp~pahd ղ+tests/data/toolkit/title_button_blurred.pngjo92jo92!ʦ9 SۍKY#4tests/data/toolkit/title_button_focussed_pressed.pngjo92jo92"5TB[Zd 5tests/data/toolkit/title_button_focussed_released.pngjo92jo92#%rwy1ݱ93O6+a%tests/data/toolkit/title_focussed.pngjo92jo92$D };vh\1T1Z tool/item.cjo: yjo: y5 ☳% oΉ)C K tool/item.hjo: yjo: y6J?QJO$T tool/menu.cjo: yjo: y7';)93Ïk tool/menu.hjo: yjo: y8,Suj hyK6}xtool/wlmtool.cjo: yjo: y9p]79JNfXkԿ valgrind.mdTREEa319 15 bxϑBo³Udoc14 0 ɾi~aï+ etc7 0 x-0(s!p \src107 3 !v ݑ9xm;input9 0 1DgV-'fgbackend5 0 "dU*jjm ,nctoolkit35 0 C7ܖƟ @apps28 2 6Wk+I~ primitives9 0 @[H[g/UGC^libwlclient9 0 < ޣrk& !tool9 1 !ŷHđkNȖdesktop-parser3 0 q96p D}2&]cmake5 0 KM9g.eڀ~2Wshare28 2 aM[C#I&}B yq+subdir1 1 kS:AO-=[q Vwlmaker1 0 eWBZg 'Ax]34)F 'U&XNBEW[9M\-0#/8RP.K; VA57<2$ HC]6D: _ Oa`G%TS=L,@?JIQ "+H ɹo&@'{Co#df5ԓC%Yp̝c././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootwlmaker-0.8/.git/modules/submodules/libbase/objects/pack/pack-8d480dafbac9b96fa6268740ddd2277b436f23a8.packwlmaker-0.8/.git/modules/submodules/libbase/objects/pack/pack-8d480dafbac9b96fa6268740ddd2277b436f230000444000175100017510000051754315203543562031257 0ustar runnerrunnerPACKbEx}SɮF-ekf^d0idl3*(%U-TT=@`y>gY "e`c*,P@=q7\ tA(Ò@=Lʥ NcyWVmu;(,K9en=K5Sn׿-p[ø᣹M#y枻ҜNJij1B LD7C'|1q;}*_󺛇xAH9:y0 ُ83HE1ό՘Q /Vysq HmK^߷;1 jŚY5ܺ p8E X%x31̒$6zθ{ݖrQ+$3=/(qRxl{] o|h*'Ggow RT{zu'IBU9&fdT0ʰm)T9Wӡ 5-%9dni)X2<\9 /$)4g2w\e7u3pCWTee1guls}#5-*tv ve[Ik[USn yB:[}ےN$<*w #Hn~J?L! 82sJSRzC꽟wm}?|fzyPS2+Kus 2ҋ2s +-PH)N]@d&%%$ҷ&>%Է.[}kdr(^#,{ %XbXUd߽?zMϕAsԯOb\μ'#%PnM8;x31촜b{SLyhUԱUͬx340031QH*IM/M*Nѫa:-t۹ [raI!̼ ؙ O] Mv:q1M$3x}RMk1x䲗j7 Bpڀ -FϻFOr1W:4>fF*j`<|orHC>VHk{ ǗC%@ц X(D3-{(w.N;V(ѩhR)/eR+w#[2o hg>-nUF ! ~|+Hؔ%G|ť܄$qCd6%$H-=z:GV/,׬O9 T45ԾDzshgZ v}?BL6Ix7~ hZً=L:;|O)D;J7`9؃,Am?OO_%9.q2_z,9z -IxSMk0W jͰfB҅66,/l'lCuyiy* R>ˌ.2.&Z4 y˥z(#Ot1_>{+x30m'ځ" P\ףc$BLX=)NVXJHb6΍0zdJAŎʨC]l"|;jT#÷$"Id`iHe}iVaj铳݇$d) :t15${ \ޏsDLw7o譬إ7ք%wzX,iRfC{j)fqigVeHEu)kLH:ЧbUC#JJhN|fアǮ4Y^BI'xURAn0 DФz_P96e+KD5K*=ÙOyLxqR3ǥN'2zu?w,b8OQ\ p  Dw92#Kau2ʞ`(´n0-G[ MX&}J'PnAD=2nJ>[}%=H0W GfX|xȼE BE})'CK5- E4FfgWM Q,vuR,"1SݺUkCM܍ $B '}ry 75cWkr`y~/MOJfhчغxXn}W8X؋a)ÌD*ْK&&u1d]:ut>Y^dy__(>==題T/iӑmZ Zi?SQUT5IQQR,ꂃ}*:ɗ'T),IUɼwBC*I3dO54>nl;KF( ‚W؞{\K W3nDӸ=Ǧ4_fAwL=ghxC:;aaM -6f!~ n_8m][=o -jR8RQ)Bȇ@IeImhZ"$U:W}R؞77a?>GSxc()si~qoh$#$}cK c~7,O{ңlxB7~rهG%Mn8lGك.%xE+&8(c#p6Z9J{[ַ8{3chBÝؔ]Ǝٕ1u$6LtB(I>XuKYm=e|BɘOnNa(\kZ7Q`ySH>\'V5浵ĥ]w~/lB`+,:z9M|H Ť' #ڦI=)Gg5ѓ/i ͬ;'Fu; [.OpR N,f IsKnBaFu3\]&׮x1~C,ɝǂ,w93.DsֵGC# MsOAްw(`mÙS b95/܇y׃e*˂tP Xj]G>,507u8'=ك`oj$- !̙TYtZnz,$xCځ,;QT\:=R(I&`k }aiA5aе|(Jm]iz}w&,d\ZQUgeYldjE\9cB}T4Q7"O>zz{ Oő'DQeg(4d./0/^W 29KRfUL*87VD(4,=vE5"GIYƍ1&QŻmE[l(#hHQShlzN>(å1YEET 46Y|hѷ gT-(!UJ6w"vUlRC!e܁3YK #1w7o@̥mL`x Zi*hcޢB/U@nV:OR(՟KSX _Z_) B \E[]euJ~H*1(0Y敒p3-Zd$ >hӉg5윮=[-BYiYK;AP NH Pڿ'0ԽZÆB qfJX;W.Q?M2LUm mgaC?1KfF i:[VgZOa-$:{ Qڭ}*g~}ҟ:.Ab (V;(E`!htq 5|&3KZխg}tPѫK?Zơd DD}|Z\F.F/ײ](Y;U&Bc+>K!-3K\b&5'p0 ɾJC7KVDGܠid 2a4vR^jA;r?Ս-mU{T s%L=3B>S(S+ T,tY wJLJNøy.iEGoXEuA{]`2np{Δ\ NNKr8`ӣJ'Sk938? w nz |G)ay1x"o#mr3(l/o`Ҭ]Q}08^d(R- Bqg耧bl )G{T|MOKx86RC^'/twrn}s53U5_owZ .CSt:mͻ݆￾pW\BxSj1+| ޖBCRRBZiv=Ej$vإ%{ a1'jKFc#Xs8f$mv{у;¼'1/t1fq {rOGl/m9#9p'c6fZdexm$N`?Gyݺ` E"% ^izעGbJt9x}p7Go0 !0"z:r@heN{h+wh.1.&[ όnE&9#|cRǛdw O0t3'.iywQ++,/$OGw_wO:WQM@^ y}ڡC(5K1m>~Y(clYK2\Ajx 4`.#]kXzNz)pD`೭8X'M!rp 3g#)-{0;R/@ك2xb{Rr@ ^Eq;Na^a a8&-eC<`㛆d+K:yf\ҧ^]t;ici`'ph!:ᔾƉv'1~ >ߨt'x~t|C7-l(0/x1+ a RI_(3{Ƽ9?ăIMS%lޤ}90F7.!EIDodz,DpVREݏ"8[GS.Ò<T^X(6Lbs8^(Crp~9:^SJpT8.1.AtEcK14ɨ@%"#[݅@ 0#:Խ50i|dFŁAX,JCl0{־xOv2^tڃU|W~C]+go Ctiȯwn$Yy(_Dc^ ¹ z<2\GgVI R^/x l%`@0DW$O(|nx1$G R^\Ys`tY=9k p(}#gb I$0A]MzS\uYy8Ń7Qlk.Rz+uL`uAs\ ,tqd^sx@C9&Dt7W?n=uI\~`upٛ:ZWf$N P0!.Մy38,^| *޲RՋ}C3A; A|ğ8L,5үaN Kah $?; ƨ!((:Rx(^|'X,sͷuXۚX랱`\Tdݷ_S}Ϡ=];tneRH䬤8mlV1v"].HkQ謮;HݳY{vܹ19w"u +Wk LL9^jwf@BPhI-Pف?\&HCH*5a.).F$M V/ONۃ-͂8UQ9[NYQ^}`v:YQL4$ͱH_l`?]FAp=z};'댉slq͞]n<>1*r=PG,RpK@THYu?a2-n5&HѠdS9VV,[$eɇv[FhCCg, G3F>M^=\ۑa13z BYYû8ePĩA'#q*SJ*[Hr>tn] P))2\I~aJUbG[]ȑLjďU2%Ae9ıQpd$:4qb% 4qMRe. 0(ax YR#7\s.+ ='6 M>ln?p+񃿴߶Qi_z+'*B]bwGq}|;QUٙ{H&Ajj_XFzA 5dQ==P {x' pJ 6lfśV#r|D6o:QJ~{ |d%w6B\$7! f և=pj`:n܊Z poH \m)rx8XJ;'&_-/_b 7;|Drݵzͫˢ- $Y&<9^Er% *I_QK oŁ+NZϗ1x|id)M|=UJL6L8?8]i@S-̠CYM I(D6Is.}baRUd Htr q[Y5qvuNs޾@XPodr=b,Vo"v^':HԲOU5P.GM$,Qhg_ljwAUZAxǫc(ΦSƖڜ /HըOf!o̙A Yӄ9'h|| [4 坙P7tO媧#R(d[A;0Б F♽u68BQx CFנL3}} @P!EZ!̄W9*XC:IF^582_x0ِ2GC>d4A@!FWD:AN憝$X} /K("e?5K`JX[=>Crn8ىmĂ?K},Ⱦh„etCi9PSW|ݻ: ݡyPH2]D2,Nv S &8֑nQ@9Mq >,m +37m1xQ!mƎ|gYeA#)_p?9J4M?Z @ Rkm _2Ҷ[r׺Gl%|<|h7?vMcdA}!˹}4pd)N|y)@2 ٲɳZ\I!L Ԟ(ɍI)qqCbER:Dga#xΘ)LHUH}l10C!Ħ"" cmըI'_I`@8!.E@&h:΢! J@xV~08'muƚ!L s[t=NިEFL7$Jl( 3QzAMGXG8zPK 1`4"$q/ٰ֥ar% !#Ƞwˣq OeLHq˿FYHyV;GuR %$%uj> A-`p<*},k\(>hE54dX;rs Gt(q3qn1QV,#hx>JYϕgȟ`WKgGR4qAG 4aO(*aJ1/$(Z,2TC%?H Mx7O t;kfZ'vu~;\ ;'zq/✓\r #W՛9:4oPeZ{kKmzi39 _5϶+ߧ~+8Ðaz^^w@SRA9Dw tŃ0y R{Vq.,A<Q%c؆QVa ,hy5M {(`03DLNq|\CAscawyvԯW$--ǵ{ɍ#⅃v]m\!$؁hbhmv9ڈ-@$`T_BQӥN!43|<ޮ ߃!>$3J :wj$>ƃ:iFJfEU>8ˇ1USq.y$mI8dڮ՞8G*]ld66jSQ^dI858bzl(\Y|%0N4ϛkE!| \\jBwC'I*BrX~aR Y]6$5Z(FϤXd38Be!0 xb]:G:0/ϔGM e3";mޚFbC(b'r <T(N(eA_w'ti2z_ {8{# x-F* Iv[Y1>; 810Y!#qb-R p:,}De\Rد('W b@DsI\ ?|&y0e?&ܹQ8Dn0"Sߓ^ۇ \e`'T RKyܜ*b*&1//`ULqHH "r#ȺB g^%͏Q4GU*ˊǺpފ&;B0{>YD*M cXL@]"̥fPdG+ZL*l+|F1۸vŊi)\ Ty7ëE _0L⫊ Zm"Yl5:aA OKġiBY ކ#Z kx%JGd:@F <+>j*KZ3%X_%Ǫ˖;|@ 䀦8^C+)l:O#₶@3@k,rVl-,#b թ)ƀp~H]AYEg^"}B;;Udsqk2Lor$egKF254a3Jp$; t[Kٕت_AЦS#[Q#ǩXMPCyqlm?/ȓRr9S1VPs!*y&7>#lqoͫz۽\W6ΊEȁwl+1w`ֶzÿ:%[L4⍪%dm+5u K HKElXI}$eA鞪0wcW7lK F5s'^ZgnA8Y 8$x,LP)U\Sh[N*[Vp $!EoG nTBLF_ƇW|À[bC38K EHd9R3ȿQ466Q+3ÕTJܫXS 7Ӷޮk6k#mY/5PW{ aCSasUh" '۬ر`i_(>gsq~-Vk¼+a+G56g*JV[ҫte ҨePAU2oʍrwAU9YFWֱ 8V^۞݊6=f>tPZ7wq'VyYpCqJ1$N(q/e[11hk-#؜BAW%X?חYVbS,S l1+K&%G%݊`+麲Z-RnwuyݹMqMyث/qVZT'D L.e} d?EOAuw%{?fLW2m(sP$FZB~L6:T4E_|gվ86 02 .L@㫼QRχ~_V\8fBÎhΘNWlYd?K;SS4g| p1ZWvVU'_V٠/Ge*x_)'E&Z*rR˨ lR*_ZxMiU1vz*i0OB,m5j\+j8*#ƛZpäWpOc`.$NR"iZ r(A9ѿ†gQK6Q:Oqͫf?)jG.A (cuyJ%+X!WQѬܻ⮚lUq$㺫jL= J&X;֚(۽z*^̜ehyޑ&$-@hVðF:S2>[Cp4Ӡc͘ "_)yw-ዷQf#uU5i#=c'-]Wc+nEsM9O4D_S” {+SD1('2)no2C+{M:o1UnȤM:% )K,3ItAɱ2JkZ5㏟Կ(Wv_wi\Ju>Fڥx7GzGpJ92.*S78+Hǩ%8ƹiC\,;UWN:, ~?tkv'ol~_#mKn[G!v)1#ɖtTm;v9kvn﬿f#t=d,oR?P?WS'mkp,?c5[^)~]wmtD4*~R$ʛwJ\2L£3a10 f^j@c0M;N)dE%N}{i#SpI5͂):$"Gyi01s= i;Jn}>/^Ϣ0SĜ}H䳧6S*P3@y꥚gMoi6خlF9Z5:YajWt_QS æVԃHyehtdN"q($ D6Kr~7jM=|6rj.КgV엤l yόq^ٹ}n7v*;۪cT--hX IAE 5-Vt(?ؽQҞjƵ\g359NaNӗ醚WǞ6Loc7 ; :SR=Wx:H5Q!%,$<C2 :\RY}EUO|brx+5.Gc췊Ŕ}qw!2TuX!6a;0/1FHl + |FeTy33:J+$"}CWv$N*;Fg(Ͱ\Wy}nwrp SA<s5t'\Mp]|U5P^v ,d5juaRpgypP'T:jw^chW:hU+w$-b=4[l| +*uZMv*bneJcw_ul܄b4 @ e(y3M,Mn֕i!T ZYcıXʖ,{b9qnZp!'<)g?reZKS&nHr\p"˧"29Jy$ݧ52GیjW}ug3!r)3PTrHnJX*͌BFAeIųrΗRrR: ԝڜNW{R6evQްi{Pg%]Ʀ5NLś$3ġ~*"8t./)dLT]i|`M:3~(?'S1c 6ߞۣ's3!p3Kk>cvܥVs1j=k9w&~ ~<?uv2Sl19H Er.,lƳ_b'xoZdiox`t _o~&4Q뿆 o7>ݦ'o: bkm Иm.7}Vlp~6=kY׍l@zF*MKР߮Ue &b*(ZgX]4n0x\B%7[z=Ϩ;#X}zʔQ9dY‰Yra<Hf|F-HSͰc>d:r69؂(-+{YU)vR2VZ|uYJË"/r׳pzǡ%tOS'0^GQF2l1e@8"Q4ֈv!E3QK' 1T=ki__[h:|z;/ecɺc6)m|ھƿmMj'5tI'X[s'"s_ɥ| : =@̶ kmʇ/o/^|=`7fT0xۣ3zDȊ~k9Ql68+Q:P`xֆ jJyqWCe/UI@p۔XN)NyL'[} ƚֺsW>75 7⹳Z.&zfL#0QLjwu#Sp`YH?p4VϮ4w$ySYXɦn֛bAMteŦ1;^>o G=Li )u)fi#Q?*JY}Щdj8fILqINƁŎAD!FSSq۸,z#+0 e~(x@9)wަ]:#5QZyTc 8ٓ( e-t6:KsrW;'<8q< &ŐBt/Q$\h#LC8ߋed\jYNMUo@2j$"yD\g+*4}/b ;ho ΁Sqy-/ 0</,˵6+YO}>EM` P GvɄz-2iRZ[{h(AmsNf1T32KcAHM0%&Do[R|cL]ܼwN!%q `-MM^4nl4fOE]1s*ܷ᳗xѹniqiJH7qqJԍv}%>BjDGAe4*XB`M¥ǜԟn:Gʮx7v\Qi0 02߷GNn1KZAȨ9L<󷝋WA*cZ7ncAJeSiF5-=ݪZԚf;^*%6-3res.YϺcŃvøK))Sah:hS垢1P`e#n!ۤ.L_tWVx.Mh1Gf{A5ڔ; 6mFk/ ckm3, ;9\hٿv}%y)[0Km7a4K,rKDN|ڎVSқ0fS\yiYk씂`D섧Ƙ /0#ue..sХz?(Sbd\x zA-Rr )8NBs{mΐRx.T[F4TiAe2kvH$6eMM%MqjwH 7PSU!Ggڑet^6ll ږc3'Y(oVV‡CDI: nbKCwY=KPd> "ߜ]~W~ nrv&vjTx.Q`mP&rJ0"@θ`( O=(7P&"Ђɵģ7Nj|x@(폀Ъ@aUi_Kӄ%-(׀̉[EH#en͵XdΩ,[4.&'QTTR]8% p[Zg%ݨEzBtv;?ȳ.Pɡ26 i،Cٜ:uwͰ+:M o5m5qAdm]%t=l/ dsk'6Ic_D g!*V>TFc&g*uB ˤ2&w;y$n/7[IT7ul@匥;$~EAǸrⵘz'i{w Av;j˭Pӌű$9P*\niKA"VC1G ]yTMܩ@4 :`ARhKu>w(OD,@(Uuk ӫʣYGsJ7GA q?Khg-[:=-@<^Py!. P㠘ZrYqx8';O؊F^(xP!ߗ9+=֮ 58bmF"IrB ٷgCYߑe%_V5ۤqk"6{ I3rʋ9gK2 6t|/Fy׷׌SMxѡFbx'ܓfw4I%{ۖ~ Tr-?4,CR'Hf栾$PYƵZsxtta9臇! 抑.w"ZV]c ]S}ם!r, vh#9ODN|?[OD(SOc[GI{u|1Y(y]/ϋخ%2 GJjnRZhNj s%7X7!<2rVcrp # o|M1#O2[)j<{Nd}O![c5o>Eg"O7{i H&gJO;-fqB8M5;\rӲm]S@4Sr2SQQQdZG17 .Z.;6-Q[qq[>esh{-R ڴo:7yy9_DP%!Xg7okjC[[ƧN>=_%j:};lmR-IH؏gáfa)S?!`$Zsyaͨ,?& 8ό+Bs詣girm]<ȇ|ʮ$oxyW^b ۙTпt'Ztu>q9=ĉm4E-MQ؎a4ۈ::0iӥ]+(d3h-oW<^'^=#{dnq'cPi n ~Lfzs~B\gAwuvyz9\tjQD ΰVRaW/TҨRi2_#ۨGtI8)\~2 g: yxW wַU[ssE2}kR8B±l+ۢ$ "Na測.psiMt?Boߪp145,tcf{ MnZjB?rWgGuu kS]9|uw.O9FMk`N|Z@êC2ۺYΣڃ[:Jh npܬ}~ѽ-~x:ǥG`m7`uϏC<ʂ`Z4i&>Fu4:oVDKd`IP=31*ϟ==5fU't6MXY7]M$ "N6,D(jMU-9FnBB2nRq)ՈWatN0={x}/򥦅:TojjIfJjbnNǝ6jdd+I2Qpx|gKķ&=&ehP;}.PL)|3YIЦ4I5"S"77zX+Ȣ=4QjɪҲ @;ZO\)4WYl3qk֬ `\e,Bc/,T5LBz<md4p{7 b M#$ˏG>uz'{־x5SI0=l2M^yDռǧ'k*aD_eu35\vx'X}v$c N)Zu(jՀB|!Cqv]X:)06J ~{1lc VduyU-Y uO4d _$/o#e41 R8%= Wߎҍm`/7뤌_<]$YXnnF%  1;Y!.kɪSf V$ Hp0Jt|apeɛ潃ԊSZ8U\~Km{P1^?^Q l1V\O:$\h~)37`ygq3>kN}(AOHe_7:&Ij\Ԝ㊘ SL٧PGz-5? μ}MD8ۃQ "AX#+&&g?!;.w.j vςOo(ݛ/C[߲,FU4/9 7+Բ9<;F͇xDAPJoX%29HDbԒc(vF$ϧ8wua<}C0; f͗wBEP[,|gp ҎʼTfQsSh$@WĪCRΌzw8 צǽ(=H*LO :=!82ުㄷٳV!^=rzJa*܇K~R\(b')քP}J:t"t)ppa!/#_ǎX! 6w;TM9m^IvMM:틁s"z.sjEsܽfpFS6OOX2i[{:Sd҅L[:N*~y9R1狅ʵb[?RXЯh(Ro8 TX'-i2%2/ošZEsVT7"iLgQA+w~h' OoSU^dirSyT)$Lc%\y8ODa1'pvcoJ r~H8cp~ٕ0pgBdmR:X4/m\ics2&v>wQd^H~m6Nk 1z3؃@ &TBH1vEy#9NsF~^YuSrq JKfΡ!:R'%D(0lo[ x\OZ`(R\|ʼnG{;i9,i'TLok#S+OϞ[cIs Xk\Rͤ‘ qֶ?%QQ++Pu"yF\?FafGy1+q0E I ~`C;ESX WѓZ>Zp:\ >x'?a-/ؒ"F,sCZtO&@@cN!pGSdTd]I`{öI;a>"m15.mnm*l(!ǹSԞ*Bc(?BGF_Jɼ]5}vEfC6i#Mcs ѿbXG%Lc#)^ap 6\ ?u \7ȿZS3K&pXTq1Q3x}"[!\ /lU\abbv.Fvi:!X$čR%i.Om,#󤣏tepVtUE*F2xq/&7N6vr֜FB $qjMC;en'WNr] $& U!\LLKXjTFEƞkk,d,J1֜K<^z̨֊]`Q__mSz]խQ|ֽxVxlQRej4#&qs2+$Q(k4%41q~\܈A kޘ%n“\vF )j`@JT92TLa0YP|HZHQľq|M ܋)dF*˕& z)Al#k lKаhd'nكC%z^k"P}*!esY 2N]Hmf{z6sӥXAG'$budfuk"):m>3qNĞr y3G<ں6A9-F˃d1*"4f2`o!_ŇLUoH9[ + NJŨ6)ޟ sr[zSyF]WFϟ= ?1߷=`I77 0o+-蘣l=w<b@ˏn _o3#@S yšTʊpi10 DZ줉J _#L)'nlO`$ bK;= ct`S\<BzɯKN_a<wgqەܺ5.oi 8ɑqG(VŸ4ppesf<Ѧ=lDRD /H)^$E.,sPcG jP M|x/iU}#f)!mXh/&qz’C>&ԧHM&Sl02(d6!"]8 7W0_CC',&-57RX lz ` JH-yJblVpR=hPY6,]6F_}ml~$̤?a)LŔߺwg9=.va{ts :I4+Vsq|Źa`W5j|Tkj͉Tr' n 1"xQ ba|+\Sʛ($-pf&CbC ~*wK`FR _s"☂ `|H[~me&Lس./i|J9c47O{M\/U{px|;l< }b) $gx# XnAf|AuĜF;QG$ _6E˥E_ֈRrɈqt;5=lBEjRxעU 'u b2Ek79o&nˆ%[3MU V=8d)@K£*$a-N_CDXʯEݩ }<21xDWPz6H³Vih*B sQxVZiEXi/]+G#K]=)3Gdk(DO\lr~'9oKRh] ,oAǩui -I n*"OU?x PhAdh(zq o.fk=ϊ 6.}>]Lڀ +\I|p#Iu0r7_C"f}XJ d@ 3SD}Z]JYCiWⱥ35{G;n1#N̛sf9,t sMG1h_KY>4s3&ePMCm*#q4 ٬y|0\C@@f09]T6g>V \eq=V/r 6>'ު,J6TRO,*5QBŸmN9IXD3lV/V׳RGH{Xwlǔ"b@fzI2%]fKw-N5Yfk.緒<禑6`oaUZ;h1x of^ jY(c 7,u iʥdW4G:Ia'C<)oF΅ْ!:~CaE=wjK e)| D+.*q4vKڂ.B9b&RZ < djv#GW_+mM@-=ZϏ^oKu!U(lM#uU:_mDvD'Ǽ £%hXz)sFXE$9rl3/=>:[)50jB*oX3lkRp ^]Z4Z)x!GYLLVW=j_2dL<ʨ,F؂0]īGccgךˇ&iP#t"l2 ۛl%r 6k6v7%(x*UdVL`7,˔S-0TV8<̴K0YImckQ*碻& D'&3REӦzM1k a $oi/nvz,awY)ٳtfcD?FPEOs9 sqbS.޶/pag@OmlݬNx&)si[s#й->&MLhHh~9n2QP;6iM|FoZVs>X˔4E[s()S 'ytVf`VB G9:M=I9YW~ @pZ.b)qy*+*HQcWcˬ}y[u [ ƶ/Fa 2/ު)9mWuKxk0Jxfl̨R׭ @9e+}.x_B(@?C3K+4pp:6Kƹ[bC pWOm=<GwPu#$uM]0IGݐ6eZeM gl. ͔sKJ&p9JOk哗O&3mϞԀnqma) ,CqYJ /<.г<8zޣܻ<9{~58`տ˳Kvrc7>Z8RkUhF=ZQ]EC+Z 5̓,e$iu9;|յ#Ȩhe҈(/6ٔ[U /NSD77>:`X8%1'QT#ZQgaZ*-5!f(_y6ŵ k$}B ymzLyi)gU8AdSPZ⊍)gmy+׆PpWgiA3> |f3/gMҼV|#ru'9z cH YMލB;+U=k 8ҿAH H=4IZo.zwH[n$Ȯ$Ho-X*+_i0Mu{=ST3ۨ3osH?]¶i:}tNjZqQ9 8:5⛒-sGH-/FKc)nX"nۚPBb|D}KNR c la q.Sޙ&ŀk"}Go.[Yi Uނ)J9pJc7v`Z|q#V ;\|H$N%+Be/ώzب=SFk[>a.sPS ")s\mkv>;%(9V%5c,p2A9U4RDCIb^X *g$QS3i$ Ytb v;_tG}#Ht'?&+H#тPx+oyݝ?ojNtdEϩib%Y4bWڜ~%a@3 : t{Qqg/Ap ڮVf?D2WX~^DewR"[RT|.銐F{5c9ќF 6ItKj~eH>)aϖ,!߀x-2L1( i<7s!(XRϕK tNO21j95 XBG118 "5{4Ĩf:2VPRo-BVdxNidcg$oWvW]\黱Uj9T;zmmQAۂq>94M~afKX>C+XyY(2r,MyTig ʔ,VIaΎa La6<|:j`x5vL[(QjUOXŦM*ebR95>չtĈ ?9lz踟 2Ds+Q³Wᚴ:Lczb@l4Y9Mm[{/HRM 4*ic.ϓĀ NTpn-2Z&Fhѡ/2˲j i{NO6~7ܲq/:j$ gѹNӆNwٰ1&i -?8eJI1&Ԉc}#}v;vrSFk'r`2nuo9?0R7cjob~oM|MIHv&+:"בцOg{oK :VyĎA )4]sE1ARo פ5GH?5MgZgRڄuCâ,QtiwqցfI\Wt5vbMIbu?}=m ߊOtWҵ.$a:;RKX t+5JIG|RYG0q]UN[:YN[jt+>x=jǮtoEmUc{>uje oyum?'[V-|Cp%l*Ng4hKqmh#LKi&EU- "cfP"Wb).(΅r7BW >Ph=Fy3PFz+Ks1!?|+jԕ;0CnS9 lGaѴTG7^$MBLJOGu.Id꬧[Ʌِ*^&Y|qms}MG'rK,ڮm +4`!Pz(D[!"r"J )]Jqcr -ig߮v޼ۿ"2t +(IƽA%/g9 e(qļEkrsځ=64"g%هq+ο`,Q JSCG)7)!ъpɂ)r9s:uUVB·@mp0Rkƛ4r]6@ N%uLla?m(-pfqSb|ԓI8RmE mu qkWO\_^F*-vH\v-7U\ƫdٗ婬=v+{mC9DMώE,j2P'P7t_`;01MTVOWRUF q;ܾ8K) AGIgHpil[sAl1?Wh+h'Ѱs6fe-U)jȢ@l qw}{ws-~]SW j.T;=rnk3' M#Z%;1Ie['dWtbtVTc_^>[i7X{!pKUAUB֌۝N>hxΔcX/cO+Mzw P  8s+lv[z!S@me#Ic$핐%I ZY/^Ao i ! RcWҴ$arxùxg,я71ɪG3/eFGqB_RWY*;w!#J N,`E+;U<׍+VN$ٹe \hЄvGI%/i;aA:d AFu`R+'3=6L\Z]^&Qy|x[<'$ְ[ )GZoUkM[p4IY3htqw3r!8#A^ ?Pv;Q|lpcl(?jkZ Q9|RoRyH\1=qtLH^cBRs g' NPn*-pc4 (KҘp#-1bZYdąn`x8[ dV9 X[BA[/eΰœⓋW#$Sc Wh7; $AP|#:Tp~*܊܅r8kiՃ&Wbi|66|y6yaX唃H!K4LouNՓ/ı0HVRT)h:Fj'2㤓 ND%"Ѩ'Ju"9S6/'\Q6 pHd6 ~J"KQMm7n;h#ZLjDe}5ie^f^˰H5腍l(uD>[_`FWPhd!R+K:"7@)AZV"r75wTXBJ v?V>f+kF/2DAfmprtTiǖȏx4`YC ʾo48U+lv8" ɭquNZMY[@(j_`)_DVOˎq-$obn.5?B c::e[VAn%L T"ƹd0)Y"(Ifb<2Aŗ5sgJ2%S/I 6}ԔAddITgu>Iy=@ ]'MTũezٔgTe Y V/s0#Pwa_fM[8"5P":LflȪ[w̤ս$ Gg~ UjlmDL;1 `D*虘80O[5Q*Ұ +s UƑF:ag|흹2b1]hSY<<ъӹJ$ΦyI۪IgD1&cةu|K͎ ^5@7:aRcK 88l]%;rmӚyEjf"Vׯ̀ ՗ vKR͍P_ad.1)X1D[(>C#S 7Ī|t{?3]B΁)wv#jL_5lqn N}:CC6ժFo; tr)~4jVQ+bӁړlȦ7_ ΔԟJ;jҶ]1 7yc`HKa>]Rušq?ˉs/q?[r=?,u07bu^ZX~_h~K'ф+U6&MDsRpM."{ `z\[z?]߽\|B~u^N~Y_so@6t@73NOB˝1<I䲝fjExPGʮT|ǠwՒ4c6Awa6Boy̗ZcsmhJЮ `ՙѹ-!Q ъ)Gkm&CW*QӹF:-l>ƌ6F]8WWXWϽG/no?ߠ iZz/o>%.(kiBB>F:Zr $;o)D_gh2!Nz..L@A.uԦ: @;vv7"zVp6/i?' (Nr@!c嶋hfqZݨ ݐM.r4F a|_PM<½kϤ!o9e/UBzG>HgO*.("#\xtn:hbBc" j7m<\<|'d{ I8zVlQsv4*>c% H`8!Qc2Zc Uy72?3ƒSDB,4_Y_|vTjsӅtD2a"J4Mjdl9l5]1wZeNU;V#J{c(}j_G#ٺ$u]ѡdWifEvO;G4HB;  !I|;kF Th>OR ֆp*Ӓx}uR s{#i f1 @-g,kPu*}$lЉ,kix}Vr6}Wĝƽ8YT6$'I04 =\ߎ~flOF ~2r;sJ_! %jzóe+Qe?fP+R@qi|( ZPH&{oqm9K]Ĭ$Mʖ'&;a 3)D: 9tE=,e>g) Pp ]s5[AE$ԂO,KՑh}*!܋K,yN!T`r/<žZ x- { nEqhG[od:ngwSxXs7lv_܈Flɛ&jmaz0*s,fdofÙSF~kh߻vT6{oWc<[ `Lф* X[ȗ-t~8j4t O9{|>LA4?[3a|&{b@[06gL\\؇-j+F)Eǁ\tu-%s :iw$WYev3gӕB>v5_L$j2UOZӛ!ȍDc%JbW"%z+JmT:uM1/G8_.K!21j/=CkRG &HHU@0!Vnw/&$4=X= 5ŷ|06G}aurlu QK)g˒Uj]aeH/CX zG݃agﰠlf2MLJi0]prD-vqWcA+R֜ڋ;8pe ̜ v*CO*}<  I_{rx31̤TV⩻lTgfwh ɭ\W2)*M*(ON-Ǿi6<{ocT]I*8&Kƅ,94iuo[Qy,OY3^ں["hڒ-Bgγ8db^t VeYS*ac@=z%-5sq އ`xYmo6_qp?,5;H-07q[cSvbZmbIɒ%q ID#zmt[৹90,+LrՊEA("-T? 4Di #%VmE" &0O#_ 절q_8^{ᥓ GFz ҆-wS)XC8 QI(Xs4v>9Lc&EA`9:QwͦÌX.S?Occ\)THvG>!@,-$o8TF Asq#5Tð`܄7`ltno{ɠ?[] &<]#\h%"@7Aۘ غ1hE2"$DIECjFjB$eFոPBVp`q pQGQp~~uZbX:Y#RDչW"ԮE$V@ZE Q8+b$1etŠDTMxj'#R3% >?G?<ƃAbCq&B)&e,A9H)ș[F )Yķ2.SټR Y Oˢf0+]&);p|~:_d8Kvz1z= /Fpu?`.G|%jbhJ1#k(.Jl"dr!WR%ΐMkK/w,Ã9 Ϡ ƓM=q*|,r&ߤX+T1fYu3$H7a̵`Q (ˎO٧g"sz5vFIh/osoq d SQ© YX0b3.yD\ `TԜXș,y]h&H>/Yq3XyZ}lNd)גvV@HRvKD "R1 Cc8~#6' ĊSE KgF@ Mء j32\NтuѽXFD`Z$10p=IꦤlpnSykVɑZ!_@"4} .ZݞeV$pMЀI`BafuAbT]/̙@V(!,wivnk $OZ=C ӷk"Xa3^p*8! XpoR.EB䂹k5eA:P1zAbj5Ú0fwX ]v uwXsǼxRSu VPVB˻QV\!'Bά !T 1[)T ,@vV3FRPVlYlQkfU+ J:ۄ0_r8ay^} úGز\?|&hcN"PJdCYkCr5A&EVUݸg*# ׊O"̋.*k c%KC ᥃V ~ 5gmK[X@߆5G Yh7v V@.xf5ԅ4}:r˴ɋ]$6)֊yeEW[0_T.P 9uØ. ӰcuKK$p2p@M dT䠚ֽ#TS2떠өzHh@7k3fט6LG_*{=5cGk7"ɁTrJeV&3).g[H↏2e;&RF2Bg12ܼ9 77ލ.?(Gs sY+l@2=UCB5)eWe_ON4zqikwI?䍎\r>d1vO+v:d!Wy,B WPk-YKPE:ڨS%e!`+z^QjMB #Sm|mu`;36Yrk&vUBJ(2.RpGDb)rxFgEkԵB/ 75}ڻW"qV\~^\ҵ݃hdzHNݫm܎˾4,bvj}hKzkkOm@|?AXtټT ÀG}48 'UNf[N};",kBQQm=у)'W2L.GVc~࡫>DhmTiC{w]Xkwjuii償[F:39?Xfgܲp=gqrwC(=ףF3jj/U,~r n&V(S h6xi}4~9帯ofOT/ Z DއahZo6Ori!&m,4=, _":$bLCHX ZUOҬ<؃]v }4q^mt5e`:ڕeA hD|f{ B`NPQxvQA=ɴBCv3-x䍼s0AcU@„KJRT^px?fJ0!b T>94!fwuܝfOfUJ:AiaIȔRd6j@CMdDs6S~,~ڲZxn)UO]m٠g|YKnCAVK3U16C?ә߱N-}!DF1nK‘! ==3w'cEjtԢTw@IʴLm}lVkg㖊_Ҫ@|qlHƠ\E[[mQdV*MVUx[sb`pL&#&LcHhBRmI:!$$Nw?;p|_:5؇Om6sܳBzf< Ax`W0a!+m\"1=*gsAO^{ϛ!Ӿ}j kĜCg&PoZ3Bs} PW.mqE`Wz6LKlLo;6sM+[% =`8 T:PLυ7jj3m f'vNhp\/+`>rd gw\davg-X6D"?[*c.{#7Z1|]\/~v2?NZQ]H_IlڤJnI8ϱ@BϡM{0w䁋σICdТm{a &PA."ԩ=b q:| o|0|c%.uM'qE{^׆e8od5 (ZӁgИfLF.,KNIm9wI`\, 9} )|~`ڰPpthmQ!K _PW0q<6$L11N4n)RO}hŸ shaWM͠F-FoѠ$m)Wƫ8NUGyG㇋L̕H\>Eܽz;Sl )&F)sLo#r.$]?HݵPUsws^:@,y@v LP#Ԁ!@}Ih:14p1ě"𿃋n62$!ٞ9ߐC{qdH0Z2'Thg#eU 7 Rs3]"D"-g<D% Lf Abs,7D l8YvAwإY9VbE{woP˻5favFI&yw%'4MkUo[GKpX"VJa:K϶ r(>3|Rg^6kFkм)5#I#w&Yt஠ZHɃӈKDY$*lBٺI'W:T,$e~`mBv]9Za3(5к g[@όIr tSkld},1+]PYV;[Y7R=yeȓ…a 8(fF-:]? ]<=lWo~ԩfЇ8ڴ lfWw%<9ɹ\sbGq?h|wn^`;k^&,7hc#H'MTY ^:u LPI$6Wwx>K}з]\ m]Y8}GvOVD~"Dh\:CjjP+8_0,T5sXiWīR^E+7bI{Xxݸ1f=XV h8%kRVkx+o G7ímNJ<ڦ# {d[&!.MƝЏ~:zvp*wdKכѱBS2@fz(ü)Uu披 #VnÕh w⺱9s {h_ЗU LwAS:S^ÂN;V"i"n?6$5K("=-,S ,K6~X#Gkh1`L6=߹!l-G4-;߽IQ7[_#}!o/۫B]ϧuJ y4?3lX{W07R{Zk>7|׋қ/&Fr#k)}9N\Wk(,4zŜuW\wAonnN&Nvo.fX$+Rh`9>Lf Bt"\%iF$ ;iiFUbjmY/rMg~TD<(cQ"WZ^ -)2E JQ`ۈmH"z;V@v'/C. ȤF)p v juz8gS5(o)]nhQĖhg6 $#F>h S}-i9,؇ e&]x=\I>..Fd| pv>{3Og-fd݅zm)ȕ<~eB"IHDUVVAU&421&1Ȅa"E3Dtxn<)921\Sx)e:X1GEZj@ _:x`8lR9°%H̶$ ,z+taٝ4Nk2ȩbaBt?E'ȯ)EIDi/dv5VWRZ8 (:tMQzR8KSDشK&ɏ*Ʋdan[NlkXXIGc_+O;_]ޖyd(*h f#aਾ"kf#cC|GTOsjpҒ3r:Fa TlXH- /. ;ʊ9>U~gw>'FzވîG'%DMv|ƪ؅%4L:sI֌i ֞DXD>nK$Ƈ*-Uܲij~?l<:)GJ)4XH/W(]ۂ;l!)Ǖʝ-Jmz0qM9p0_o܈(Օ1$35oW&E/ꨍ8lÖ^LD^;wl1'Ceq@O Tg1ƾa-{IJuFjG=kU$rr5WRS-ޞ[3!ԈoЌ* 22Xv-"l}y'rdAP( 1le/5ȩϏĵ01OHI]y]-չ}Mbs\'gV6A9H-D3#i6E4"İex F 06#} o& YKh; _E-ʐ*Uh­OɌ~"vEZ۝<0q=PNRV%9JcBe}@;D,~8xb{"yS3ۗ3[5t_U+0Pjb!$;lZb<;Bcod[8l-n_veπ9SR3Q L@h[X1צ PbiOj0|Hovq5)P(xsc?TK tfӽ _Bp{A zcڀ UJҬs1>*1pdž @zmLݼv늎tᕽ]̓iM/q[4S+5E@( ,Qh=VK#Ff+42L&eҚ($̏ ʏafqr釫`b ׋pEHG5' E}9)YJ:" )552)V( uF !Ôr#0ݧ ot.s!3'Y|ǣs,+|fgy0#F*4*w J6CxݘڷdŭF=C`e@a~YeV߆t67 uĦ԰q(U"o\ != 3$Vջ +?:__jv΅jM{Ecve֍0,SV8;BӋ {8?=ͬRE)Cvlu/Ѵȸ/K,@bY(-퇘`|÷>eW/ըrV,#!/J+a䴗םK~g>MhGIMMZ??~f:oyt*g͉NU7j y|Պگ{b:Mt- .b6ׅtq.ϩ84ַjO!6\| ~[A䥁stwxM˷9 +䡰xVmoFί/`|J^TBh5uw/%33zuzq[V2BSoxSd:]Fx*rX~/pgC5N{*T0RBJn诰oL .bS;PBj0<>[ }GRa+&S0oR`.:ta kcn۾uvϸh<ĸX)v@P![ udoiZ݊&&rZJ~TL(h=xmz '>dngדvFop̮{.ʃqMɭ0뛇X9%c W2:k~DQEcr/ KJB佴fKGuq"u&WdXO'WTؿ/$R 4Dؐ\j-75&[y Rÿ>Z*ڣ6|m+׭.a6b.FF%4~| /Gi" 9 rH#r+Fr_[@ݠz{8c 8sv<7(ЪZf$iRnEܟRSJ'bheKJ5rpv.hqsex!~gk픤,>.QK htfÀRnj~<y>Owwo@g 2,E'z7zG7^oZ AiN8FQZ&x ٴ2<:_ACu9x)wt:u-|5 аZMAlԥ{ƨ(wLЦtVBI |0hy(.!iVP0h24 .v[ ox[M9 .#0QD*B, ??LdJs)khӂlsA"2! bgK p\&1E`ɤnɐ3rQu!&0&j:-3حf|}"fZb\5 " |dbR?W $+ h ggC 2_` | 4|f2\\ '+ n>Û0 D,Ah\>0%"HZrM.@!ء-HQG8獆Иkj4'Gk.X$nA8Q(T~&`4IjmG^1߯|ˈu-#Y'_7ӞK)@LF?G[kBx0ct! ׷v}d~oiHy9aĬ<ڹt(K(TLQUNm۵CK3i%e@!RR3 I(B`Q 3qL% 3 Ia1[2a4ޓCk=h`EMjlc(=92wy+U0/dφ }^KRhiQGon)3nHIao׾@Ne;XES-52Q(8qc)qn xx2)CaV^ xB/ax"2xR,baQ.mŭPmmoxeswuZY^V1a#MN)抧MFaE5؊ jʐ0Q>򅎳=ed &iC xTΓ-*eE _B2D*_0cj& FT4](RaH '\mc2JPv0w 3v׆*tɜ8a7$q'RjD5|^>ghƸNڥяcRU#ye5ءb1Jug}:#tߺrPȥ0!{g ^BiU'W8N *c}א\ʯMv;Z ruq8oW&~=tG:h,(zTS0`_LB HL\\vc#Ǿ=*ˤsTN4ymY(tn۩pet/s`o+uK7v rɳO!;8q%,QrTm3ݍg:l)9dWaayYr$4KlEô O9Ku 4K u2S *:gTp Mye 4Ӂ}<š&^Ab&e/^EӤaRQw|=vow5޽kaߡ>xQxXS8~_>2̽@{46S SPd9X>I&+ɎB ea,K~{~_?[NxL@̧e*ZRKO^z|TjGNNa"7"2  mu hC;jR0gKȕĐ3qEaAռ$˹P"02΀47tjmab16Rz>? O=dN\0.Ȉ)6YEZZϺ`TbL ZNKf܀&c9c~0mt 1ht1:;ŧ08GG]h.#n M MIX5 L!L$GYffF5B4Rc\Zf=8>І_SɢL<7+S d&& \X_ĭ9XcLN#X[*jmQڦċEVk[JC{؆Z/D_C"6xȲ%̴Z0#4")3g\-XLd^6"$_rrU '2f, #ў%cIaa 6,L3 m2[Cm\F:*U 1e)svr[*\K f옕,Cae>g9xm!"fs;{ت먖fT`C%o5Zƫ8T6L9&{)G{^eEiMk/LӺanX:0h;ا ?Ƃ*m&oDT8ְ[g0ڧ;ȝSm2[rp6 91i <8f{j`U{nd k"~Aox^FAM#©* f|I(8cjBcմb})}CN}d88R%x(ݟyj.ol#i+,ڂL;? Ty +lpGߘN;?^m|"~XkYX5oA8ʌ&H@[qĨFX{4cBrޛb^)map`Bnhvݥ&FRvC}zu25bBy::BW K>( $%Y\lK1f**| ;_E^Z1/fzN!톄\*i(!, t"6 ~mȚt2=CW[:ASrB0H]V_&4w#mI6ݛY9 7Ȱ˞&.R$yz̪j쥛WRlQ(cEq !VOMfn%Tc0"JyԝL(rR8*WƓџGӠbluaHBKi+ xlƧ}jh,a}>w'ߢyٺLH. /lc s.Iؚx ۑwT]uyC9[Ns555?~<`fh.ec?m=FEkp>4lo?U곏^ L@kZ %@(g7I/>kP$K'n"nqbn!>+%a 'xYob$(TP irV npݥi샢d9?5;̬}[}?Ї3Qp_њƗ|eaZ2@q2:˸L Wu#xf~oDV;%q}(e\M~T$ 5(u=-3eThĪ6 2VBgɢ?ŀ|~—hv;/`~v2F/8 _+E TS,CјFd"Aʼf9\>pUU\m&jd0%2Ìz$]4x%2\ Pq|=Oq| EO-2)3ZzfWD܅IUԚ~.WB@xTd7WʑhY߲mx\ d+/=: cU=1E,M?b mbq -}7Y 4\ѣ$U(J3x`EոR// X]Nt#+}FΝt0d;B$j޽g\sc^VP@%,8r17vq$X:2Q8_=ͿKj2}dVf@a'hOP?Eszv>?;ŏrek&Y]4tr=B&3<5]G׈6M27끵wATJj#0"K`N()`>Gͪ׳9(f8?3ʶ NCPQi^лj^XD*Pp1^䍍3m |ŃFWpUJÆd pe2< Lv0eD(󈰡jnjQR] Bt4@.A5t(#MQ7\ly|;O糏bT!ɭ?F6 H{أ_6@t 'j_^I8㾄%/FFk*M7HjeT ;H@\ d ntҲAPHIFRMAO&2fθ &&>$!8|ݖo#8+bV c@׊$KDO[9OK|f[OW,%"y;hF, cE1`حs Yyv#eǙxڧ.mZBS6 a_L؋Yu~Q ?Aʷn ғQR+^\bQ//9.#AR'!xBFv'T¿ -7+ !n٭SJʢOUCFQm=ypK0S 46%^7lo̐tS?+lBkd5Q8rdWա\ǣ2(eE=UyK"/ wm _ѭYqf+bGEкRJK6pJFJM06i-rsAV@wԊ%f(`?vd9ȷ5+7PK#WWt||k]˱]ē َl)E9mT/kn'Fe{Ъ}0ؚS[߃nL֏^R\n}ܔ ؚ뽃x׷ 2?HBκܕd&e/ H1.%O˾}D|'Z|Җ-_пxYmo_1mˡ=x.i,H*qR(YN6V@ G._=OKsX-w|[l/jʽ&MWHrwwUO_@,0X>qDythi1uzvA,i$Ր+<#E< M DB>RHrK!0]:Smi{\cNFWd-J\P<&1W $5e(Q(g̶J`+qN$V -U*]-,Rs]3X &p9&14Wfmo`pﱋœXBGQNN~5t`+ɲZr#+GY>Xu@YMy*p8xDrQrtL_bmh* !y]` "$N23 f֠'ړy.fa0Wuqv%9(@,h8~/cm]w+[#1(Ezz+ s_Nd2|vv?]4VJӲB? \y'9_s?y빴ye2%6LqK-we w9%sx nI8hlL MLƂaV. q<1cr5'+!;q CĎexsJ؍2E1d6.!| lyCW22Yf!t J5kΆRNmlKdÝ.&0Sjh eR ƛZbbҽ^(xi[MrE+hs~]\2q6mhSٻ0Zx`G@b{䙙C]3_\zەFߠ@no b[scݸcA(~ aγ-)W!ҵz?7ř8*T] śGSpË̚.b- 19]_ƴqSgpo:-5%f*,i CY\(%0!DXZĊKQP.[ǣ0o1 rIJ '4A]#؎YgY8Η_q[dh h]@(k♉XFDb3R[ Hc\W@u Deޏr wvwb<_%,`r3 W͜>xWC@c9ɧ@bt["PX2ԊM%6DK%5D0aL 떎@ALdH!f{Jf}Z-t]GQLV\Y 16Y+wV >/M*=|M:Nw DAp}הL#mw%2"-Dk6,t_.{m3N4 "H0m_i1og3.9jtoVw"H~.$j׈}ry%WO!m c9H{z2fnF{*"Yڌ@WL{h,Kv8Ԓ"Vjm!]w LŮ5*py^!8mĎPS-h^uɤZ/\Imޢ<{24 ]/-OK:81QoE EaBERYdݼ>uN!K)w~{;wM+-<ߏ`?IF.fz`p=Oɻo泋>`Z$\g>_DXINx]N0%|.(1!Q@*EjJ@4F8 2 l<›G-v|{c~^;d *L}^*Զt O= Hب\f(Τ[R~\޵767Y1)0-oJg@h,ôQuw[t---$dtA$- kv- z9q!%i3u). Hd(^YXD7ʭgԣmQsov5c&=SN+ػ_?go!T~qxVQo0~W6DIսu4J TP"'k)~gN|_?x=o0%4|0D2/2DPqٟS{Y/Ft6 9S;ϫjюJ̟L5E\[< %WmVPD m YRKҞ6JqE:-S1M̵Q<,M`(s 菗/p?^ˡuWOOʟ.a_9x?E[l&x_%b+DBF<&Ғܢrj 0n2sxB ̿dc;h_٘ 3 NU? G$[z-h7j#u'zN=\qZʝ}l6 u9k]g] RY(wG4<譱bq8Nf9!=պwE' ߶s0}4ϯxYgUI÷xVmoHίQ.ͽ5uԜ S%w1 dvu&җRv<;<̺݂VnjЂ"吪@-B. SF@;|>9o*}py>s mX,-;/R @$\R\bRKAW F( ~P/_՛brX He!G\v) 6!!Q,L&.]2 we 5  P]G`63/z3WzN ?^4!rDc.4ne(aS: vj/4wxaQsfS0Vin!]Icu8أ oQ]1 GFu8pՇ zх<6a Ϝnc P}d<s 5قBaH*0t(L*V2L(QLx#^<^Wq\{f!opL|%>cX<~úgվmTJFP H47WYA[Og\΃VR5!PTǣZŌ%}$Ӈh2ؑ{O8'_*9j,CZtn7'\"O5#]'pQ(xkFX/gP ]qS"dmyI5L ͇@^h42l?īmv vᆥ97nT'.sJREҩ3Ю辆CnGJ-q!pDG2{X9y9wኧ.͍*8X,NjI,$KE2l꧜*bX@_ . tq.?GatݏƉ?6KcL iz#7,Nquc7*oQgSS)J,P|E]#{UD64*muGyaHZqk%wʭ;4%^o\&Pm@ JB6Q/fVL.24| #}?HV{{؈I^l8F΀.7.OO|ߢ="84O ƮU7z^V<~8ű&q! ELYMց.t.*;i!\2O?{v?)޿\xVao6_qs4KK1ua]ɂY:$R%(n#E;rjn7`"yw|߃Ao^ zeHJFERWfz bCz%ZCΐlmD>6\,dJpӁg)-6&P% bs3NA8뿄1hv %P*L17,Eb#2lBօzUU#/dgp4~"gHT2IlkTC0*T"Q B%f<@"&L”lQꝄm,!b0枭kp}5l6\9Lg0NtBKL>oHu𾐆d&ؼw ,E I%O(EHJN3eJ`bd,g:v+^f!u–40DfÇl^aL( 2!rvc-fpY/Y̯@{؆/ [RpQ| (hSĝW]ϐnzl2PdO)2I fKEm5ڏJC$L Zzm c_ƩW G8ߠnFHw:" .Bbf3RsX(2THfzB47gPwdJa Ӷ4ۗY'/>Уk6)QTY PIl\}@7m6 ܭRsݽIq2гطf}j :˿[g"%o%etX5Sv|٣AQl-'ک}vfяh^:f~ .+Ȱ!25UD*.7}F./U%#JG0UXa:# {`/3kΙϻݯ6d8xm?O[1şdFYXQx:4UZ@ERD@Cf/ otYB7|A ,Bta g;&P!DǤ-"&Llчd}=)-3SQD\4PP6!Eß5-̽wMXy Ev!z8EڔهbI8 I.WyɴbL6ǧkttՏKxәcLSmsmv-\:b)?,FM"Ҝ͠{/tEKt1* iZx340031QHIMOI`^o`!8?|mfXBUxo-SM}_;TIAbQ1ȔENީX^u{k"响l+չ x\]o:}ϯ ҇چc_E:&Eܢ,юveKIqEC$ʒ1Ħ3ΐ"]}HtH9|J\.=@ /t1P?ɧ0C y .I2?P2XW/"/ q)B< YO$cD0pfC1ℋفCʋx;%F8m&( gĎ%8^F'jٜm/d/WxxeQDO1vD%0r) µ8D+^0(+Qq(f4s S@lPƇ|0m4|s7!h8&7r4\ïdpct}%KMUI]19 PPfĞS2) @"lEإt^lǼ$6?xŒXu9Z- {5\J'.%8~ZҨw4iB@IJp&bLp~l10lD[7l[5_Zm\]]__D/coY%؄K䍲q\ [`*pt9q99סu7|x- /oίw󛛫b^,OnGןwKFҭwM G]eB;!y:h9mpNX`b,A}"չ4- K +o܂e%2!nD&dv=7tf,E%!PMĎ(Mhcffl>NZ7U`ˆs-#Ika%hذ;W2s*M1^:8ZJߦ bzdJIѶèc2d)d >ǔuw5q62Mf3L>z?{BR^1TR9 a9E43ipunSWLlcO)(ċi_b[4~.P90x>u罯Ց om8p/Ġ|C]@KC%TvQm>$2Ёn%}Z| X/&;Eb[]ࠔ,Ӷ>D)jK`BbMKsDxAk+Er %b@-#cX||{| ]\Ri \ukx&(jQf`(m@bl%3vU*'_{lb OF3'`uɉXq( gg;R]@Bi/f`=s NxaP/<@jz/ 8iJL2?¨A}_3i4S' p@.Lw=\lJƉ_!qLFNa:)]( )rzY@/}u^I.UuB2)g"=w/ {@=FkTi zofUͺ-62뭐Ьff]ylb>[ӍYE ][!y'{g][ar`N*tjϰk1A݅]ov-*ǮqQF}4ZJ3L^&&4߃q"tΖQ*lZl"ܦ7-M-8ګ<Qd^n": VHhiir43.oU6OƭNhƬW~q˧5R61иՓwT<75HI37ۡdzvgt1p55Sާʷ>~z*"5<7ՋԶ|WaxZ[o6~ R`s =4:cY2>ٴDd#sHJ4y>d0w OȘ5LعHn_ .yëG)Wү$1SĬ'C'SKA^~ }\pA2#@4$ hpM`l!\Hn)"bdTFH㋧!rJP "p! 6f_ǻnD#Vԭ%Hw|)Ӛ(Oh| t Et rtG"t<3%)nX QŐL̵Q|r@p rv1%y1LHd|qwwq3\M0Mno` B~|>lUє,v2V!N$eOxUFW=S4"[6\K5#oNBF \pu=u>`Y.XlQŌj6GiE4c{23_1O+k0/RKb( @e# pK%PEsC*7/p?خ{G$c=M3yIޜ Q)Ȯgp>ݝ73X#T!D_&7ć,bJ|}uH#3 S K djw"" -E2f+HeB94Ly +1 )ۂ7|kT> t5Krř˥j!<.cHhj /->Z +p nL O+A$Ar8m ltucܻ<DшΕ٢\8 3?jIb}̗^,vɕO7 Iǰo,/i/iEXlMҺtj,s nڷo/-I= Z t K&;,k{2`eJڐq^3[[o5̔TPXT\xя0#DkȠ upUb @BhH-+;cG]^"AɶX Rq ?iMf;XiG#ר}kLp[w 0&`)9nFV?$$(5YB^ATd%O6ucUzr}NGxWⱃl 2x\O <4u $+]v_Ph蚼:_ 5L!aQzA) K`6NԅÞfIգ_Q.Zx f<ŅUdZ-JnLZW{zϤw R8x ?oWv/ԮI:͂nQeU֡&W$`*hhu^\d;ʑR4(xzIޘvi5bxSJ1u_BJ "@pXJq"Ld{%V@_0Q*Iyqsu`5uUW!>nW7Iz; Ј< A`o5K{68P51XB,P'>iJQ=(g( zՏЕeMeؖQ ^Z(sW HI⋨ARiI܈kn@8.m8pd9Z's\"#2wil}@ rHptfRD~gyN9m0݉0ITgضolj]oI] ;-r> j/@:f?z`3 Ͳ#RQI+#Yרr.p* &>xV]oG}W\ܷB۠Pl(O˰{FYv3,Vԕ,3_sLC_u~ӢMuTx9Ͻ&e~{RcIM ~sF> ƦZ\y ]0f 0s y–iPJqmr轡گ.֔V[ʍ1lhG%~s kFU'X)0 ] o$l6=Ug}? '@N;G.ŖTDZg6$ /-c jeIFY3vE@x$S9SMn0Mbh~Fwwlt=;LFi0DF.1~(0LI۔AEƮX:|Y%|alIH&b&k핯%:BJQ4vvMg}^`Q: qV&L8`so[snaL&ٶg{L;qNZ6zyS~G*`u.jQOmb+c[OM;q3^s2tWHn@ʢ#Og>Y;4p9-W}8{0x+$q"evie[+] JiFu Z'`d@r8: g- R^Fv?὞WI A+k*Enׅ'%vZ 8]<1.)hk"5Pr-#)kS#ׅaIϏ3g48J)KL2ylĵ Y tCOh{op[\B]OODXr4KC,Ar쫛&\߸QH#,tyGay썲{O?4'<ȴxVQoH~"vSJ)ZUê]p\i-!f=M&stS6DE!S-ɳ7=$;=&Q^ݨWWJ c29L0BUHa)r>|$p ^ v9S!C,T CI}Y@(w) "vSq(7|vje `cG%4aQg(τh4.Ɨ{O%5]AQ"Vg.v%^k$URiieQ Jc\U`m~D؁J& -zv}-?ni8hFhͦtW4}rQ/534YӺn ēX)|oJLd&V+FX;1VnSVZaoxqл2IȌ㿗xq]UAI^MWJCaϢGY>){=Uxԃ ,RO,lmi}X%p.UZca!aI N70o-xiT!c gp i Sm u>KzsRpdzqocQ+_n[v൛,*hdDNҴ{Zl6VRёTIBjoJE'O_).r 0H;?kʦvʺUn;W5ԉ 45p +pK΄榣'Zg&^Sye&swqvfʥ2K\q-C wQ ;t ֕S-YVT !V~'? pCfecLf#uB8FZ^tyâ767?~zƋ&.UxTQk0~ׯ8Ĕxa5BֲQcϪjk#5+;) -m}.dȳL@i܎r ni|Nb>]9gI.ʃOcTb)1-J(l[QBIfF}Dg*}409hU Zo}Ë%+UFY6Ko4[.&,]Ѻ2=fnrw>kkh'aWeѦn<)PП\Irs%uA,|%"Ugm8:)2G/I/ojDE~rslΈH}DP1B!t+Zr"x\Zo$XX9#2BѭRX?E}<=aPa#Ȩ"~¡_S/ En9G8xJsO :sT-фV*KqфlL@ ⿌mYbbТHr"T ku/9a/- U+œCBŌ0͔VWŵr\֊B"2+W70 u G&7՚K* hmK[YM)B3\PN)ΏRӷoq;r w̮ %JJ-xg^v^^~{^ݠ6DyaU]%)HCtj(9tJRvnHt;NSu O'?9~FopxRJU(S##*Hd270O'bf} i"\~,TogkbX\bjFʕ2:+KV)f|V*|p.DJ&+y?=E4NGOg} vuJ9R@ gP*ksIs}%3'R2JIRY/6kp7ۣ֝O$#n.=+s^jɕ F) &X] \;趪Υ|8UjpQ\LwcBJ4a M1;K\2cq5ZOhȁ~kHb.7b\r[Gh"/\7h"5+MUEXw.QŭO0v upDjĘGtw=|0˫ |m ΁ɿl6^z;oz}M D/_)g^A3~tbeCxX[s8~ϯ8>lt6۝Rm)+.S1v4pSFze V&ⱉ)[}ϹI(,":T \΅Vr sVg/a lg5 c!ɘ%$.>[~dyC(@<  *+di+\a,X^L+Л%ȓҺf 39<'(zr^;{6dj+Qt rbYh5%jc|^e-Tgz:w_+!-Bwl BL!5d jbKb2 yΝo2 q!:(Q NlUj]5s~%o.%W ?aT3o5>x.CĒ |g<:^uM#{af%g)z]Hn(k+'t 1\Wk?K{#K外 zE%VQ-8B_:IߒhԴ7Zj|xN ~8VH֎ V'ЪA2K$lFVFBcZ{9 j'hдfFkwF?ΑP :nh`扭"ntYΜcPʼHKB(J5}YlXUцPtA=t)iP +N tk <3 ||qsC|Gɋ7 tjBr}ò)=>y_/.w#)DR!u`%SlJ|Tm1QJ(]Kr tCn&~'3S Ԅk{1Ý"Bboz’lAڔTĜ}e[^,٘kG-UW ѤnΚhU+ϗH|5F*h!u R o9mfƁm}R5'PAf_ Vҝ `0 :'^H 3HM5ɷԷ6l3[ *s:l9Ttu1nû?Ti]RWqt$u1cVIs̥v╖"~e Pdh4\N^+vKïgi`'Ʊ2t4\P]P\B~xz&0cGz}DQ۶[Es<qƭV9-@)DFq*eRTVeBh-z)}+d5 a2a!Y*^'qvYn.^&arql傞~x~Kc.e&r*e t!ZXbNtՈJBeKuv踤ۈa Lt1Q4JHyrEbff5ӛ4\ jy|^չj ?]#Ef:4~_KVZ5yI Cwp!u%@DS$u{t\簣zݏ|X:GK[қ<[L{-ޅDgzV{g¢z3M97 2*9/kygdZr%Q&E T1,D T]S~BxxZSāQ'a(L$ʳeEXIX6t2`y cVZ(3§h$f|lB,=xܱW!sP֊sz:N'_ih|mαd|s99}z{4::$thxeJ@ y^4RDAPhi@{Tfz+(yͷjէ0%Q`/!)WҠRg0[CXu 'Qz | pe%ELؔ1JHfx<07۝#CTۣ-j|Bp r%LFH}ֈ#x2֢R|0BIEuYs6Y^., D4aI:O<ƤjC;\O.==wZg&iֵs5ߙ},*x顊32%\r͕@+( JDJLr&(`Dp ;g>_|sfu_7Su?#&3^U9ƊK KʭuO&YZk=E9AF$L9 |iSlJMT6X<'5y:@ mSuޔuOA_KiSG?Ӕ ΃p2=IQtLcFp!qKtʷ[B9JR m"(*P8% h"4 Q}ܻzK-s+w"O,hPLcS~lm^_:PX{様EX|Tqe-06xm=O0WΆf/ #C[YN6'gUq  n:{Ewm6X ^fנNCQ%c n<-fm0ĩ粘'ybn=}c`'1XNttنTj 9S_\pX}@h}+7ùsܸe Cˏ]' ;gn>]'ן!OX=GpRCX+R%K'WU4c{& lJ&7\I*boES^oŋ whp9~ul[h*3=4$Ǘ7W'؋0tޒ>NICg΅;p=+ϭgH(uɕ(fn6T&/!;ViJj<)+^$AOB-%JU9_;&Qm)ŋ$R\RŦ()KDʰ[nX?ΕT*v?J*w6AV^3D%Cڊ6c<5I+gGHRtD %yP_8b/ѣİwq0 w)&Ϊ]BϠVeJuuqr ʌ[8ۛgR-'hrn _ }ƼQxDU>#b\xc1w|O%76;ՠ&$ ؾDư/oWD#yRJ[4lc0wڛ[$7ܯ;dR qoJr^ܵJ&N3{^4SCE=k(0Bx-^wȥc͜4?\TZ9g9 q7'uf[WZ,ol+5mVщ4xyRAbAN&y~>R# St5P % #cd_hhSSTT*oZv6Һ2FTMZfȦ.o lox}ڬIUš** dj}jZCD} G㥇.@Q_Q T'yEqߪOk3yV1.';^9nܹZ}|O3BS ̎I+v9UPJx)Skt5,u͇()*-*ޘ.,fm`SCY|)*M*(ON-.Sb]DQ :+IG|atoaI7-s2%E)@5DLZo׎|Y9b`j2sAA`]n4!)Y6>PxmS]kPgi!n.ZVWFc[ff4''(\x ONypʇ/~l~n=Y9dڜID"44cHG Bh}(rt]8`/-O'y.u8c'B͕W CD")#D L.Ѐdܢ G{)! ER2tFqS)|iC( LFAk@ @ *l XaS8d!kaS8!u'pWq]_8dQϮþߑ~4vdJ.XCW9H1 [4TQ6]Z;nt׊^U4-Sn.[f+dnyTe!ߧVW U?]1SגV>~[$?-!G7cx'uU;}ڷzF<6_1ŁԄ[KJ9.=7kp1Uhg+: |bՆ|mH/⳽x=iWȲ` &C$fsM[OYfUoL[RUՋ9YEvvo'5B{Swso;$S$uIk R'04{/TJA<Sr<?4rJ8p'1|2&c.y[Ci 8:vu $Q_$N̉Itp( {ٻvy+F z!p{D P4pα@8Ѕgq?^: ]38&0A"s|RiuYB~luϺuY狫uy]rqI]tޟ.:픴:u׉ <$DLEܺp0;n揦% }L΋P8D0c΋Jv%? ⧉۸}oFݱw}D.Aa~>8@Q"4(^An^r{kד`\ z &E7qCeOȟk.l XJcߺ߹hLN^=i\xJ-;w Z,sc(>{y{S``cLGx/I M,ޤuֱG frh1/ \?^\ ԯ9d6 qf˳mN}$㵵;OFn,>81 &q/XҖuOoBE"1¸`Pa^6D}KmV:r98aru8xGh/Dr1b2-_'NaL;h=xxCALqe_L"8]t~Ne[c 4*RGp)Go("gCv¯IV86JFҚ zm{1xB~WnBeUzID BQUȼ{K B%4BO z#V3h-Ae:">m6ot摏;=4*tiV9F hVgo# ~$<'A9#:͑e]zDDMT mtl:_c!W1 GTЍOX_&/-<H`NU9L lFFG}>fܴf.8Z'1gN`:w8RGaVE=Alo{4JGلtɑ$̩L>uF*B"zܣ2W`:tU})m4pT(еL L wYmoa*n]2KK`H>}$kC(:RRA33~jͰ1щt_CSz,|Ǩk7TVE]d%jU PI x?kAo@64.H]Ti R*RĸpIb`cEq0_[OuR|GSm=!%⚲:jw5?)@HF1U'0 SIE%AaU4Sh4Ohg2'4nX ZS~ ae*|>IXVuFOև `ڿ%G$Nj^XZ ]4d%B<:xFWӝIh4HǼH}Av_n(eVqS׆!,}9CYڄh5mϦYNyIu:GO"\%O#gVOd s;K|6q|@'ȍ>zAжQnn* #wRЖB>KHP75;Wt QJ9Ui QNV+԰# 5t]Ol05m[z˳ARTºq[[U`Bra>AzI`@념gq @tKfBȩ t5b $~&U@(-(5p'4&ל:1 /8w;w FQ’rq}HDŦV2հν}v,}ȓs MX1,й$ixTi -UBv ]MGՒԸ UV& `m\, =]LX7sh'Y }|9RM-%`DDNOJT$x O-^%8W%f2ϦS=+*r[rcg{ДO! ۉo`:=bӽʅwƚD8rHGoaZw׆FR)^|uQ>j7,# L`3NRuWӳNPdz.&`#VUJfIQpRJB^f۾kViV5[ւWPS{7Trl^hѹy{lwS &t{$y}Xr'E(sҳȷ'i@2y(? %\]S5*Tָ-DmTA3sll-@IhJT^RFm~bH_KĴ\}E1%晭CLsR{@*@q*Ay[$UfBšU6ZfR%9ǜF׬hfb<+]KGxO 6d"N[Na\gpWyPY/b0f/]Ḽg|gc7W[7m_"e1-z3"QǸGdhˋ#6յ:bRx-Qf`upG޽닉 iD%e[ѮJ߁T<#KZc ƪH][>-GUtͷܖU\"I|p^P`ϰpnhg_6ߋ=v]ˬ,2tN3 R^$!_p-KY^}ee˷ ,׾,]SoUf k{$~Ъlog93IgJČ?H o3"o9<ҲV/bJOܐ͑Fq@gC0 ,_Re%2yPܻNe*F,%)׾چtlߪ V֩}r׆(˖–⊷j,7)FyXLfڑZ(( W7quR5-nJrv{! yeQ0 shLWdq\Ҏ/N/VU/} ܋:Zv:Zg]ʦSV-"gv&fŖZ_LE *DM:(^^LEXTnEmW泵r>]пO9Hzlu}0y f68Lρ{6rގ`b4eiN l ;aOVEVt=\.^Rz!XǝAsNAO)&VSDTsW ࠽B5(jFk ]J_' uCEbR^)pN5ZrN]ȾЮwPucop9PL߳;=|==dxndZj~h`<2YIet1DTpD.4=?{_.y@T3>*WCW6V:׵Cb K-;I^rQ׫cfsc:ue% Cyw1lݔK־XD%5m,VCQB0G5iVKr hG='*Rqlwm¶ȫ3wkJ=kS 2幈A fCtOɌF@IT{oOA22=is8l9|J!S%Pxc6so˜̤4W°{9]/l [Yt{Y8j5"79;1p5W\U cJCu1s~),eJ<ͅ92_Ѕ qrCktƈe_>|qGJ, I% 41dǚ.n}ɴJ;;@Qv 3aɄh&t[O׫$~HerL{)mUV@ 9b}Cӌ%Ey@,P|~kZg /R̀:c>d, h*aMӿɒx&S'J $)`tz>b 1 %s55+-@# g-̖p0%<1dI3I1 g1m""Kf|qzy5K̦ҜTxebMeed)#Gȿ=W k.VϦ8|IqX25h'm5xj|+^1ٔ[V$95?B75Dgl!#p n!K/tKly9*[g>݃EU b70tP̐sx9Tyt+s*-0A?.F->Ʈy_F6-W6gy%*J OH}&CwImFBףQpIؙm u'?B-7JX$V+La{G#x.UM#[2#lfaeRA+|Xf)VMp(u}q-p_uИ9̳_K [JV($֟\~p3;mƍc3aLQ8rՊ F)KroZ6-8<Ţ4EArUCo$XEa0:`MLjR2Nf:)&neAJ_7O߾"߽;zsvI޾#^z~H/~&~sq: x21Foj$2NRa<%qCZIE\%{d G*eOAAy9ʄT: #:s0e4ͲzhgzS&A.:y9(),! 'փ_%L$_v|_8-kg/iAۋ s#pbk(X :Q cK/?n, j1t@)-HQf( LC+~ Mz' ه-YĤ5PsW@Aeރ7Jk.|NTB'yE Iw0`8i)9J{DsS6pG3.@ Lλ#Sq* OUo#PެDI}Z-T ˔**$uvlD^)b9Ƌ\䞼Z\`^eImd}aV'`<ǚ!ǖa,``HeTD~aM'!zѲ^V@WUc1)ƕ*2iSi!Q6xtci!MNU,Ls**YICi6:H>` b 3Ӭ,\zh0հO.U~(7\+GF, |ex)6.Tŭ@wkw%*H !+J-B¼}4 yITZe_[&{8t*j_{kc [-/'m ,4ǀ5( WPXjc)υ+#3<4?k2?fyފQ0 pYR !:XhXS'P_Բ$ǚՖrdT։+F$А Eqz@Ux,l‚*lnT slpcf{qp): M{̪5۹ P#)(L`c0wzT& $pZ3HEehcpL>(s>HRS^?|=#sobSI* e4%SUְcrc݋/v6g6hv֝%#bi+ Hl㯮 { ؕRƩlAUU9ĶLJ„xq =wMrwdjl2ªz6"*+b]qm@֔l -Ä]LU)'k*j y((@Zc٬1-Qf6u_0Fq|{W_(*sJ|U7h};ԯ|$%_%_n؇ mcb&8i(PiZ"雉2B.DS\¼2M&}9oՊKv0w_JM W?u {0rJ5iȝ!Z5iFU: {}Sn|0&b5e ~4I3w BVvX"mțXZ8{Y57HXHlݗ# /~>r&vsh%M(sLO{[ FX`Qpʣ l,)5S"kNq-s);{F:hI?آ[t\.Q*?ׁ/Y$@E:䀐#24<(G%hmCZm$Wvq8\jރAq=T&4;=ni`N;0X۝[҆ny)We(Zd ʐ zNA.e+U3"QvϤhKY^cOmb}*κ@{$ [󘛀[*Omb |:vU7l-NC7W`k˨ww,oD2nƸNYs`v,o wa ,X{(m0LRihMbbrx`M\GC]|iȠtsO봤mnlt鶇%хu5kc3d[׼?! 6 Vu 06Z>2Fy2'V-fOA&װ?gVue&Uua7f47]e6$IJ G}YƙX-[ͳ vTõB,dlsTPzo#}F2MkV4JtkSbH.R{ 9G~h^)nR60|sko鋕TqI+K(iz6\'Lm]+O($u&2AkU1yZ%mxRuMN__f z5o1W0 _*,P*HI+yu7HqVʆ =~}MӴbI^nw>?qMbkK[ѻt gvUKEcWfv|jV{VÕooUv"MFqŗJL%Ge)ۑ+hc̯lRԼ (ѿ [.mN܃uM}q^v?H:/9(Tƴջ M>?͕'?K Ad"^ju> Gs[OX-JM"Jc0Ʈ8ڵĩcOW0Iwuv"@-t-:7 0HyFY~b@EKJnBVB!Qb&&9 vӀ<ȟts|+/8sH.·eҬV'"E@GhgvA(ob^ЈqpwL]GV.uNʆӺ`[USO[1CuJZU0OA-!A3cmA܁8ǧp {Y(m ŸVP[b/ D5(l ½Gzr;q 5 tHo⢔/%S;LQŜ;tuKI]^*ӷe.jęQtnHo؜YCq/E6wū[e/TfǿaWq (֗x+≾ M3gݾQV7}{FKy/ՑՁ&"@yQvs\%?4bE4^_'# x[sHb+'`;ڲcבup-u=JHVEHZi<iAvo`èׯhzVkggN<wIӁ>< ?>MNaKI}94HK1a#ہIB r#ZYÔ9 BF҄/!\2zЈ/ N8|JMS/msbG cAN߾"aLiL Qc^0m$+;{ie*@fF^A\t:;v 9듗gWa^vWSA((F @L.m@&$"%uj4LO4@#x%htT jmm ?u7 mIǻ)824qCbJ},aXXű4+͓VJG<>`8t!vИOpZ`tA\)@yeCF. $H*s1یMnh}8 }2NFOF^1kk1Ȏ|&g:¤g#cs̾ԷG #rL,eoG[ƛk_9'*>;SI9/M%S~B跭泛lI3.,c7J"ye-*M#]* fvP(w~z^h$n@@wO\n+;ߌVԹ> P ɞـ htf= |3rk!*QZLV[FG|cHVx9TAfԺS"DADσ]Sҿoeu t1[˧5\q8WkyG@X )I]fM8Z%^H Ւ 1~dd_ʅU~r$S!Szi1T1sDL!O"4qvƸxdXi1(ܮY h*يPuofiC7B:g积^)a?iqmKeQn Ĺk9 E9iOC@cP"{d8<#<|Xq Ҏ$gtv4쐢F܄$DLE^' (x.1;ROb07ʪxo6a97\(CԧzJ;/Fz0Kk@,me)uzg)e0}Ѽu@K|m'jLGDYH'y^\.*V3'a4 F M R Fw~(-֓a"fh P%'(,w[ЖlS4~i"sXDCΏFC:+8 ? !(%A[@ݿxw.@wowgucGyM}L0,]]A$fnCӂuVBF4* A_,ԥE1caɳz6\8LoP-~5Vuhd(X:~ 4d!&0om ݉o B4?Ļo2'fb\7Y4j*t,S*T$D G ]3 _$f"% fFn&mm-HD *^kl;e aQb 5KJV\ !U UᱺgVO^\z}@]i,զQ }j'쐴Y8{FΞfQ9Byf8ƔoTVv|fِ]r\/ )jxi*f!7+;`cv#]rS$c3 ըj<љͭ64~!_|p6r\"60:uLڐ5QG&LXߗ?z^}C>l%sk~Zmȅ?x\y9 ZDo:Sljn6GƱy~Sc'4i"Nl.>Oe{4/܀asPqj|k31$r4Wc%JZT .h7EzZ-%=x A]vѠ3q+ sa b+JW^ 23z~'3h/~^Y =+[)ùtȴBے{f9aA7s~&35j4w+\>k_WC\Q^K,nO2 p1FnuD!ZlqEϭ5`gd<׿eqGc0v[d}C @ܒ3OdgV:Of& nCr▗0eN`c4cQbr0ߩ޾lۧ8@Y!} Z#,cJXH I< (@V)+<}jAKN7=ݍ?/x<|VZvjouzP!i:ku] -*(:YEѳ$몤tg BE{@`fǴ[<+ 5->W9cu#9=l8x~[VL/#cGxSMO@!pRTcHLH,D4B6d7Jvu[ p?coΑ+?Ɖ+Ǜy㷗?o_M⦋]|^rW^IĜn|&2Y1\CPٲ VzW=m}≲qU[I4BO@WI;ЙԪfN X- A- c -ːd>ǖ,MR7q;q%7{驨$_<HM'?*|Z GTUGH'zԉ9 8ni`)k^K.cloLY>-ygGʐfTgu٤ 0K1CC ?Ԛ=+aG5}&2+_{L(I|4L?'S?ʅAw@LTN{ۻn@sjȩvߑj7H]gZzj."g/5z;bqmAP%(`D`UT<=Lx]s6ݿ$E,}Li=,NP$dqJ*IYq@$/YTܙb_X,9쑳]}Hox0H|7w\Jluh`vWs׻1c;}X M֞M-(9_wgcpD:Rz=E,Yh8!aO]E/Wcz%'ZqUg &I.3L/h7dnrpxub<8ϥaH `b#˜!~@̻½G79]<ژE26:pf(#? "3=|B.'/Iry[bBoȻc%'Ogl:(Jj3M(Ͱ09KZܱ`jڼο3"+,4m$:K'2#vI4<8Yڦ[י̐0A4XܳV=}Wx D-0r=u,AoiDz˾O}&@vgjzCG$ 2Q[S964 }?卦2% hGB 6'UNjHv;{BDו( 緐K:b)#^-%Vgq"]}X*IpT>kXڋNs"J>X@H;IH<5mЅGf3puE^%#=|OQJ4!54Z^<˝Yh*\E-.> /b:3?:z71dLL'd.Ads+ə: b׎,?ӑWّ0!CtYBP u!  H4w3ٞTt^7D5u4z:pQIъ<^2]Zy:{蜤ħ:Y?&jU%jV$\!fMZﳞtRe;m+4b]iYIv,2 iF:%e#ݕbzE~bJT&>&`b6Sqrid!{V\Xҿ),X ;){&hK)*;lQڢ&ciAˊ\xbv)B'Z#Ҫ6ݾa]0s?9 giRK*ft7[h d'*I!iSyBq! efME")DQ2LK%5W+՞.sP瓬`GY+xaj @2s~~,UP j2K/P9kVչU[JV tEAO$Tt)=e9kJמmr&3pR&^itߔE~qfy3mNZ}_ڔ֢1읮ږ +x& 1mk@숦wrlUɂX9!u>n}/*onB(X}D {gjJ&R%lKAjd'Ǥbj:GS$]XX&\%l{ 4w!\J`=~rA#.|f%mOUв!i+=6ZʥK!a`eclyX YϸWL*D۳tG r(Pь>P0Eܛ!0 bϏ}[^ifؒ&NHN%Jߦ3%Cbׯ]‰N0$%x MS8Urqn$Qc,'y̠‗q1(9l¡sg-lxOW1eM>s?2=܇3\~dǤ#S+3tugm϶SD|޶dzM̡4Ϗ&Lφч3JZ2Ns/oSxnUטrnW$,#z WyLZ>9[%I:V} -=+oǏ''F lqƜڑl1M{hĊ0[aM!Ak, ;]؎;,[MFJe?xq$eC݁{$vIݐ 9RzƁDQQr@.qq+_ĢPZ@w&g0 isC֞q~HҐB8x|(Pܦ7D/X6vƲFcG;cyˣA8^{@^5|y{_rwYk G /v>x{愕P=ϻu/ܰ Rˡȟ'oE΂ӣ'ksUo+h?(I4"\U%97D2ǻ!sRMRQOWyRTtSBuX&TtϮtJΥ#yרg&s|<⊺uM:D \Κs=jklc(x}\60 \hxw̠DjU9F;\o`;7  Ejl-%~yKl.3 y&R?F;n:UKI2.EKި+y41atԤ)u`0jK5Mٯ):b|nRl@hi{njdɼPO&?Lŏq:Njz]q>##!ĥ}DmzH݈,)s3 *.zzA?2̵ۨo>y3dZßr蚮[&[& o س{crP\yBJ L6>^x|oPe0^> KF˹z4Ei |}NiN>4_bj>[T3k,BWn-+5V*1|Ţ*;0ki%O걕nLZ*M:y| b# 8#Fy܊ynҁZZ·%rO;r)bjG9l'FMx$G Mx=~6~ 7{{cx6x_ǁU;QhҍUH6yYmAZcRj d@HA`:֛ "g!w]S2T~q˺фDVʶ9ڒ+ ɀ?N_?tp`凧<({d ەА?8vrL.S]2@(+ɇ+s]o/7E2Tҋ@B7ʇΪ L-naEˆr(BxOBz[<!1 T)es#'~V1Oћ60îˆ8@.9\nS4@@CfsFEj-,K,Z14FܕX oE us}KbQ4+5++$<܊Ypm,;Piz Jς6仄(N *nPiB%\jT[됕O}4 ڊƆ" sݢCv= T =+ʴMAל殏kWGє\?<6IU.Lj cqNghծk=ǐbv/k=m)w.Կ giJzzr  5ڼ}4P27ǷoU%)lxt}SԠU?L0yll:jL!R.]66" rl-^`kܥp3䖈"Sv/yWRJj~-3s8.,O\!)f|3Hso|Ka8&ϊrF464uxJq#j]eF}H ST.ԛin-ZCGMdEBI Rlm8lLiv"wƷL,sxQ"wiʲp?ۣʨHz9~QTPz 7~ \nNx[.ֹefa K$Z߮3ᓬԚv󪘕nQĂ*HCG|TQL}E?[a;@K6O<5h1p[@>%dF&T4IJ,K0v4H}TB ҤϘ#{@՗uSі=5I"ˎLv`z"w~(RN?b$uTiN57LnmVT(2%SVՕ?wRI/ߐ Ͱ0L_} 6˭,&Gc*NwOergLRZHK%n-Le!Ny69r=LbWaD6NL^\1T`@r4~' U)4*f \ѥ6F M2kDU#j o22بCr5)i D犚JAjRujԷ|k3B;Gso ]EJ-$AQ$,^r\=RDLi҃tYr|| C>= j.hٖ[ESsv~usKTRƳO/ i5{MVp$Μ~Ŭ\V[ot7,Zccu ;nMezM[+]Lѭ>pEBfѠmSzIц~ m*!7 x~vFz6I1:7~f'AR|rKo~PrZ1SK:H=ح Ҁx! !Y6S aty@lSM[O{g]Lw})'W%HwJ `Ņ{r2L('.vlSV- b?9%As[qCWRzVd.^gٶ8+!@^ںoon@Sܓ .jӁKxz 2ېƣ&4 *ldu,lۊi PAIYΥnR4o?싛W`RŇ i*B*Brx ʤ-Gea ySex.a98o(*bH~GQ:fIY؄b`֟i&6BCU9B{{i){P)+h]Nn4TK28hNg ϵV a)`fiSB<@fW5[gw+FYa8ѲyRQu\7vMkز1H YPUFqją)aWi~m OSϺaݘygx )!kvHI'3C5iNoڛP͞Ų]vE+ww5 P>o#|+{he _?mսPue ZPl٘Fq DExw+{MoD\p%:( )8aGg`y5VI'U(6e獍9& {iꆵo)AK8u#|kũm3' UY_Yxí%Qe!@s(}Sd{ai{ӥ}/Z+}ƍYRϯq<s9/eCA'G輘#~( X^Pƴ.Rw#/Tpt]-Vw'NCS4(;|:E8ow`_gNf*߲i66_ЦPݍիe|;upU`nׄ.d]Mĥ&I//guVt_/gÂ7_hAtFWV>47 #zqVAgJ~\ժC9v,}cjWuFkhy3u\FfI ƥ0$=vv"t Yt|G'rIB]р! đ?[QԺC?4ȏ#cw0螏!wQ?w&a.]/tPz20KYԱ@4oxHD,X8!4m$: '*Ʌvvxیt(Z-Y؜F]g2!k0dAgF{?1 zD΂w4#adO|- X1\H`uc2vZ2 D5d Y. c)K` }/SS@ks (}ʂ&BIu&" )|'(.De :1e E!DI86GD8#qdӈQ ǿDX 'AS>؄h㝯'Q8:(@rXI g"P I؍4 QT#Z;v80{ũDϐ꼐BE4IP/ti$r z-y,cA[^q'LWs2>o:cwc>۷$}]HP1c (ΈЙG_9v47I}p1N:H! P6A[ơI@$qDd ˳3ZJ:_}0mQq%$D1~+"wQ:kBN,Uqu'qAkhNd@d ordx=_I6auWtcXMǘn@!۰l@:z63x|T8\6JF)"PzXf6@t h RzH?Va<QDzUM wll(elXo<ژ(r>GRJm[! -<D"] Y.A̤Ȗ3wqb5XV1y*OiJK3,T&DA  X}oMNCJ߲0/d qcJ8WXMWv4M5iR<"I#6;KZ>J7fA.J̽ %a=ҾNp>Hf;QZz26JVcHd`d{ỳ+XUeY~^eT$Z-׏hp /g15#Ջ~!0@1cN]d;:OL$]R} ?{jh 0$k @fp]߆O~~VJ*k7; @%K78"湏,p77hAaYZ06_ |ܕ'[yr m6,s,4n)"lp196r@ETXNQ.n}02ŦHy~ߟT|=?T ps<<6omoy|AwjJ$%*`=ߛ|3 Ů` Oh(^$4Qk^jpٓ9c/A)?Ii}^ΘȮuUR'V*L!@*<ݡwL-Ctt7uL?,dv=xj?FǴ!H~Ufr8cD}4˟ɒ#1ψG˜Pᇼh[pe/ #ܴ''8؞6l?.)APtMZ2X=u;ߟ{D]yT?]UTm1\Yr,we7?(T/&q1 ӫ!z?A67_!gP#@- Ӭmx[{s۸ߟbLl=YVܴǞ:>'uRɹ.#S$qB, wI(9/픓 oC=o6w d9ZLpvUt8Aӷ t;.8Lpl "3/c,nuPS]X +! 7![:,smapࣨOF06`+g-mtߓ9Jf|=/Q"}A4b&rO- #Fdn#w9Pm}BfP;@oP7""zW{ >WzW-^]`C[ib% DR8d;qT͟.)zJE>j!nL&Q1ܹm. zx1_=;њ\ScoȎپ,kza/]'\ hwNbCИGAݠЄD̶6 [Ls<;RH+ P`,m8.xA>-B!/0`.hTԘMp8+4a:@CjrHzD 5s8f fs@ҁ%J#iyCCe.%'fna-i(fl~N}£h(fd\΂, 1a((߽ >m] 6M:ڹ%2fNLy ͑j8]OL:94'Dʐl6i:ReYQ-G٘RrX,aݼq|V1c$H-ی嗸ihE{)Bc[`Ӊ͕QT VAϙ3TPqB6Ủ0h3> ߘUN\h )`- oy@U ԟliUv~ ]ӑj'b|pBJrSXQ^ӳ6iy Jt_xٌDȼg9n'ۃƷ8*/{L-rMWesD c֡BV,ARADrpmOk3-Vz{\򐃘q 38βXbkg{jTJǖʺ6[ Q"X]Qy}-4ȲkTNgO>~Uo`e90$dF`ƌfx%Ĝ4R?Bs! (fFC-.BskI3p93]c$>eΚ)cmVwTC4=NўUEڿ˼Fbw>\'[-!Auv~BR;&B'7UNGU0}psxaNDe=fYO[[E̪]2[ Q( =ײB̜'%i-(`p޿**_l|7t(F1߂!p؎7'733\3VA`ӍzNfrv'dW=`M;z 0m':VYH߰ɤ'H2'L&J,>loqө)JĘȉmMPjt<9gXf  2L/>0%wXԲ0Q6YBvdm{O( wo f28W1vn{y-X0kcmgԾ'!/ɍ͛EwoL-_|g80j+kCv_".|ʪ&]7tp>E,8xYϮDgR孳g3tB=6GWHYC|yX^nK=w!l m2t 5#U]=/p z"Ez08 hݮ?f@~]l9I3a&㠬V@'%?",4.ұS̷N:[\ !Vyd)sq~LPMfmAQLNȖA"ǘX=m2N0ڞ/!,̈́?x f~Y(GhwOdNA .ʬ!"S-1i,TTvkwy}ϟ1F& ǚM*d󃱡04.dEaȕ?1toQjtv חpp~%v%B6EXvMъ4_ìօ.^WAno6~J5d4N':kGllÛW'*gUAb6Gw>ikmWKLyUyV%gyc]Ԃ.rE&98ZRFLYO9\ᵷU3{V986Wk|nxx/{dmy[(kX?Eo9#;Lwa-t{=h3Xj$וH;)\f1Ac \$"uu6K՛SFBE=n.᲎ݬv:u}8;vT- ncncV~v%QgE6H$/  r/sʽ`O-LY7 m~c`,'3]rIa)(vqCo܀θ77mx:hLz1 Gp4&N38{c  aAH YBoc$l?f) ؎y1c0YDpDdu7 U䢅Zkkr<]Z >0SdweLFZF׿#}Պ7<\rgZEPHE2Y6}Bt +Bkv!R`5Իج\z^pc8c?Vy#n;Jd8ñ; >ﯭe&:snbV ]ĉU{xA78npD7'IxWp\Ա:Y%qέR||<蕨d陱p.$Lx)JE 4MbikP{U ުkiHB~< ChLFA\;;w#@X6(mxjb]HXgŷaƉ[1/Qz t/ClÍXL}X˶bzu =\2A)YE!e菹 9x8hxlch6#] `?{6h@c)3@:2@Йg)K/8#"&[ IVn\Q52`Y.Ǜe#^g'XoȌ 0][}6a2V{P8 m%LC@1YE%ElX4%8J@+vߗ~.c+?0ϙ7(wKX"Uuh"F{xgsM*`Qroj)藍|m)fi*@Kco(jrp_S wmwk~WU<]/.`/]4\H0M~LbHŸ=q0Jǖ5AiNkZ[?] 0?b^-(dOq}h"}BLCՂe(t<OT0J <Ђv#drXߗZ' 0d/9' ߘ01^0mL,=BLjX`BL7C&9 BZқ ‰'5Dzo- DLf2WqUx(bnDTFKu_K}(OLa\#K5O4Jo^鴱Yv(jX-Bȋ˝t$ rk#VQH`Y N<+,|~Cϥ&4hVl{aHknxL,h&\6K#~YUz;tGs}<uzMe]|vK,]72tb1 'Rk>UuWVC}49.@$ kΥעt2bt7F' wUsxY:uθIo+x "F .1$! Ilyi IEg/ɢOlt HnOM%T A^<Eei,|/nc=YGSUGx.HOqW| 7y Z 5. iH5ftsYjN0GRHOmpWڴ/ Ts4 Q xIxh_jvu+]"|<_EY RrM~z %c'nѧL;7(Y2)RA?zIMN#j\DžlTZysKik{!Oj;n#9osfw4vDuI&!^0+S}x\/ҥ</v/qAOSۻz3b~)xK6P*)7jܷ߾7( x340031QpMN,.)+(a8̴ YOniMDž=!Deb^bNeUj^Ç=[6J8?ztkU94 B+O[^@&秤%3ljxкO)sBmjҋss*]]z4ETkAWznd*4Gj{_wP%E >8ĥQV|>ʹ=J/I(`xSܬs60ioXgC{lxVmsF_ 93L&&^e7ԻSB߻wwEٷgowAwŹ$)R%(3:SJSL/DŽ?O荟Jס 6''?;!bJt+A#*A/(43|SߩTLp8bnʭ!%p!Wm06zL4K!%FoVL0.m&'`1e[>Kh=!c?Y ZKOR _9aD!a Y@bI&dJHjDLifC&>L{ns7A0#o>7fwC׽A}нEɤdJy۱\CR ٜsS*9f)S LmE2|9Z9ū9G">#$Sr הfƒ)ud.5Ek5gV-&d,xhF*BX2nꀅ" 4,Ty&g3Na<4Coƒ*p/$O9)ȦP#QTWx0B1֢jZ :cl.GS.l@K^۫i ŌR@LHx͖aO$nb m30,m #=e{K|ÔOkMZXkn0%;wE7gΥ[q:v"r7fmO!뇑އM ]g?QJ8O|b;UCwP* A"RIXY%UT@h]t>rrBWTQ-ckk+3i|J΂|1,L#]9RxE"_74L#r"-,چkd4VUm9H"/=QGQEcHW:# <u[mԄ C|L:4G,c"EF,GqFl(Ba`7lty`( Q3|=`J痏]wmcYdeGYs1Q}sP-Q~g&h~`@"ZLv`;QN>%D *6$ُ4@ԺHԼZ:0џNsϜtqw:w[M5% 'foT8L%1L$.(_*M 9HƧp^e/[؆6#N,G(̣d1AkNxobh8$P @b$&Y$h"_fSB(T1Am_8!4(xuR)Hk#i{oR4k)%#tx~G`BE`Nh:Ӱ6pDIK0xc5{@ԝX,F&l."9O\AT 4lbzgggjv!޵i{aʣF#)d*ʘ䙃bmntTl]oSAZ%mӘ\/xܴh n:llwC쏍YxM7cK𭁲u5ɐs*jXk(&FtnϷh>(VrFɰI4'+*vV{ޠ~E&bQ9(H:};MǠ&D*)::<n,Mܘ:̴_d#[~E-.:h֢DBB7Ïuj#˼?lT\?Þt^Glj)#ޒ1 @H 6%Jy{s_vD!I s&T8xgM >P BLF* 4WZI kHF9-XmmEg8"b3IbB{=4Hd08^ ƖԂPqH$Aj2lNs4o`/'b jD$ N{s=,rD0.?;>7b8#fŘZ p7d.̃N )βQL OgGv(;o8BA"ɩr[Cy-{Z\aKg+-TD-ŵ6rPRfb'4u0W3m5IokSI)7CN>vJv2JcΌzKWfC`V0OYm D)`l*B2/%0.c&0s-~F27F$VJ~9t@b}^6%°VBza.bȭݳeiGج: 2_êL)}j] fq&ᅮ-o{$|%/8:9z6]dL3`kK4c8J |}R7 ,`ķ ?mWG13VZ4lW4 {;cщ,Z}7Ώ -1}󽿘GӏOW!:-C-TRnFV`*p'#A!//Æ[k5ejuܦngn+m {pڴCBD2^*XA"FgL).LQgfwf_Ij4fekI8]KH*'Y;Ȋ"xWS Fz̸oJWiy1dq|֏3葆(bSl˞qYv(8b'Ea/j6 PcQCzZΜZq*K6DuBSdj=yHgEEIY _;)?s?<\S[RਆcT 2WmQ~uzW3ue/w/V,%/OO p.[qu%gL[K]"euV($E9*fG S1H~pⵓfU byaMV0 I%Z^=FRPHM d %OQbb+;".23_2Y~2ҪL,-9ih'MJ8{4%[0z.iJ*@^q7 )y(q:t=\8i$[ $ S.7Y<kw0RBZj^\ܵOm -lVa/#36L!\#_T$Aur$]$c p kU3 fdԟ*ctU? 5g)O{,!x韹2+Yf䜓.HڛuIP䑍{aXź+Az_GY~XFn#)yM`u"|&ٔ䚫R/1FXAN$] I"9u:TIͺ#^QSrz] M[+Xr5^rz3况?ƚD[_piЧ@.Gju6BQ-C-eі>'drMhGB(όՖ 1H nڊ0xȧrFs'1u`ti^|pxm+!rBi Rc^@Sru=q"\$P @6V5JwֵV~o^2M-܄&n(1v*X ńBgQMj:r o:] SC?vEb \oF/)d9¦'IvB'[!x*nI^ɠA) b9dmr( 1ŲבqQo$rlsfZK` 4d*-E)cDgUZK]2le`ϫ2lW$lG2?2iVeX֜6uJ?iʞUBTmJoLX yLNm%rЕL'}%ޤ>K_ճ{bL(]N#~5~ PV~퉩)_BB(mҋ'۠ug^exlVe+L<$0N0G@ =rDmKW!7XU"H;r8%wxd)<1@vRfxbj6Ov m&P̾ή9,.L4cPGn4iuh9NS6x8흶 hWuáF׌o8 ui \+@ٗ|~OY]GSX@ݱ|ɀN~GϏU@:P3'8Jrxd+eVnNmϨܘ\h7>#EzVNT$E2>Gѿd !.fy8*.쀊-F8(]׸/BhP@ "^`׆(bQ'$ڣsA sΈFP*NGhUȳndHTtFp% hb2-zE)գiG&^a;eUS# ̛Vj`4Q>5;mӡ@λT%Bݰ'-)XT(Rm_\[S z`~odH)Ftc:Y03ޠv!|AW7B`JM_>V"AC!xVi-!cy?~U!W9HoR./R]=h[W{.]H2a2S^'ط8Z nVUZ%}Us3 ڧQ]ze%UvKI; GW/,(p3 bX(Vj 'M"Dt|s4J뽣K]L Yұ~풨jCC~;W5W+s-O3LJ/%#/ë́Qbg9*r:ϤGHΠTn]:do:klPEy~ Bf9[jR[ 7󳋞H4 \ l}bLM16D Kpwz}p)hTBZPU82\D=hE}4p4KbC!@DM;pxT ՃXǂs SԃX ,]"kliתU?jΚ+X,W:Ygi' _^o;d d] \R3JJsݬDIu#yhJrėb`#YIDOU*ƃ+g*2_UTO,ź`ৱnWRBye^| .#Ίl6hx;ԑovmcbu;{Yngz/V/?MGU*N>t~vh_lTI  H`2!$e(ki2G86Vc٧syMXUI\3}tlp;8;Qt UƱUfT:8Y<{k_ݮjZ| OݩhyTádu1=:dxth:3yt0NCPǟ?X˝c;7gБo"~M%"jUU]7 oa Vs9IGrوU+]aFdyFfƶD@iDeUlύ?#>YOD(2Wk;EZ@VV*G1?@$8- ydQRʶnëc,͢YT+oӀsXW\sÙQ`X-Epp;ؿ+B8ʔi*:G R% G9R`e,K!D*tZ:+U,Q n(TU'ˆ ֭9*6rT=Zl&U3G9fw $% bN&a{h-ևl7J0QpQC*?c)GŧЖ29MjكkM/eV^u q#>K7QVUO&k*mW#c~V-qh+drΎ-ɹ~'њ$"n>%ևJ=CNq}CHj4]]BzK f3&G Ee)E>`bQpvNqBy~S+wLk?(dPی z IݰAR/B6j=@N 0zH|H 5z4 ¼r"۱郵0ַu*&JR}ԭn>f\dh(lO[jZZk>~7*N5Ê1dO ֟L3;7j+<a]GVJ# IJ N6> O;ʣ8,oReF'2%7M_lw.,OÁz'%Xm-xSc4g xo%HФ&]62k)p_ xQdW-]Te^{@g6ɞЌ}햖w-iiw&ԅWaA~Qfj+Krӓ2UP*ֿv|l "i6nf k$+:0v/U)Xlhұ=l%cl4frGm:F&VQXk!!SA*%mSB(AWSɃ+j"0XNtrn7^@sQP$Ys4kџ<%?]"#w8ܒ2/m 6zm<"4ȇF|e4!_ &0E8ĤwkNӪŒ_vK3}ˢSsd64VbOHi>ROMZܹs$ rf+ JAMܾ+G0UK/ץp#7h-XmX8ԌB#!XN~B2Ës(ieumbSX5\A*)`u@,/8bjchcEPnzHmI:ov#j J ~Nhk z1pd*(S,Vawm&dR 6NN6aخPh/YWo>6zFgjۃn5hu;+x\Tbpzt*iXۀuaK2C)"# -"*#ť=-6\ iJ! JCԦ f߇ǫ!$L&(IBͅ_Fl<&6{5[̲Tత ǜG{DTKjM󊷛ܝ^6ߘ;ƨ>Vx`{ŐNXL!!B >c.eՒT z(% %gad@aq{=I4HbXk5RP (Q|D6=p̘L 7Nwb=oda|76E8K]?WWe*Uu .'?T.\BjɓrnFPh|)o+f[<5LjlëڏZq1O3_NȬZaRN`_4Pu3/9Do uL ^@vÀ;ã8oNwxDaƌk, ʒ`>JR9+b}ex=vRNY@{1aIb<T_roF J/BVzipiv1Z2bW'fs4cp2"r=yLS֜Ik&\2exUFlScOOZ.re2~z"]7Iΰs୅t4ٯKm#_ aimY\{ Hχ|cHj"]$j=ad޸oܹ9=-DԜG*KQrzi_eIvAG>R:|>X&aLUȳouu$,4^tKsBhzǸ5'D$cZvk27<}p6塃 7ZI%^?=Ҟ[4#WlR<ِ#LHۼ86oum@)O8ږ-(QP nEG&LH݂1%̢v}(`e~4mNr-1&D>|&tD$x!8''A>icxbт̼F)Y$|:OI?ͧ-G>>>ԃA$H4V+/@ߦ 8$Sf$i,R`>@[$B:Ap<88g|?$?_]_ ryEN./Nφg9@~vq qyj #R2~0|@-, %!`D4 4G4^8޳gt1ips%tO|vo /$_1F^ %&BXBX&9ԋ"O`8Ü$L`2?OX X)qonJ>?#`CіM gmshY+:1[:ei ShY5o~|`y] oJf4Fo 'w]Dd,KHw7@8+@AgSJqİa41 pV@ cɡFi}\GPPv꠭j_B.@i㦒Vq0'y Ka%b+Kurpn6F A{DHi1EtE;t $cݩ6w.XvU2uY\y*iMf.4<\Q"3F0ln x'|n<4A6d>6JܨtvCT0X0(R+ $)WG;vy.&*M6&__D .*+b5GE.#AvJ$Bx9 :hO)zzEx^Hdހ?Ö«tKds(IOsk%N"z#ԟةgcҾx~h*oa.+n__R2YSE$[T]X(>pGfJwֲv? 5lHb}p`]K5"RnERlC"% P%I=M@KnNlE~JøܤБG8!pnڴs\TmwY;o7{Ax#ZsGcol{9Y7nw tfnχ;(L=prBbXԟUjFU|9 YEvx[9,6r-qd!_mUa dBaxi{ch$lQ00bVyy"o{.ɭK?zERV¼h&`}Xc9P^vι*"+=@]7h&8@B62T9WQH39&-nMekK2GM6‹G\$%+P5 ZpV1mkFkvQ_i'ˋEʽ|W lG nЁ;ap#)d5o2Q&JijNN5L)8հ0e.9r|d Ph ΗI[>c3 +7fɆx67PpFҌ)FU6|Pj̭,LVpz| P&4Nqv^w2(^ebc)@=:8Sm1&4]&k`Q`skNg4fF-,B *R(?Y;rS_.}.LYېg1[mZuz+hp{Ÿ t6Fѳjr3X7{0][9̂mƹ!%2$+]Ґ~J ;>Q-j2 8ll]8D# "J L( ,c/5|vg}4ڳFMvLP/o6mds29ϳPJRQ:ezn.3[5pJqdZHn]ı3W齢w+fy"U*?sLuo2Ko*N]Fq(%Tׇ0{?z7^Tv0_` qu&NY5{f pG`cIytVu)6Is&g`o SDKD>+̽؛)S Oo9;¬d&,ƭ\E 6WP`UpurGNMUJx$ZyFԑyf lN 2gb&-X7o$r]̅Hݯ1SrQ 47uS5 uPɟ{q9D,[f!6.!+LMm،!s/'1K#~"Lk)‚"QmlKPBoWC9?INd'P~XyfAfF0`uc0rcEsUG_iGy`q5kC5Q܎0_BVⷺ$5&P!w^0H;MxCIZuMHc3"=<̆|CQ#y ~8!GВ8J8HBbJQ.|i WVHhhj>nWԐk8I$HKEv0('8tFHɿV0am\ Fv?֣-'8M.euقj+ڇw2vw l|l[$%*`|1q- *6|`g 8xȖgjRuOC^2K`+$Y3[/l,|}jxyzkz!K\_eA]RJħ.ǪmwR֮ tuӪWm7 B!&/]rWT䦯vgzy?l@n9pS/v(jIN2v& 4 HYfkco.C4Ta,"L#9Uj/5}y7N(+[Z MMR* ,V<~ݪqdt[&k:E7Hy!/M{6;GIjL.e6H+7imbfD8'l/.q/ #}h/9:Y'LߌnʤM;D0uc^JsZr}Z47ӳ>b`cB~ʄ]J%Ne?XN ~TH]3Tc\4l.F}~+1"b4}⫢, N CZը[#W h W<|F:7yP5o xL 6֗ Q1$xS[9E.yx Szi7 U.l5-,,/vh)Z/qҬe rN)lH No%M),y:eT!QC-Jw[ ?P05\Pz(@jm(d>{_AI}*мgJ)nPwvE8ѧdAhURPmu>eT$`BAF8AFɚ慱' |)yF 7LqGnդ>q!#0qK2)Hv:8ftD!;})o,}]"T {/pO;n4cH^2Ven?4I쀱IFo⃘ɋS|(_}v{^k+Y,u[<:z%DHVaKb~52KuRYC)A {r<軃LU'cBSkGˠ?]*3zGd2qKsL9&dt"rbkN3rW]jS4W`gK6oVu[oӷЫߤ'8==AуeJqYʄPRl_-{pXZDK %;AhdkUs8 CŬ(hMI?_OʚDIhRu&^C=%L$/6d(>-"- U=V˼ZEx~-Vb@z:jŎh%TW^ʽ_],_e&z>TiSM@:EUlC_박5q#*/g?^,9Ϭ^/a߭0imi*f^YQ(bՊdcUo*WeP u LZjtזKRch&}.&>(~^>;N} Lq݆cό;U&& U'۲9V%Hbv<="K˷@YMp0@B ]Gz-M`o g[uH^ʬy/CC^*אt3)sp2>9Y[.AQYt6ceKUd麅FxkY5ŦJ\Aj4RK C U:1*Cfxg0P,YtW٫PPyxmlb&͵ٗ1m۞xAV}&Ӕ7anC:qi37סӹR#97b]ռYl ^xXOWWȅx :ѮҖ -dvZOݗݾawǾ/VscwΙ ~03}T'}tÆq[1J5OHFϚCU%.+p=TˢNJ"#rSx֜cX)4:$S礥9yԤ2EE:/Wᆱ\~s~~tkAy˨ V&?mUgI]P놧( M ] DaRr >a G5:5)Q^h]ks8ԫ:A0;r&=㯫*&RPE\<2^0ޣxHVtJLQ7G&],4S2疦 6KR4Z1W):W+JtY";2l4i!_ϒ%e7j^~͒1g,-g Ƕ8@ڬ/EYz}OٻLۥgAv5 r,\i_k/> Abo[UD%bRja~x+^̄tӡ, <댽qT!=llP%-xg힏/Ucĩ rƫ7RT2_Yt\[IO+&U-RNLlPDZѠQOA7{#q84A6QD `%$m0bWv[EKܞ!v~]kQ wmvv?'rev 1 1XO XH칛K#ߙN MrB1&`S)wB7lfssXr 5yF7x Wߩ*IS]h6(hJư7d_UEf~ p"Ix=^h2^WkGbS$%5<11| _;}@֓;mZjV)u4^`BÈ9{ջbWM)_=s';eHDO5qw(ȗ7Znq*ܻ\"SiiDZ^7I(T?g_C~0FrE4!dLSLSߖGX/`+ĥeCM cjh_Af̕aJ*lF m4Auͅ,lbQMCKbj!!| lW\G\nv=q!Tu]{rwR';}`pIZopwXY;q4n\kgta =DA ˅ NI3D4tNv*${dW#sw|`?R'|Se#2'Xi~0X/1_ *(ܫY чwLtE%AH{D*qGĖGs"ҝE " * wAIgpՍgO>Ws~8,c r5]*Le/[`o$f/Yk-e`R#.&r]|8;:,bw=܉4x3+u2ZjwKⶇa˴ a(aV_َ7SdT]®Mvl- ѹBA1PfyfOΧb$o]6[|67%i6C6Dq|xoq> px}Q=KA;vuGȁm!`!6:ln{΁B6x{ˆq`}3yyuv\֑hg囏uHt|t4lt7+D԰τN*#8+TĎP8lӲoQv`LI(Q° Xhw $  6^,b Rԃ0Rula).78}#$["έ湩QzaDrH_ɲݐ&j9l؛= `#~4~.xYmo6_q6kQ7UVcENbZmtߝDɔ%'qD_sϽ:G=5zN:~.BNL>r!#蟾 4T= [B$5GBAjyADK.Xl^-r3 #@mA`ڀ^j_{fsR2YLNƣ3g:'جB$ߵHX|6C!1H8!a-E K8 ҉um e,ЅۄCwvIɗ //q.&Ga8 &.܇ Y0) R\K(P[sib'Z1OVBK HM(VB3U좍zO"uPfL52v|W3yD(&A)fk%<\(̤ +00g#C0BH)4|o>$'JlR!uCV2Z tAeиd8>6t]FyE&ۓr6[Lmٞ6x7z\3w[ H_$)xg?TR-wxB 2c"]Ci뵧ˋc0@}@YxNG q{m=# {/y0c K%#)N)HPmobtwY(ut_8S| 7u3|BBzp@⥣9!,-xRC8 Q} G"v<4f%¼'BKCN4CqdYVA/կjj1%yۍT߻D+JAENna5,BXB o%gzNjB@LB@16#x׵{v|~pz Cz7xo~@ LS7 W>wL8h7ٜ\~假σPRK6 vыڕs9n8 S͔n-ޔr^!z*RDJwh=\qY\dńDJ07@%cvRZq/UdOLxzg?{ZFRQdO8uxUuzbŖY|ɖ\/@bˠX)4m"qVQ668Bk蜗K`t%bDCU Šr t;ӜӐCb_T*xz5=x՝YY &u=5 >6k()w*-|ca^G EUgfX;IwGp x3p_$I@dxœ]J29Q2ͲFYXΛb$ͳ_K:c%6x''7ȟĦS[JB5C{LShݫAͻ`k@w]a44=`‰%N^)I/1~c7껲8d.a3>O4+ZheTz^StFa7p10Q$ F=_tmkl[8~VCׅ-Y@P"E:&[̊ǻǮX020Y94cǮo[;q1ל&b`Py_{Oְx4ڼ'̘x>HR.#Wb朒$ma3pt7t,%SlVIڛ"d8_a1ԋ-?Y_ys0fIWpaNyɉȞ2Mڻ>&=e7(=H{KY羷ƒ5q4%ؿ u k>mʴ+7X9o27xVKoWk(&2N,;/,G ҴI%;_Ct`*En}m骿J,+/Tl*v5c^Xq|~k~P8cA.#>-zbϧ?7Rkw4AUL}Â|5IbrfLzMŚ9aH 'qϱi35$`AIGo3 EQ& "&<=4 ɂMj "\%ACv \6G,$@]`HǚqXZ.gY}F:Knzg[vS`f@dnh96hPgXluUirq{R7(jߎWL%dEN* !F׾ў;sޘ!]C[oN/!LxB-tZ˕FR*_3\Ea1V!Ed th (y;锳VGv[=3![֪$3\9;JҭqM_AMnaDhZwX\GwC=_H!=t'7O?&-DwIC*(J u-뿐^ F&,'{ǘ.q? "Hַ#WeBo(:.fN,]tIF[BKKz*N<0вzXIc՜0 1b0m\PôY1r7iSAqRɐlhuWF<oO/cP'xzF5O:2 XQ$kg ٛ`i1bMmkH^&Nl ٴ"o.{FNfW=JZKOppt\)ٺ]l(121W\\j Q] ͓ف2 b>)rDC+ }7>CYۨ00<à7)_y/AUQ=I̊'tv=GU/.G_ Sƃ"z'٥ ;zGR˦ӻ$Aidљ5ks2}5OxU]o0}ϯJ,KR2iu$P&ƑMU;GP ~hqN߅S-pVu-puFsd~^'|R80 _%iY»9[*zA$RB rymsX7O?%㒤"rRq\O&!9{rkmWy9kVLh0^6#ko[Di~k aD4{12++^כ0a0ĵ[X'~BD#ejUȹ ĪYTg@ 'D 2"|ɐmX0sj U+zy+l(x28 4xo;z\Pˬ{X*^㬻=4}!ExТ>ņG^%b'5V e| RɸUXo'EB@;1Hbж"1Ū9Nx_/nsi4sw76^]{|=^O8mD!l yBU!>(j0bx1Ko qn9|v[S|/s U_[l/*`{4Y~/vwd:Qَ"~xWmo6_qː@vIabntB]9v0DdQ#8)[l^Qw`\:ViA~ ˆ`=ߵcɖ˕To N_[Ɩ`:=7У>O9qB<33]@Y XJL bRX-LB*(b_Σ0($GaʕcPz ` IP XPT"+)clzD{c|ُ2=џg>9F͊8B!h[ z 0d)I
L|@B^A9]w_R"`ALK94u!AtZזp6hXgQp,s}&).– 9q=#箋b)]"T%a"ǜ"C2E|rKs'S Qc;Ós{ W7~r%xAJ>$ӊ;<Q>ɒ{4X"F,wHY5%^ȫ b&Z`wp`Hl2)4֫|T,E}Z'\]k4e'Sфad$DAxhu~p@+<7FX;~a1X,Q,ˑb]Q,oCl( `6̆P0 fCl(M`6̦T0 fSl*M`j-E-heZheQ⠥-8hQ@hh:(Cs]u`}uuТ k }u1Q5> uutKl)J̖Kl)J̖ 0WM"eڨ8ŏ*|D2wBE.qArhwY˦ i刭s /xp|jM[`tllE) 6tز9I? 3ϳ~>oPW`Ro ݖ °c2s)WI lI zKZS>Á`nB NOx5-jZ9>oŶVKDfNos;sbY6MR_U8gVziCӾcc(4eC)?1zd?\z|wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/85/0000755000175100017510000000000015203543564023460 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/85/d9f3adde30307ce131c46a63cc90739da26c740000444000175100017510000000061315203543564030662 0ustar runnerrunnerx+)JMU017g040031QHK/M+)Kfdx_֝$Ҷ^P%ٺ9E)@}GKK:9>ʭ,]mͳY=e<}u\&XAfV&'&d>6{:#8M&P7 +*YXp[U]_N*M,JCI}38R'ZSWX\T 3{-v/t:\U1ЅI9) &r ދ|,?/߭CtG xh^`LyqrnI~zzNj1ĽyF-xZGʲԢb`f楁"(ˢEU}ާwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/79/0000755000175100017510000000000015203543565023464 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/79/ffca9cc19ec0fb012bef702ab09ab4e8e577500000444000175100017510000000216615203543565031170 0ustar runnerrunnerx}PTUƗ%Pi $%<. `hd9r{/e٤iG)jb R ('+A!d:-vas=g!Q(%J"8(G9TKnuu{ݍZ{9`j9ӀÑ8"qb̲鞃*%%CC;m8!0 'H hX-A!}cD՚/5d*eC!I䐏ℼuwoM}'D]*lkN>p]vbcqk9HY iX;CP,x<ީs 8q-yqumTG dybMMWCͯrkI YSvMVA j1^lL%W˟R'G E yW薚 ruMŹUkZRtY=STE_0'aC8eSqsgNhޡv/Kz"?۞NY G%/zGsF1v$]3rtAXG7Zk36"vXؑ6ƱDe6-_Xc.kﯣ)Yru R>ŗ] /f0 & O?AXU u̘9*̻}a5`#̽S]i-q}[˦P(ݏԴy,Nrzzm)wSTzޏ=Ξqȡg% Y;fy<+M)Kg;"W=fT$~;ycL"'Ҭs wYAiI_y95jHӸ1J4 Ckt}} m mN}gFݷ t/,?bĨl`I0>#FU6Mo_$)R{*~h#>҂P[W>O0 sW;0T1E ˆO*K;>xٔ=6)JKB~Js MաFb3gЋ{@H0D @lE?J2}K'|%wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a2/0000755000175100017510000000000015203543564023526 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a2/713541702599f2355bc4c3fe027039a88757cb0000444000175100017510000001722015203543564030312 0ustar runnerrunnerx]m8+Y`%;;I dyL$ e[r'ˮH,R"t>3ӖOꩧHz\/>}?eO?^}O/cgoٛ;;ߎ]!Iز|[W?6s ɋ4X%[kWm\T,_uO*Ygv_UŎaGt.WUZcI6?xfq6g+0Ys=/La"V右|<8E, uQwlgHiY宂#{tvV 7%?`囏uRe%EfvtGNgIV&,$񍐗9܋/x(P؋E$`*)JH:;m5PsW o93w =[<͸<KJCs Wʷpd+c ەb! 5ū/7OO߿'`\r؛\%5TٮS8Pqg< yCxq~){g>LfgbLubIKo@t Ql_%@,I Ƙ͠>Zl=u-QCx =H|flӄD}^*( x.{{^Vyv򔱓drrrz2>~xʏǣ? ({ysOѻF Al'YكGG~p$nV4_Xדd6y~Nx=toth:N}˷H!4$4+:  lfwA rZ ʢkZ@w6O3" b+lc_P/qY{mOa&x"Bi^G+eDG\ݾHuZ|m1!ijHT<ە !n=*>mmyݭt\3jjuyY2ZstdE$L)LuJmK կoŬ#SwYS5eoGIևũv$ X/AXuJhWvu?nVY[``VAsX %^?~+}IM䖫/$R!m72P2|ǝ6QX4ݲou$ٮ(aQsz c| ݺ~~A,逸nWM\R(%50a܉Mg_+XPv u'^K,؈o"u p,y!s8O{^P} i 8qDyNt2~&7Y?ǣeRE9$8-v|)?U`+/N6x܏bWϑS [[q>_)efyњC4wl 8+wي 6.80 3im-hCOh˟і-?--v -VH Bg_ve޶1x&nkdi${־<{k[+?R]~ꚃ&+`UC@t6T%(T B19](aaTNU,zU3KGq R>?! Iu;,EUdB]6+qF`B'ZO)$y<=VY+kKmr.r U4Y% W] P]t]oZxuv7'.L PLJ]B;o3(Y^#mnI1 t3I܍)!C&n!L|7TueFpcKm Supt2TYi(mơ,-y (e{{ t*pn\%LwC!a׷ {Qu͡DЂ4-itVn,MAo+sPjtBӲz )Y}+I*l5C)܊t*nGūNmWzG@}z;uSz u R@(*Gu u 0}" G{# Y `kZK uM/~kI ˛n1P_" UfXb <&ۍ;.mũ6JXbwcAkE ^Cm nUxuz̰J#7:CRd4:}Baa+a)?J/|鄦mw'tUꯋЕOnNz$t yՌD'̪UA+#_GP>?HN*} <<4@3]fO!:3VI>P %0B\&GBD2*/7H:uFQ/mjn0NpMp99He+FS68֥Wم!;9^+``.:D;*TB=ґHaݑ(%=أ*$up$C'g|\ArK|+ D$4JB:1G(ҬQ]㙑27HH6;&@#桨_(>tΠBkݰeW>3S;;k3 EO: WJw{7 #{D chHI^DժwՐfd;DciƂBĪ J{( [y SHא2 #WP:o6<^G+xʐz+Dhy:cȈ!΄UA V?I.}A1F]UHIU `hi̇[! PL]1F Ot&]tMpQL @WL1@:ObVqg [cG"S4t=y*ˎFΪ13DK6qe.3y6ӵhBsvO:f -Or*0=م@ S:(խ:ܻȬseMT1 *q8:?H,V"ʡ MC!9 Z,((tN¨pQ ]H׏2 "WP:o<6vHK"5@-QеdAFsbՒՒlFdoPGdQPW-ySTʭ@D桟?]9hf[bIozwX]R2n|(U>c/!!PXE?txx ey*:*Z1LXJw$[j}Ae1F]UHIULayB,PtBeG.C.Ah]p U~t~U1^ɗr=xA|!6 BSuʪ5KZObv '0WyQuo4;T3Cb C!Jt`6޳g*"xqZCS#tRzDTⵎEVנ6>~\E^v\'$=l8LXIx[ .h3h%(aՀ02 yJ@!'%1HWAYS>?9Gp;,qV֡pTaD{!8=E N/x|#J۹al.2w`Eo \@7ėe Zvzr_%rg Ox4޳[kޒަ_(hu? ZC@tڇ lݜ:0bxjAǠaCDoq}6eP1@:MFЄDĭay|  ? Ƙh)WUMHp,e/s)@t)F[q B*}cPhbS2 sU o!/: *r((6.7+ƵT!ɀ&&CS CeLNԫzjwLJSTtTYeM/H,H49KnS{Cxt"4CׯK_yÑvC@Uln"YK|'F mRtݚA<Ŋ@X*@)7V2#; $"} Pt$:MT q3岽؉( #GEڑso9꼎;D>hB?䬉lJX1 ^ToQbsP ) Z/( ;ţW>'fUXtU4~+M"~8s{Rso90 K˜#@]GMqF`\eaSʠ~(iIB . 3<H$(H/FDc$1HW!AҩSEUfef%<pӲ\0=&5f82JKuk;0H׭BUc>t&MJknu$nL.az ;Tk$5ψ/(/K5OaZ*V3\`42+]1Kj3Lrkz4XcCp8}`g@:ю#<$E _fwDWT~J+$\3ĘDQ(cw%(0jIa֥J`>ѧW d uBu3Nivy\%*U?\_척Ej;k'6Kn-٬oIJSnswʙ1lJ}YYR)@%AC1kɷHځ$Z-@ gF :a&AF} ɥakj[[rvm~tvFocx`wre(p N:Aφ/[_)V٦)޵.)ޤٍ 0t~H2?İ2{̟,CQڅAX:j_/8_q* u߮C齫!Z]Ç ` eҳd^wK$mnP۫fp{ߠ Z+^'gL ;ѧ7go?Eޟ:wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/2c/0000755000175100017510000000000015203543564023530 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/2c/e0e0c5df2aa95b40adf9eb665de4dd9f3281f20000444000175100017510000000104615203543564031243 0ustar runnerrunnerxTQk0޳~AإPHK֧"mmd$9i$ˎOºᄏJ\}Ae")&+uIBU~P  [O ]V 3$NQM i/z*DIW4zinI%{EL[e]ndI^;{ޏK 7vҥ %s{C X! M;<._*O=.+!lppJS*Rry@0$Qn]ӺXݏǧ4e5] fvmK9!i~OߞκC0><ɚZ44Ao$zSo +#k1b\f-_= ,  l r/_z;+dVh_Da0)ǸRm(*V rڰ5%[zxvdN7p;nOHg0E|c8Zgpc{7v\t\!uٿSˮ^ā߻! qwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/93/0000755000175100017510000000000015203543565023460 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/93/557fb56e59cf408d38979cf813e7601f39da950000444000175100017510000001032515203543564030521 0ustar runnerrunnerx[]sͳ~EGlge#2U\5@fؙAX JmRe%}?0>oN?,H jZ0(c:Q?2^<'t?Tx1 zL'vɝNAqDJ3᳚&>kjhdk*=NRlDA4U(_Oh4Gzjs0g<(iuس;!c*@MkKldxDYHEp9&0nSrL!y<&WX0 YM.3L(F9ND: I!";YSoU$f, T4Y&RgCerOzr>0^QQʛY?dlcZU:!p9 rf~,Dݔ1q^PzSwΏ͆P~?fsWXѭ_UJ_՗fQS^:]ռk5=|l_vZMDǁTþ+uu/o@~l5_ko]o^޷]uw߽<v})ޭT|[-9~eky}W7VÇ8_d L #\g$af_ǠMTP_)`< _P$%B:kYNV/Cif6OOXkQ"BԖSl.N['1_ 0A鏱g[#12 Rx8SSa3"N) fs" RF+Ԝ٫3a0 N#F$/!`eq<jE -4*V#kL >1@j\kS-֥u=hx^٣xx$9t|._v㌮q(7 lyڵwn8ː E/w̆,^fRbB~o/"O y'Fz!"! OLP#d(Lq"jhJeL'ѡS  nh"(*%ICH{D&[dnX撒g2-#)݌>H:$pMy:V+D%2z®"x~?RY(k h90S7Ćl(:Wtwp]ޝ?]Ց4~C̩lD$(x%8DaD#+x !/F#D~={Rea84k s0S K-3%:Ɓ!a((N$j  u7ڀČ;ߵ[-3؈% Tx Rp)ī4ȯRXw$hHTITԉjIJLnY`i+314 \ YA3NlPAtrq iYRE&$q7iN bKe5b^ iіi6$+tc)Ndp~IȐ!չY'.ezHH1w34iCك$/#rGA,1mJpv(n{4I)NXR)X2oxאgR5i՚ D`z]аVqĨd:SDuQQoUr16[@nJ?*Z Y];=]t*8(|ҚR)X]DqI{ĩ$F1Ua5Ə]a̺\VU&cɭ&86:g6x?ʖ(QF5t8y@|٭?5QnGOB~o VR#VNY[#Ř0#$կ :+ `pNn?<2p @e`*6`Ca :{VA_sWkVv b/ߦ%pļx`1Z(hHP@}86|'m->Y͚ƥ6Q.dT]42)p+][=OfGͷml V`ogPMMlǝ.k.;z][}SGb nsFB?~ĕ1q4*^֌ #F$`Hw1?ˍ!b>j97DU@™#+$߅I_|Y $BK¡0 芞"tN03uc{P[m O(x[#rw^B=0=Ey74;!ܷKmnxG^sE {`Xc$dԭx*x 2fFiT{7A:9 x-J/yisT16u*ԾQT&+ɐ]"6<#a+etF]p TJX^ 6k&LvԶ;.wUt hJ |;3u7kM":zoJJA+-Mɨچw M?c6EUTUE,m* U.z%; &|9k9cg- wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/93/befed8cbfb20ce3f2c9b7094ffecb28263791c0000444000175100017510000000374015203543565031265 0ustar runnerrunnerxX[oHg~ř@dBOK3ۑp p&O˸ ĎweCL'3w:<7y4qQȃB#uwOb;qݻޔeCMs{çb>[:P?ة;_@Lh9 -D`B7?/G컉SSÉ>Z41[ ^h2c󃣏k蹳)1MZrᵬұ^KbNpp̦7uj)jmuptkȱ'L_sJ?MaUXQi/<Bq\Q/]OolsP?< l$LJ󇭎N~(J=6׆PG29,.oP;d_s؏C_04#k'C-zQ2S-[ut:ܿ}yYi6Y jڤg>ran3}m=VZv:Zw-k?%/c?dJݱvGcǫoM"٣1Y̭#"5GJk&isj%Jψj_װ5WR>{riښdl"k:LҦQ( N<8Мݛb=l`0 7n`*D͹Kv'mv 2RX$7Z*sD֣k6( LHw b1 w%v~e`wrM@qHԮz)hiѻ|a q,5\?2lQk e|hR*R݀U5 /6@CXV{Fo)3*#kKX  jگʤϭ оыTiDM&ëɬϬ#Q͔ig#AoZ<Yb$4m}]X@ <6o4"UA6PK:EEE@e S<'. l_BT[An?w6ބdxۤ^: oіjd)5Y>}Gb8zwjp_GsuuH[5zfasg0_sK,S\-}*zpZ t29dѕ%! =Doh0L%ߧ{JpP$zKLE? /=1W #:SqтWA}>|tC4:S9tZVу;snG?_rm/Q(Qv7ޞ, /ON fp_?t\J9M502Қq׊dpʷD_;}*wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/f9/0000755000175100017510000000000015203543565023543 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/f9/8046b5a9b27c59010168f7fc390a728c8bc07e0000444000175100017510000000731715203543565030552 0ustar runnerrunnerxZ_s۸?ΝdWRrSg[v4Q$'EB/#);M{dsmD`X`sׯbr!"ʤXD@& 6 e&> f'tˢ_~%fIq%4ȝQGiBV2Xf>@¶XdRt!-e[,ǂt^Q%K tQ0 k0DEXIq:+NϘH(XD Ip=F*"yEh(!`hi ; >bтvG-ˆPϷ sdi/L2 Cy;!* k1U"Zl$! S).FE#m-H0jaޟ7teZ=|l`a8dLCfOv~lҌ5Lfvߛ`*&kqڛ-w3Io4,77,Fmt7Ob<wAc`t+bh< Hgc1AjǺ?z̽`-nҞMfD}܍}`t3hUowI> w5<ܾwuovWao-{zD4w<z=w5Gh6gʥi-zr3=+Hf#Z7+,$j@H`钗~o\PϨL*}yrWmklvɷdwuۼHXp òyw1|ډ4hzw[8q5OO1lGn/*Umm`UpAVWM:TP@c1Y7M? Vi[Qe,NF*ϣ'$0[ I/~PlQEa ]I!nDđD I?_K lYZ>с$9U[Rxq+wr c;tbaj7D2^M=ԥ}vE_/`"[`AQΥ Ch9CmjP NS3kR lP{i H/#;*y8B4z!QSafG>Uhm^@C}E(ɾ50NӉB ~ \yYW,*hFy:G)ߪNDu >l8Jng*(DŹX陇aNZ[@ ar8-TU̢S;*Pd\,m9AuZXѢƚxξ=t:*k–Uw"WhHsVN6VuQ[ZG͡.T2U%Ns!zSR.plfeb ,F82Wrb1Y\0:YCQr\R udVNp AW1f ؘ:dK䍹jK2.MJj*}BB8=+Vx)~t q}RԂxJ5a<`åKƉ,UTVH Q l}CDZEQ홂֕ Ƙ1VT0_ZOtԞ@4Y)-&R-+11=Uiz|p GS K&)4bbI3٩ތ(Qn8#w:HBW+H%2WOaN @YXaՌvDL1~`ޔ BQRci̪ <* ˤF`TٚE[0I&ɻL1$qd#BwI=O g dӔԘp⚎+; Gj_XXS?˦_Y5}/ghw蚿Ϛ5ͳZ ҴتZ\HW7c gƾ='!żK()(M!P <*35,y&TDfOtTEEu&vJEr+M4JqoO&P'h,YDT>)`?aQkƖSejj1m9tQK;9='88lqK<f 2\S+EOqHّ] hg?#"q/opXxkV~A;%s xUL:^EqG&Rر~#%3q4}XEP0?9֕f(fLrQ7j7&O.,FWU3&@3a4tEԇ@ϹJ̓t쭚HwC@88H0;Dō.W45$NUza%g}gy ǕO]\O?;qf;ClhY_Eh8-E β)jKˢƿQ3pG|{h μx^vInj!Ҭz}2rṬpjF2IYO(aqѵ1n*-8'7yAŢt/_:ہ"ox"5!5rc_1ʚYz?7EmLia7aPͪRKe ҹJjtt4xr]{Ւ Й)8hAo7xf{=P ogK)gޤrnHO^|ѢW૨hvFۣ\lh[.kQn);U ˞P-j_o9{V )*㷓4wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/c3/0000755000175100017510000000000015203543564023531 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/c3/a0ef12330ea71c38a156c86f89abc60017e9050000444000175100017510000000150015203543564030562 0ustar runnerrunnerx}TNA ~xaRZ jSU]ft. مV}cO?vNQ ZUv'˔,2-%A{;C -B AifdOh 1uhF?HR%IτXAcTa*XI[\Eܠth{~q;;rv(2aԱ oM} X6[*tdFud5=`UptuB3*AI *oLz;Z" `^gt$GnP+˝2h`UiIJ$!P Yh=H砽&6חgtFCI.'$҆Z 42oҠj6h {bg/y2sVhrZԈh`4?'Fכ:^1>md}G OqS>$b/-Ŵ|BJVE n ih2@(`) uijwv4~5WG]יLHӤ Ym>ͷ#Cr,wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/44/0000755000175100017510000000000015203543565023454 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/44/2dee4e1c1044cd1b6384b7f7cb4a0bf0f3e9820000444000175100017510000000065115203543565031014 0ustar runnerrunnerxSn0 ݙ-LNդI/(D :~Nh eLƁTUl}~þy rm,.֩c?Ϣ8xzb0֢󎚶98DY[ *0oݨ{tW G*+ 6΃T } a[zW!|gz$a"> +yq?f)]C4±`'9I*ɤQP9؀17bEe*Ԇ.thrXFy ظԉnG\'#x~^]ۦUItPk;1|YYPdV`KxO~#yUJuV)H3w)=*Gң[ǭ]E?wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/info/0000755000175100017510000000000015203543560024153 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/1a/0000755000175100017510000000000015203543565023526 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/1a/8f96f9eb5a5c124e76cfe19ad326e2fffc978a0000444000175100017510000000654315203543565031225 0ustar runnerrunnerx[[SHg~E:' ^6R1Gd_TؒK!d&}}Zjɲ P`si1Y&q?NNH(2ΣTyTƙM<4Yߦ"'O^Q^DR\Fq.SteYĄi!S9WiY[S)E2E^ɶƷb- IFq_PLA s/KMx&,KQ|bL7+0DD+_Hq8/ "MkFvdTfyM G@fFHѽY$$P| `,iNOy\&7$4gɛc=$R m'9U:WVPhR!N1=22A$aX'"Zax|v܎8tF|]|0 <3?N[?=w4Ot/gioߋwׅC?>2}gNρyumq H1t<{:9`|h*_* >ѹ)R{}A;9z&N{N-:΅ { ֹ+MGJ,&Rk 0}laTo1-wչ> .\H9Ṗjm~ڬ$ѽ**Z'YczsiU'94Ҥ9aSH4c%7ǃS7>yoddcHG-pfjl*EV#p,A[8$f3˴v$ X3(f'| ñ A,+<̣ M:*D( 5EQ9nIPf-Fΐo LR/~|7p<__162S)2bV}[ ~?Vh0](:ǀFCZGpmxSMbRd±hՇ:-4f4 6KDބiwbwV-@fLB>sz?Y>))U1 X?,U 2Eէ53<ﵝo)ٿZf}jt"S`dmG )dֈ p]QI2sm,4ԣ- eB2TZ9h K5z@d"İ*-LӐ&tgWK%WUw.eH; 1 kx/~<> 䲪[ .k&;oᦱ42|٘2)gl>+|nkRPwpَk('vj.բNP>~x+ @m};RY0UkR1@Jw&O[jxM4fcR<,𻵙ptIu_\7q?PkS#v4eظ<灾Myjw(LɕӞCm\UL[~e+Dvk)LG< }Po;5Ҷjiye^.,]{P]W>wt[J%\`+5eȼƥAI?`Mw+> b,.O7(\k]M { [^. b^զ%#ԋJN,( j2Nf~;z<;VݜlD{+9wAZ]Ua}&SP.X=y|O%Gգ} M}iUh _+xػ4,FVʨz DM~,# -VC$5PktQ Ǵ=?pcny3vRBA?0k%2Zm;GJ3cE]et1Io$wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ac/0000755000175100017510000000000015203543564023607 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ac/6c8b0e4fd760401466e3ff94d76537f658a7f60000444000175100017510000000022415203543564030633 0ustar runnerrunnerx+)JMU04`040031QrutuMadǸL&?ferBCWYjNx.fڼe[x"yzI9) <<ۆ*>'抍#2 JX[??ZPqSwGO[M,7wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/0b/0000755000175100017510000000000015203543565023526 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/0b/9f66de48e88ddbda2137287d7072db5a3929340000444000175100017510000000023015203543565030621 0ustar runnerrunnerx+)JMU043`040031QM-K*Ia9J )O-N-*K-KfX@֒wwzkXf0<[fosV0q*UZ 4OQ1B~eκ]E9wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/29/0000755000175100017510000000000015203543564023456 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/29/e8395acfda0996880204f3e6a61bca931ff0720000444000175100017510000000354715203543564030626 0ustar runnerrunnerxXkogEpR2+۹ Pu Fbʔ@ $W E\rl$Dr2(Yw}a{7`c I3+6KҽQ9:)BJLH_=]bیslzf;n)rqgbMQ!Ǒ8mUsc C>@ϛ.H `Ƞrؽlpnb#7sPMm?pcg:90?z#V{ 3/lvgʔ}`2v7~pa씦`lGg8+1㝣`ƟAN< c0_-D;s,f2'tBr摞(ju]PtбBy~EG,Jv;avl3P79"Nxn+cuf.qxe-.Ɂz^dû7?> ]yyI]bP-ću0|?Mp>ͩU6x#iVF\Y@go%*(Kk0( + Ny(Ri:7/JK+4 4b]ȋOte^oP<9,ӼeW3k!4FEzeEE4,H^aW=+fBmI #G]bvnk%!h#o[>jr:I5.SHW+h%|kbZ)@Bϩ7DTk3JU?@[n-,,(RʇY>%1~O/4l?f% ݯCɸP9Ec q">Qy 4qa=Qw.u9N$z/j«}mP)JtIkjTs QUQ)%4+[x@LI0EPtL<1DJ'&͊_PSRYϱW2^蒀YQJLst⧲ڠ]׊= k$V35(0gnI4Ivڗ^|'~x{W9ӃޥpkCH:5-DsZjEUF2/s U28C9`<J@yJ`Ttq"TpNep~VfTe\tUY::K*:l>bE< D1Ijkq<$N?$~=46[M7vʇ7Wn{Wt+}3F!21J`A$oJRH/(,z/cJɚ > c:! Mwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/98/0000755000175100017510000000000015203543564023464 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/98/077e48a9786d85af131b7662569cc1fa8830790000444000175100017510000000404215203543564030353 0ustar runnerrunnerxX]SFg~ +(@#MKlcF HU]4TsRYݐρ8` R9 \Z' 8lirF1m9|௮)yD~سAD B:4!D( DQZy%Z!5n?H{Q{-RMKS  TF᡽K]RƜM"NwOipEn#w+b6_Rfpy:2B8+EwvU/JSvSwa^W`Yt94 _P FhE%C XB;7$-9`ӥ&a"5e7n _77Zű/m栗 8 \]>TV k.0'{C@`xc|^THpq"DWѠ0VY jt:qP}s4 ~- 7c0u%A2*E-\b^.+:! l<<pB"<@iL̚8 YxTߔq҃Q= wR?"wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/dd/0000755000175100017510000000000015203543564023613 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/dd/47d527a757da3dab5dcc7ea9b3b0fd07efbb5e0000444000175100017510000000014115203543564031455 0ustar runnerrunnerx+)JMU0a040031QH/IL--H`Z5/C9?'[ޚ UZT°Kx~ 0MwUA5"dwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/c2/0000755000175100017510000000000015203543564023530 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/c2/6a43994e7328d7a6d3ab28a04ab493f85f2e670000444000175100017510000000533115203543564030622 0ustar runnerrunnerxXr5dܙH8NeIQHFf8rI,ɍ,D}>Z9 @Z$bw,3?ǟZmtE+e4ćz֕3 _o:օ[᪮K&ILleSs'UM\K9*UmLvOub[-\ՐQR55/zbv/^53jΓuVEeek;Kf5ɕu4,r%c#JGA|̳QcHTRKTږ$TzE5)G*t߅bZ,+U Cmh Ql$UW`MEkSv K3T3.5 ?M]6!vuk0Լm rU V5{ƥVuSiGgMj%lJd r|Hh\Ml{\hMЦ^!}Sņ`e8{+w=!F 5J nSlqHLTѵ Y\V zs;orA" uq#Arn{؋=/_Wt,f*ά[,8`| *aK]A465pdA"g鋓CݫA77ɛ%l!YΉφ& %tJ>/a;VGkѡm-d2ې :L "HٙrR'3<'O&?_]\`GpLnL5D/\m"v+*6j2Xfi4HQ#8g 'TQo|M-A#:b4i9w6qfy[/J*܌:zeh,3 SZ3K"5+BiΧ_TK! 炛Fx]c8|,b;=/G@"'[>ک,'߶7_軧0|w{s,Jp'#`J#TV3^jKI"|׿̳NIf M"-W;mjECcmdВ,l 㸅s8ӻ+\/R7@%+o3QR.}SBKc/0籠X8{<!:`QTtKqYFw\恒n's:+$R{vA ˳I4N*D)0zcqY!􊺛 H;\`.&,VfUQ%Φ9^84ox-`CB\V.PnFnO5Vg0<52t +WN|n}F=Q\aʀEFďXߕ]<*"aUH^su,8N Z x B/Q6ZE Q @D<3m1"LcفBReKm3ʲ۹ O aw=t cG&L&SmU"W֮\RoH6, Hj^413oUNZ)[m {AlQc&Ѫ3DP(x WJz`j|`+Z{aTT&&DzKo"`_:`RCʥ.xTnC8}[w腽>ݹ6 H٘lz>9}gOt>qpr;r7t =ouQ0֦̈\zc:kw4vmaԦvb4糅#\oË3u`X#-^hqcO&ƕ} 7nfkSv}S i8f l w1 |w ؟<3<¡6XF=-;X<-,tSkT[*P*BaQ(Ǖ\S6upq;3 ijzdYl\txE11k0̢flz= Lj _ٙeG2(Ts_|R#.%f6> iÈbz~D( 1Ŋ0좿W}Z;#xh\""U F^ynxquY0-T(0]*p+2BDznU(mTNUc1:46@c8=C"9MjEb ㍈?|03LHI:厛@$S(\K zF &/"ǜ~2: {c!_euwk4{RӦNiMvSӷ [WQ~t(úEnPt`r=ѭ#~Ohy굦n.q u if[5$cz?YO7!ڳm侭NGbFq[DV*iΑ #M: ͉M^ W9m55$RB62| G#ɦDȒnQY ~M=p"W[bpU*f짉^i*;~S##FcYyhMDC1$ha8MRS;>mq#M y6%,Zɴ['j_2[ngJȺB8oTFꮬwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/4b/0000755000175100017510000000000015203543564023531 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/4b/cb95c16e4c6b26dc859026e75eb093694147cd0000444000175100017510000001513715203543564030630 0ustar runnerrunnerx]nܸ~ ",l=ٛdgq>`; P;'"%HD%;Ay:uحyv^/svtx|OO~lmvlV\?_ӣOG/n"v&cMX$Y d.9[E,>Ο/z-dvU̓-6}|-0OUc*f9KE~F_0p8ɢXfQbq/{ȳYp.s8evY܅yg+6 SQ,bU es7Y\U9+bV͒7v/p}y8g_WdG$8],)0 qW*Wػ |eqm/a٤V0fXYq<X59B)ܹpfW z"fe|p×ol}tйh|PbaZ܃>:;cワ}u)뗳L0gNz"L@/yĮ>[1dS`c=-pyY|qz˒KfQ,&8a?;9Zƻ䲸bYﰿg"Kwا}vǣdwþ3|Sr d?> Ύσǿ6~I l8tbld鋫g"樐i~qQԏo76^dU=>ds8OJxN^/v꯿m$ݍ77#́i$iVIZL, n,{EC Eou|y$z.E^/?5j0*] 8\u-"Džv,JM3a\6mG߂h} pg)҇m߄K rmKS/]1DogsHY  >ufilገ"#?u˃Uڞp9h>-"6zKPdi^hkð|aմB\fܨݨ1pxK BC @Fٝ8k,e6]-4x קjP/;ÏLM0)1h.Ca x R9@(4/5."S aSΫ=(< .|@ReդX('Xd$ᜏNr,nœ.DL#<@vGyx"'CmYDi$]L"2 LE`Z(&[|C+A#^KXqNpΠ ?o B\L25\^?-^VaOuKY;>խn*Ou.Uآ=cWU!{1 S%{dUS%{dOW`y<]7Cz&~Z]YWʕ "u\0pr u\=B[Wݽ]Jsqe*{x?,n UI.=xq1|Y7QeoD kwC<2K6<fY:߀6y W|<*sD[wq'mO9N}_s( Qg( ?T ً[@_^d;qWn46]ߦ4GcXy<|k>5ǚcDc ZZg-p--hqZ}Z-ـl@Kk65{mAsuG:f|nno1hmAmV۝h>O4&L4'& Ƅg.8^K+c$\͋Z$MC7}ptZ,rGY:Q1^ufR։?X<5666}1L6*kWBqhBc1lI*v?Jm6߼y>mCan ECeyJu 4uSJǶusS_c-a-Gi# k 1xy/u Yyo>;Nj;>|62%Fğ$zʬ}qL ZI,jX`IİǒI\SPy SnU}-ˇdTRaiH7HQPP+(xfP86qCTFT(ՠKmmhN 2\%V:DN o߁GgZ"`=rWM}_ué ”2Mx](չI8D!ЁZI/ưʊPu,E#L]J=ECա>1!atzQo6nSߟJ2j\fQ D * Q[c+Fݳc+Gm.J>XWu \:kp7C4i脓uk `h% C ~f`J!2!dVV} V~@W]GtU#"t|!n u\~]_)Vq/$1ҩ֧?fqr]A.{85j84P:P in8"v)heXn)i4Sx]3ۨ4 I3%X:5SaL1N4IA!)%LjšdNd\3@PRj`G7N|%`-ı uO_$cxz4Ֆ.z&.ULj]Nj5:ZT?"wŠ4Uw>*1o4[@:ETt((XHe+<#JQل,cГ3 8'5s3X+T&hZ)8{|.A E IHҀѹѧ1<ԥP {X`j(>sP/T, p7EQ<@wt:QMR35u=r/IO;(2 F NQᡖdhAk}QMC EŲwgt'}qAS[_SiZD"1AFx2SAWm7ZPxQ5.ħ ћjOTqT ': EGz{QՍZEeAwK,)xt gۼo y"rltQ (=в[Չd֋mЍV˰E IONO'718>H8mφI98ptNtUvc|PZwAMÔfeI{H^` l,NIs!̻aZԃHѣH&uC҉ӣKI6a|PZ0@Ba:!ZrҫAtjNCD{BU|:U{4L!0 Raѝ LUk>Bl/QHa:R It@_|Czacp ^ ӂ$t@Q4AGdSxUm>Ȇ!tBFv 'd'ݰEӦK?'ڐÃTHd0Pbk x9P7CwCz7{vX#\O$t^ֲ7 p)4 JNk AABջRYGX4 /ٽ`N݋&4z xn ]]3,5 J~k AABk=}:K){ќۄF=" ^BK6dx.xаA(IP\AǺ'++x!e6T}}D:+^>hǕS[9,CƗ7%{':8'{d@Hɮ]F/K+x!+"ߐ^}(TA, aLP~d:eucF.AjpF hfHFDbpKxl5]}U/F.P XVRND'/Iz%f❑a.W 5oP|txѐ3.,Totؐ4jCzoIIhFFP\3׌ZK<Ԅ_ڵMx%ܘ n(L✯OէElEٍQ`aVJ *.;!M2AK*d-̈́--¶lĚEp'4,g]w{uE\|hkɒ‰c* ! $ $QGHaBR!)S$Kxl%utK& ֩(9:#;\:YB$LM;GqPI{dP -Hpcn#ְWGݔ[Xɠk!Mw50bY(Ux DW_8L Ù5ڠ6L$x0Rd Δ ak.C|pZQtz㰕giIuVtb7DQ [pj`2 S&#Ee3Gtx%>7I77S&҈28) o:Ju\ôI8Sڀթ8\$D:_IX9)PbJԣAWhHzؚRN3@lЙ(UyEL"^_w7_u8I gP՞M<* UglqRՋ,+W*W{qQF/H&m v\d#o1I1Il1I|1)$ӤKG6HD Ŗfa\ PYcgHeafS :ѐ4[>F!~"5H|ZXsVt-oybYN1Gn6;BӔ6MQz1) .s7=i|+4yp tH狭4q`ӌwiˉܴzZ4fhnNNvC pA߅|Fd\\-Y^ѦUt~ցF.ZrVapY)l.CܭTMڵ$MxAx3ɴNhK4=3`@ČQqg3Rgad$K˲Wɒ?l- *NGbd_Hښq*9TIRQ~Y&g8EphSL{!=NIخe B 0Y1QH.@:YzF7_>=F7)7[xFtlI(VCi/UiP3}U/kCpb .i %ELxQG4{x+|kӺiA%aJٙp! 'rAA%3/Vs>!1vag&EmnLwJd+ݞ•W˃%mj~W^ocCpU9,=nS|KdlYǢ3t5 #*DҰf6kw p_Bke}b >V48'LfH3 9t7C=Χ.9=E@HcAU)ʄo[ >jFyȗc8WHbKJvdUj?6DdMX5=I߲ӎ)}ڡn\ kEF-P)Q[m5{s8j_ a=ե ./O?N?gG߂7y+wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/54/0000755000175100017510000000000015203543565023455 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/54/254a8b7758a2c69ee072a53ff802cab00cae1c0000444000175100017510000000536715203543565030743 0ustar runnerrunnerxYo+NSiJ"y4# ;8e4߹LiW:{fv \D0$fJk3RW6-S!(Yb+ѦRJI%bVŚY 'uo ]\лl5 ?p-04>|*09'^eWҿDӓ7/*򢛚8VEHm2n֖N"BY><;+~io-g}e_.YLit;~AUZM{hˮln5ZѦ"W†τ4yaXO&}U0н%Ɩʵ^j1VDe/m Z7PƆS6 { : Jz߳u3ufo=.}50i(ڠM߂Մ/L40J=c=S NӘUU(CͿ~U]ґ(k4%J Vf72VM`y)Q+@vvd+pǩO1Od|g'oIE,Ն6Ek.7|3' !Mf /"߄-O ,W<;Y;f L/a%@݂&MLV`kAK#D!++|oAFkgx@5_[y|O"2M6/6 iҶHxe*#0Vށ/ke *4)ifr,37TÌ F'pɜDXzI+*yд?Le,@,l<d2d3Ӳr1v}1/^'##2Asm^N?5^ Q{0XDiH 9Ðق𚤁\,›4$piq~D&==,mM8j&TXЈUcϖm7@b>*8l4x(|}ԃm2nVKoco5#Q^ƻ$Nj_ fX"umQf5ƿ-*xω*?_U*|F :h{ nY鬲]oԫb)&-oD$R^(9FVN ʜ\!Xi/;[5 A%XZ yc4Y,/5+fM.[, a4̖0[fKli- a5̶0f[lkm a5̶)av4ZJYECRESb}JlZJlZJl4̲Тa.}4eա:0U0C15>²GCXh50 )0; )0; ffSZ4SqT918I .$ݔ/E뀼èn yk1VT;^V~O?9aalC {=A$4Zટ<jDn;y1Op3Tb|8{-&-0\Tq>׌@}Pmk2FEQ> 6ڽԶ sU㷐#=G~r0S^t_H]Vq#s=CX>?a|oJ~AkU,W~LGQ5֊]g#Z+6 j1*gjڮ{ڲ%E R=ssc}jw!ws1E_KL ?[CPڞ>1Dj͹;wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/fb/0000755000175100017510000000000015203543564023613 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/fb/5f1753b10a5f2c9339e22567ea5c084d12e2110000444000175100017510000000130615203543564030507 0ustar runnerrunnerxTn0_@rK @OCɕ̆&U>ﻤ$Gu>p9}~u0/\jޡ+.3BOGt^YbYWCVGB}8zvY}VñĚGmQMz + ĝQaz6jǼYaKJٌKɆ,wM< ~>/-[%oY&zeH$57MMG$UޑآhDtS%M[W^N#ɄV$pXj+FwwT%A#_H;/~aqf@͵GJ F^pcWsv1 VF*-sS kjռ*(ܘj[A#^)u|E6@j8?rNYx)xQZEMBc'gw*wRyQ"A#2@ɗS 8,;e漄d^2,kD7S1>N!2&Z>jhp4V$817!Mx 4 J <5͓/k3,}kBCBԸ!aD&MmGKcHp )c6\c^8&w׊ 09<uz| 7P|L_I9Tm7?Fq4vmZjiC'6c_?;wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ef/0000755000175100017510000000000015203543565023617 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ef/d5d2983227352e1fcd9ff6492a5931b56d0a200000444000175100017510000000056115203543565030615 0ustar runnerrunnerxSMO0 ̯؍ ĕ_rSH8q mb=elWʒzcMP[RF3 9:{(RB.(Su~cx{N읯r#ÖOz*#PA$&Oҡkru>ijTPY;\ TҊfNzU~#D&,3{X2F{q6ȁ㯞#kvECm%nv Knٙn82xTŰ-4Qq!JL.Ty` mSGH9;ѡU7#+BAЯ`V_@eN,b߼}=R~[t [$wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/63/0000755000175100017510000000000015203543565023455 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/63/2a182bc422631cf22b09cabb89855020e70a0a0000444000175100017510000000114115203543565030462 0ustar runnerrunnerxeTMo0 ݹKF[)v+l1}AߏV4nE=>>Ү>}xݢP \DW7<йewIkEDP6H ΀1[w.Eu [ 0([41dͪl^mہ\1H@X K&f>=W.PRTVIiYnB)y9,|fP;g ; ;d2$+A+X5#$^gxAV?f 4f@J#I Tl3(0$K|kG^ȣ`< d"EgDT("׻d<1rAr oC~=a];^]E~s|ʩ)L+SBI۲dﴩ*8^p)n.qp4r,jr'GxpqL^D3Zu0qȞ][,Оn3 aϳ3XoeSj%qADoA]@OZ~XS(N7Kg1X;I&^!_^+˖1пf21%nɲX`] H.4NPb 6'kD& 2Êcp]J8lw|lőnh&c@ kP=|Z:O "W*4mbX y8(I~MC>`ym$y&8蒋[4Y9vbY2zEo߰iYR"')ُ)~fkP,pApŽUҘ=~{ ԸY]O7B٨;W^! ݺp}/Ogʣr? 'kTMrQfXVELp |-W 7qtwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/7a/0000755000175100017510000000000015203543565023534 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/7a/cb28e53bd755113910b3a3f3f80ababf70b5db0000444000175100017510000000275315203543565031065 0ustar runnerrunnerxWnFb<8MZhR@Rq$PRܘ ܥ̒T-q/s93sfԚ>}·RlJf"EC3?oݾ?Pvt-k#B4[T5K*E#{4) ʴN4ڤRʠOOܧ0sJVL!r[k:5qrf"H֐&`u/^RM#3PV9pخV_孆lC[˂?uk׮+Krɢ׭I͋X8W iQU,Anu k6Cdޗj@TM wrȬ/"3–=:[tuq 쵘(XK[LIcQr`KbїN5V?1(Ok7ȏi?coL'nd:_&&74П~8vkyqL"=(X.q/'HhM@a/poB3/M!ٽ?qh'!˜@K 7J2p#Z,<~ N"hf^VO %}& MTQ3̽b"뭻zv \G?ٍ(2NU<`/;V;Z]^s{Nj9s8@HjۦH3qqXo^\)mk@4ڋYZm6.Qq'/]E#U/w~Dg٘܄~nyf:?Jٳm  Y0&-75ӭcfSN3Z3hDMwD+NɜN:P`ctq b:֢PlQ2hSR[C9}l]{ˠ#jD[ |ma+ڶՊ6n7m2P>+؃H+MI=R;kw4<nXǷ^G8YX~,PG(G Jm$K4dp[:_kRt$Yۦ۬<^237p:lz+D07/|(Z4[$ k:ԢXGyt4eӫ]g[y'2]^-R B@+]jUc A/8XP26}8y%IsAp,?1:0.a~ԘE EGv f[tG/ݴ"=}vTL;*eE-n_ ;̫gss!-w1|a6˺8%@0ر9q9f>iTURB5\#.i aҫ޲yñdEi_ okKȠNOeRET #_i?iQ [`?\OBbJ(H$*z܆0`^rM0*+D?Ay!vm.-]t+; m]@Y@ 97.4ɻz.ּxzw>'M=$4s P\Mא|^` f,s:~年Ow|BμćƝCht*U9w@K|}z>r2U`xx76]97gF}I_r o>c3YՆ~P{ &쐉?xv'8g3曹J Z#v\+b^'wyxVImC]5O× ?%Ӌ^)OS-"U&_IJ"9#STqXIjIvz!wbW}Q̪ Qj.z_IQVE h#*p;am"4CZA;Ed)by-nC_Kd}O%6e1qgzH34*4l%H^"kRN~Q?DY1@7G\$C&!zPT DaF:DcKU 8;M]Z-0n"݅rdmׂ$J >8e w1no)5*\k{t JǸ6u|7 #BØVP˰ML*g" F5Q_"fnJN*a*2񗓛{l)aOs},M4.(z sIwtr"Twh!M5NTUr%16m>"i}S6SJD]p|K.'6?ma8ܶQrhߒ%(|DB/b`:`|^bfF}"~Q4> ٶIS6ikhC -z,n2=NpJ1-CLK2L$èVCR 5 kNӣ{ oD=5 iHM#ޮ *Uq:Wncj:^ NJe< $RZkѲ[b Ku&̭%e e~,s4TIRPgf6Ź1Lx]Ƽ mI^i)^-"Fʜ]`s b,GYN*%Xpob"gyRE.{Έ%]#HXzEZ_v4tOC?vՖ_Mi*]@y-zp1%/)Ȥg4Qڰ TyIS*qyKF%i\9=>©G@e>ghd&ZG!Ɲ5(:. N~Җ{5w{9߫5> ݁cLkO7M0g2/-!3B4֦9g {8c<wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/3d/0000755000175100017510000000000015203543564023532 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/3d/7209633c8d324ceb12b01e7dbd375eeb5c79950000444000175100017510000002463215203543564030676 0ustar runnerrunnerxr6ﳞǒ[vtb.[T>83OrV\y{{khzw-m4<:|vt?wwg?}8<;}~zw^\F^] E4Z7ng'oϞnѪMYQVWYVGۤjrMmZ}[_y.*UjGGO&MO۴X?fyQR,.+H8-$_bsvmS(*Ua*j]OIn]HJYT|@VweJܔluˮ%\Y/?Ivi<,lu%H"2򌯙++~ߤU ҄'H(iX2e׷QV2sfP]ܝr 5POH0O]vf&w?_F'?rOU MoTTڸJgޞ}xd97*Iūߜ|gO#7E;3#Φڹ˴IW/etܤ "n$Z@͋)K`.$yYyXo([EE<>Ut7Ex$0z u`ujyYVO*,DoOo>~xjtUt~cO~/޾zsc)P"0U,2YMY<࠹ݦ>@[4Q|%+ Uud_zypwD8ߓ++&+@lW`_f't+W+Nj/tBjt9rLU0ml0.aJ9B`F`ZЁb% 3f[4|U.v;GQmdbxa@?Tȃ&-.)<蚃'6n Pa^}sL4XZfp[l-ܲpWzOڛā22xsp."}44n5F i>53&^\%U4kx:OL*ݝNdyOVngdG(aAKܷ=Pi92]U,"2cs!;]+, \#i7p};ÂR31 f绕9fsn>Óų̰jL}SMYOMnܷ;)BQܰr >[tBob'oc]VFɻBmM ad~+5SX+Fiy 7Ђ{'VhJ-F3Æ>+2]_CB9si*30}!FY傮8kծX!^4Jimp k.gu|\5`/^j;u5:)<-[o%LUU@u)cXw㋿洺YMpJ %d"]= &ϰ]6 V24S﫲)e.Ad Ű /+h6 F} ҽ_EC}Nj-E(z!C>bhhyo/PGqq'78[`.N7c ^œzlQ!A$0:khhY~{wr[4 P]`3>cV.PISn"=Okȯ;0|5~b͖gy.TYhvp#,/(/vv|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]Udkପ`n6[$.ea&L?gN.7+ xe|zy̖fݞ::4X0|n.p-r@7pI*wk7fjK宖/؟#2wle KI~ɖ y/[xq}YhWTU!czvv`_aGaGyr3">B>ʩOsy{{k=C<wbn2n؝3>7Xȭ>Î۸}cW!v[!uCtДA}crlĽ\Ivp===מх;zÎ^:f5׭ix$gwo>3+0'J1W6Y["9x3=L`u ?2<q< xnuW+G֕cs֕?ZWd]OʟW----------,,,,,,,,,,{>[jWz+Z<ҳWl;ր4ր+ր+ր+ր+xsA~4^i,~+>i,} X<1Hcj=vl|lcc'[>[= ǝQ4<]h8* PZG #ox\8 CG;xq%WZ6Q{g$yƼ$_aJ|K#4yn{tŋv'QSoJ=RW+1jĬޣDz]u{d:=v~_u`/OٱG\GJ7_Ÿᙂi;Yz4ef@BSdwk9fҐ?7X U{2[b567kֵ* *8Z̰t儫 }^zOFC^aSQV='=R6.ba/7P 73|x*_+1"#CLIO)=!@ UBK&$Dt {/g4`!^ Ňvҽᄥsy}Ƭk^ K<21-͔K2>H 7Bs!!n HR|JwGz>{K{ƻ _g LSr ›Z #"K@`%MaV%A/ƃsΤ::p KCH +$x{mRw#p{ Vz3SM p KH +x{h[ @0^͂k"p2}`"`a/\:vqECFKNd;_6ް!ʷ  {ÅaP>=C9C3cITi t L\&O!T௹/} (yYirpr{C;k,'| ku&`زZq`\-ڻ*bo kl+!+Im.\Ktpzz7SzYWZusCu`,$L|x/"U}DZU[%MVM"V-keeOC2'_'EDfqpS xHYbg{B1Ysi~7zMK^&.yz,KD&J 'ûy29CFcmX-kpE'D454j^A~# J6`;\(#dMx.spI| [[]'tP y|p S7"ܸinz+ om~NE.=b g:[pʊ$i6p'ODTYeeK+o0-=LLP]!bN8RFDH:lKQ(. ͦ-f4ܕ3A+9pmBUl8 8VN#G&wVEl胨U(Bʄ"׌LIZ5F-儋0ʦMZUْ$dzos{jܙ!:+Gpy}uuft3<%Xv.\0^^l'Ee):>2FnLPug8M苰^&Ig\#)fꗈHM̸,`V]`;Sψ1>u+dZ;Cħm2\E_>2qeL><#|I4̧ GDħnrpe`8γzڜ˅돐yʂU| E={+aepcӇLu~ ,߉ p֍& a Wa="2U;e/\I m{ɓiFA'NIިp ?]P"RkGLP¥iH"ke)~ffT=u9&6: ć0!]{HPL0tP M愧UsX&C.#- 6b^MH5q \& . hI# BQ6@$MM5!}&@4.,2=Cx i . BȮENlB2M&bh?յj7\颬&+ z0W|t= 㒝dVzѦծ/sZ8&yZFd57"*5#e+ʆYIB;'32C0USp1}0-9ʨbUC92&ګ.pԻBjH"c+.),_B<(L[(pBOӛlFٿ|4e!~ʲ?/V >t[2+­L-CİD h9=e0zl`w2$vt.ϵDd1g1\wُ]4:HU㳻B:#e$E2Ӹ-Wj-]JUUGȤVo>BЀ+_poOl\ڟT~x@WYWw!xt  ͇cT\k; ش.'B![4("k^vrrC}$$!ìV-kZӒ`!Sf4o (7۲Κ cVe%8𭨤%&S45 n}J1S9B)5=@D] "P.!\KeZ7Uye3U}f6\u'dB gzN`L>ș%⍯3sNt y.p !tm`Uٔ2'mO{q-=;q3ΫÁ!ZB@!b3\QBqeݷHL|dM N5f-댐[mݤU,W`LxWpBJ&h,P/ov5]i,݂JnnzTI!nQd!bTߞ@sY!,S6po 8-w+X-,kѫ=ܻB)zG HhGKL C2t;-CwH\Ew/ 藽-cԮ'= XY)#\NE6i <$98ψk螺L6.}2q Ćߞh(/79VJgs˘{nw2$bYgSdū4[_tg$k!">6(L .D IIj'u׈x6l9BxT*&ʉwht)dt*E/b3\J|BquV&7naj-'\VdY~#0MBUψT>ӕ\ˮ8+&fM?Kxֆה_deₙyNRLI/#MMDijnrp18lwv^f DÒ2?2UZp 핉kRۋk/bՑQӻDfX)"l)5<>8c_oT[60ui oWk^@NYUl'9euJ fP\*V+@$ľU'ҺF@es쵵z__a½B ucF.nv8"ٶ•G 8b8_xU%lg(e1=UaZPnnڇ'1Ӿ}1'H?3S{LuSxG?-NG;Z ه{GR|2+C~*et#OrWf pÔߏ4ܡF6n6\Z]RݕdBNw.2SZM<(u_ݑW9zݖy'o>9qCxt_ :*ÁDZKm&Ycr`ٖ;?/^kN,*y2ZZ>v0:Wkd|>_f<"7KDԯHջ QXF l@vcO~/޾zscOwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/8e/0000755000175100017510000000000015203543564023540 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/8e/03f52f9a2679cac849a305d583b4b6575312a20000444000175100017510000000056515203543564030457 0ustar runnerrunnerx+)JMU0d040031QH`L;=moe;jV˔IOɎ/OIK/+I-OI,I/(,i,nΓ]YlH^GkZQbn* WT^/ݘ5!kRPXԃy>jwZ'=09,-%I9%(ɮ/=tEfnnKf~ђ +]ᯢ1:Vfg'$Üj[ęlkZh\^1SqK^4͟Tm*1=?8tƊOፗ2+h{}56sN{:'DJ(}ܻV0LPŋ M Tg~2>rS]wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/05/0000755000175100017510000000000015203543564023450 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/05/85f0efc57078d9218db850d0493d49741ec9820000444000175100017510000000035415203543564030415 0ustar runnerrunnerx+)JMU025d040031QH,.+dAgTVSU[W˧ۃ[iwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/42/0000755000175100017510000000000015203543564023451 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/42/86cb50f9b0a6b046f6cdcffc2d0dbc0ba6e9c70000444000175100017510000001422715203543564031320 0ustar runnerrunnerx\rz|3SEKIQ:D% %#{0 J8! h2yF߷w6픓X"on::ǿiۭw+uMUZGn*2_eh.beTVHom1YO͋4U\M&mSU&GNjխ)pdu0e>&E35)|MOfIE|lSݥ٥+Hfd {Ȫ|S"-"F͌4׋l .|SS|3֛EV,#M% {E^2],!«N dFD._Ɯ@DMJsf9D&N+>!|4_2[ljrߧ‹*@[ DhWߤF` emn u^:};T'W]ϣz6g=atˆ:?Qϣb0Ot.X]__`Fg'c`]g|QojԏћW_APSGѻ:!ucu2PP]_{r|~]{j0]R 's812;㼳BQ N#BӎhphʖTZ䷷ּUz\^Iugm)M+S΋lWr`(E>MѹkB夬,=B!B>eEVBZiu*:I,$Z0Wơj+ ^Ytb⚳7($9rmb{">}sqcv8q~H lkm|NVv>()Z:f!J@@_:PZ&3 0+>c '9K~RD_$ +md0z rL*]|M=wAZ e1~IL"w:92 fk!:èb+T"sEhLPZ _}.!Rj2GȎ*KLMtf+LYcvkM1z^l{*.]ZմSpuOV, .+(NyuNy^뼬c `bDa21]Uݿk]TkM۟]?*Ow7J|)w_>EZ!WvVs_p),)ar4zSbz̲/7 ]xt`H > #W=טƸk!;+8aYIPkoAB ?Dhx V]|&5YkhE^愒Ȋt!K'e JQu@j:B<ﴏ*f*7b`S2yc! 2-4_.LJ}M` %ukPBb66&;):,է{2÷|7z64WL`7CLPU(}QyK7Qݣ< KNP0a9tO|1T7((rػ'Է? 4 +#:&; H rn$o$dbF(\H ʽd"xxpZ*$/kh&լ$ՐAS9{` P{4!V% ȧ6)JE2)cC^gq/ >YUq'v BGaF"5!!52XK#\~/6+F A_?A1YCXiTZ"O'rͯP"1hRT%&ځiA?ixhSr}sk裏IlgCܭRQYqZȎG̞V:(1& :OU([{\GubkϮ78oAx7:)%~i>RRY\$%ӯj2 kmh,l_Q١ƙ̌;0~gEY%\?"!(cwd`~9 P75Jyb)Gv?UN0~4AT1Uko'bl}/1xvbLTeS 8sɻz|Id67GmW!̅jDCa ByH2HIFDh&7#X6sPz:L@Eg&&+o1X﬒YWT&P.l).iE ^MmG.qǁm2E3ARyqnK`7eoiy}ײWaLȈǷPMBbXu@ax2L{nT:F+6 = b[ qQ-]Ta%|j.yCF 1Si^LQ0 XmRVAy601 Xbai 2:)ךU?aL̠}tN˪k;_/ē%{+*)^׵+ܑRhIeyKoyl,$KTNzsgށ m<ha m1 nRW{=<^–v'CN|Elh=؝G,T hSaJ2*F~[v؀MmG4_a ;Zgv܎'i6.Fܱl "%cp|=&Z,f=u%[\v p̣k&Xx.%nM{q{ڤ&.a"lhC$'\"I؊Cyx]@UjW$v-#A r/ I{@9H_vXaϐ(`5brSnK8 fm j^il*Ui?Ul5<#@vl aq ssiDvcX4z4=L/&%Y#\-N#YaxgԑZa i2ذmun<8 ˺cj%s找x-'"-mIvBB-9Nf3+4c~`pn?-2kB`7' Yqo|NZ$cT8<%k9,\G(÷3llF4EyJ$|@gb>Fn~?SԁE{8`>zhm_~6':'a]ӧ>Yz@8βYՐ>q pL,#U{#/B<'pPN铉14®=ܐ [s?4) O6^s}1/ӡ624a_I~fB)4lonM+XNc7 JB潃Ƈ+)6j};ַqB:ڎ~@݌Ʋoђ>@, 4N>W<wmQ|5it>mo/wC, 64KKcסc]~٤:G qOoc%Mc4NޗG 7vkܝ] .$~1 P5%@Isnմqf(p3)>p@k U֭mUYݱP2͏}-aI<Ҡw"nko8+x6+NĖFuS!桦D䛷*Į޹^ʘ`*7]os~\.p<]H\2x/?o Q @DĬv5B qk;P%xЎ ~z i;D luG{a?1>1`T,Ŭ$[Z;+SmfWHŮ? h4BFhrY?؏XcL ʺe34 B6}1J_CzóŽ|r|6 5wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/bb/0000755000175100017510000000000015203543565023610 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/bb/4a61173d478eb402f933f795ca01f458d4377e0000444000175100017510000000013415203543565030533 0ustar runnerrunnerx+)JMU0`040031QM-K*Ia8Pah׻>rMr*+I-.M*`(%h'[oga?"wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/1e/0000755000175100017510000000000015203543564023531 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/1e/e09f37c77c4e0cd1f017162df317c58aaab2fe0000444000175100017510000000015015203543564031066 0ustar runnerrunnerx+)JMU`040031Q((ɉ/J-,M-./I-I,IManlכ>/Xrˋv7=zM~ڗ`i'#wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/65/0000755000175100017510000000000015203543565023457 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/65/3939a4e2e98433073133fec330f8c13f56ff900000444000175100017510000000317215203543565030406 0ustar runnerrunnerxWmog CuN?rhI5ȕȚҎRwiQÀyyf晙koWW<:ZAuM!Z frVRjJ+/+AUDkDU]WɆ-WڶD 6$7Y)IYJ{vPkUMl)XR1NnKYSPu22أB3~G*Y+ai'jX@mTVtr@(1uVwth'jk߯+**6$;>ԉu8KR'-Tc=2" EmIN!2"W|7 ˦8l |:ƍTs 5W]!5H11!~)TBNb*I#E8Ͻ9Y -d' Gn Պe{8Yp?[^% 4&%phLn΋g XvOmKK7N}ƴʃ9̆~xËw;xyV 7+co-cv" = sopf94w[FS3>?ďBcIꃿrc #tBs^ V8"#o_ 11Ph\vyWZ%%ՎYDF&(8Y^RN5!Sb*H?L&Ph4_DqVr v-e-JمK=(-2M~~gYgdfR0it+샾6M<=w? `h% S{$Nx٪tԩQzQwdr0 rNy+0BlV) 11' ||JШу~/&Me Lִb+Z԰ֵ,#y7tx׃[0µ= .{iƵCngN{qWtyG]Po@Ԣ.gאl;]ǻoӥY%Ztcsq۵\uoCw /]bV+n+ܻյG׃nsY][-.kSYWK(aZk[>z~.:.`/ESQ4l~DQIW{kۮ?]gk=GVYNYY,}^΅;Pz4AR13|F-&wuE0gxs |$p(b,[-OK;ʧ G5(h'Br]5D[nPrH9}^YZ4`֝2%2U<~O5潣a V\5%H;B ӕ窷Լ*L%Ae$M}̇IjL[l c)FINJ:{l19JR%rƀgc[cc2$Zi$ ^ . b0ݨ (֢.T (wTgaQ'$fs3XF1N~ EGZy$'褣hDCv 5%IFa5q~q6N8V^GΔ^ ]V%3@qq p7 }\i$/Dr%QxQ"G^,DR͉PЄ"4oU=N=]+ʦcgJTq\Tc7h_\[{ϕg,uJGFJh6wEQk\ݫh3jx.NʑuPy&2i2)YIKy0Ŗ)Ot}p(騔gc\]ʁK`)vڼˋc⑾ZW!1K-4޶fJ̤ mN=1MY,2$$+&8cx<Nz[őaSjוd?ea*[zgmIB횾Bµ7&5m:gվ<5Ol!If6]=XhmJ*oh=OW.q} lg QviTn8hG}~S[JZ;MVy 3q]6ʆQ@X 6?&oT\ςEo>j8>/!ðI7tRJZ:&4/;{|`70}, F&P!&K"|| loM FsaG@|i )}@>fa{g||Pqg!LA,/bԫ;pk3 fK_+6]@׭\t3l6yW(SFQPI~]H>,qM + <E!/XPwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/5f/0000755000175100017510000000000015203543564023536 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/5f/71546112fd284aced13e7b2391a601204bcacd0000444000175100017510000000161615203543564030636 0ustar runnerrunnerxmIVW\)K5pLO/ 3ۛ3Lf0ؿ>~d׵E;%Ꜹr4?>M,͊1)D""xFHcc#6Di3N24a,dR(Lf 0/BJ4 DY*$ Gg,8Fpne |[ hiB >(5_)ߛO+,WIF߃\}Ǟ憎 %-j؝:ifw>]e4'; oh;xMNA| Cg˼ .ì^sFS[̕Lwˊ1f$F"5/j޿x6{uTU8R+ӼYj]V Ub^RL28A>דmd~Ľ;,^~̘Q:[Vb%4u.elTCI?Z!ͼܵq;F$`Aڽ# /J8Ҥ4#};j^4ܵB+>* ^ l<@Rz ZHUCY/u2L{ ̴SMU>yArk%Ϯlr>I>AU pH??mwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/fa/0000755000175100017510000000000015203543564023612 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/fa/62199af92422c14e02c9621a2b72883bcc6a1c0000444000175100017510000000164215203543564030572 0ustar runnerrunnerxU]o6ݳ寬 @m,-P2%][%R)+w.;Njahq!6h:gp׉.UfTh6!oUj$&*)6FHu X_ tsQPBrUguBW䚪zҜ\;m مSx2Rc#Q1Y:O6Z[HRS⸷ ;X{|Nm4&2 '̆|S%xGS B@ @0#Wnۏ/_>.c9w^ؚV@n\ \YR)8SUEi\B0 1!/9C^* :d}J kOsNW$HE*mZFƉ'|OkzFSdK]d֍y4 ?|ʪ`7\ՠS 2.k68Tvsꄛ[w Bָ[ c܈ټfsmP%8n09 XlGh2@eG=;g/8+TDpT 4Ur wC ZI2[f&TT^of΢2~i;eF]I&K-ܶ!ט%U)p{;PnSTj0,,,{\,A4b=H'H,~~{_w!,7I'kؖmg'/0N1ce&¯-ޑ.vL E`vG8F5AAXEY49r I:\r29LV9; óWa$4Ûq"qd*$=B^L ˽*oO<5&gO{r )Ãt}zwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a4/0000755000175100017510000000000015203543565023531 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a4/3ae0b73adb00ae2d4e22c1999847a14230a1da0000444000175100017510000001041315203543565030701 0ustar runnerrunnerxZr3bbW ./6٨ThX)Iqi],XT<5U&I(ƛ5,hϾ۟yo&Co4[?U:Dh_0՞ڇNѼh\F!7L:{ufQz燙NNv~QH[Am^[UVlt[erÃubB\?Ír 2XiΞ\憞r4Z.)/Zw͸NU3jbfgh"v!ecL%:EmGvN|bvme۶|^3P|)ls(Qr!쵔Nh tdݧm*ZKB)EP^e|CQDO* =M/0<>jًleU-!UPu!n" úP/_4}>YxPӡdzJލ7ws`<`^~4j'ljvr;h|y{5Q1o|7W#x/FC̻Vopݎz45d0.oS5NfC,zUoyjv3~*]MOGonjlvh¦.omu5x;xCOJf(x>c΋Fa[ r={31獇 U-R vM\ k]AK%`?Fx4@B5Cu `_ppvyDy0}n+?.E+2p Nf*J!ʾlg~?eNqtZfD;; $aC٘;th<<4.6: *pwKM[ʍc Еpxd`1qHaC5Vv}XU }=|h`R͡aDIsG<. 1( OFgOfŒhI:U/C9P| %J ",pq5ve(SK7<PXɠj$@p5]MV.@UiwǠ%[bZӁ (>ץ f\lWC7]/p:(@c7:{nFi:}jj҉L6JV)K1<VsnZ5BSȀ$Nj 5D=`'Gk5~4~w-b Ea礬iGӈ˸td)kS ;;37Jz4^f/aONO%.Cь[VLt`A=Iڬ ydR&h>nC6w4:^0regݳs1 Y]շ3yC>F5Ma3'꩒IՙwU+~F71f)ܺ¨I1\!U"A wB&r߷",,R;ع;2Pic1X:DmF%/T>,  O C&y\LMP YW'f%,)(.լԒ#G]f{@ϖ+tbDžNJ%/+sb%;OMV,g*{?Q_)?t܇)sB#頞z"s,* ch]gM̕.r-X' Q3paF]?]Jf5)6!s^}'IakɾoȈO,E@4DK/T`1~8Z%_Q0Pb s>QN̓9Oi5Jr,J㲲IczeqY5V;1G:%[UYgxAA{y]TbC]|/A ţ6&>^!'P"K/ɠ%G_HK"jE91#3bO q-L(؝07 y}hH4ak0g/i8|АJxhB*:˒ Ol kDέЬ8MS:;Zp&G hՖa(UCƋvBJXDB^s+gɶ~r+NqZN7߲)o '{0XS Ӭ )%J@ 3"4v_!2g(tY;KKNmkۃnWK=iܛ)<|қe#AaFyeCcb"e_yoה ,Ňb^ \r] U #6.|QpM2L8B 4Ns [N?B9rLy[nyckoksVUn"%"xz:m}rzXTr.78qrⶋ3fCYeI 9X^;'QǕ\P4MrpVȁ8T>PXy\ /Ϗ >A 񵃣>|͝׬I^ OLD#)`p`L!=,35+_b}~QXR/_V,QQtɑ揼kc>MM!EV|HvQ||87k8^bY0d 7v..g@Qh:fV}4,jU1R~npΤ,ImN0+E%33+OI 9ܨR)&YI4X+@ ./O tjN@y%!l WйjUSkDUtHbpT6kiՋg.]~łB-ҽ^GU&/Q 3_7g94ej*1&TWܬLg'b86Z,fo^CV>)I( Il5qrmy!~wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/1d/0000755000175100017510000000000015203543564023530 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/1d/ae3f75c2c8dca9a236466d4643a46a996df41c0000444000175100017510000000417215203543564030757 0ustar runnerrunnerxZn8zA Hr{.;nA%"E;]5vHI$rI4FǏ9q`ٶo'uhߍnA{4s7#vz#݋Qހ: )x8<t۝܃ 0gs"|,11L.MT(?g4R ? ]m?3taigYɦ ~$y"Φ% džl" b pIz~,?^/ 9JG9A<͓WwЧqhH9 v1|W. c )FdJP)d_y%1R 4`#jDp`#D :H5%OuNMk=Jg"l cb[ 1EL'!C 57zs7=|l9:÷tIP< |l(rI(V^F\"_z^67py36ܶ]=ۻͰĔgIHnVBـw\"QvP,0ƥ?3  ł 0m2i5nؖ-<19ί}}q~wtڣs;7ޢh0\ ,lXETցe pdXq@EO ~K뀩I'3WX4Xu<59 (wdTU,[m;?#G(X҆]pdNs3CѲK+'163b,bS>w8=D;w9 E‬[FIerI}D͑ss#)B T3-si7tc8!w?%}}c[ q:IX n=8ji828S*tr|vFpٴ'8dî)?WIQlxHB9C G>gK)ڑA0²GCXhCղN͖)aniJhi[Z斦3SZp B&Ͱ0SgƋ(b\t)I2R:r*w[[GaHfg;o·!jP/Ln1Ï`YS8SV_Y;_É#/orp`]gHU$e<Я_vA>siP=GwMɭ^ԽK5 qSKRK+Ω$kUT"o!E\R"} <[*}H͎cV֎cUQ1 H]U_ xwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/6c/0000755000175100017510000000000015203543565023535 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/6c/bc596ae948b8a8fa1f86f095671381b4fb67690000444000175100017510000000020215203543565030642 0ustar runnerrunnerx+)JMU041e040031Q+IM/J,-I-.MKfH\e1?ڇ@@SCCSDEU#"TpTSnj1ܤ̜&jn1{Uʪ@k;wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/e5/0000755000175100017510000000000015203543565023536 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/e5/5c4ea63f356a8cdacb3597ffeea984020b0cbb0000444000175100017510000000312215203543565031240 0ustar runnerrunnerxVkOFg~UZj[ ʮL‵Ng)r qxR?Yc'NM֯CȾl k{sFs\6 N}|6l v}9<Է}}Ԥvw<0=;߃[vP ;@YF`1{_^yٹOga3t`PEzy F( kuL! nbA^ˋ,9F󆼚YUV,!J0vvv]O>[^x$VqnC.d֙6~^WN}|gUZ͙5I;WlI* q Gk1D51nn8qR褩oClȥ"">;V!g.a0 |>u!"YyB>eM.zkYзj2{iY=:#䭆2aT1(_ɓ!A?  oZd[D!Z yCܘ4> 4WMӺ5(ʧL{s5IvߢW:XwLoqG yzioBfV? ED@VkalP]<7>0TeZ^\*E!b ۍ-Q6|*-2J<rT!:n#doRjj<"g N!"wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/e5/4c6ce9110920199e696f4c6ea54f5730209c850000444000175100017510000000223015203543565030405 0ustar runnerrunnerx}U]oF3*Pذ;T۪gi`4ƃfơ;lvVC{9k/?4Rd㔑TZkʂڦGXNF*Gn?J*%i'MgfUH4r{0)T)IW䐜&ќ :HP(t aΕ@<*tA^8ct<& .QJΨc am.kW{ P%K/nke!Co[Kˇ>C1҆kk0 r.EXJ5 ) 3/u]#KuS(kte^l5n_s_\l4>kG ۭuBx\HCZ%l!E+ZhN*XjH('" 쉒)GdH4\(I)zX.gQ<^d~qOSag~bᡂGO=q|Jn}=k`LںMWR8}彫`F] (t焜eO{q8@l@Vb=NqpOdqBnlkJ} |Klx_D>D=`yjto6P5-.ʹ2pۛ\G]'i>b`+󯃮E^;@Cq1.ꐼlIIpsD}܉-6Rɿ&C3saS \ 7]7q*Q8`^YU#GIh?\\wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/78/0000755000175100017510000000000015203543565023463 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/78/3323167f4eeb1f9f3283721ac904f1a17492de0000444000175100017510000000350615203543565030457 0ustar runnerrunnerxW]o~ŅP[$C- e(5ACijrHkG_n$&g~9E/_>+ITRf"EM?Q?4P]-~fjeՈnyZj-UŖV-[rZRe^ Ei5EJVKJ)+p|Z6EiSd {]#af%jfO\'HK DmmeRmCM-3`SV9ǰ_.ZZ|9V#ӡe ֦]R%^ vj~iu8{Ue$63{  ]y&h . Yo8Brjrk.ԛ0 uw\-HKz"HcPs`̯9!vܾlTmnأt?Gҕ;C~0`s$}elz³|'Y@B /Jfc/</>aIVUXEWYVufnuuc[ַeV`ݢo_nRB#/g=ޭ"xLzI@BŠcfg>ߐ1 R]I :sfRi;茢',1\CFc$Þ]J hs  x贛]PLO*k` ˩l^n:5Eh|Fl[+Lb@yxa!{__١} ttijޛo[ t}f¡ƀ|R5[mc!]cT^{;Wt:0L%AyXLraqO#0 ũj삌~}p] ϙz9:?k 7,8srl>6~H]v]m]SLse}3QpbϚ#DfbEC(a1T5큮mpc8 xO) zJn(qW8j!dOwEleB]w`@uq)wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/78/f25781d3fd03663d721f20e881f45dd7a327d60000444000175100017510000000261515203543564030467 0ustar runnerrunnerxWnHgE)# 4#y$ ʓe6bVMF{.hDruSU:bk޿^݌ [0tYЙɣ 6⩝pGn2; ?X'ML X^t9X@Ãeq@8-A./74g7$&܋>[Gԗ''qJS`}%E  yP ߾'n+YoxB&%&>@!:(u$W℆l[gl11gE&$̢ Y`OaetGgeIb1bq@2h>#'g,e cg9-0{{t{bԁ\v%27>|c@GH#:EQ[oO>{聏n1$/b&.BD 9E 2:ic$)R,ci(0-Riz۰t [fմw4Dyp3쮜pr't&bϝ/=i}3( \lcqg{iy4/ V4س_yߠA)|I_Ӻ]R*"Z4g[?:VOrMյ|_r%ھ2$! Cll&{ʲzǮ6UxU M ɹ:ӡŜDKI|#i򪷰 H%mc|)6nc,Z`<ϹB\B#)l\. M( MїA+|tSA>*Eh,-•+߽dєQ0GuFNʎƌW ]ױd>g%6D <`p 1;O,b1XzJXR,+u`6̆P0 fCl( `6̦T0 fSl*M`6̦T0[5̖Բ@K- Բ)qRZqjlO @K @Kn:D:Q0U> G\W]Ok - ºGAX(Ca]c裰ZXO̖Kl)J̖Kl)J֜xO KEIm3>;உiA(`"Wɧ ײ('s ׋|Nt}}hڙcO]uFrgvKHI^0+I_㒝~qEFͫe3xSAw~%7\V6.ƅ{DUcp>>(bxEGb iQ~wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a1/0000755000175100017510000000000015203543565023526 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a1/a8ea0e66d56a21e589b9200d370efe125305f30000444000175100017510000000070615203543565030573 0ustar runnerrunnerx+)JMU056b040031QrutuMak]W 4YT!9#59[71'G$X7(U73O778?Oijmzuy[ "}mGњWZ R,sߥbolK dL @!3$5($3?lnr~nn~CΞȬ;Vo05]qL:*spށߌ 6iyԫPGfT0h?+? ߥ?@UC0`SRx{FmA(KMh( wXb`mu~Jgp_d`P@+Qֽo OSO1~b^u%$>KtSrRS@.[i >@L-,!ɭ‹|zgٟ%qRwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/80/0000755000175100017510000000000015203543565023454 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/80/85b81bcad140151524f7fa8dfc141464e5119e0000444000175100017510000000221715203543565030516 0ustar runnerrunnerxUo6qu[ڒ!ɂm0hʢ+RqaQ 8t<޽w?7лoRUBUYLFֹlhtQһ7Pj+R6;e5G*e#7ϴm\! Jl听&Q?^6 UzK2bO.AsL ģ\g| PߖzFoRTjD@} vPԭFۨc UmNǕکcF0`C\_:ZvS)S)WzZx6ǍnȪ ׯgAmX"PK&(Q65R(keGf-UL-uyl[sOz\k I)l04jH ۍBqI::D} Z}0'|h cIBQLb9|؂p<_Mnq/RhM#J*qoJ ?ٻ A8is-8 ƫr/G †A8_a:BVȿ%3o>w8Z>,Y40@.H^[xw.cvD0 Iz> .github/workflows/check_pr.ymljujuà38Vo!.github/workflows/deploy_docs.ymljuju8"ZOp[/] .gitignorejuju~HxmvbV0y CHANGELOG.mdjujuMp-/m<5ZR͈LICENSE_GPL.txtjuju h|R$LICENSE_LGPL.txtjugjug_1{ZcB#5'φLICENSE_MIT.txtjugjugjCNs(צӫ(J_.g README.mdjugjugb$"Nb+r;jcompatibility.mdjugjug7)f($d^Gd߯ doc/gtk-layer-shell-docs.sgmljufjufqoia\@@}5p>@&doc/meson.buildjufjuf\,H3&:yL2L2udoc/xml/gtkdocentities.ent.injujus)t :Pdoc/xml/meson.buildjujugtk-priv/scripts/version.pyjuʯjuʯ3UnY@8`9ڕinclude/gtk-layer-shell.hjuOjuOp>Т&Eyinclude/meson.buildjuOjuO_S _,9%g\M meson.buildjuOjuOe85U$t@LXfmeson_options.txtju՚ju՚,*[@f]ݟ2protocol/meson.buildju՚ju՚H/тG s)p(protocol/wlr-layer-shell-unstable-v1.xmlju՚ju՚‰Y=nhprotocol/xdg-shell.xmlju՚ju՚rc*+"c+ ʻP release_process.mdju՚ju՚'q{QjK?Bo3$&4 src/api.cjuގjuގ ?,p첂cysrc/custom-shell-surface.cjuގjuގ ?z(;U9 psrc/custom-shell-surface.hjuގjuގ"ьpmJ@k#src/gtk-priv-access.cjuގjuގRLl ioLnOW0 src/gtk-priv-access.hjuގjuގ T%JwXƞr?ʰ src/gtk-wayland.cjuގjuގҲ=L\Q/XDsrc/gtk-wayland.hjujuAcWOAlLx x8Tsrc/layer-surface.cjuju x3#N2rtsrc/layer-surface.hjuju muיni9FڮuCsrc/meson.buildjuju3w{{TWtF ?Ysrc/simple-conversions.cjujuO)IQ:I&ьm'src/simple-conversions.hjuju/F|Yh9 r~src/xdg-popup-surface.cjuju5G!m+#ET Wsrc/xdg-popup-surface.hjujufU2X+vmOsrc/xdg-toplevel-surface.cjuju9;sڶ[bJrsrc/xdg-toplevel-surface.hjujuڰ-B{է:+)test/README.mdju1ju1 Tߑ͆.~npOZYL3±z$test/check-all-tests-are-in-meson.pyju1ju1NӺ{ıtest/check-licenses.pyju1ju1 ^e993130?V6test/integration-test-common/integration-test-common.cjujuC@$d6test/integration-test-common/integration-test-common.hjuju 4)(0=P^yz(test/integration-test-common/meson.buildjujuҘ2'5.͟I*Y1m "test/integration-tests/meson.buildjuijui ݍ8$ ̈́zyT3test/integration-tests/test-adapts-to-screen-size.cjuijui  i9/˝$`r8h;test/integration-tests/test-auto-exclusive-zone-no-margin.cjuijui0.MI]@}'Ctest/integration-tests/test-auto-exclusive-zone-weird-bool-values.cjuijuifQuPxׁ$޿vUkf=test/integration-tests/test-auto-exclusive-zone-with-margin.cjuijuiU.xaX.ɟy2p~.r1test/integration-tests/test-close-layer-surface.cjuijui6-2GgI/test/integration-tests/test-create-subsurface.cjuijuiE8K"8>Z%1test/integration-tests/test-creation-properties.cjuijuiQj ٰnHcGB=test/integration-tests/test-exclusive-zone-below-negative-1.cjuijui,qm 3m3E9)test/integration-tests/test-expect-fail.cjuijui|6I'?˒0iϏ5test/integration-tests/test-get-auto-exclusive-zone.cjuijuicyԓ8Uj9test/integration-tests/test-get-explicit-exclusive-zone.cjuijui+?F{SLTG8test/integration-tests/test-get-keyboard-interactivity.cjuijuig~ӆ-s-5?h/test/integration-tests/test-get-keyboard-mode.cjuijuivhsMDJMDž"'test/integration-tests/test-get-layer.cjuijuidqr41  u6:)test/integration-tests/test-get-monitor.cjuijuiʻS,tLܼ=1ȎqO.test/integration-tests/test-set-size-request.cjuajua ZX-\boVۈ,test/integration-tests/test-single-anchors.cjuajuaH-L6" ]Ctest/integration-tests/test-window-with-initially-attached-buffer.cjuajua+zYY&lxM-,\test/license-ignore.txtjuajuaD-NDcJ test/meson.buildjuajua VZ ;p)"31 Otest/mock-server/meson.buildju (ju (B jRptest/mock-server/mock-server.cju (ju ( U\N?5j5 test/mock-server/mock-server.hju (ju (5Z\Nv&test/mock-server/overrides.cju (ju ( )'::-N"GB0test/run-integration-test.pyju (ju ( UW0[EH [bDtest/test-common/meson.buildjuju tJ f✽;N?test/test-common/test-common.hjuju hR sBXx?:dtest/tests-not-enabled.pyjujuܣ%$SY{_test/unit-tests/meson.buildjuljul$]9'p~~I:cBaT"test/unit-tests/test-get-version.cTREE126 8 Wo"Eػ*@sedoc4 1 MFK8!qoJxml2 0 vn c'JW@zsrc16 0 :}`,Zu7|8( test49 5 fj!剹 7Sunit-tests2 0 4(IpPkmock-server4 0 fH!7(}prZ9)4test-common2 0 Ja=G3X7~integration-tests31 0 yʜ+p*wPintegration-test-common3 0 lYjHggi.github4 1 7|N -Ŋworkflows3 0 w>]9:)wlmaker-0.8/.git/modules/examples/gtk-layer-shell/info/0000755000175100017510000000000015203543560022522 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/info/exclude0000755000175100017510000000036015203543560024100 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/.git/modules/examples/gtk-layer-shell/config0000644000175100017510000000210415203543566022762 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 eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUl2ZEhkcGNuQXZaMmwwYUhWaUxtRjFkR2hsYm5ScFkyRjBhVzl1TG5Zd0xrTnlaV1JsYm5ScFlXeE5ZVzVoWjJWeUx5SXNJbUY2WXlJNld5SnphWFJsTHpFM01UTTVOamd6TXpRNUlsMHNJbVY0Y0NJNk1UYzNPVE0xTnpBMU1pd2lhV0YwSWpveE56YzVNelV6TkRVeUxDSnBjM01pT2lKbmFYUm9kV0lpTENKcWRHa2lPaUk1TXpZNE1qQTNNaTFtTldabUxUUXpaVE10T1RSaE9DMWhZV00zWkdOaFpEbG1PR1VpTENKemRXSWlPaUpwYm5SbFozSmhkR2x2Ymk4eE5UTTJPQ0o5Lmltdzc1X01raEoxX0FnQ3VZWWU0RnlKOWhrWFkyR094WWRjTm1SbVBEQXFUS01BRUhTUVIxSWE5NHdkUEtGRnE4dXZ5QVpzamt1SHB5SjlsTDdkaXh3 [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8/.git/modules/examples/gtk-layer-shell/FETCH_HEAD0000644000175100017510000000020015203543565023121 0ustar runnerrunner5f71546112fd284aced13e7b2391a601204bcacd '5f71546112fd284aced13e7b2391a601204bcacd' of https://github.com/wmww/gtk-layer-shell wlmaker-0.8/.git/modules/examples/gtk-layer-shell/shallow0000644000175100017510000000012215203543564023162 0ustar runnerrunner1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 5f71546112fd284aced13e7b2391a601204bcacd wlmaker-0.8/.git/modules/examples/gtk-layer-shell/packed-refs0000644000175100017510000000016215203543561023676 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 refs/remotes/origin/master wlmaker-0.8/.git/modules/examples/gtk-layer-shell/hooks/0000755000175100017510000000000015203543560022712 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/hooks/push-to-checkout.sample0000755000175100017510000000533715203543560027332 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/.git/modules/examples/gtk-layer-shell/hooks/pre-applypatch.sample0000755000175100017510000000065015203543560027052 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed # 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. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : wlmaker-0.8/.git/modules/examples/gtk-layer-shell/hooks/post-update.sample0000755000175100017510000000027515203543560026371 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/.git/modules/examples/gtk-layer-shell/hooks/update.sample0000755000175100017510000000710215203543560025402 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/.git/modules/examples/gtk-layer-shell/hooks/pre-push.sample0000755000175100017510000000253615203543560025671 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/.git/modules/examples/gtk-layer-shell/hooks/pre-merge-commit.sample0000755000175100017510000000064015203543560027271 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/.git/modules/examples/gtk-layer-shell/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315203543560027733 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/.git/modules/examples/gtk-layer-shell/hooks/prepare-commit-msg.sample0000755000175100017510000000272415203543560027635 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/.git/modules/examples/gtk-layer-shell/hooks/pre-commit.sample0000755000175100017510000000316115203543560026175 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/.git/modules/examples/gtk-layer-shell/hooks/applypatch-msg.sample0000755000175100017510000000073615203543560027057 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/.git/modules/examples/gtk-layer-shell/hooks/commit-msg.sample0000755000175100017510000000366415203543560026205 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/.git/modules/examples/gtk-layer-shell/hooks/pre-rebase.sample0000755000175100017510000001144215203543560026147 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/.git/modules/examples/gtk-layer-shell/hooks/pre-receive.sample0000755000175100017510000000104015203543560026321 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/.git/modules/examples/gtk-layer-shell/logs/0000755000175100017510000000000015203543561022534 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/refs/0000755000175100017510000000000015203543561023473 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/refs/remotes/0000755000175100017510000000000015203543561025151 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/refs/remotes/origin/0000755000175100017510000000000015203543561026440 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/refs/remotes/origin/HEAD0000644000175100017510000000035515203543561027067 0ustar runnerrunner0000000000000000000000000000000000000000 1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 runner 1779353457 +0000 clone: from https://github.com/wmww/gtk-layer-shell.git wlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/refs/heads/0000755000175100017510000000000015203543561024557 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/refs/heads/master0000644000175100017510000000035515203543561026000 0ustar runnerrunner0000000000000000000000000000000000000000 1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 runner 1779353457 +0000 clone: from https://github.com/wmww/gtk-layer-shell.git wlmaker-0.8/.git/modules/examples/gtk-layer-shell/logs/HEAD0000644000175100017510000000075315203543565023171 0ustar runnerrunner0000000000000000000000000000000000000000 1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 runner 1779353457 +0000 clone: from https://github.com/wmww/gtk-layer-shell.git 1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 5f71546112fd284aced13e7b2391a601204bcacd runner 1779353461 +0000 checkout: moving from master to 5f71546112fd284aced13e7b2391a601204bcacd wlmaker-0.8/.git/modules/examples/gtk-layer-shell/HEAD0000644000175100017510000000005115203543565022214 0ustar runnerrunner5f71546112fd284aced13e7b2391a601204bcacd wlmaker-0.8/.git/FETCH_HEAD0000644000175100017510000000017415203543557014642 0ustar runnerrunner7407625b606e3df0c02d4d2597a8c6ca4a85da24 '7407625b606e3df0c02d4d2597a8c6ca4a85da24' of https://github.com/phkaeser/wlmaker wlmaker-0.8/.git/shallow0000644000175100017510000000005115203543557014673 0ustar runnerrunner7407625b606e3df0c02d4d2597a8c6ca4a85da24 wlmaker-0.8/.git/hooks/0000755000175100017510000000000015203543557014426 5ustar runnerrunnerwlmaker-0.8/.git/hooks/push-to-checkout.sample0000755000175100017510000000533715203543557021046 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/.git/hooks/pre-applypatch.sample0000755000175100017510000000065015203543557020566 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed # 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. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : wlmaker-0.8/.git/hooks/post-update.sample0000755000175100017510000000027515203543557020105 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/.git/hooks/update.sample0000755000175100017510000000710215203543557017116 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/.git/hooks/pre-push.sample0000755000175100017510000000253615203543557017405 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/.git/hooks/pre-merge-commit.sample0000755000175100017510000000064015203543557021005 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/.git/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315203543557021447 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/.git/hooks/prepare-commit-msg.sample0000755000175100017510000000272415203543557021351 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/.git/hooks/pre-commit.sample0000755000175100017510000000316115203543557017711 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/.git/hooks/applypatch-msg.sample0000755000175100017510000000073615203543557020573 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/.git/hooks/commit-msg.sample0000755000175100017510000000366415203543557017721 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/.git/hooks/pre-rebase.sample0000755000175100017510000001144215203543557017663 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/.git/hooks/pre-receive.sample0000755000175100017510000000104015203543557020035 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/.git/logs/0000755000175100017510000000000015203543557014247 5ustar runnerrunnerwlmaker-0.8/.git/logs/HEAD0000644000175100017510000000034415203543557014674 0ustar runnerrunner0000000000000000000000000000000000000000 7407625b606e3df0c02d4d2597a8c6ca4a85da24 runner 1779353455 +0000 checkout: moving from master to refs/tags/v0.8 wlmaker-0.8/.git/HEAD0000644000175100017510000000005115203543557013723 0ustar runnerrunner7407625b606e3df0c02d4d2597a8c6ca4a85da24 wlmaker-0.8/submodules/0000755000175100017510000000000015203543557014624 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/0000755000175100017510000000000015203543566016225 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/libbase.pc.in0000644000175100017510000000156215203543566020563 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/submodules/libbase/Doxyfile.in0000644000175100017510000034032115203543566020343 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 # , //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/submodules/libbase/src/gfxbuf_xpm.c0000644000175100017510000003646015203543566021336 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/submodules/libbase/src/thread.c0000644000175100017510000001057615203543566020440 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/submodules/libbase/src/subprocess.c0000644000175100017510000010353215203543566021354 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/submodules/libbase/src/arg.c0000644000175100017510000010572315203543566017741 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/submodules/libbase/src/gfxbuf.c0000644000175100017510000004556315203543566020456 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/submodules/libbase/src/time.c0000644000175100017510000000567215203543566020130 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/submodules/libbase/src/strutil.c0000644000175100017510000002746415203543566020703 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/submodules/libbase/src/log.c0000644000175100017510000002363415203543566017751 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/submodules/libbase/src/CMakeLists.txt0000644000175100017510000000421115203543566021552 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/submodules/libbase/src/file.c0000644000175100017510000003646615203543566020116 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/submodules/libbase/src/plist/0000755000175100017510000000000015203543566020147 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/src/plist/analyzer.l0000644000175100017510000000566315203543566022163 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/submodules/libbase/src/plist/decode.c0000644000175100017510000013736115203543566021551 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/submodules/libbase/src/plist/grammar.y0000644000175100017510000001217015203543566021770 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/submodules/libbase/src/plist/parser_context.h0000644000175100017510000000233715203543566023365 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/submodules/libbase/src/plist/model.c0000644000175100017510000010402315203543566021413 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/submodules/libbase/src/plist/CMakeLists.txt0000644000175100017510000000467715203543566022725 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/submodules/libbase/src/plist/parse.c0000644000175100017510000002605115203543566021431 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/submodules/libbase/src/plist/conf.md0000644000175100017510000000226315203543566021421 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/submodules/libbase/src/ptr_vector.c0000644000175100017510000001461715203543566021360 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/submodules/libbase/src/dllist.c0000644000175100017510000007274615203543566020473 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/submodules/libbase/.github/0000755000175100017510000000000015203543566017565 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/.github/workflows/0000755000175100017510000000000015203543566021622 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/.github/workflows/build-for-linux.yml0000644000175100017510000000223515203543566025367 0ustar runnerrunnername: Build for Linux on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build_matrix: strategy: matrix: compiler: [ "gcc", "clang" ] runs-on: ubuntu-latest container: image: debian:trixie steps: - name: Install package dependencies. run: | apt-get update apt-get install -y \ bison \ clang \ cmake \ doxygen \ flex \ git \ iwyu \ libcairo2-dev \ libncurses-dev - name: Checkout code. uses: actions/checkout@v3 - name: Configure libbase through CMake. run: | export CC="${{ matrix.compiler }}" cmake -B ${{github.workspace}}/build -Dconfig_WERROR=ON -DIWYU_MODE=FAIL - name: Build libbase. run: | export CC="${{ matrix.compiler }}" cmake --build ${{github.workspace}}/build - name: Build documentation. run: | cmake --build ${{github.workspace}}/build --target doc - name: Run tests. run: | ctest --test-dir ${{github.workspace}}/build --build-run-dir ${{github.workspace}}/build -V wlmaker-0.8/submodules/libbase/.github/workflows/build-for-bsd.yml0000644000175100017510000000147615203543566025006 0ustar runnerrunnername: Build for Free BSD 14.2 on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code, including git submodules. uses: actions/checkout@v4 with: submodules: true - name: Configure, build and test libbase through CMake. uses: cross-platform-actions/action@v0.26.0 with: operating_system: freebsd version: '14.2' run: | sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf sudo pkg install -y \ devel/bison \ devel/cmake-core \ devel/pkgconf \ graphics/cairo cmake -B build/ -Dconfig_WERROR=ON cmake --build build/ ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/submodules/libbase/LICENSE0000644000175100017510000002613515203543566017241 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/submodules/libbase/iwyu-mappings.imp0000644000175100017510000000045515203543566021551 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/submodules/libbase/.gitignore0000644000175100017510000000030715203543566020215 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/submodules/libbase/STYLE.md0000644000175100017510000000547715203543566017464 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/submodules/libbase/tests/0000755000175100017510000000000015203543566017367 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/tests/libbase_benchmark.c0000644000175100017510000000220215203543566023142 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/submodules/libbase/tests/subprocess_test_sigpipe.c0000644000175100017510000000173715203543566024512 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/submodules/libbase/tests/subprocess_test_failure.c0000644000175100017510000000164615203543566024500 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/submodules/libbase/tests/libbase_test.c0000644000175100017510000002321215203543566022173 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/submodules/libbase/tests/plist_test.c0000644000175100017510000000262715203543566021734 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/submodules/libbase/tests/CMakeLists.txt0000644000175100017510000000471715203543566022140 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/submodules/libbase/tests/subprocess_test_success.c0000644000175100017510000000261015203543566024511 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/submodules/libbase/tests/data/0000755000175100017510000000000015203543566020300 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/tests/data/string.plist0000644000175100017510000000003515203543566022661 0ustar runnerrunner// With a comment. file_valuewlmaker-0.8/submodules/libbase/tests/data/abcd.txt0000644000175100017510000000000515203543566021725 0ustar runnerrunnerabcd wlmaker-0.8/submodules/libbase/tests/data/gfxbuf_equals.png0000644000175100017510000000012715203543566023641 0ustar runnerrunnerPNG  IHDRwSbKGD IDATchpP$DOIENDB`wlmaker-0.8/submodules/libbase/tests/data/array.plist0000644000175100017510000000023715203543566022475 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/submodules/libbase/tests/data/dict.plist0000644000175100017510000000025515203543566022302 0ustar runnerrunner// comment. { key0 = "value0"; // comment. // more comment. subdict = { // comment. key1 = value1 // comment. }; // comment. "quoted+key" = value; } //more.wlmaker-0.8/submodules/libbase/tests/subprocess_test_hang.c0000644000175100017510000000202115203543566023752 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/submodules/libbase/CONTRIBUTING.md0000644000175100017510000000205415203543566020457 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/submodules/libbase/CODE_OF_CONDUCT.md0000644000175100017510000001062315203543566021026 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/submodules/libbase/CMakeLists.txt0000644000175100017510000001333515203543566020772 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/submodules/libbase/BACKLOG.md0000644000175100017510000000116115203543566017610 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/submodules/libbase/include/0000755000175100017510000000000015203543566017650 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/include/libbase/0000755000175100017510000000000015203543566021251 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/include/libbase/ptr_vector.h0000644000175100017510000000500215203543566023606 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/submodules/libbase/include/libbase/gfxbuf.h0000644000175100017510000001613715203543566022713 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/submodules/libbase/include/libbase/ref.h0000644000175100017510000000252215203543566022177 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/submodules/libbase/include/libbase/atomic.h0000644000175100017510000003246015203543566022703 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/submodules/libbase/include/libbase/log_wrappers.h0000644000175100017510000000563015203543566024132 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/submodules/libbase/include/libbase/gfxbuf_xpm.h0000644000175100017510000000265215203543566023574 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/submodules/libbase/include/libbase/def.h0000644000175100017510000000462015203543566022162 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/submodules/libbase/include/libbase/dynbuf.h0000644000175100017510000001233715203543566022717 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/submodules/libbase/include/libbase/time.h0000644000175100017510000000251115203543566022357 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/submodules/libbase/include/libbase/avltree.h0000644000175100017510000001230015203543566023060 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/submodules/libbase/include/libbase/sock.h0000644000175100017510000000441315203543566022363 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/submodules/libbase/include/libbase/ptr_stack.h0000644000175100017510000000606315203543566023421 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/submodules/libbase/include/libbase/test.h0000644000175100017510000003266115203543566022411 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/submodules/libbase/include/libbase/plist.h0000644000175100017510000000255615203543566022565 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/submodules/libbase/include/libbase/libbase.h0000644000175100017510000000503415203543566023025 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/submodules/libbase/include/libbase/arg.h0000644000175100017510000001776715203543566022215 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/submodules/libbase/include/libbase/dllist.h0000644000175100017510000001345615203543566022726 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/submodules/libbase/include/libbase/thread.h0000644000175100017510000000426115203543566022674 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/submodules/libbase/include/libbase/dequeue.h0000644000175100017510000000605315203543566023063 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/submodules/libbase/include/libbase/vector.h0000644000175100017510000000370015203543566022724 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/submodules/libbase/include/libbase/assert.h0000644000175100017510000000413115203543566022722 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/submodules/libbase/include/libbase/subprocess.h0000644000175100017510000001217415203543566023617 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/submodules/libbase/include/libbase/plist/0000755000175100017510000000000015203543566022404 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/include/libbase/plist/decode.h0000644000175100017510000004675115203543566024015 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/submodules/libbase/include/libbase/plist/parse.h0000644000175100017510000000410215203543566023664 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/submodules/libbase/include/libbase/plist/model.h0000644000175100017510000002060215203543566023655 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/submodules/libbase/include/libbase/strutil.h0000644000175100017510000001125615203543566023135 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/submodules/libbase/include/libbase/log.h0000644000175100017510000000602715203543566022210 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/submodules/libbase/include/libbase/ptr_set.h0000644000175100017510000000502715203543566023106 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/submodules/libbase/include/libbase/file.h0000644000175100017510000001452215203543566022345 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/submodules/libbase/tools/0000755000175100017510000000000015203543566017365 5ustar runnerrunnerwlmaker-0.8/submodules/libbase/tools/plist_parse.c0000644000175100017510000000552615203543566022066 0ustar runnerrunner/* ========================================================================= */ /** * @file plist_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 #include #include #include #include #include /*** Initial size for the output dynbuffer. */ static const size_t min_size = 1 << 16; static uint32_t indentation = 4; /** Specification of commandline arguments. */ bs_arg_t args[] = { BS_ARG_UINT32( "indentation", "Indentation to use when writing the parsed plist. Default: 4.", 4, 0, INT32_MAX, &indentation), BS_ARG_SENTINEL(), }; /** Main program, runs the unit tests. */ int main(int argc, const char **argv) { if (!bs_arg_parse(args, BS_ARG_MODE_EXTRA_VALUES, &argc, argv)) { bs_arg_print_usage(stderr, args); return EXIT_FAILURE; } if (2 != argc) { fprintf(stderr, "Usage: %s PLIST_FILE\n", argv[0]); return EXIT_FAILURE; } bspl_object_t *object_ptr = bspl_create_object_from_plist_file(argv[1]); if (NULL == object_ptr) { fprintf(stderr, "Failed bspl_create_object_from_plist_file(\"%s\")\n", argv[1]); return EXIT_FAILURE; } bs_dynbuf_t dynbuf; if (!bs_dynbuf_init(&dynbuf, min_size, SIZE_MAX)) { fprintf(stderr, "Failed bs_dynbuf_init(%p, %zu, %zu). " "Insufficient memory?\n", &dynbuf, min_size, SIZE_MAX); bspl_object_unref(object_ptr); return EXIT_FAILURE; } bool rv = bspl_object_write_indented(object_ptr, &dynbuf, indentation, 0); bspl_object_unref(object_ptr); if (!rv) { fprintf(stderr, "Failed bspl_object_write(%p, %p). " "Insufficient memory?\n", object_ptr, &dynbuf); bs_dynbuf_fini(&dynbuf); return EXIT_FAILURE; } size_t written = fwrite(dynbuf.data_ptr, dynbuf.length, 1, stdout); if (1 != written) { fprintf(stderr, "Failed fwrite(%p, %zu, 1, stdout)\n", dynbuf.data_ptr, dynbuf.length); bs_dynbuf_fini(&dynbuf); return EXIT_FAILURE; } else { fprintf(stdout, "\n"); } bs_dynbuf_fini(&dynbuf); return EXIT_SUCCESS; } /* == End of plist_parse.c ================================================= */ wlmaker-0.8/submodules/libbase/tools/CMakeLists.txt0000644000175100017510000000215215203543566022125 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( plist_parse plist_parse.c) target_link_libraries( plist_parse PRIVATE libbase libbase_plist libbase_compiler_flags) target_include_directories(plist_parse PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/") if(iwyu_path_and_options) set_target_properties( plist_parse PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() # Add 'install' target, but only if we're the toplevel project. if(CMAKE_PROJECT_NAME STREQUAL libbase) install( TARGETS plist_parse DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() wlmaker-0.8/submodules/libbase/README.md0000644000175100017510000000134415203543566017506 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/submodules/CMakeLists.txt0000644000175100017510000000267715203543557017400 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) add_subdirectory(libbase) # Falling back to a provided version of `inih`: The distribution's appears to # use the default settings and definitions, among which there is a maximum line # length of 200 characters. That doesn't work for `.desktop` files. # # Obtained from: https://github.com/benhoyt/inih # # (C) Copyright (c) 2009, Ben Hoyt -- under "New BSD license". # # For wlmaker, the library is configured to permit much longer lines, and use # the heap for dynamic sizing. add_library(inih STATIC "${CMAKE_CURRENT_SOURCE_DIR}/inih/ini.c") target_compile_definitions( inih PUBLIC INI_MAX_LINE=65536 INI_USE_STACK=0 INI_ALLOW_REALLOC=1) target_include_directories( inih PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/inih") set_target_properties( inih PROPERTIES VERSION 62 PUBLIC_HEADER ini.h) wlmaker-0.8/submodules/inih/0000755000175100017510000000000015203543566015553 5ustar runnerrunnerwlmaker-0.8/submodules/inih/.gitattributes0000644000175100017510000000015515203543566020447 0ustar runnerrunner# Ensure MSVC CI tests with the same line endings that are used in the tarball /tests/baseline_*.txt eol=lf wlmaker-0.8/submodules/inih/ini.h0000644000175100017510000001443115203543566016506 0ustar runnerrunner/* inih -- simple .INI file parser SPDX-License-Identifier: BSD-3-Clause Copyright (C) 2009-2025, Ben Hoyt inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #ifndef INI_H #define INI_H /* Make this header file easier to include in C++ code */ #ifdef __cplusplus extern "C" { #endif #include /* Nonzero if ini_handler callback should accept lineno parameter. */ #ifndef INI_HANDLER_LINENO #define INI_HANDLER_LINENO 0 #endif /* Visibility symbols, required for Windows DLLs */ #ifndef INI_API #if defined _WIN32 || defined __CYGWIN__ # ifdef INI_SHARED_LIB # ifdef INI_SHARED_LIB_BUILDING # define INI_API __declspec(dllexport) # else # define INI_API __declspec(dllimport) # endif # else # define INI_API # endif #else # if defined(__GNUC__) && __GNUC__ >= 4 # define INI_API __attribute__ ((visibility ("default"))) # else # define INI_API # endif #endif #endif /* Typedef for prototype of handler function. Note that even though the value parameter has type "const char*", the user may cast to "char*" and modify its content, as the value is not used again after the call to ini_handler. This is not true of section and name -- those must not be modified. */ #if INI_HANDLER_LINENO typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value, int lineno); #else typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value); #endif /* Typedef for prototype of fgets-style reader function. */ typedef char* (*ini_reader)(char* str, int num, void* stream); /* Parse given INI-style file. May have [section]s, name=value pairs (whitespace stripped), and comments starting with ';' (semicolon). Section is "" if name=value pair parsed before any section heading. name:value pairs are also supported as a concession to Python's configparser. For each name=value pair parsed, call handler function with given user pointer as well as section, name, and value (data only valid for duration of handler call). Handler should return nonzero on success, zero on error. Returns 0 on success, line number of first error on parse error (doesn't stop on first error), -1 on file open error, or -2 on memory allocation error (only when INI_USE_STACK is zero). */ INI_API int ini_parse(const char* filename, ini_handler handler, void* user); /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't close the file when it's finished -- the caller must do that. */ INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); /* Same as ini_parse(), but takes an ini_reader function pointer instead of filename. Used for implementing custom or string-based I/O (see also ini_parse_string). */ INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user); /* Same as ini_parse(), but takes a zero-terminated string with the INI data instead of a file. Useful for parsing INI data from a network socket or which is already in memory. */ INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); /* Same as ini_parse_string(), but takes a string and its length, avoiding strlen(). Useful for parsing INI data from a network socket or which is already in memory, or interfacing with C++ std::string_view. */ INI_API int ini_parse_string_length(const char* string, size_t length, ini_handler handler, void* user); /* Nonzero to allow multi-line value parsing, in the style of Python's configparser. If allowed, ini_parse() will call the handler with the same name for each subsequent line parsed. */ #ifndef INI_ALLOW_MULTILINE #define INI_ALLOW_MULTILINE 1 #endif /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of the file. See https://github.com/benhoyt/inih/issues/21 */ #ifndef INI_ALLOW_BOM #define INI_ALLOW_BOM 1 #endif /* Chars that begin a start-of-line comment. Per Python configparser, allow both ; and # comments at the start of a line by default. */ #ifndef INI_START_COMMENT_PREFIXES #define INI_START_COMMENT_PREFIXES ";#" #endif /* Nonzero to allow inline comments (with valid inline comment characters specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match Python 3.2+ configparser behaviour. */ #ifndef INI_ALLOW_INLINE_COMMENTS #define INI_ALLOW_INLINE_COMMENTS 1 #endif #ifndef INI_INLINE_COMMENT_PREFIXES #define INI_INLINE_COMMENT_PREFIXES ";" #endif /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ #ifndef INI_USE_STACK #define INI_USE_STACK 1 #endif /* Maximum line length for any line in INI file (stack or heap). Note that this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ #ifndef INI_MAX_LINE #define INI_MAX_LINE 200 #endif /* Nonzero to allow heap line buffer to grow via realloc(), zero for a fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is zero. */ #ifndef INI_ALLOW_REALLOC #define INI_ALLOW_REALLOC 0 #endif /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK is zero. */ #ifndef INI_INITIAL_ALLOC #define INI_INITIAL_ALLOC 200 #endif /* Stop parsing on first error (default is to keep parsing). */ #ifndef INI_STOP_ON_FIRST_ERROR #define INI_STOP_ON_FIRST_ERROR 0 #endif /* Nonzero to call the handler at the start of each new section (with name and value NULL). Default is to only call the handler on each name=value pair. */ #ifndef INI_CALL_HANDLER_ON_NEW_SECTION #define INI_CALL_HANDLER_ON_NEW_SECTION 0 #endif /* Nonzero to allow a name without a value (no '=' or ':' on the line) and call the handler with value NULL in this case. Default is to treat no-value lines as an error. */ #ifndef INI_ALLOW_NO_VALUE #define INI_ALLOW_NO_VALUE 0 #endif /* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory allocation functions (INI_USE_STACK must also be 0). These functions must have the same signatures as malloc/free/realloc and behave in a similar way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ #ifndef INI_CUSTOM_ALLOCATOR #define INI_CUSTOM_ALLOCATOR 0 #endif #ifdef __cplusplus } #endif #endif /* INI_H */ wlmaker-0.8/submodules/inih/meson.build0000644000175100017510000000747515203543566017732 0ustar runnerrunnerproject('inih', ['c'], license : 'BSD-3-Clause', version : '62', default_options : ['cpp_std=c++11'], meson_version: '>=0.56.0' ) #### options #### arg_static = [] distro_install = get_option('distro_install') extra_args = [] if distro_install pkg = import('pkgconfig') else if not get_option('multi-line_entries') arg_static += ['-DINI_ALLOW_MULTILINE=0'] endif if not get_option('utf-8_bom') arg_static += ['-DINI_ALLOW_BOM=0'] endif if not get_option('inline_comments') arg_static += ['-DINI_ALLOW_INLINE_COMMENTS=0'] endif inline_comment_prefix = get_option('inline_comment_prefix') if inline_comment_prefix != ';' arg_static += ['-DINI_INLINE_COMMENT_PREFIXES="' + inline_comment_prefix + '"'] endif sol_comment_prefix = get_option('start-of-line_comment_prefix') if sol_comment_prefix != ';#' arg_static += ['-DINI_START_COMMENT_PREFIXES="' + sol_comment_prefix + '"'] endif if get_option('allow_no_value') arg_static += ['-DINI_ALLOW_NO_VALUE=1'] endif if get_option('stop_on_first_error') arg_static += ['-DINI_STOP_ON_FIRST_ERROR=1'] endif if get_option('report_line_numbers') arg_static += ['-DINI_HANDLER_LINENO=1'] endif if get_option('call_handler_on_new_section') arg_static += ['-DINI_CALL_HANDLER_ON_NEW_SECTION=1'] endif if get_option('use_heap') arg_static += ['-DINI_USE_STACK=0'] endif max_line_length = get_option('max_line_length') if max_line_length != 200 arg_static += ['-DINI_MAX_LINE=' + max_line_length.to_string()] endif initial_malloc_size = get_option('initial_malloc_size') if initial_malloc_size != 200 arg_static += ['-DINI_INITIAL_ALLOC=' + initial_malloc_size.to_string()] endif if get_option('allow_realloc') arg_static += ['-DINI_ALLOW_REALLOC=1'] endif endif if host_machine.system() == 'windows' lib = get_option('default_library') if lib == 'both' error('default_library=both is not supported on Windows') elif lib == 'shared' extra_args += '-DINI_SHARED_LIB' add_project_arguments('-DINI_SHARED_LIB_BUILDING', language: ['c', 'cpp']) endif endif #### inih #### inc_inih = include_directories('.') src_inih = files('ini.c') lib_inih = library('inih', [src_inih], include_directories : inc_inih, c_args : [arg_static, extra_args], install : distro_install, soversion : '0', gnu_symbol_visibility: 'hidden' ) if distro_install install_headers('ini.h') pkg.generate(lib_inih, name : 'inih', description : 'simple .INI file parser', extra_cflags : extra_args, ) endif inih_dep = declare_dependency( link_with : lib_inih, compile_args : arg_static + extra_args, include_directories : inc_inih ) if get_option('tests') subdir('tests') endif #### INIReader #### if get_option('with_INIReader') add_languages( 'cpp', native : false ) inc_INIReader = include_directories('cpp') src_INIReader = files(join_paths('cpp', 'INIReader.cpp')) lib_INIReader = library('INIReader', src_INIReader, cpp_args : extra_args, include_directories : inc_INIReader, dependencies : inih_dep, install : distro_install, soversion : '0', gnu_symbol_visibility: 'hidden' ) if distro_install install_headers('cpp/INIReader.h') pkg.generate(lib_INIReader, name : 'INIReader', description : 'simple .INI file parser for C++', extra_cflags : extra_args, ) endif INIReader_dep = declare_dependency( link_with : lib_INIReader, include_directories : inc_INIReader, compile_args : extra_args ) subdir('examples') endif wlmaker-0.8/submodules/inih/cpp/0000755000175100017510000000000015203543566016335 5ustar runnerrunnerwlmaker-0.8/submodules/inih/cpp/INIReader.cpp0000644000175100017510000001467015203543566020613 0ustar runnerrunner// Read an INI file into easy-to-access name/value pairs. // SPDX-License-Identifier: BSD-3-Clause // Copyright (C) 2009-2025, Ben Hoyt // inih and INIReader are released under the New BSD license (see LICENSE.txt). // Go to the project home page for more info: // // https://github.com/benhoyt/inih #include #include #include #include "../ini.h" #include "INIReader.h" using std::string; INIReader::INIReader(const string& filename) { _error = ini_parse(filename.c_str(), ValueHandler, this); } INIReader::INIReader(const char *buffer, size_t buffer_size) { _error = ini_parse_string_length(buffer, buffer_size, ValueHandler, this); } int INIReader::ParseError() const { return _error; } string INIReader::ParseErrorMessage() const { // If _error is positive it means it is the line number on which a parse // error occurred. This could be an overlong line, that ValueHandler // indicated a user defined error, an unterminated section name, or a name // without a value. if (_error > 0) { return "parse error on line " + std::to_string(_error) + "; missing ']' or '='?"; } // If _error is negative it is a system type error, and 0 means success. switch (_error) { case -2: return "unable to allocate memory"; case -1: return "unable to open file"; case 0: return ""; } // This should never be reached. It probably means a new error code was // added to the C API without updating this method. return "unknown error " + std::to_string(_error); } string INIReader::Get(const string& section, const string& name, const string& default_value) const { string key = MakeKey(section, name); // Use _values.find() here instead of _values.at() to support pre C++11 compilers return _values.count(key) ? _values.find(key)->second : default_value; } string INIReader::GetString(const string& section, const string& name, const string& default_value) const { const string str = Get(section, name, ""); return str.empty() ? default_value : str; } long INIReader::GetInteger(const string& section, const string& name, long default_value) const { string valstr = Get(section, name, ""); const char* value = valstr.c_str(); char* end; // This parses "1234" (decimal) and also "0x4D2" (hex) long n = strtol(value, &end, 0); return end > value ? n : default_value; } INI_API int64_t INIReader::GetInteger64(const string& section, const string& name, int64_t default_value) const { string valstr = Get(section, name, ""); const char* value = valstr.c_str(); char* end; // This parses "1234" (decimal) and also "0x4D2" (hex) int64_t n = strtoll(value, &end, 0); return end > value ? n : default_value; } unsigned long INIReader::GetUnsigned(const string& section, const string& name, unsigned long default_value) const { string valstr = Get(section, name, ""); const char* value = valstr.c_str(); char* end; // This parses "1234" (decimal) and also "0x4D2" (hex) unsigned long n = strtoul(value, &end, 0); return end > value ? n : default_value; } INI_API uint64_t INIReader::GetUnsigned64(const string& section, const string& name, uint64_t default_value) const { string valstr = Get(section, name, ""); const char* value = valstr.c_str(); char* end; // This parses "1234" (decimal) and also "0x4D2" (hex) uint64_t n = strtoull(value, &end, 0); return end > value ? n : default_value; } double INIReader::GetReal(const string& section, const string& name, double default_value) const { string valstr = Get(section, name, ""); const char* value = valstr.c_str(); char* end; double n = strtod(value, &end); return end > value ? n : default_value; } bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const { string valstr = Get(section, name, ""); // Convert to lower case to make string comparisons case-insensitive std::transform(valstr.begin(), valstr.end(), valstr.begin(), [](const unsigned char& ch) { return static_cast(::tolower(ch)); }); if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") return true; else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") return false; else return default_value; } std::vector INIReader::Sections() const { std::set sectionSet; for (std::map::const_iterator it = _values.begin(); it != _values.end(); ++it) { size_t pos = it->first.find('='); if (pos != string::npos) { sectionSet.insert(it->first.substr(0, pos)); } } return std::vector(sectionSet.begin(), sectionSet.end()); } std::vector INIReader::Keys(const string& section) const { std::vector keys; string keyPrefix = MakeKey(section, ""); for (std::map::const_iterator it = _values.begin(); it != _values.end(); ++it) { if (it->first.compare(0, keyPrefix.length(), keyPrefix) == 0) { keys.push_back(it->first.substr(keyPrefix.length())); } } return keys; } bool INIReader::HasSection(const string& section) const { const string key = MakeKey(section, ""); std::map::const_iterator pos = _values.lower_bound(key); if (pos == _values.end()) return false; // Does the key at the lower_bound pos start with "section"? return pos->first.compare(0, key.length(), key) == 0; } bool INIReader::HasValue(const string& section, const string& name) const { string key = MakeKey(section, name); return _values.count(key); } string INIReader::MakeKey(const string& section, const string& name) { string key = section + "=" + name; // Convert to lower case to make section/name lookups case-insensitive std::transform(key.begin(), key.end(), key.begin(), [](const unsigned char& ch) { return static_cast(::tolower(ch)); }); return key; } int INIReader::ValueHandler(void* user, const char* section, const char* name, const char* value) { if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled return 1; INIReader* reader = static_cast(user); string key = MakeKey(section, name); if (reader->_values[key].size() > 0) reader->_values[key] += "\n"; reader->_values[key] += value ? value : ""; return 1; } wlmaker-0.8/submodules/inih/cpp/INIReader.h0000644000175100017510000001171115203543566020251 0ustar runnerrunner// Read an INI file into easy-to-access name/value pairs. // SPDX-License-Identifier: BSD-3-Clause // Copyright (C) 2009-2025, Ben Hoyt // inih and INIReader are released under the New BSD license (see LICENSE.txt). // Go to the project home page for more info: // // https://github.com/benhoyt/inih #ifndef INIREADER_H #define INIREADER_H #include #include #include #include #include // Visibility symbols, required for Windows DLLs #ifndef INI_API #if defined _WIN32 || defined __CYGWIN__ # ifdef INI_SHARED_LIB # ifdef INI_SHARED_LIB_BUILDING # define INI_API __declspec(dllexport) # else # define INI_API __declspec(dllimport) # endif # else # define INI_API # endif #else # if defined(__GNUC__) && __GNUC__ >= 4 # define INI_API __attribute__ ((visibility ("default"))) # else # define INI_API # endif #endif #endif // Read an INI file into easy-to-access name/value pairs. (Note that I've gone // for simplicity here rather than speed, but it should be pretty decent.) class INIReader { public: // Construct INIReader and parse given filename. See ini.h for more info // about the parsing. INI_API explicit INIReader(const std::string& filename); // Construct INIReader and parse given buffer. See ini.h for more info // about the parsing. INI_API explicit INIReader(const char *buffer, size_t buffer_size); // Return the result of ini_parse(), i.e., 0 on success, line number of // first error on parse error, -1 on file open error, or -2 if there was a // memory allocation error. INI_API int ParseError() const; // Return a message that describes the type of error that occurred. // It will return "" (empty string) if there was no error. INI_API std::string ParseErrorMessage() const; // Get a string value from INI file, returning default_value if not found. INI_API std::string Get(const std::string& section, const std::string& name, const std::string& default_value) const; // Get a string value from INI file, returning default_value if not found, // empty, or contains only whitespace. INI_API std::string GetString(const std::string& section, const std::string& name, const std::string& default_value) const; // Get an integer (long) value from INI file, returning default_value if // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const; // Get a 64-bit integer (int64_t) value from INI file, returning default_value if // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). INI_API int64_t GetInteger64(const std::string& section, const std::string& name, int64_t default_value) const; // Get an unsigned integer (unsigned long) value from INI file, returning default_value if // not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2"). INI_API unsigned long GetUnsigned(const std::string& section, const std::string& name, unsigned long default_value) const; // Get an unsigned 64-bit integer (uint64_t) value from INI file, returning default_value if // not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2"). INI_API uint64_t GetUnsigned64(const std::string& section, const std::string& name, uint64_t default_value) const; // Get a real (floating point double) value from INI file, returning // default_value if not found or not a valid floating point value // according to strtod(). INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const; // Get a boolean value from INI file, returning default_value if not found or if // not a valid true/false value. Valid true values are "true", "yes", "on", "1", // and valid false values are "false", "no", "off", "0" (not case sensitive). INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; // Return a newly-allocated vector of all section names, in alphabetical order. INI_API std::vector Sections() const; // Return a newly-allocated vector of keys in the given section, in alphabetical order. INI_API std::vector Keys(const std::string& section) const; // Return true if the given section exists (section must contain at least // one name=value pair). INI_API bool HasSection(const std::string& section) const; // Return true if a value exists with the given section and field names. INI_API bool HasValue(const std::string& section, const std::string& name) const; protected: int _error; std::map _values; static std::string MakeKey(const std::string& section, const std::string& name); static int ValueHandler(void* user, const char* section, const char* name, const char* value); }; #endif // INIREADER_H wlmaker-0.8/submodules/inih/.git0000644000175100017510000000005315203543561016330 0ustar runnerrunnergitdir: ../../.git/modules/submodules/inih wlmaker-0.8/submodules/inih/ini.c0000644000175100017510000002174715203543566016511 0ustar runnerrunner/* inih -- simple .INI file parser SPDX-License-Identifier: BSD-3-Clause Copyright (C) 2009-2025, Ben Hoyt inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include "ini.h" #if !INI_USE_STACK #if INI_CUSTOM_ALLOCATOR #include void* ini_malloc(size_t size); void ini_free(void* ptr); void* ini_realloc(void* ptr, size_t size); #else #include #define ini_malloc malloc #define ini_free free #define ini_realloc realloc #endif #endif #define MAX_SECTION 50 #define MAX_NAME 50 /* Used by ini_parse_string() to keep track of string parsing state. */ typedef struct { const char* ptr; size_t num_left; } ini_parse_string_ctx; /* Strip whitespace chars off end of given string, in place. end must be a pointer to the NUL terminator at the end of the string. Return s. */ static char* ini_rstrip(char* s, char* end) { while (end > s && isspace((unsigned char)(*--end))) *end = '\0'; return s; } /* Return pointer to first non-whitespace char in given string. */ static char* ini_lskip(const char* s) { while (*s && isspace((unsigned char)(*s))) s++; return (char*)s; } /* Return pointer to first char (of chars) or inline comment in given string, or pointer to NUL at end of string if neither found. Inline comment must be prefixed by a whitespace character to register as a comment. */ static char* ini_find_chars_or_comment(const char* s, const char* chars) { #if INI_ALLOW_INLINE_COMMENTS int was_space = 0; while (*s && (!chars || !strchr(chars, *s)) && !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { was_space = isspace((unsigned char)(*s)); s++; } #else while (*s && (!chars || !strchr(chars, *s))) { s++; } #endif return (char*)s; } /* Similar to strncpy, but ensures dest (size bytes) is NUL-terminated, and doesn't pad with NULs. */ static char* ini_strncpy0(char* dest, const char* src, size_t size) { /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ size_t i; for (i = 0; i < size - 1 && src[i]; i++) dest[i] = src[i]; dest[i] = '\0'; return dest; } /* See documentation in header file. */ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user) { /* Uses a fair bit of stack (use heap instead if you need to) */ #if INI_USE_STACK char line[INI_MAX_LINE]; size_t max_line = INI_MAX_LINE; #else char* line; size_t max_line = INI_INITIAL_ALLOC; #endif #if INI_ALLOW_REALLOC && !INI_USE_STACK char* new_line; #endif char section[MAX_SECTION] = ""; #if INI_ALLOW_MULTILINE char prev_name[MAX_NAME] = ""; #endif size_t offset; char* start; char* end; char* name; char* value; int lineno = 0; int error = 0; char abyss[16]; /* Used to consume input when a line is too long. */ #if !INI_USE_STACK line = (char*)ini_malloc(INI_INITIAL_ALLOC); if (!line) { return -2; } #endif #if INI_HANDLER_LINENO #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) #else #define HANDLER(u, s, n, v) handler(u, s, n, v) #endif /* Scan through stream line by line */ while (reader(line, (int)max_line, stream) != NULL) { offset = strlen(line); #if INI_ALLOW_REALLOC && !INI_USE_STACK while (max_line < INI_MAX_LINE && offset == max_line - 1 && line[offset - 1] != '\n') { max_line *= 2; if (max_line > INI_MAX_LINE) max_line = INI_MAX_LINE; new_line = ini_realloc(line, max_line); if (!new_line) { ini_free(line); return -2; } line = new_line; if (reader(line + offset, (int)(max_line - offset), stream) == NULL) break; offset += strlen(line + offset); } #endif lineno++; /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */ if (offset == max_line - 1 && line[offset - 1] != '\n') { while (reader(abyss, sizeof(abyss), stream) != NULL) { if (!error) error = lineno; if (abyss[strlen(abyss) - 1] == '\n') break; } } start = line; #if INI_ALLOW_BOM if (lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB && (unsigned char)start[2] == 0xBF) { start += 3; } #endif start = ini_rstrip(ini_lskip(start), line + offset); if (strchr(INI_START_COMMENT_PREFIXES, *start)) { /* Start-of-line comment */ } #if INI_ALLOW_MULTILINE else if (*prev_name && *start && start > line) { #if INI_ALLOW_INLINE_COMMENTS end = ini_find_chars_or_comment(start, NULL); *end = '\0'; ini_rstrip(start, end); #endif /* Non-blank line with leading whitespace, treat as continuation of previous name's value (as per Python configparser). */ if (!HANDLER(user, section, prev_name, start) && !error) error = lineno; } #endif else if (*start == '[') { /* A "[section]" line */ end = ini_find_chars_or_comment(start + 1, "]"); if (*end == ']') { *end = '\0'; ini_strncpy0(section, start + 1, sizeof(section)); #if INI_ALLOW_MULTILINE *prev_name = '\0'; #endif #if INI_CALL_HANDLER_ON_NEW_SECTION if (!HANDLER(user, section, NULL, NULL) && !error) error = lineno; #endif } else if (!error) { /* No ']' found on section line */ error = lineno; } } else if (*start) { /* Not a comment, must be a name[=:]value pair */ end = ini_find_chars_or_comment(start, "=:"); if (*end == '=' || *end == ':') { *end = '\0'; name = ini_rstrip(start, end); value = end + 1; #if INI_ALLOW_INLINE_COMMENTS end = ini_find_chars_or_comment(value, NULL); *end = '\0'; #endif value = ini_lskip(value); ini_rstrip(value, end); #if INI_ALLOW_MULTILINE ini_strncpy0(prev_name, name, sizeof(prev_name)); #endif /* Valid name[=:]value pair found, call handler */ if (!HANDLER(user, section, name, value) && !error) error = lineno; } else { /* No '=' or ':' found on name[=:]value line */ #if INI_ALLOW_NO_VALUE *end = '\0'; name = ini_rstrip(start, end); if (!HANDLER(user, section, name, NULL) && !error) error = lineno; #else if (!error) error = lineno; #endif } } #if INI_STOP_ON_FIRST_ERROR if (error) break; #endif } #if !INI_USE_STACK ini_free(line); #endif return error; } /* See documentation in header file. */ int ini_parse_file(FILE* file, ini_handler handler, void* user) { return ini_parse_stream((ini_reader)fgets, file, handler, user); } /* See documentation in header file. */ int ini_parse(const char* filename, ini_handler handler, void* user) { FILE* file; int error; file = fopen(filename, "r"); if (!file) return -1; error = ini_parse_file(file, handler, user); fclose(file); return error; } /* An ini_reader function to read the next line from a string buffer. This is the fgets() equivalent used by ini_parse_string(). */ static char* ini_reader_string(char* str, int num, void* stream) { ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; const char* ctx_ptr = ctx->ptr; size_t ctx_num_left = ctx->num_left; char* strp = str; char c; if (ctx_num_left == 0 || num < 2) return NULL; while (num > 1 && ctx_num_left != 0) { c = *ctx_ptr++; ctx_num_left--; *strp++ = c; if (c == '\n') break; num--; } *strp = '\0'; ctx->ptr = ctx_ptr; ctx->num_left = ctx_num_left; return str; } /* See documentation in header file. */ int ini_parse_string(const char* string, ini_handler handler, void* user) { return ini_parse_string_length(string, strlen(string), handler, user); } /* See documentation in header file. */ int ini_parse_string_length(const char* string, size_t length, ini_handler handler, void* user) { ini_parse_string_ctx ctx; ctx.ptr = string; ctx.num_left = length; return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, user); } wlmaker-0.8/submodules/inih/.github/0000755000175100017510000000000015203543566017113 5ustar runnerrunnerwlmaker-0.8/submodules/inih/.github/FUNDING.yml0000644000175100017510000000002015203543566020720 0ustar runnerrunnergithub: benhoyt wlmaker-0.8/submodules/inih/.github/workflows/0000755000175100017510000000000015203543566021150 5ustar runnerrunnerwlmaker-0.8/submodules/inih/.github/workflows/tests.yml0000644000175100017510000000142615203543566023040 0ustar runnerrunnername: Tests on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Diff Tests run: | cd tests ./unittest.sh cd ../examples ./cpptest.sh git diff --exit-code build-meson: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - uses: BSFishy/meson-build@v1.0.3 with: action: test meson-version: 1.4.1 build-meson-msvc: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - uses: BSFishy/meson-build@v1.0.3 with: action: test meson-version: 1.4.1 wlmaker-0.8/submodules/inih/meson_options.txt0000644000175100017510000000416515203543566021216 0ustar runnerrunneroption('distro_install', type : 'boolean', value : true, description : 'install shared libs, headers and pkg-config entries' ) option('with_INIReader', type : 'boolean', value : true, description : 'compile and (if selected) install INIReader' ) option('multi-line_entries', type : 'boolean', value : true, description : 'support for multi-line entries in the style of Python\'s ConfigParser' ) option('utf-8_bom', type : 'boolean', value : true, description : 'allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of INI files' ) option('inline_comments', type : 'boolean', value : true, description : 'allow inline comments with the comment prefix character' ) option('inline_comment_prefix', type : 'string', value : ';', description : 'character(s) to start an inline comment (if enabled)' ) option('start-of-line_comment_prefix', type : 'string', value : ';#', description : 'character(s) to start a comment at the beginning of a line' ) option('allow_no_value', type : 'boolean', value : false, description : 'allow name with no value' ) option('stop_on_first_error', type : 'boolean', value : false, description : 'stop parsing after an error' ) option('report_line_numbers', type : 'boolean', value : false, description : 'report line number on ini_handler callback' ) option('call_handler_on_new_section', type : 'boolean', value : false, description : 'call the handler each time a new section is encountered' ) option('use_heap', type : 'boolean', value : false, description : 'allocate memory on the heap using malloc instead using a fixed-sized line buffer on the stack' ) option('max_line_length', type : 'integer', value : 200, description : 'maximum line length in bytes' ) option('initial_malloc_size', type : 'integer', value : 200, description : 'initial malloc size in bytes (when using the heap)' ) option('allow_realloc', type : 'boolean', value : false, description : 'allow initial malloc size to grow to max line length (when using the heap)' ) option('tests', type : 'boolean', value : true, description : 'build the test suite (noisy)' ) wlmaker-0.8/submodules/inih/.gitignore0000644000175100017510000000004215203543566017537 0ustar runnerrunnerfuzzing/findings fuzzing/inihfuzz wlmaker-0.8/submodules/inih/tests/0000755000175100017510000000000015203543566016715 5ustar runnerrunnerwlmaker-0.8/submodules/inih/tests/unittest_alloc.c0000644000175100017510000000225415203543566022115 0ustar runnerrunner/* inih -- tests with custom memory allocator */ #include #include #include #ifdef _WIN32 #include #include #endif #include "../ini.h" void* ini_malloc(size_t size) { printf("ini_malloc(%d)\n", (int)size); return malloc(size); } void ini_free(void* ptr) { printf("ini_free()\n"); free(ptr); } void* ini_realloc(void* ptr, size_t size) { printf("ini_realloc(%d)\n", (int)size); return realloc(ptr, size); } char Prev_section[50]; int dumper(void* user, const char* section, const char* name, const char* value) { if (strcmp(section, Prev_section)) { printf("... [%s]\n", section); strncpy(Prev_section, section, sizeof(Prev_section)); Prev_section[sizeof(Prev_section) - 1] = '\0'; } printf("... %s=%s;\n", name, value); return 1; } void parse(const char* name, const char* string) { int e; *Prev_section = '\0'; e = ini_parse_string(string, dumper, NULL); printf("%s: e=%d\n", name, e); } int main(void) { #ifdef _WIN32 _setmode(_fileno(stdout), _O_BINARY); #endif parse("basic", "[section]\nfoo = bar\nbazz = buzz quxx"); return 0; } wlmaker-0.8/submodules/inih/tests/baseline_multi_max_line.txt0000644000175100017510000000304515203543566024330 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comm; ... [colon_tests] ... Content-Type=text/; ... foo=bar; ... adams=42; ... funny1=with = equ; ... funny2=with : col; ... funny3=two = equa; ... funny4=two : colo; normal.ini: e=1 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=4 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... name=value; long_section.ini: e=1 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=12345678; ... [2 assigns] ... _123456789=12345678; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=10 user=111 name_only_after_error.ini: e=1 user=111 wlmaker-0.8/submodules/inih/tests/long_section.ini0000644000175100017510000000015615203543566022103 0ustar runnerrunner# check that section is truncated [_123456789_123456789_123456789_123456789_123456789_123456789] name = value wlmaker-0.8/submodules/inih/tests/baseline_string.txt0000644000175100017510000000052315203543566022626 0ustar runnerrunnerempty string: e=0 user=0 ... [section] ... foo=bar; ... bazz=buzz quxx; basic: e=0 user=101 ... [section] ... hello=world; ... forty_two=42; crlf: e=0 user=102 ... [sec] ... foo=0123456789012; ... bar=4321; long line: e=2 user=103 ... [sec] ... foo=0123456789012; long continued: e=2 user=104 ... [s] ... a=1; ... c=3; error: e=3 user=105 wlmaker-0.8/submodules/inih/tests/baseline_single.txt0000644000175100017510000000315615203543566022606 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... single2=xyz; ... [section2] ... multi=a; ... [section3] ... single=ghi; ... multi=the quick; ... name=bob smith; ... foo=bar; multi_line.ini: e=4 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/meson.build0000644000175100017510000000317215203543566021062 0ustar runnerrunnerruntest = find_program('runtest.sh', required: false) if not runtest.found() subdir_done() endif tests = { 'multi': { 'args': [] }, 'multi_max_line': { 'args': ['-DINI_MAX_LINE=20'] }, 'single': { 'args': ['-DINI_ALLOW_MULTILINE=0'] }, 'disallow_inline_comments': { 'args': ['-DINI_ALLOW_INLINE_COMMENTS=0'] }, 'stop_on_first_error': { 'args': ['-DINI_STOP_ON_FIRST_ERROR=1'] }, 'handler_lineno': { 'args': ['-DINI_HANDLER_LINENO=1'] }, 'string': { 'src': 'unittest_string.c', 'args': ['-DINI_MAX_LINE=20'] }, 'heap': { 'args': ['-DINI_USE_STACK=0'] }, 'heap_max_line': { 'args': ['-DINI_USE_STACK=0', '-DINI_MAX_LINE=20', '-DINI_INITIAL_ALLOC=20'] }, 'heap_realloc': { 'args': ['-DINI_USE_STACK=0', '-DINI_ALLOW_REALLOC=1', '-DINI_INITIAL_ALLOC=5'] }, 'heap_realloc_max_line': { 'args': ['-DINI_USE_STACK=0', '-DINI_MAX_LINE=20', '-DINI_ALLOW_REALLOC=1', '-DINI_INITIAL_ALLOC=5'] }, 'heap_string': { 'src': 'unittest_string.c', 'args': ['-DINI_USE_STACK=0', '-DINI_MAX_LINE=20', '-DINI_INITIAL_ALLOC=20'] }, 'call_handler_on_new_section': { 'args': ['-DINI_CALL_HANDLER_ON_NEW_SECTION=1'] }, 'allow_no_value': { 'args': ['-DINI_ALLOW_NO_VALUE=1'] }, 'alloc': { 'src': 'unittest_alloc.c', 'args': ['-DINI_CUSTOM_ALLOCATOR=1', '-DINI_USE_STACK=0', '-DINI_ALLOW_REALLOC=1', '-DINI_INITIAL_ALLOC=12'] } } foreach name, properties : tests test_src = 'src' in properties ? properties['src'] : 'unittest.c' exe = executable('unittest_' + name, src_inih, test_src, c_args : ['-Wall', properties['args']]) test('test_' + name, runtest, depends : [exe], args : [files('baseline_' + name + '.txt'), exe.full_path()]) endforeach wlmaker-0.8/submodules/inih/tests/no_value.ini0000644000175100017510000000012615203543566021225 0ustar runnerrunner[section_list] section0 section1 [section0] key0=val0 [section1] key1=val1 wlmaker-0.8/submodules/inih/tests/long_line.ini0000644000175100017510000000046615203543566021372 0ustar runnerrunner# These tests are # only interesting # when # INI_MAX_LINE=20 [width = 18] _123456789=1234567 [width = 19] _123456789=12345678 [width = 20] _123456789=123456789 [2 assigns] _123456789=12345678name=value [no trailing \n] # trigger a false # positive in the # incomplete line # detection _123456782=12345678wlmaker-0.8/submodules/inih/tests/baseline_disallow_inline_comments.txt0000644000175100017510000000365415203543566026411 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test ; name=value comment; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3 ; only this will be a comment; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345 ; key should be "test;3"; ... test4=4#5#6 ; '#' only starts a comment at start of line; ... test7=; blank value, except if inline comments disabled; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... [section3 ; comment ] ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith ; comment line 1; ... foo=bar ;c1; ... foo=Hi World ;c2; multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/runtest.sh0000755000175100017510000000010015203543566020747 0ustar runnerrunner#!/bin/sh set -eu cd "$(dirname "${1}")" "$2" | diff "${1}" - wlmaker-0.8/submodules/inih/tests/baseline_heap_string.txt0000644000175100017510000000052315203543566023623 0ustar runnerrunnerempty string: e=0 user=0 ... [section] ... foo=bar; ... bazz=buzz quxx; basic: e=0 user=101 ... [section] ... hello=world; ... forty_two=42; crlf: e=0 user=102 ... [sec] ... foo=0123456789012; ... bar=4321; long line: e=2 user=103 ... [sec] ... foo=0123456789012; long continued: e=2 user=104 ... [s] ... a=1; ... c=3; error: e=3 user=105 wlmaker-0.8/submodules/inih/tests/unittest.sh0000755000175100017510000000511715203543566021137 0ustar runnerrunner#!/usr/bin/env bash CC="${CC:-gcc} -Wall" $CC ../ini.c unittest.c -o unittest_multi ./unittest_multi > baseline_multi.txt rm -f unittest_multi $CC ../ini.c -DINI_MAX_LINE=20 unittest.c -o unittest_multi_max_line ./unittest_multi_max_line > baseline_multi_max_line.txt rm -f unittest_multi_max_line $CC ../ini.c -DINI_ALLOW_MULTILINE=0 unittest.c -o unittest_single ./unittest_single > baseline_single.txt rm -f unittest_single $CC ../ini.c -DINI_ALLOW_INLINE_COMMENTS=0 unittest.c -o unittest_disallow_inline_comments ./unittest_disallow_inline_comments > baseline_disallow_inline_comments.txt rm -f unittest_disallow_inline_comments $CC ../ini.c -DINI_STOP_ON_FIRST_ERROR=1 unittest.c -o unittest_stop_on_first_error ./unittest_stop_on_first_error > baseline_stop_on_first_error.txt rm -f unittest_stop_on_first_error $CC ../ini.c -DINI_HANDLER_LINENO=1 unittest.c -o unittest_handler_lineno ./unittest_handler_lineno > baseline_handler_lineno.txt rm -f unittest_handler_lineno $CC ../ini.c -DINI_MAX_LINE=20 unittest_string.c -o unittest_string ./unittest_string > baseline_string.txt rm -f unittest_string $CC ../ini.c -DINI_USE_STACK=0 unittest.c -o unittest_heap ./unittest_heap > baseline_heap.txt rm -f unittest_heap $CC ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 unittest.c -o unittest_heap_max_line ./unittest_heap_max_line > baseline_heap_max_line.txt rm -f unittest_heap_max_line $CC ../ini.c -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 unittest.c -o unittest_heap_realloc ./unittest_heap_realloc > baseline_heap_realloc.txt rm -f unittest_heap_realloc $CC ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 unittest.c -o unittest_heap_realloc_max_line ./unittest_heap_realloc_max_line > baseline_heap_realloc_max_line.txt rm -f unittest_heap_realloc_max_line $CC ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 unittest_string.c -o unittest_heap_string ./unittest_heap_string > baseline_heap_string.txt rm -f unittest_heap_string $CC ../ini.c -DINI_CALL_HANDLER_ON_NEW_SECTION=1 unittest.c -o unittest_call_handler_on_new_section ./unittest_call_handler_on_new_section > baseline_call_handler_on_new_section.txt rm -f unittest_call_handler_on_new_section $CC ../ini.c -DINI_ALLOW_NO_VALUE=1 unittest.c -o unittest_allow_no_value ./unittest_allow_no_value > baseline_allow_no_value.txt rm -f unittest_allow_no_value $CC -DINI_CUSTOM_ALLOCATOR=1 -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=12 ../ini.c unittest_alloc.c -o unittest_alloc ./unittest_alloc > baseline_alloc.txt rm -f unittest_alloc wlmaker-0.8/submodules/inih/tests/baseline_alloc.txt0000644000175100017510000000014415203543566022411 0ustar runnerrunnerini_malloc(12) ... [section] ... foo=bar; ini_realloc(24) ... bazz=buzz quxx; ini_free() basic: e=0 wlmaker-0.8/submodules/inih/tests/user_error.ini0000644000175100017510000000005115203543566021601 0ustar runnerrunner[section] a = b user = parse_error c = d wlmaker-0.8/submodules/inih/tests/baseline_heap_max_line.txt0000644000175100017510000000304515203543566024113 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comm; ... [colon_tests] ... Content-Type=text/; ... foo=bar; ... adams=42; ... funny1=with = equ; ... funny2=with : col; ... funny3=two = equa; ... funny4=two : colo; normal.ini: e=1 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=4 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... name=value; long_section.ini: e=1 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=12345678; ... [2 assigns] ... _123456789=12345678; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=10 user=111 name_only_after_error.ini: e=1 user=111 wlmaker-0.8/submodules/inih/tests/baseline_heap.txt0000644000175100017510000000331315203543566022235 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/unittest.bat0000644000175100017510000000300515203543566021262 0ustar runnerrunner@call tcc ..\ini.c -I..\ -run unittest.c > baseline_multi.txt @call tcc ..\ini.c -I..\ -DINI_MAX_LINE=20 -run unittest.c > baseline_multi_max_line.txt @call tcc ..\ini.c -I..\ -DINI_ALLOW_MULTILINE=0 -run unittest.c > baseline_single.txt @call tcc ..\ini.c -I..\ -DINI_ALLOW_INLINE_COMMENTS=0 -run unittest.c > baseline_disallow_inline_comments.txt @call tcc ..\ini.c -I..\ -DINI_STOP_ON_FIRST_ERROR=1 -run unittest.c > baseline_stop_on_first_error.txt @call tcc ..\ini.c -I..\ -DINI_HANDLER_LINENO=1 -run unittest.c > baseline_handler_lineno.txt @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -run unittest.c > baseline_heap.txt @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 -run unittest.c > baseline_heap_max_line.txt @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 -run unittest.c > baseline_heap_realloc.txt @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 -run unittest.c > baseline_heap_realloc_max_line.txt @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 -run unittest.c > baseline_heap_string.txt @call tcc ..\ini.c -I..\ -DINI_CALL_HANDLER_ON_NEW_SECTION=1 -run unittest.c > baseline_call_handler_on_new_section.txt @call tcc ..\ini.c -I..\ -DINI_ALLOW_NO_VALUE=1 -run unittest.c > baseline_allow_no_value.txt @call tcc ..\ini.c -I..\ -DINI_CUSTOM_ALLOCATOR=1 -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=12 -run unittest_alloc.c > baseline_alloc.txt wlmaker-0.8/submodules/inih/tests/baseline_stop_on_first_error.txt0000644000175100017510000000316415203543566025425 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 no_value.ini: e=2 user=108 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/unittest_string.c0000644000175100017510000000235115203543566022327 0ustar runnerrunner/* inih -- tests for ini_parse_string() */ #include #include #include #ifdef _WIN32 #include #include #endif #include "../ini.h" int User; char Prev_section[50]; int dumper(void* user, const char* section, const char* name, const char* value) { User = *((int*)user); if (strcmp(section, Prev_section)) { printf("... [%s]\n", section); strncpy(Prev_section, section, sizeof(Prev_section)); Prev_section[sizeof(Prev_section) - 1] = '\0'; } printf("... %s=%s;\n", name, value); return 1; } void parse(const char* name, const char* string) { static int u = 100; int e; *Prev_section = '\0'; e = ini_parse_string(string, dumper, &u); printf("%s: e=%d user=%d\n", name, e, User); u++; } int main(void) { #ifdef _WIN32 _setmode(_fileno(stdout), _O_BINARY); #endif parse("empty string", ""); parse("basic", "[section]\nfoo = bar\nbazz = buzz quxx"); parse("crlf", "[section]\r\nhello = world\r\nforty_two = 42\r\n"); parse("long line", "[sec]\nfoo = 01234567890123456789\nbar=4321\n"); parse("long continued", "[sec]\nfoo = 0123456789012bix=1234\n"); parse("error", "[s]\na=1\nb\nc=3"); return 0; } wlmaker-0.8/submodules/inih/tests/bad_section.ini0000644000175100017510000000010715203543566021666 0ustar runnerrunner[section1] name1=value1 [section2 [section3 ; comment ] name2=value2 wlmaker-0.8/submodules/inih/tests/normal.ini0000644000175100017510000000135515203543566020712 0ustar runnerrunner; This is an INI file [section1] ; section comment one=This is a test ; name=value comment two = 1234 ; x=y [ section 2 ] happy = 4 sad = [empty] ; do nothing [comment_test] test1 = 1;2;3 ; only this will be a comment test2 = 2;3;4;this won't be a comment, needs whitespace before ';' test;3 = 345 ; key should be "test;3" test4 = 4#5#6 ; '#' only starts a comment at start of line #test5 = 567 ; entire line commented # test6 = 678 ; entire line commented, except in MULTILINE mode test7 = ; blank value, except if inline comments disabled test8 =; not a comment, needs whitespace before ';' [colon_tests] Content-Type: text/html foo:bar adams : 42 funny1 : with = equals funny2 = with : colons funny3 = two = equals funny4 : two : colons wlmaker-0.8/submodules/inih/tests/bad_comment.ini0000644000175100017510000000002115203543566021657 0ustar runnerrunnerThis is an error wlmaker-0.8/submodules/inih/tests/baseline_heap_realloc.txt0000644000175100017510000000331315203543566023736 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/bad_multi.ini0000644000175100017510000000001315203543566021350 0ustar runnerrunner indented wlmaker-0.8/submodules/inih/tests/baseline_allow_no_value.txt0000644000175100017510000000345015203543566024330 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 ... This is an error; bad_comment.ini: e=0 user=103 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=0 user=105 ... indented; bad_multi.ini: e=0 user=106 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section_list] ... section0; ... section1; ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=0 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 ... name; name_only_after_error.ini: e=5 user=112 wlmaker-0.8/submodules/inih/tests/duplicate_sections.ini0000644000175100017510000000011515203543566023274 0ustar runnerrunner[section1] single1 = abc single2 = xyz [section1] single1 = def single2 = qrswlmaker-0.8/submodules/inih/tests/baseline_handler_lineno.txt0000644000175100017510000000415215203543566024303 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; line 3 ... two=1234; line 4 ... [ section 2 ] ... happy=4; line 8 ... sad=; line 9 ... [comment_test] ... test1=1;2;3; line 15 ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; line 16 ... test;3=345; line 17 ... test4=4#5#6; line 18 ... test7=; line 21 ... test8=; not a comment, needs whitespace before ';'; line 22 ... [colon_tests] ... Content-Type=text/html; line 25 ... foo=bar; line 26 ... adams=42; line 27 ... funny1=with = equals; line 28 ... funny2=with : colons; line 29 ... funny3=two = equals; line 30 ... funny4=two : colons; line 31 normal.ini: e=0 user=101 ... [section1] ... name1=value1; line 2 ... name2=value2; line 5 bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; line 2 ... user=parse_error; line 3 ... c=d; line 4 user_error.ini: e=3 user=104 ... [section1] ... single1=abc; line 2 ... multi=this is a; line 3 ... multi=multi-line value; line 4 ... single2=xyz; line 5 ... [section2] ... multi=a; line 7 ... multi=b; line 8 ... multi=c; line 9 ... [section3] ... single=ghi; line 11 ... multi=the quick; line 12 ... multi=brown fox; line 13 ... name=bob smith; line 14 ... foo=bar; line 16 ... foo=Hi World; line 17 multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; line 2 ... key“=value“; line 3 bom.ini: e=0 user=107 ... [section1] ... single1=abc; line 2 ... single2=xyz; line 3 ... single1=def; line 5 ... single2=qrs; line 6 duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; line 6 ... [section1] ... key1=val1; line 9 no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; line 3 long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; line 7 ... [width = 19] ... _123456789=12345678; line 10 ... [width = 20] ... _123456789=123456789; line 13 ... [2 assigns] ... _123456789=12345678name=value; line 16 ... [no trailing \n] ... _123456782=12345678; line 23 long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/unittest.c0000644000175100017510000000420115203543566020735 0ustar runnerrunner/* inih -- tests This works simply by dumping a bunch of info to standard output, which is redirected to an output file (baseline_*.txt) and checked into the Git repository. This baseline file is the test output, so the idea is to check it once, and if it changes -- look at the diff and see which tests failed. See unittest.bat and unittest.sh for how to run this (with tcc and gcc, respectively). */ #include #include #include #ifdef _WIN32 #include #include #endif #include "../ini.h" int User; char Prev_section[50]; #if INI_HANDLER_LINENO int dumper(void* user, const char* section, const char* name, const char* value, int lineno) #else int dumper(void* user, const char* section, const char* name, const char* value) #endif { User = *((int*)user); if (!name || strcmp(section, Prev_section)) { printf("... [%s]\n", section); strncpy(Prev_section, section, sizeof(Prev_section)); Prev_section[sizeof(Prev_section) - 1] = '\0'; } if (!name) { return 1; } #if INI_HANDLER_LINENO printf("... %s%s%s; line %d\n", name, value ? "=" : "", value ? value : "", lineno); #else printf("... %s%s%s;\n", name, value ? "=" : "", value ? value : ""); #endif if (!value) { /* Happens when INI_ALLOW_NO_VALUE=1 and line has no value (no '=' or ':') */ return 1; } return strcmp(name, "user") == 0 && strcmp(value, "parse_error") == 0 ? 0 : 1; } void parse(const char* fname) { static int u = 100; int e; *Prev_section = '\0'; e = ini_parse(fname, dumper, &u); printf("%s: e=%d user=%d\n", fname, e, User); u++; } int main(void) { #ifdef _WIN32 _setmode(_fileno(stdout), _O_BINARY); #endif parse("no_file.ini"); parse("normal.ini"); parse("bad_section.ini"); parse("bad_comment.ini"); parse("user_error.ini"); parse("multi_line.ini"); parse("bad_multi.ini"); parse("bom.ini"); parse("duplicate_sections.ini"); parse("no_value.ini"); parse("long_section.ini"); parse("long_line.ini"); parse("name_only_after_error.ini"); return 0; } wlmaker-0.8/submodules/inih/tests/name_only_after_error.ini0000644000175100017510000000037715203543566024000 0ustar runnerrunner; Test case to ensure name only options are still processed after an error if ; INI_ALLOW_NO_VALUE is set. ; Deliberately cause an error due to unterminated section. [broken ; This should still be processed (unless INI_STOP_ON_FIRST_ERROR is set). name wlmaker-0.8/submodules/inih/tests/bom.ini0000644000175100017510000000006615203543566020175 0ustar runnerrunner[bom_section] bom_name=bom_value key“ = value“ wlmaker-0.8/submodules/inih/tests/multi_line.ini0000644000175100017510000000043315203543566021557 0ustar runnerrunner[section1] single1 = abc multi = this is a multi-line value single2 = xyz [section2] multi = a b c [section3] single: ghi multi: the quick brown fox name = bob smith ; comment line 1 ; comment line 2 foo = bar ;c1 Hi World ;c2 wlmaker-0.8/submodules/inih/tests/baseline_multi.txt0000644000175100017510000000331315203543566022452 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/baseline_call_handler_on_new_section.txt0000644000175100017510000000337115203543566027025 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [empty] ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comment, needs whitespace before ';'; ... [colon_tests] ... Content-Type=text/html; ... foo=bar; ... adams=42; ... funny1=with = equals; ... funny2=with : colons; ... funny3=two = equals; ... funny4=two : colons; normal.ini: e=0 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line value; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=0 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... [section1] ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section_list] ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... [_123456789_123456789_123456789_123456789_12345678] ... name=value; long_section.ini: e=0 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=123456789; ... [2 assigns] ... _123456789=12345678name=value; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=0 user=111 name_only_after_error.ini: e=5 user=111 wlmaker-0.8/submodules/inih/tests/baseline_heap_realloc_max_line.txt0000644000175100017510000000304515203543566025614 0ustar runnerrunnerno_file.ini: e=-1 user=0 ... [section1] ... one=This is a test; ... two=1234; ... [ section 2 ] ... happy=4; ... sad=; ... [comment_test] ... test1=1;2;3; ... test2=2;3;4;this; ... test;3=345; ... test4=4#5#6; ... test7=; ... test8=; not a comm; ... [colon_tests] ... Content-Type=text/; ... foo=bar; ... adams=42; ... funny1=with = equ; ... funny2=with : col; ... funny3=two = equa; ... funny4=two : colo; normal.ini: e=1 user=101 ... [section1] ... name1=value1; ... name2=value2; bad_section.ini: e=3 user=102 bad_comment.ini: e=1 user=102 ... [section] ... a=b; ... user=parse_error; ... c=d; user_error.ini: e=3 user=104 ... [section1] ... single1=abc; ... multi=this is a; ... multi=multi-line; ... single2=xyz; ... [section2] ... multi=a; ... multi=b; ... multi=c; ... [section3] ... single=ghi; ... multi=the quick; ... multi=brown fox; ... name=bob smith; ... foo=bar; ... foo=Hi World; multi_line.ini: e=4 user=105 bad_multi.ini: e=1 user=105 ... [bom_section] ... bom_name=bom_value; ... key“=value“; bom.ini: e=0 user=107 ... [section1] ... single1=abc; ... single2=xyz; ... single1=def; ... single2=qrs; duplicate_sections.ini: e=0 user=108 ... [section0] ... key0=val0; ... [section1] ... key1=val1; no_value.ini: e=2 user=109 ... name=value; long_section.ini: e=1 user=110 ... [width = 18] ... _123456789=1234567; ... [width = 19] ... _123456789=12345678; ... [width = 20] ... _123456789=12345678; ... [2 assigns] ... _123456789=12345678; ... [no trailing \n] ... _123456782=12345678; long_line.ini: e=10 user=111 name_only_after_error.ini: e=1 user=111 wlmaker-0.8/submodules/inih/README.md0000644000175100017510000002330715203543566017037 0ustar runnerrunner# inih (INI Not Invented Here) [![Tests](https://github.com/benhoyt/inih/actions/workflows/tests.yml/badge.svg)](https://github.com/benhoyt/inih/actions/workflows/tests.yml) **inih (INI Not Invented Here)** is a simple [.INI file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a couple of pages of code, and it was designed to be _small and simple_, so it's good for embedded systems. It's also more or less compatible with Python's [ConfigParser](http://docs.python.org/library/configparser.html) style of .INI files, including RFC 822-style multi-line syntax and `name: value` entries. To use it, just give `ini_parse()` an INI file, and it will call a callback for every `name=value` pair parsed, giving you strings for the section, name, and value. It's done this way ("SAX style") because it works well on low-memory embedded systems, but also because it makes for a KISS implementation. You can also call `ini_parse_file()` to parse directly from a `FILE*` object, `ini_parse_string()` to parse data from a string, or `ini_parse_stream()` to parse using a custom fgets-style reader function for custom I/O. Download a release, browse the source, or read about [how to use inih in a DRY style](http://blog.brush.co.nz/2009/08/xmacros/) with X-Macros. ## Compile-time options ## You can control various aspects of inih using preprocessor defines: ### Syntax options ### * **Multi-line entries:** By default, inih supports multi-line entries in the style of Python's ConfigParser. To disable, add `-DINI_ALLOW_MULTILINE=0`. * **UTF-8 BOM:** By default, inih allows a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of INI files. To disable, add `-DINI_ALLOW_BOM=0`. * **Inline comments:** By default, inih allows inline comments with the `;` character. To disable, add `-DINI_ALLOW_INLINE_COMMENTS=0`. You can also specify which character(s) start an inline comment using `INI_INLINE_COMMENT_PREFIXES`. * **Start-of-line comments:** By default, inih allows both `;` and `#` to start a comment at the beginning of a line. You can override this by changing `INI_START_COMMENT_PREFIXES`. * **Allow no value:** By default, inih treats a name with no value (no `=` or `:` on the line) as an error. To allow names with no values, add `-DINI_ALLOW_NO_VALUE=1`, and inih will call your handler function with value set to NULL. ### Parsing options ### * **Stop on first error:** By default, inih keeps parsing the rest of the file after an error. To stop parsing on the first error, add `-DINI_STOP_ON_FIRST_ERROR=1`. * **Report line numbers:** By default, the `ini_handler` callback doesn't receive the line number as a parameter. If you need that, add `-DINI_HANDLER_LINENO=1`. * **Call handler on new section:** By default, inih only calls the handler on each `name=value` pair. To detect new sections (e.g., the INI file has multiple sections with the same name), add `-DINI_CALL_HANDLER_ON_NEW_SECTION=1`. Your handler function will then be called each time a new section is encountered, with `section` set to the new section name but `name` and `value` set to NULL. ### Memory options ### * **Stack vs heap:** By default, inih creates a fixed-sized line buffer on the stack. To allocate on the heap using `malloc` instead, specify `-DINI_USE_STACK=0`. * **Maximum line length:** The default maximum line length (for stack or heap) is 200 bytes. To override this, add something like `-DINI_MAX_LINE=1000`. Note that `INI_MAX_LINE` must be 3 more than the longest line (due to `\r`, `\n`, and the NUL). * **Initial malloc size:** `INI_INITIAL_ALLOC` specifies the initial malloc size when using the heap. It defaults to 200 bytes. * **Allow realloc:** By default when using the heap (`-DINI_USE_STACK=0`), inih allocates a fixed-sized buffer of `INI_INITIAL_ALLOC` bytes. To allow this to grow to `INI_MAX_LINE` bytes, doubling if needed, set `-DINI_ALLOW_REALLOC=1`. * **Custom allocator:** By default when using the heap, the standard library's `malloc`, `free`, and `realloc` functions are used; to use a custom allocator, specify `-DINI_CUSTOM_ALLOCATOR=1` (and `-DINI_USE_STACK=0`). You must define and link functions named `ini_malloc`, `ini_free`, and (if `INI_ALLOW_REALLOC` is set) `ini_realloc`, which must have the same signatures as the `stdlib.h` memory allocation functions. ## Simple example in C ## ```c #include #include #include #include "../ini.h" typedef struct { int version; const char* name; const char* email; } configuration; static int handler(void* user, const char* section, const char* name, const char* value) { configuration* pconfig = (configuration*)user; #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 if (MATCH("protocol", "version")) { pconfig->version = atoi(value); } else if (MATCH("user", "name")) { pconfig->name = strdup(value); } else if (MATCH("user", "email")) { pconfig->email = strdup(value); } else { return 0; /* unknown section/name, error */ } return 1; } int main(int argc, char* argv[]) { configuration config; if (ini_parse("test.ini", handler, &config) < 0) { printf("Can't load 'test.ini'\n"); return 1; } printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n", config.version, config.name, config.email); return 0; } ``` ## C++ example ## If you're into C++ and the STL, there is also an easy-to-use [INIReader class](https://github.com/benhoyt/inih/blob/master/cpp/INIReader.h) that stores values in a `map` and lets you `Get()` them: ```cpp #include #include "INIReader.h" int main() { INIReader reader("../examples/test.ini"); if (reader.ParseError() < 0) { std::cout << "Can't load 'test.ini'\n"; return 1; } std::cout << "Config loaded from 'test.ini': version=" << reader.GetInteger("protocol", "version", -1) << ", name=" << reader.Get("user", "name", "UNKNOWN") << ", email=" << reader.Get("user", "email", "UNKNOWN") << ", pi=" << reader.GetReal("user", "pi", -1) << ", active=" << reader.GetBoolean("user", "active", true) << "\n"; return 0; } ``` This simple C++ API works fine, but it's not very fully-fledged. I'm not planning to work more on the C++ API at the moment, so if you want a bit more power (for example `GetSections()` and `GetFields()` functions), see these forks: * https://github.com/Blandinium/inih * https://github.com/OSSystems/inih ## Differences from ConfigParser ## Some differences between inih and Python's [ConfigParser](http://docs.python.org/library/configparser.html) standard library module: * INI name=value pairs given above any section headers are treated as valid items with no section (section name is an empty string). In ConfigParser having no section is an error. * Line continuations are handled with leading whitespace on continued lines (like ConfigParser). However, instead of concatenating continued lines together, they are treated as separate values for the same key (unlike ConfigParser). ## Platform-specific notes ## * Windows/Win32 uses UTF-16 filenames natively, so to handle Unicode paths you need to call `_wfopen()` to open a file and then `ini_parse_file()` to parse it; inih does not include `wchar_t` or Unicode handling. ## Meson notes ## * The `meson.build` file is not required to use or compile inih, its main purpose is for distributions. * By default Meson is set up for distro installation, but this behavior can be configured for embedded use cases: * with `-Ddefault_library=static` static libraries are built. * with `-Ddistro_install=false` libraries, headers and pkg-config files won't be installed. * with `-Dwith_INIReader=false` you can disable building the C++ library. * All compile-time options are implemented in Meson as well, you can take a look at [meson_options.txt](https://github.com/benhoyt/inih/blob/master/meson_options.txt) for their definition. These won't work if `distro_install` is set to `true`. * If you want to use inih for programs which may be shipped in a distro, consider linking against the shared libraries. The pkg-config entries are `inih` and `INIReader`. * In case you use inih as a Meson subproject, you can use the `inih_dep` and `INIReader_dep` dependency variables. You might want to set `default_library=static` and `distro_install=false` for the subproject. An official Wrap is provided on [WrapDB](https://wrapdb.mesonbuild.com/inih). * For packagers: if you want to tag the version in the pkg-config file, you will need to do this downstream. Add `version : '',` after the `license` tag in the `project()` function and `version : meson.project_version(),` after the `soversion` tag in both `library()` functions. ## Using inih with tipi.build `inih` can be easily used in [tipi.build](https://tipi.build) projects simply by adding the following entry to your `.tipi/deps` (replace `r56` with the latest version tag): ```json { "benhoyt/inih": { "@": "r56" } } ``` The required include path in your project is: ```c #include ``` ## Building from vcpkg ## You can build and install inih using [vcpkg](https://github.com/microsoft/vcpkg/) dependency manager: git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install ./vcpkg install inih The inih port in vcpkg is kept up to date by microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. ## Related links ## * [Conan package for inih](https://github.com/conan-io/conan-center-index/tree/master/recipes/inih) (Conan is a C/C++ package manager) wlmaker-0.8/submodules/inih/examples/0000755000175100017510000000000015203543566017371 5ustar runnerrunnerwlmaker-0.8/submodules/inih/examples/ini_dump.c0000644000175100017510000000172415203543566021345 0ustar runnerrunner/* ini.h example that simply dumps an INI file without comments */ #include #include #include "../ini.h" static int dumper(void* user, const char* section, const char* name, const char* value) { static char prev_section[50] = ""; if (strcmp(section, prev_section)) { printf("%s[%s]\n", (prev_section[0] ? "\n" : ""), section); strncpy(prev_section, section, sizeof(prev_section)); prev_section[sizeof(prev_section) - 1] = '\0'; } printf("%s = %s\n", name, value); return 1; } int main(int argc, char* argv[]) { int error; if (argc <= 1) { printf("Usage: ini_dump filename.ini\n"); return 1; } error = ini_parse(argv[1], dumper, NULL); if (error < 0) { printf("Can't read '%s'!\n", argv[1]); return 2; } else if (error) { printf("Bad config file (first error on line %d)!\n", error); return 3; } return 0; } wlmaker-0.8/submodules/inih/examples/INIReaderExample.cpp0000644000175100017510000000352615203543566023161 0ustar runnerrunner// Example that shows simple usage of the INIReader class #include #include "../cpp/INIReader.h" int main() { INIReader reader("../examples/test.ini"); if (reader.ParseError() < 0) { std::cout << "Can't load 'test.ini'\n"; return 1; } std::cout << "Config loaded from 'test.ini': version=" << reader.GetInteger("protocol", "version", -1) << ", unsigned version=" << reader.GetUnsigned("protocol", "version", -1) << ", trillion=" << reader.GetInteger64("user", "trillion", -1) << ", unsigned trillion=" << reader.GetUnsigned64("user", "trillion", -1) << ", name=" << reader.Get("user", "name", "UNKNOWN") << ", email=" << reader.Get("user", "email", "UNKNOWN") << ", pi=" << reader.GetReal("user", "pi", -1) << ", active=" << reader.GetBoolean("user", "active", true) << "\n"; std::cout << "Has values: user.name=" << reader.HasValue("user", "name") << ", user.nose=" << reader.HasValue("user", "nose") << "\n"; std::cout << "Has sections: user=" << reader.HasSection("user") << ", fizz=" << reader.HasSection("fizz") << "\n"; std::cout << "Sections:\n"; std::vector sections = reader.Sections(); for (std::vector::const_iterator it = sections.begin(); it != sections.end(); ++it) { std::cout << "- " << *it << "\n"; } for (std::vector::const_iterator it = sections.begin(); it != sections.end(); ++it) { std::cout << "Keys in section [" << *it << "]:\n"; std::vector keys = reader.Keys(*it); for (std::vector::const_iterator kit = keys.begin(); kit != keys.end(); ++kit) { std::cout << "- " << *kit << "\n"; } } return 0; } wlmaker-0.8/submodules/inih/examples/ini_example.c0000644000175100017510000000245615203543566022036 0ustar runnerrunner/* Example: parse a simple configuration file */ #include #include #include #include "../ini.h" typedef struct { int version; const char* name; const char* email; } configuration; static int handler(void* user, const char* section, const char* name, const char* value) { configuration* pconfig = (configuration*)user; #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 if (MATCH("protocol", "version")) { pconfig->version = atoi(value); } else if (MATCH("user", "name")) { pconfig->name = strdup(value); } else if (MATCH("user", "email")) { pconfig->email = strdup(value); } else { return 0; /* unknown section/name, error */ } return 1; } int main(int argc, char* argv[]) { configuration config; config.version = 0; /* set defaults */ config.name = NULL; config.email = NULL; if (ini_parse("test.ini", handler, &config) < 0) { printf("Can't load 'test.ini'\n"); return 1; } printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n", config.version, config.name, config.email); if (config.name) free((void*)config.name); if (config.email) free((void*)config.email); return 0; } wlmaker-0.8/submodules/inih/examples/INIReaderExampleErrors.cpp0000644000175100017510000000117515203543566024354 0ustar runnerrunner// Example that demonstrates the ParseErrorMessage() method. #include #include "../cpp/INIReader.h" int main() { INIReader reader_file_not_found("/file_that_does_not_exist.ini"); INIReader reader_parse_error("../tests/name_only_after_error.ini"); INIReader reader_success("../tests/normal.ini"); std::cout << "reader_file_not_found errmsg: \"" << reader_file_not_found.ParseErrorMessage() << "\"\n" << "reader_parse_error errmsg: \"" << reader_parse_error.ParseErrorMessage() << "\"\n" << "reader_success errmsg: \"" << reader_success.ParseErrorMessage() << "\"\n"; return 0; } wlmaker-0.8/submodules/inih/examples/meson.build0000644000175100017510000000062315203543566021534 0ustar runnerrunnerruntest = files(join_paths(meson.project_source_root(), 'tests', 'runtest.sh')) tests = { 'INIReaderExample': { 'args': [] }, } foreach name, properties : tests exe = executable('unittest_' + name, src_inih, src_INIReader, 'INIReaderExample.cpp', cpp_args : ['-Wall', properties['args']]) test('test_' + name, runtest, depends : [exe], args : [files('cpptest.txt'), exe.full_path()]) endforeach wlmaker-0.8/submodules/inih/examples/cpptesterrors.txt0000644000175100017510000000022615203543566023051 0ustar runnerrunnerreader_file_not_found errmsg: "unable to open file" reader_parse_error errmsg: "parse error on line 5; missing ']' or '='?" reader_success errmsg: "" wlmaker-0.8/submodules/inih/examples/cpptest.txt0000644000175100017510000000056515203543566021622 0ustar runnerrunnerConfig loaded from 'test.ini': version=6, unsigned version=6, trillion=1000000000000, unsigned trillion=1000000000000, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1 Has values: user.name=1, user.nose=0 Has sections: user=1, fizz=0 Sections: - protocol - user Keys in section [protocol]: - version Keys in section [user]: - active - email - name - pi - trillion wlmaker-0.8/submodules/inih/examples/ini_xmacros.c0000644000175100017510000000223115203543566022046 0ustar runnerrunner/* Parse a configuration file into a struct using X-Macros */ #include #include #include "../ini.h" /* define the config struct type */ typedef struct { #define CFG(s, n, default) char *s##_##n; #include "config.def" } config; /* create one and fill in its default values */ config Config = { #define CFG(s, n, default) default, #include "config.def" }; /* process a line of the INI file, storing valid values into config struct */ int handler(void *user, const char *section, const char *name, const char *value) { config *cfg = (config *)user; if (0) ; #define CFG(s, n, default) else if (strcmp(section, #s) == 0 && \ strcmp(name, #n) == 0) cfg->s##_##n = strdup(value); #include "config.def" return 1; } /* print all the variables in the config, one per line */ void dump_config(config *cfg) { #define CFG(s, n, default) printf("%s_%s = %s\n", #s, #n, cfg->s##_##n); #include "config.def" } int main(int argc, char* argv[]) { if (ini_parse("test.ini", handler, &Config) < 0) printf("Can't load 'test.ini', using defaults\n"); dump_config(&Config); return 0; } wlmaker-0.8/submodules/inih/examples/config.def0000644000175100017510000000022015203543566021310 0ustar runnerrunner// CFG(section, name, default) CFG(protocol, version, "0") CFG(user, name, "Fatty Lumpkin") CFG(user, email, "fatty@lumpkin.com") #undef CFG wlmaker-0.8/submodules/inih/examples/cpptest.sh0000755000175100017510000000050015203543566021405 0ustar runnerrunner#!/usr/bin/env bash g++ -Wall INIReaderExample.cpp ../cpp/INIReader.cpp ../ini.c -o INIReaderExample ./INIReaderExample > cpptest.txt rm INIReaderExample g++ -Wall INIReaderExampleErrors.cpp ../cpp/INIReader.cpp ../ini.c -o INIReaderExampleErrors ./INIReaderExampleErrors > cpptesterrors.txt rm INIReaderExampleErrors wlmaker-0.8/submodules/inih/examples/test.ini0000644000175100017510000000063615203543566021056 0ustar runnerrunner; Test config file for ini_example.c and INIReaderTest.cpp [protocol] ; Protocol configuration version=6 ; IPv6 [user] name = Bob Smith ; Spaces around '=' are stripped email = bob@smith.com ; And comments (like this) ignored active = true ; Test a boolean pi = 3.14159 ; Test a floating point number trillion = 1000000000000 ; Test 64-bit integers wlmaker-0.8/submodules/inih/LICENSE.txt0000644000175100017510000000274615203543566017407 0ustar runnerrunner The "inih" library is distributed under the New BSD license: Copyright (c) 2009, Ben Hoyt All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Ben Hoyt nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. wlmaker-0.8/submodules/inih/fuzzing/0000755000175100017510000000000015203543566017247 5ustar runnerrunnerwlmaker-0.8/submodules/inih/fuzzing/testcases/0000755000175100017510000000000015203543566021245 5ustar runnerrunnerwlmaker-0.8/submodules/inih/fuzzing/testcases/case1.ini0000644000175100017510000000011015203543566022732 0ustar runnerrunner; comment [foo] ; section bar=1 ; name=value [bar] name = Bob age: 42 wlmaker-0.8/submodules/inih/fuzzing/inihfuzz.c0000644000175100017510000000226315203543566021264 0ustar runnerrunner/* This is a slightly tweaked copy of tests/unittest.c for fuzzing */ #include #include #include #include "../ini.h" int User; char Prev_section[50]; int dumper(void* user, const char* section, const char* name, const char* value) { User = *((int*)user); if (!name || strcmp(section, Prev_section)) { printf("... [%s]\n", section); strncpy(Prev_section, section, sizeof(Prev_section)); Prev_section[sizeof(Prev_section) - 1] = '\0'; } if (!name) { return 1; } printf("... %s%s%s;\n", name, value ? "=" : "", value ? value : ""); if (!value) { /* Happens when INI_ALLOW_NO_VALUE=1 and line has no value (no '=' or ':') */ return 1; } return strcmp(name, "user") == 0 && strcmp(value, "parse_error") == 0 ? 0 : 1; } void parse(const char* fname) { static int u = 100; int e; *Prev_section = '\0'; e = ini_parse(fname, dumper, &u); printf("%s: e=%d user=%d\n", fname, e, User); u++; } int main(int argc, char *argv[]) { if (argc < 2) { printf("usage: inihfuzz file.ini\n"); return 1; } parse(argv[1]); return 0; } wlmaker-0.8/submodules/inih/fuzzing/fuzz.sh0000755000175100017510000000012715203543566020604 0ustar runnerrunner#!/usr/bin/env bash ../../afl-2.52b/afl-fuzz -i testcases -o findings -- ./inihfuzz @@ wlmaker-0.8/submodules/inih/fuzzing/build.sh0000755000175100017510000000011415203543566020701 0ustar runnerrunner#!/usr/bin/env bash ../../afl-2.52b/afl-gcc inihfuzz.c ../ini.c -o inihfuzz wlmaker-0.8/RELEASE.md0000644000175100017510000000113515203543557014044 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/src/0000755000175100017510000000000015203543557013231 5ustar runnerrunnerwlmaker-0.8/src/files.h0000644000175100017510000000603315203543557014506 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/src/action_item.c0000644000175100017510000002634615203543557015703 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/src/subprocess_monitor.c0000644000175100017510000007706115203543557017347 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/src/lock_mgr.c0000644000175100017510000012745615203543557015211 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/src/launcher.c0000644000175100017510000005426715203543557015214 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/src/layer_shell.h0000644000175100017510000000300315203543557015701 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/src/dock.h0000644000175100017510000000365615203543557014334 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/src/config.c0000644000175100017510000002672615203543557014657 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/src/corner.c0000644000175100017510000004363215203543557014675 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/src/toolkit/0000755000175100017510000000000015203543557014716 5ustar runnerrunnerwlmaker-0.8/src/toolkit/bordered.c0000644000175100017510000002671615203543557016664 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/src/toolkit/base.c0000644000175100017510000001511615203543557016000 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/src/toolkit/image.c0000644000175100017510000001615215203543557016151 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/src/toolkit/util.c0000644000175100017510000002257515203543557016052 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/src/toolkit/root.c0000644000175100017510000011340215203543557016046 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/src/toolkit/menu.c0000644000175100017510000011061215203543557016027 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/src/toolkit/test.c0000644000175100017510000000405215203543557016042 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/src/toolkit/titlebar.c0000644000175100017510000005565615203543557016711 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/src/toolkit/input.c0000644000175100017510000000724615203543557016232 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/src/toolkit/primitives.c0000644000175100017510000004020115203543557017252 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/src/toolkit/menu_item.c0000644000175100017510000013157715203543557017062 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/src/toolkit/buffer.c0000644000175100017510000002375215203543557016344 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/src/toolkit/output_tracker.c0000644000175100017510000003753415203543557020151 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/src/toolkit/titlebar_button.c0000644000175100017510000003367515203543557020301 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/src/toolkit/tile.c0000644000175100017510000002521015203543557016017 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/src/toolkit/box.c0000644000175100017510000003575015203543557015664 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/src/toolkit/element.c0000644000175100017510000011117315203543557016517 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/src/toolkit/workspace.c0000644000175100017510000024671515203543557017077 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/src/toolkit/layer.c0000644000175100017510000004307415203543557016206 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/src/toolkit/surface.c0000644000175100017510000007564315203543557016531 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/src/toolkit/fsm.c0000644000175100017510000000702615203543557015654 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/src/toolkit/gfxbuf.c0000644000175100017510000001461315203543557016350 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/src/toolkit/resizebar_area.c0000644000175100017510000003135515203543557020047 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/src/toolkit/rectangle.c0000644000175100017510000003311715203543557017033 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/src/toolkit/button.c0000644000175100017510000003564015203543557016405 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/src/toolkit/dock.c0000644000175100017510000003334415203543557016011 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/src/toolkit/CMakeLists.txt0000644000175100017510000000446415203543557017466 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/src/toolkit/panel.c0000644000175100017510000004321215203543557016163 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/src/toolkit/titlebar_title.c0000644000175100017510000004337615203543557020106 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/src/toolkit/container.c0000644000175100017510000023404115203543557017050 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/src/toolkit/window.c0000644000175100017510000024730315203543557016402 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_wlr_output. */ struct wlr_output *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_wlr_output( wlmtk_window_t *window_ptr, struct wlr_output *wlr_output_ptr) { window_ptr->wlr_output_ptr = wlr_output_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_output *wlmtk_window_get_wlr_output(wlmtk_window_t *window_ptr) { if (NULL != window_ptr->wlr_output_ptr) return window_ptr->wlr_output_ptr; wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace(window_ptr); if (NULL == workspace_ptr) return NULL; struct wlr_output_layout *wlr_output_layout_ptr = wlmtk_workspace_get_wlr_output_layout(workspace_ptr); if (NULL == wlr_output_layout_ptr) return NULL; struct wlr_box wbox = wlmtk_window_get_bounding_box(window_ptr); double dest_x, dest_y; wlr_output_layout_closest_point( wlmtk_workspace_get_wlr_output_layout(workspace_ptr), NULL, // struct wlr_output* reference. We don't need a reference. wbox.x + wbox.width / 2, wbox.y + wbox.height / 2, &dest_x, &dest_y); return wlr_output_layout_output_at( wlmtk_workspace_get_wlr_output_layout(workspace_ptr), dest_x, dest_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. if (NULL == window_ptr->workspace_ptr && NULL == window_ptr->wlr_output_ptr) { 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 == window_ptr->workspace_ptr && NULL == window_ptr->wlr_output_ptr)) { 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 = window_ptr->wlr_output_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 = window_ptr->wlr_output_ptr; if (NULL != window_ptr->workspace_ptr) { desired_size = wlmtk_workspace_get_fullscreen_extents( window_ptr->workspace_ptr, wo_ptr); } else if (NULL != window_ptr->wlr_output_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_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_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); 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 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/src/toolkit/style.c0000644000175100017510000002150415203543557016224 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/src/toolkit/popup.c0000644000175100017510000000664615203543557016241 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/src/toolkit/resizebar.c0000644000175100017510000003372615203543557017063 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/src/toolkit/toolkit.md0000644000175100017510000004431615203543557016735 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/src/xdg_toplevel.h0000644000175100017510000000355115203543557016102 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/src/x11_cursor.xpm0000644000175100017510000000343115203543557015766 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/src/tl_menu.c0000644000175100017510000003310215203543557015037 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/src/clip.c0000644000175100017510000011006415203543557014326 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/src/corner.h0000644000175100017510000000405415203543557014675 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/src/xwl.h0000644000175100017510000000554515203543557014225 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/src/root_menu.c0000644000175100017510000010566215203543557015416 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/src/files.c0000644000175100017510000001503515203543557014503 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/src/icon_manager.h0000644000175100017510000000343615203543557016032 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/src/clip.h0000644000175100017510000000367115203543557014340 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/src/xwl_surface.h0000644000175100017510000000367515203543557015737 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/src/xdg_popup.h0000644000175100017510000000347515203543557015420 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/src/layer_panel.c0000644000175100017510000005061515203543557015677 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/src/action_item.h0000644000175100017510000000653415203543557015705 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/src/layer_panel.h0000644000175100017510000000354015203543557015677 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/src/idle.c0000644000175100017510000003571015203543557014320 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/src/xdg_popup.c0000644000175100017510000002243415203543557015407 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/src/xdg_decoration.h0000644000175100017510000000354015203543557016375 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/src/launcher.h0000644000175100017510000000403015203543557015200 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/src/icon_manager.c0000644000175100017510000005054115203543557016024 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/src/input_observation.c0000644000175100017510000003651115203543557017155 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/src/xdg_shell.h0000644000175100017510000000455215203543557015361 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/src/config.h0000644000175100017510000001314415203543557014652 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/src/subprocess_monitor.h0000644000175100017510000001064015203543557017342 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/src/xdg_decoration.c0000644000175100017510000005471215203543557016377 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/src/dock.c0000644000175100017510000003504015203543557014317 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/src/CMakeLists.txt0000644000175100017510000001111615203543557015771 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/src/task_list.h0000644000175100017510000000645615203543557015412 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/src/backtrace.c0000644000175100017510000000767015203543557015326 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/src/tl_menu.h0000644000175100017510000000313115203543557015043 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/src/server.h0000644000175100017510000002100215203543557014703 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/src/layer_shell.c0000644000175100017510000001135615203543557015706 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/src/wlmbacktrace.h0000644000175100017510000000274515203543557016051 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/src/xwl_surface.c0000644000175100017510000010531415203543557015723 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/src/input/0000755000175100017510000000000015203543557014370 5ustar runnerrunnerwlmaker-0.8/src/input/cursor.c0000644000175100017510000005404315203543557016057 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/src/input/pointer.h0000644000175100017510000000537615203543557016234 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/src/input/manager.c0000644000175100017510000005113615203543557016154 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/src/input/cursor.h0000644000175100017510000000747715203543557016075 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/src/input/pointer.c0000644000175100017510000002574515203543557016231 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/src/input/CMakeLists.txt0000644000175100017510000000311215203543557017125 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/src/input/manager.h0000644000175100017510000001423515203543557016160 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/src/input/keyboard.h0000644000175100017510000000420115203543557016336 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/src/input/keyboard.c0000644000175100017510000004463215203543557016345 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/src/backend/0000755000175100017510000000000015203543557014620 5ustar runnerrunnerwlmaker-0.8/src/backend/output_manager.c0000644000175100017510000004744615203543557020035 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/src/backend/output.c0000644000175100017510000002730715203543557016335 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/src/backend/CMakeLists.txt0000644000175100017510000000310115203543557017353 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/src/backend/output_config.c0000644000175100017510000006756115203543557017670 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/src/backend/backend.c0000644000175100017510000006722415203543557016366 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/src/xwl.c0000644000175100017510000003023715203543557014214 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/src/lock_mgr.h0000644000175100017510000000320315203543557015175 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/src/wlmaker.c0000644000175100017510000004105515203543557015044 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/src/xdg_toplevel.c0000644000175100017510000016175415203543557016107 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); // Use current output, or pick the center output if nothing was indicated. if (NULL == wlmtk_window_get_wlr_output(wxt_ptr->window_ptr)) { struct wlr_output *wlr_output_ptr = wlr_output_layout_get_center_output( wxt_ptr->server_ptr->wlr_output_layout_ptr); wlmtk_window_set_wlr_output(wxt_ptr->window_ptr, wlr_output_ptr); } 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); // Sets the requested output, or pick the center output if nothing // was indicated. struct wlr_output *wlr_output_ptr = wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen_output; if (NULL == wlr_output_ptr) { wlr_output_ptr = wlr_output_layout_get_center_output( wxt_ptr->server_ptr->wlr_output_layout_ptr); } wlmtk_window_set_wlr_output(wxt_ptr->window_ptr, wlr_output_ptr); 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/src/wlmbacktrace.c0000644000175100017510000000771215203543557016043 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/src/action.h0000644000175100017510000000754115203543557014666 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/src/background.h0000644000175100017510000000357215203543557015530 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/src/background.c0000644000175100017510000002243315203543557015520 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/src/task_list.c0000644000175100017510000004517215203543557015403 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/src/xdg_shell.c0000644000175100017510000001225315203543557015351 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/src/idle.h0000644000175100017510000000462715203543557014330 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/src/root_menu.h0000644000175100017510000000414415203543557015414 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/src/input_observation.h0000644000175100017510000000401415203543557017153 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/src/action.c0000644000175100017510000010035115203543557014652 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/src/server.c0000644000175100017510000005214315203543557014710 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/libcairo-fontconfig.supp0000644000175100017510000000206515203543557017274 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/build-cscope-index.sh0000755000175100017510000000103415203543557016455 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/.github/0000755000175100017510000000000015203543557014002 5ustar runnerrunnerwlmaker-0.8/.github/workflows/0000755000175100017510000000000015203543557016037 5ustar runnerrunnerwlmaker-0.8/.github/workflows/build-for-freebsd.yml0000644000175100017510000000217715203543557022064 0ustar runnerrunnername: Build for Free BSD 14.2 on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code, including git submodules. uses: actions/checkout@v4 with: submodules: true - name: Configure, build and test wlmaker through CMake. uses: cross-platform-actions/action@v0.26.0 with: operating_system: freebsd version: '14.2' run: | sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf sudo pkg install -y \ devel/bison \ devel/cmake-core \ devel/evdev-proto \ devel/libbacktrace \ devel/libepoll-shim \ devel/pkgconf \ graphics/cairo \ graphics/wayland \ graphics/wayland-protocols \ lang/gcc \ x11-toolkits/wlroots018 \ x11/libxcb \ x11/libxdg-basedir \ x11/libxkbcommon cmake -B build/ -Dconfig_WERROR=ON cmake --build build/ ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/.github/workflows/build-for-fedora41.yml0000644000175100017510000000245415203543557022055 0ustar runnerrunnername: Build for Fedora Linux 41 on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build_matrix: strategy: matrix: compiler: [ "gcc", "clang" ] runs-on: ubuntu-latest container: image: fedora:41 steps: - name: Install package dependencies. run: | dnf -y upgrade dnf -y install \ bison \ cairo-devel \ clang \ cmake \ flex \ gcc \ git \ libxdg-basedir-devel \ ncurses-devel \ pkg-config \ plantuml \ wayland-protocols-devel \ wlroots-devel \ xwayland-run - name: Checkout code, including git submodules. uses: actions/checkout@v3 with: # Not using 'recursive' prevents fetching extra submodules below # dependencies/. These are only needed to build wlroots from source. submodules: true - name: Configure wlmaker through CMake. run: | export CC="${{ matrix.compiler }}" cmake -B build/ -Dconfig_WERROR=ON - name: Build wlmaker. run: | export CC="${{ matrix.compiler }}" cmake --build build/ - name: Run all tests. run: | ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/.github/workflows/build-for-debian-forky.yml0000644000175100017510000000251615203543557023021 0ustar runnerrunnername: Build for Debian Forky, with libwlroots-0.19-dev package on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build_matrix: strategy: matrix: compiler: [ "gcc", "clang" ] runs-on: ubuntu-latest container: image: debian:forky steps: - name: Install package dependencies. run: | apt-get update apt-get install -y \ bison \ clang \ cmake \ flex \ gcc \ git \ libbacktrace-dev \ libcairo2-dev \ libncurses-dev \ libwlroots-0.19-dev \ libxdg-basedir-dev \ pkg-config \ plantuml \ xwayland - name: Checkout code, including git submodules. uses: actions/checkout@v3 with: # Not using 'recursive' prevents fetching extra submodules below # dependencies/. These are only needed to build wlroots from source. submodules: true - name: Configure wlmaker through CMake. run: | export CC="${{ matrix.compiler }}" cmake -B build/ -Dconfig_WERROR=ON - name: Build wlmaker. run: | export CC="${{ matrix.compiler }}" cmake --build build/ - name: Run all tests. run: | ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/.github/workflows/build-for-debian-trixie.yml0000644000175100017510000000262115203543557023170 0ustar runnerrunnername: Build for Debian Trixie, with libwlroots-0.18-dev package on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build_matrix: strategy: matrix: compiler: [ "gcc", "clang" ] runs-on: ubuntu-latest container: image: debian:trixie steps: - name: Install package dependencies. run: | apt-get update apt-get install -y \ bison \ clang \ cmake \ desktop-file-utils \ flex \ gcc \ git \ iwyu \ libbacktrace-dev \ libcairo2-dev \ libncurses-dev \ libwlroots-0.18-dev \ libxdg-basedir-dev \ pkg-config \ plantuml \ xwayland - name: Checkout code, including git submodules. uses: actions/checkout@v3 with: # Not using 'recursive' prevents fetching extra submodules below # dependencies/. These are only needed to build wlroots from source. submodules: true - name: Configure wlmaker through CMake. run: | export CC="${{ matrix.compiler }}" cmake -B build/ -Dconfig_WERROR=ON -DIWYU_MODE=FAIL - name: Build wlmaker. run: | export CC="${{ matrix.compiler }}" cmake --build build/ - name: Run all tests. run: | ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/.github/workflows/build-for-opensuse-tumbleweed.yml0000644000175100017510000000225415203543557024442 0ustar runnerrunnername: Build for openSUSE Tumbleweed on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: test: runs-on: ubuntu-latest container: image: opensuse/tumbleweed steps: - name: Install package dependencies. run: | zypper --non-interactive install \ bison \ cairo-devel \ cmake \ flex \ gcc \ git \ libxcb-devel \ libxdg-basedir-devel \ libxkbcommon-devel \ ncurses-devel \ wayland-devel \ wayland-protocols-devel \ wlroots-devel \ xwayland-devel - name: Checkout code, including git submodules. uses: actions/checkout@v3 with: # Not using 'recursive' prevents fetching extra submodules below # dependencies/. These are only needed to build wlroots from source. submodules: true - name: Configure wlmaker through CMake. run: | cmake -B build/ -Dconfig_WERROR=ON - name: Build wlmaker. run: | cmake --build build/ - name: Run all tests. run: | ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/.github/workflows/build-for-debian-trixie-wlr019.yml0000644000175100017510000000607715203543557024235 0ustar runnerrunnername: Build for Debian Trixie, with wlroots 0.19 submodule on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: INSTALL_PATH: "${HOME}/wlmaker" INSTALL_LIBRARY_PATH: "${HOME}/wlmaker/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)" INSTALL_PKGCONFIG_PATH: "${HOME}/wlmaker/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/pkgconfig/:${HOME}/wlmaker/share/pkgconfig/" jobs: build_matrix: strategy: matrix: compiler: [ "gcc", "clang" ] runs-on: ubuntu-latest container: image: debian:trixie steps: - name: Install package dependencies. run: | apt-get update apt-get install -y \ bison \ clang \ cmake \ flex \ gcc \ git \ hwdata \ libbacktrace-dev \ libcairo2-dev \ libdisplay-info-dev \ libdrm-dev \ libinput-dev \ libpixman-1-dev \ libseat-dev \ libudev-dev \ libwayland-dev \ libxcb-composite0-dev \ libxcb-dri3-dev \ libxcb-ewmh-dev \ libxcb-icccm4-dev \ libxcb-present-dev \ libxcb-render-util0-dev \ libxcb-res0-dev \ libxcb-shm0-dev \ libxcb-xinput-dev \ libxcb1-dev \ libxdg-basedir-dev \ libxkbcommon-dev \ meson \ pkgconf \ plantuml \ seatd \ wayland-protocols \ xwayland - name: Checkout dependencies. run: | git clone https://github.com/phkaeser/wlmaker-dependencies.git cd wlmaker-dependencies git reset --hard 377b858ad0d07869e0afb2c2f7ba5273aee9460f git submodule update --init --recursive --merge - name: Configure and build dependencies/. run: | cd wlmaker-dependencies export CC="${{ matrix.compiler }}" export PKG_CONFIG_PATH="${{ env.INSTALL_PKGCONFIG_PATH }}" export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" export PATH="${PATH}:${{ env.INSTALL_PATH }}/bin" cmake -DCMAKE_INSTALL_PREFIX:PATH=${{ env.INSTALL_PATH }} . -B build/ cmake --build build - name: Checkout code, including git submodules. uses: actions/checkout@v4 with: submodules: true - name: Configure wlmaker through CMake. run: | export CC="${{ matrix.compiler }}" export PKG_CONFIG_PATH="${{ env.INSTALL_PKGCONFIG_PATH }}" export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" export PATH="${PATH}:${{ env.INSTALL_PATH }}/bin" cmake -DCMAKE_INSTALL_PREFIX:PATH=${{ env.INSTALL_PATH }} -Dconfig_WERROR=ON -B build/ - name: Build wlmaker. run: | export CC="${{ matrix.compiler }}" export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" export PATH="${PATH}:${{ env.INSTALL_PATH }}/bin" cmake --build build/ - name: Run all tests. run: | export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/.github/workflows/publish-doc.yml0000644000175100017510000000467615203543557021010 0ustar runnerrunnername: Generate and publish Documentation on: # The principal run is on pushes targeting the default branch. push: branches: [ "main" ] # We also run on pull requests towards main, to catch issues with generating # the documentation before they're merged. pull_request: branches: [ "main" ] # Allows you to run this workflow manually from the Actions tab. workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow only one concurrent deployment, skipping runs queued between the run # in-progress and latest queued. However, do NOT cancel in-progress runs as we # want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false jobs: # Generate documentation, in a container. generate_doc: name: "Generate documentation" runs-on: ubuntu-latest container: image: debian:trixie steps: - name: Install package dependencies. run: | apt-get update apt-get install -y \ bison \ cmake \ doxygen \ flex \ gcc \ git \ libcairo2-dev \ libncurses-dev \ libwlroots-0.18-dev \ libxdg-basedir-dev \ pkg-config \ plantuml \ wmaker - name: Checkout code, including git submodules. uses: actions/checkout@v3 with: submodules: true - name: Configure wlmaker through CMake, with doxygen. run: | cmake -Dconfig_DOXYGEN_CRITICAL=ON -B build/ - name: Build documentation. run: cmake --build build/ --target doc - name: Configure GitHub Pages. uses: actions/configure-pages@v5 - name: Upload the generated documentation. uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: 'build/doc/html/' # Deploys the uploaded documentation to GitHub Pages. deploy: name: "Deploy the generated documentation" environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: generate_doc # Skip deployment unless it's the "push" on "main". if: ${{ github.event_name == 'push' }} steps: - name: Deploy to GitHub Pages. id: deployment uses: actions/deploy-pages@v4 wlmaker-0.8/.github/workflows/package-release.yml0000644000175100017510000000272515203543557021601 0ustar runnerrunnername: Create source package for release # Needing this, because wlmaker uses submodules, and github source package # creation will not include submodules -- and cannot be tuned for that. # See: https://github.com/phkaeser/wlmaker/issues/133 on: push: branches: - main tags: - v* release: types: - published jobs: release: name: Create Release if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - name: Checkout code, including required submodules. uses: actions/checkout@v3 with: # Not using 'recursive' prevents fetching extra submodules below # dependencies/. These are only needed to build wlroots from source. submodules: true - name: Create source package. run: | export WLM_VERSION="$(echo "${{ github.ref }}" | sed "s/^refs\/tags\/v//")" # Setup folder with package name, for apprropriate unpacking. rm -rf "/tmp/wlmaker-${WLM_VERSION}" cp -a "${PWD}" "/tmp/wlmaker-${WLM_VERSION}" mv "/tmp/wlmaker-${WLM_VERSION}" . tar -zcvf "wlmaker-${WLM_VERSION}.tar.gz" "wlmaker-${WLM_VERSION}" echo "WLM_ARCHIVE=wlmaker-${WLM_VERSION}.tar.gz" >> "${GITHUB_ENV}" echo "Created source archive wlmaker-${WLM_VERSION}.tar.gz." - name: Upload source package. uses: softprops/action-gh-release@v1 with: files: ${{env.WLM_ARCHIVE}} wlmaker-0.8/.github/workflows/build-for-debian-trixie-wlr020.yml0000644000175100017510000000557215203543557024224 0ustar runnerrunnername: Build for Debian Trixie, with wlroots 0.20 submodule on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: INSTALL_PATH: "${HOME}/wlmaker" INSTALL_LIBRARY_PATH: "${HOME}/wlmaker/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)" INSTALL_PKGCONFIG_PATH: "${HOME}/wlmaker/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/pkgconfig/:${HOME}/wlmaker/share/pkgconfig/" jobs: build_matrix: strategy: matrix: compiler: [ "gcc", "clang" ] runs-on: ubuntu-latest container: image: debian:trixie steps: - name: Install package dependencies. run: | apt-get update apt-get install -y \ bison \ clang \ cmake \ flex \ gcc \ git \ hwdata \ libbacktrace-dev \ libcairo2-dev \ libdisplay-info-dev \ libdrm-dev \ libinput-dev \ libpixman-1-dev \ libseat-dev \ libudev-dev \ libwayland-dev \ libxcb-composite0-dev \ libxcb-dri3-dev \ libxcb-ewmh-dev \ libxcb-icccm4-dev \ libxcb-present-dev \ libxcb-render-util0-dev \ libxcb-res0-dev \ libxcb-shm0-dev \ libxcb-xinput-dev \ libxcb-xkb-dev \ libxcb1-dev \ libxdg-basedir-dev \ libxkbcommon-dev \ meson \ pkgconf \ plantuml \ seatd \ wayland-protocols \ xmlto \ xsltproc \ xwayland - name: Checkout code, including git submodules. uses: actions/checkout@v4 with: submodules: recursive - name: Configure and build dependencies/. run: | export CC="${{ matrix.compiler }}" export PKG_CONFIG_PATH="${{ env.INSTALL_PKGCONFIG_PATH }}" export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" export PATH="${PATH}:${{ env.INSTALL_PATH }}/bin" cmake -DCMAKE_INSTALL_PREFIX:PATH=${{ env.INSTALL_PATH }} dependencies/ -B dependencies/build/ cmake --build dependencies/build - name: Configure wlmaker through CMake. run: | export CC="${{ matrix.compiler }}" export PKG_CONFIG_PATH="${{ env.INSTALL_PKGCONFIG_PATH }}" export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" export PATH="${PATH}:${{ env.INSTALL_PATH }}/bin" cmake -DCMAKE_INSTALL_PREFIX:PATH=${{ env.INSTALL_PATH }} -Dconfig_WERROR=ON -B build/ - name: Build wlmaker. run: | export CC="${{ matrix.compiler }}" export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" export PATH="${PATH}:${{ env.INSTALL_PATH }}/bin" cmake --build build/ - name: Run all tests. run: | export LD_LIBRARY_PATH="${{ env.INSTALL_LIBRARY_PATH }}" ctest --test-dir build/ --build-run-dir build/ -V wlmaker-0.8/etc/0000755000175100017510000000000015203543557013215 5ustar runnerrunnerwlmaker-0.8/etc/ExampleConfig.plist0000644000175100017510000001002315203543557017007 0ustar runnerrunner{ //! [Keyboard] Keyboard = { XkbRMLVO = { Rules = "evdev"; Model = "pc105"; Layout = "us"; Variant = "intl"; Options = ""; }; Repeat = { // Delay before initiating repeats, in milliseconds. Delay = 300; // Repeats per second. Rate = 25; }; }; //! [Keyboard] //! [Decoration] // Configuration for XDG decoration protocol: Server or client-side? Decoration = { Mode = SuggestServer; }; //! [Decoration] //! [KeyBindings] KeyBindings = { "Ctrl+Alt+Logo+Q" = Quit; "Ctrl+Alt+Logo+L" = LockScreen; "Ctrl+Alt+Logo+T" = LaunchTerminal; "Ctrl+Alt+Logo+Left" = WorkspacePrevious; "Ctrl+Alt+Logo+Right" = WorkspaceNext; "Ctrl+Alt+Logo+Escape" = TaskNext; "Shift+Ctrl+Alt+Logo+Escape" = TaskPrevious; "Alt+Logo+Up" = WindowRaise; "Alt+Logo+Down" = WindowLower; "Ctrl+Alt+Logo+F" = WindowToggleFullscreen; "Ctrl+Alt+Logo+M" = WindowToggleMaximized; // TODO(kaeser@gubbe.ch): Swap with F12, to match Window Maker's behaviour. "Ctrl+Alt+Logo+R" = RootMenu; // TODO(kaeser@gubbe.ch): xkbcommon emits XF86Switch_VT_n for Fn only with // Ctrl+Alt presset. Means: Here, it should not need the modifiers to be // listed. Should determine how to handle that w/o modifiers. "Ctrl+Alt+XF86Switch_VT_1" = SwitchToVT1; "Ctrl+Alt+XF86Switch_VT_2" = SwitchToVT2; "Ctrl+Alt+XF86Switch_VT_3" = SwitchToVT3; "Ctrl+Alt+XF86Switch_VT_4" = SwitchToVT4; "Ctrl+Alt+XF86Switch_VT_5" = SwitchToVT5; "Ctrl+Alt+XF86Switch_VT_6" = SwitchToVT6; "Ctrl+Alt+XF86Switch_VT_7" = SwitchToVT7; "Ctrl+Alt+XF86Switch_VT_8" = SwitchToVT8; "Ctrl+Alt+XF86Switch_VT_9" = SwitchToVT9; "Ctrl+Alt+XF86Switch_VT_10" = SwitchToVT10; "Ctrl+Alt+XF86Switch_VT_11" = SwitchToVT11; "Ctrl+Alt+XF86Switch_VT_12" = SwitchToVT12; }; //! [KeyBindings] //! [HotCorner] HotCorner = { // Delay for the pointer occupying a corner before triggering 'Enter'. TriggerDelay = 500; // For each corner 'TopLeft', 'TopRight', 'BottomLeft' and 'BottomRight' // there are 'Enter' and 'Leave' events that can be bound to an action. TopLeftEnter = LockScreen; TopLeftLeave = None; TopRightEnter = InhibitLockBegin; TopRightLeave = InhibitLockEnd; BottomLeftEnter = None; BottomLeftLeave = None; BottomRightEnter = None; BottomRightLeave = None; }; //! [HotCorner] //! [ScreenLock] ScreenLock = { IdleSeconds = 300; Command = "/usr/bin/swaylock"; }; //! [ScreenLock] //! [Autostart] // Optional array: Commands to start once wlmaker is running. Autostart = ( "/usr/bin/foot" ); //! [Autostart] //! [Outputs] Outputs = ( // The "Eizo EV2785" monitor on the DisplayPort connection: Configure // for a resolution of 1920x1080 (at any supported refresh rate), scale // with a factor of 2.0, and place it's top-left corner at (0, 0). { Manufacturer = "Eizo*"; Model = "EV2785"; Name = "DP-*"; Position = "0,0"; // Note: Mode applies before scaling: The effective resolution // visible to clients becomes 1920x1080. Mode = "3840x2160@59.997"; Scale = 2.0; Transformation = Normal; }, // Any output at HDMI will also be placed at 0,0; mirroring the "Eizo". { Name = "HDMI-*"; Position = "0,0"; Scale = 1.0; Transformation = Normal; }, // Any further monitor will be automatically placed; to the right of // above setup. { Name = "*"; Scale = 1.0; Transformation = Normal; }, ); //! [Outputs] } wlmaker-0.8/etc/RootMenu.plist0000644000175100017510000000165315203543557016047 0ustar runnerrunner//! @page etc_root_menu_plist etc/RootMenu.plist //! Compiled-in default configuration for the root menu. See @ref root_menu //! for details. //! @include etc/RootMenu.plist // Definition of root menu. Follows Window Maker plist order. ("Root Menu", ("Applications", ("Terminal", Execute, "/usr/bin/foot"), ("Chrome", ShellExecute, "/usr/bin/google-chrome --enable-features=UseOzonePlatform --ozone-platform=wayland --user-data-dir=/tmp/chrome-wayland")), ("Outputs...", ("Magnify", OutputMagnify), ("Reduce", OutputReduce), ("Configure", Execute, "wdisplays"), ("Save State", OutputSaveState), ("Reduce brightness", ShellExecute, "brightnessctl set 5%-"), ("Increase brightness", ShellExecute, "brightnessctl set +5%")), ("Workspaces...", ("New", WorkspaceAdd), ("Destroy Last", WorkspaceDestroyLast), ("Go To Previous", WorkspacePrevious), ("Go To Next", WorkspaceNext)), ("Lock", LockScreen), (Exit, Quit)) wlmaker-0.8/etc/HomeConfig.plist0000644000175100017510000000201015203543557016301 0ustar runnerrunner// Base configuration for wlmaker: Keyboard and autostarted applications. { Keyboard = { XkbRMLVO = { Rules = "evdev"; Model = "pc105"; Layout = "ch"; Variant = "de_nodeadkeys"; Options = ""; }; Repeat = { // Delay before initiating repeats, in milliseconds. Delay = 300; // Repeats per second. Rate = 25; }; }; KeyBindings = { "Ctrl+Alt+Logo+Q" = Quit; "Ctrl+Alt+Logo+L" = LockScreen; "Ctrl+Alt+Logo+T" = LaunchTerminal; "Ctrl+Alt+Logo+Left" = WorkspacePrevious; "Ctrl+Alt+Logo+Right" = WorkspaceNext; "Ctrl+Alt+Logo+Escape" = TaskNext; "Shift+Ctrl+Alt+Logo+Escape" = TaskPrevious; "Alt+Logo+Up" = WindowRaise; "Alt+Logo+Down" = WindowLower; "Ctrl+Alt+Logo+F" = WindowFullscreen; "Ctrl+Alt+Logo+M" = WindowMaximize; }; ScreenLock = { IdleSeconds = 300; Command = "/usr/bin/swaylock"; }; // Optional array: Commands to start once wlmaker is running. Autostart = ( "/usr/bin/foot" ); }wlmaker-0.8/etc/State.plist0000644000175100017510000000146015203543557015353 0ustar runnerrunner{ Dock = { Output = { Name = "*"; }; Edge = RIGHT; Anchor = TOP; Launchers = ( { CommandLine = "/usr/bin/foot"; Icon = "terminal-56x56.png"; }, { CommandLine = "/usr/bin/chromium --enable-features=UseOzonePlatform --ozone-platform=wayland"; Icon = "chromium-56x56.png"; }, { CommandLine = "MOZ_ENABLE_WAYLAND=1 /usr/bin/firefox"; Icon = "firefox-56x56.png"; } ); }; Clip = { Output = { Name = "*"; }; Edge = BOTTOM; Anchor = RIGHT; }; Workspaces = ( { Name = Main; }, { Name = Second; }, { Name = Third; // Color is optional here, overrides the style's BackgroundColor. Color = "argb32:ff508050"; }, ); }wlmaker-0.8/etc/Config.plist0000644000175100017510000001142415203543557015501 0ustar runnerrunner//! @page etc_config_plist etc/Config.plist //! Compiled-in default configuration. See @ref config_file for details. //! @include etc/Config.plist // Base configuration for wlmaker: Keyboard and autostarted applications. { Keyboard = { // Use system-wide configuration. See keyboard(5). Takes precedence // over `XkbRMLVO`. XkbConfigurationFile = "/etc/default/keyboardFIXME"; // Alternatively: Specify the keyboard properties directly. XkbRMLVO = { Rules = "evdev"; Model = "pc105"; Layout = "us"; Variant = "intl"; Options = ""; }; Repeat = { // Delay before initiating repeats, in milliseconds. Delay = 300; // Repeats per second. Rate = 25; }; }; Touchpad = { Enabled = True; DisableWhileTyping = True; // How the touchpad determines which button is meant on tap. // None, ButtonAreas or ClickFinger. ClickMethod = ClickFinger; TapToClick = Enabled; // How scrolling is initiated by the touchpad: // NoScroll, TwoFingers, Edge or OnButtonDown. ScrollMethod = TwoFingers; NaturalScrolling = Enabled; }; Cursor = { // Optional. If set: A keyboard modifier that will emulate a mouse // right-button click, if the modifier is held while the left button // is pressed. EmulateRightButtonModifier = Logo; // Optional. If set: Start moving the window, When pressing that // modifier while starting to drag the left mouse button. MoveWindowModifier = Alt; }; // Optional. Full path to a Theme file. // ThemeFile = "/usr/share/wlmaker/wlmaker/Themes/Other.plist"; // Configuration for XDG decoration protocol: Server or client-side? Decoration = { Mode = SuggestServer; }; KeyBindings = { "Ctrl+Alt+Backspace" = Quit; "Ctrl+Alt+L" = LockScreen; "Logo+L" = LockScreen; "Ctrl+Alt+T" = LaunchTerminal; "Logo+Return" = LaunchTerminal; "Alt+Tab" = TaskNext; "Shift+Alt+ISO_Left_Tab" = TaskPrevious; "Ctrl+Alt+Left" = WorkspacePrevious; "Ctrl+Alt+Right" = WorkspaceNext; "Alt+Up" = WindowRaise; "Alt+Down" = WindowLower; "Alt+F10" = WindowToggleMaximized; "Ctrl+Shift+M" = WindowToggleMaximized; "Alt+F11" = WindowToggleFullscreen; "Ctrl+Shift+F" = WindowToggleFullscreen; "Alt+F4" = WindowClose; "Ctrl+Alt+Shift+Right" = WindowToNextWorkspace; "Ctrl+Alt+Shift+Left" = WindowToPreviousWorkspace; "F12" = RootMenu; "Ctrl+Escape" = RootMenu; // TODO(kaeser@gubbe.ch): Swap to Plus, Minus, once finding a way to // exclude the Shift-modifier well. "Ctrl+Shift+Page_Up" = OutputMagnify; "Ctrl+Shift+Page_Down" = OutputReduce; "XF86MonBrightnessDown" = (ShellExecute, "brightnessctl set 5%-"); "XF86MonBrightnessUp" = (ShellExecute, "brightnessctl set +5%"); // TODO(kaeser@gubbe.ch): xkbcommon emits XF86Switch_VT_n for Fn only with // Ctrl+Alt presset. Means: Here, it should not need the modifiers to be // listed. Should determine how to handle that w/o modifiers. "Ctrl+Alt+XF86Switch_VT_1" = SwitchToVT1; "Ctrl+Alt+XF86Switch_VT_2" = SwitchToVT2; "Ctrl+Alt+XF86Switch_VT_3" = SwitchToVT3; "Ctrl+Alt+XF86Switch_VT_4" = SwitchToVT4; "Ctrl+Alt+XF86Switch_VT_5" = SwitchToVT5; "Ctrl+Alt+XF86Switch_VT_6" = SwitchToVT6; "Ctrl+Alt+XF86Switch_VT_7" = SwitchToVT7; "Ctrl+Alt+XF86Switch_VT_8" = SwitchToVT8; "Ctrl+Alt+XF86Switch_VT_9" = SwitchToVT9; "Ctrl+Alt+XF86Switch_VT_10" = SwitchToVT10; "Ctrl+Alt+XF86Switch_VT_11" = SwitchToVT11; "Ctrl+Alt+XF86Switch_VT_12" = SwitchToVT12; }; HotCorner = { // Delay for the pointer occupying a corner before triggering 'Enter'. TriggerDelay = 500; // For each corner 'TopLeft', 'TopRight', 'BottomLeft' and 'BottomRight' // there are 'Enter' and 'Leave' events that can be bound to an action. TopLeftEnter = LockScreen; TopLeftLeave = None; TopRightEnter = InhibitLockBegin; TopRightLeave = InhibitLockEnd; BottomLeftEnter = None; BottomLeftLeave = None; BottomRightEnter = None; BottomRightLeave = None; }; ScreenLock = { IdleSeconds = 300; Command = "/usr/bin/swaylock"; }; // Optional array: Commands to start once wlmaker is running. Autostart = ( "/usr/bin/foot" ); Outputs = ( { Name = "*"; Transformation = Normal; Scale = 1.0; }, ); }wlmaker-0.8/etc/CMakeLists.txt0000644000175100017510000000565515203543557015770 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) include(EmbedBinary) embedbinary_add_library( embedded_root_menu "root_menu" "${CMAKE_CURRENT_SOURCE_DIR}/RootMenu.plist") embedbinary_add_library( embedded_configuration "default_configuration" "${CMAKE_CURRENT_SOURCE_DIR}/Config.plist") embedbinary_add_library( embedded_state "default_state" "${CMAKE_CURRENT_SOURCE_DIR}/State.plist") # Found in Debian's gnustep-base-runtime. Using to keep plist format consistent # with what GNUstep expects. Optional. find_program(PLDES pldes) if(PLDES) message("Found ${PLDES}. Adding consistency tests for GNUstep plist syntax.") # Sadly, the gnustep-base plist utilies don't return EXIT_FAILURE upon # parsing an invalid plist file. But eg. `pldes` reports the parsing error, # so we capture this as a failure. set(pldes_failure_regex ".*at\ line.*char") add_test( NAME root_menu_plist_test COMMAND "${PLDES}" "${CMAKE_CURRENT_SOURCE_DIR}/RootMenu.plist") set_property( TEST root_menu_plist_test PROPERTY FAIL_REGULAR_EXPRESSION "${pldes_failure_regex}") add_test( NAME wlmaker_plist_test COMMAND "${PLDES}" "${CMAKE_CURRENT_SOURCE_DIR}/Config.plist") set_property( TEST wlmaker_plist_test PROPERTY FAIL_REGULAR_EXPRESSION "${pldes_failure_regex}") add_test( NAME wlmaker_example_plist_test COMMAND "${PLDES}" "${CMAKE_CURRENT_SOURCE_DIR}/ExampleConfig.plist") set_property( TEST wlmaker_example_plist_test PROPERTY FAIL_REGULAR_EXPRESSION "${pldes_failure_regex}") add_test( NAME wlmaker_home_plist_test COMMAND "${PLDES}" "${CMAKE_CURRENT_SOURCE_DIR}/HomeConfig.plist") set_property( TEST wlmaker_home_plist_test PROPERTY FAIL_REGULAR_EXPRESSION "${pldes_failure_regex}") add_test( NAME wlmaker_state_plist_test COMMAND "${PLDES}" "${CMAKE_CURRENT_SOURCE_DIR}/State.plist") set_property( TEST wlmaker_state_plist_test PROPERTY FAIL_REGULAR_EXPRESSION "${pldes_failure_regex}") endif() install( FILES "RootMenu.plist" "State.plist" "Config.plist" DESTINATION "${WLMAKER_INSTALL_CONFIG_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/RootMenuDebian.plist.in" "${CMAKE_CURRENT_BINARY_DIR}/RootMenuDebian.plist" @ONLY) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/RootMenuDebian.plist" DESTINATION "${WLMAKER_INSTALL_CONFIG_DIR}") wlmaker-0.8/etc/RootMenuDebian.plist.in0000644000175100017510000000151215203543557017551 0ustar runnerrunner// Definition of root menu, for Debian with wlmtool. ("Root Menu", //! [wlmtool] ("Applications...", GeneratePlistMenu, "@PROJECT_BINARY_DIR@/tool/wlmtool GenerateApplicationsMenu @PROJECT_BINARY_DIR@/share"), ("Appearance...", GeneratePlistMenu, "@PROJECT_BINARY_DIR@/tool/wlmtool GenerateThemesMenu @PROJECT_SOURCE_DIR@/share/Themes"), //! [wlmtool] ("Outputs...", ("Magnify", OutputMagnify), ("Reduce", OutputReduce), ("Configure", Execute, "wdisplays"), ("Save State", OutputSaveState), ("Reduce brightness", ShellExecute, "brightnessctl set 5%-"), ("Increase brightness", ShellExecute, "brightnessctl set +5%")), ("Workspaces...", ("New", WorkspaceAdd), ("Destroy Last", WorkspaceDestroyLast), ("Go To Previous", WorkspacePrevious), ("Go To Next", WorkspaceNext)), ("Lock", LockScreen), (Exit, Quit)) wlmaker-0.8/LICENSE0000644000175100017510000002613515203543557013456 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/libdrm.supp0000644000175100017510000000010715203543557014622 0ustar runnerrunner{ Memcheck:Leak ... obj:/usr/lib/*/dri/* ... } wlmaker-0.8/NOTICE0000644000175100017510000000103115203543557013341 0ustar runnerrunnerwlmaker Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) The copyright held by Google LLC before and until 2026-05-18 was waived and transferred to Philipp Kaeser by 2026-05-15. This product includes software copyrighted by Google LLC (https://github.com/phkaeser/libbase). This product bundles the inih library, which is available under a "New BSD" (3-Clause) license. For details, see the original license and copyright information in submodules/inih/LICENSE.txt, or at https://github.com/benhoyt/inih/blob/master/LICENSE.txt. wlmaker-0.8/iwyu-mappings.imp0000644000175100017510000000036215203543557015763 0ustar runnerrunner# -*- mode: python; -*- [ # Weird iwyu complaint to add "#include // for tm" { "symbol": ["tm", "public", "", "public" ]}, { "symbol": ["wl_list", "public", "", "public" ]}, ] wlmaker-0.8/libgallium.supp0000644000175100017510000000011615203543557015472 0ustar runnerrunner{ Memcheck:Leak ... obj:/usr/lib/*/libgallium-* ... } wlmaker-0.8/dependencies/0000755000175100017510000000000015203543563015065 5ustar runnerrunnerwlmaker-0.8/dependencies/.git0000644000175100017510000000004515203543560015645 0ustar runnerrunnergitdir: ../.git/modules/dependencies wlmaker-0.8/dependencies/libdrm/0000755000175100017510000000000015203543563016336 5ustar runnerrunnerwlmaker-0.8/dependencies/libxkbcommon/0000755000175100017510000000000015203543563017551 5ustar runnerrunnerwlmaker-0.8/dependencies/LICENSE0000644000175100017510000002613515203543563016101 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/dependencies/.gitignore0000644000175100017510000000006315203543563017054 0ustar runnerrunner**/*~ **/CMakeFiles/* **/CMakeCache.txt **/build/* wlmaker-0.8/dependencies/wayland-protocols/0000755000175100017510000000000015203543563020546 5ustar runnerrunnerwlmaker-0.8/dependencies/.gitmodules0000644000175100017510000000137715203543563017252 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/dependencies/libbacktrace/0000755000175100017510000000000015203543563017473 5ustar runnerrunnerwlmaker-0.8/dependencies/wayland/0000755000175100017510000000000015203543563016524 5ustar runnerrunnerwlmaker-0.8/dependencies/CMakeLists.txt0000644000175100017510000001046115203543563017627 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/dependencies/pixman/0000755000175100017510000000000015203543563016361 5ustar runnerrunnerwlmaker-0.8/dependencies/wlroots/0000755000175100017510000000000015203543563016576 5ustar runnerrunnerwlmaker-0.8/dependencies/README.md0000644000175100017510000000031715203543563016345 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/.gitignore0000644000175100017510000000017415203543557014434 0ustar runnerrunner**/*~ **/CMakeFiles/* **/CMakeCache.txt **/build/* **/build-clang/* TAGS cscope.files cscope.in.out cscope.out cscope.po.outwlmaker-0.8/STYLE.md0000644000175100017510000000547715203543557013701 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/apps/0000755000175100017510000000000015203543557013405 5ustar runnerrunnerwlmaker-0.8/apps/primitives/0000755000175100017510000000000015203543557015600 5ustar runnerrunnerwlmaker-0.8/apps/primitives/segment_display.c0000644000175100017510000002506015203543557021136 0ustar runnerrunner/* ========================================================================= */ /** * @file segment_display.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 "segment_display.h" #include #include #include #include /* == Declarations ========================================================= */ static void draw_segment( cairo_t *cairo_ptr, const bs_vector_2f_t origin, const bs_vector_2f_t longitudinal, const bs_vector_2f_t lateral, const wlm_cairo_7segment_param_t *param_ptr, const double length); /** * Encoding bits to indicate coloring of segments for each digit. * * The sequence follows https://en.wikipedia.org/wiki/Seven-segment_display, * as follows: ``` <- 0 -> ^ ^ 5 1 v v <- 6 -> ^ ^ 4 2 v v <- 3 -> ``` */ static const uint8_t seven_segment_encoding[10] = { // 6543210 <-- segment. 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111 // 9 }; const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_6x8 = { .offset = 0.6, .width = 1.0, .hlength = 4.0, .vlength = 3.0 }; const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_7x10 = { .offset = 0.6, .width = 1.0, .hlength = 5.0, .vlength = 4.0 }; const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_8x12 = { .offset = 0.8, .width = 1.0, .hlength = 6, .vlength = 5 }; const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_16x24 = { .offset = 1.2, .width = 2.0, .hlength = 12.0, .vlength = 10.0 }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void wlm_cairo_7segment_display_digit( cairo_t *cairo_ptr, const wlm_cairo_7segment_param_t *pptr, uint32_t x, uint32_t y, uint32_t color_on, uint32_t color_off, uint8_t digit) { BS_ASSERT(digit < UINT8_C(10)); uint8_t segments = seven_segment_encoding[digit]; // Vectors spanning up a segment. bs_vector_2f_t longitudinal = { .x = 1.0, .y = 0.0 }; bs_vector_2f_t lateral = { .x = 0.0, .y = 1.0 }; bs_vector_2f_t origin = BS_VECTOR_2F( x + pptr->width / 2.0, y - 2 * pptr->vlength - pptr->width / 2.0); bs_vector_2f_t pos; cairo_save(cairo_ptr); // Segment 0. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 0) ? color_on : color_off); pos = origin; draw_segment(cairo_ptr, pos, longitudinal, lateral, pptr, pptr->hlength); // Segment 1. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 1) ? color_on : color_off); pos = bs_vec_add_2f(origin, BS_VECTOR_2F(pptr->hlength, 0)); draw_segment(cairo_ptr, pos, lateral, longitudinal, pptr, pptr->vlength); // Segment 2. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 2) ? color_on : color_off); pos = bs_vec_add_2f(origin, BS_VECTOR_2F(pptr->hlength, pptr->vlength)); draw_segment(cairo_ptr, pos, lateral, longitudinal, pptr, pptr->vlength); // Segment 3. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 3) ? color_on : color_off); pos = bs_vec_add_2f(origin, BS_VECTOR_2F(0, 2 * pptr->vlength)); draw_segment(cairo_ptr, pos, longitudinal, lateral, pptr, pptr->hlength); // Segment 4. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 4) ? color_on : color_off); pos = bs_vec_add_2f(origin, BS_VECTOR_2F(0, pptr->vlength)); draw_segment(cairo_ptr, pos, lateral, longitudinal, pptr, pptr->vlength); // Segment 5. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 5) ? color_on : color_off); pos = origin; draw_segment(cairo_ptr, pos, lateral, longitudinal, pptr, pptr->vlength); // Segment 6. cairo_set_source_argb8888( cairo_ptr, segments & (0x01 << 6) ? color_on : color_off); pos = bs_vec_add_2f(origin, BS_VECTOR_2F(0, pptr->vlength)); draw_segment(cairo_ptr, pos, longitudinal, lateral, pptr, pptr->hlength); cairo_restore(cairo_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Draws a segment, from `origin` along `longitudinal`/`lateral` direction. * * A segment spans from the point `origin` along the `longitudinal` vector, * and expands by (width/2) along `lateral` direction. It will use the current * cairo source color to fill the segment. * ``` +---------------------+ / \ +--+ + + +--+ ^ \ / | width/2 +---------------------+ v <----> offset <----> offset <-> width/2 <-> width/2 <-------------------------------> length ``` * * @param cairo_ptr * @param origin * @param longitudinal * @param lateral * @param param_ptr * @param length */ void draw_segment( cairo_t *cairo_ptr, const bs_vector_2f_t origin, const bs_vector_2f_t longitudinal, const bs_vector_2f_t lateral, const wlm_cairo_7segment_param_t *param_ptr, const double length) { bs_vector_2f_t rel; rel = bs_vec_mul_2f(param_ptr->offset - param_ptr->width/2, longitudinal); cairo_move_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); rel = bs_vec_add_2f( bs_vec_mul_2f(param_ptr->offset, longitudinal), bs_vec_mul_2f(param_ptr->width / 2, lateral)); cairo_line_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); rel = bs_vec_add_2f( bs_vec_mul_2f(length - param_ptr->offset, longitudinal), bs_vec_mul_2f(param_ptr->width / 2, lateral)); cairo_line_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); rel = bs_vec_mul_2f(length - param_ptr->offset + param_ptr->width/2, longitudinal); cairo_line_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); rel = bs_vec_add_2f( bs_vec_mul_2f(length - param_ptr->offset, longitudinal), bs_vec_mul_2f(-param_ptr->width / 2, lateral)); cairo_line_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); rel = bs_vec_add_2f( bs_vec_mul_2f(param_ptr->offset, longitudinal), bs_vec_mul_2f(-param_ptr->width / 2, lateral)); cairo_line_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); rel = bs_vec_mul_2f(param_ptr->offset - param_ptr->width/2, longitudinal); cairo_line_to(cairo_ptr, origin.x + rel.x, origin.y + rel.y); cairo_fill(cairo_ptr); cairo_stroke(cairo_ptr); } /* == Unit tests =========================================================== */ static void test_6x8(bs_test_t *test_ptr); static void test_7x10(bs_test_t *test_ptr); static void test_16x24(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlm_cairo_segment_display_test_cases[] = { { 1, "6x8", test_6x8 }, { 1, "7x10", test_7x10 }, { 1, "16x24", test_16x24 }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlm_cairo_segment_display_test_set = BS_TEST_SET( true, "cairo_segment_display", _wlm_cairo_segment_display_test_cases); /* ------------------------------------------------------------------------- */ /** Test for the 6x8-sized digits. */ void test_6x8(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(60, 8); if (NULL == gfxbuf_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(60, 8)"); return; } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { BS_TEST_FAIL(test_ptr, "Failed cairo_create_from_bs_gfxbuf."); bs_gfxbuf_destroy(gfxbuf_ptr); return; } for (int i = 0; i < 10; i++) { wlm_cairo_7segment_display_digit( cairo_ptr, &wlm_cairo_7segment_param_6x8, i * 6, 8, 0xffc0c0ff, 0xff202040, i); } cairo_destroy(cairo_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "segment_display_6x8.png"); bs_gfxbuf_destroy(gfxbuf_ptr); } /* ------------------------------------------------------------------------- */ /** Test for the 7x10-sized digits. */ void test_7x10(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(70, 10); if (NULL == gfxbuf_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(70, 10)"); return; } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { BS_TEST_FAIL(test_ptr, "Failed cairo_create_from_bs_gfxbuf."); bs_gfxbuf_destroy(gfxbuf_ptr); return; } for (int i = 0; i < 10; i++) { wlm_cairo_7segment_display_digit( cairo_ptr, &wlm_cairo_7segment_param_7x10, i * 7, 10, 0xffc0c0ff, 0xff202040, i); } cairo_destroy(cairo_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "segment_display_7x10.png"); bs_gfxbuf_destroy(gfxbuf_ptr); } /* ------------------------------------------------------------------------- */ /** Test for the 16x24-sized digits. */ void test_16x24(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(160, 24); if (NULL == gfxbuf_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(160, 24)"); return; } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { BS_TEST_FAIL(test_ptr, "Failed cairo_create_from_bs_gfxbuf."); bs_gfxbuf_destroy(gfxbuf_ptr); return; } for (int i = 0; i < 10; i++) { wlm_cairo_7segment_display_digit( cairo_ptr, &wlm_cairo_7segment_param_16x24, i * 16, 24, 0xffc0c0ff, 0xff202040, i); } cairo_destroy(cairo_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "segment_display_16x24.png"); bs_gfxbuf_destroy(gfxbuf_ptr); } /* == End of segment_display.c ============================================= */ wlmaker-0.8/apps/primitives/segment_display_6x8.png0000644000175100017510000000054215203543557022203 0ustar runnerrunnerPNG  IHDR<bKGDIDAT81k1J&P\J_Mp?Z RIp4m.[y/ofK)Y֢]n4!gG5< >>ȧ\w>Sq>_>N}qټ<{I֕1+M/1+<^tuKt:wz0H~sh4tiQ9Nr_?e_uώ T_EZsV#4 l>xIw0Oi? s.qZIENDB`wlmaker-0.8/apps/primitives/primitives.h0000644000175100017510000000416615203543557020153 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 __WLM_PRIMITIVES_H__ #define __WLM_PRIMITIVES_H__ #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * 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 wlm_primitives_set_bezel_color( cairo_t *cairo_ptr, bool illuminated); /** * Draws a bezel into the cairo, at specified position and width/height. * * TODO(kaeser@gubbe.ch): Share this code with the server. * * @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 wlm_primitives_draw_bezel_at( cairo_t *cairo_ptr, int x, int y, unsigned width, unsigned height, double bezel_width, bool raised); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLM_PRIMITIVES_H__ */ /* == End of primitives.h ================================================== */ wlmaker-0.8/apps/primitives/primitives.c0000644000175100017510000000524415203543557020144 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" /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void wlm_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 wlm_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. wlm_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. wlm_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); } /* == End of primitives.c ================================================== */ wlmaker-0.8/apps/primitives/segment_display.h0000644000175100017510000000567015203543557021150 0ustar runnerrunner/* ========================================================================= */ /** * @file segment_display.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 __WLM_SEGMENT_DISPLAY_H__ #define __WLM_SEGMENT_DISPLAY_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Parameters to describe segment properties. */ typedef struct { /** Offset distance, from origin to start of segment. */ double offset; /** Full width of the segment, along the lateral direction. */ double width; /** Length of a horizontal segment, along longitudinal direction. */ double hlength; /** Length of a vertical segment, along longitudinal direction. */ double vlength; } wlm_cairo_7segment_param_t; /** Parameters for a 6x8-pixel-sized 7-segment digit display. */ extern const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_6x8; /** Parameters for a 7x10-pixel-sized 7-segment digit display. */ extern const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_7x10; /** Parameters for a 8x12-pixel-sized 7-segment digit display. */ extern const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_8x12; /** Parameters for a 16x24-pixel-sized 7-segment digit display. */ extern const wlm_cairo_7segment_param_t wlm_cairo_7segment_param_16x24; /** * Draws a digit using 7-segment visualization. * * @param cairo_ptr The `cairo_t` target to draw the digits to. * @param param_ptr Visualization parameters for the segments. * @param x X coordinate of lower left corner. * @param y Y coordinate of lower left corner. * @param color_on An ARGB32 value for an illuminated segment. * @param color_off An ARGB32 value for a non-illuminated segment. * @param digit Digit to draw. Must be 0 <= digit < 10. */ void wlm_cairo_7segment_display_digit( cairo_t *cairo_ptr, const wlm_cairo_7segment_param_t *param_ptr, uint32_t x, uint32_t y, uint32_t color_on, uint32_t color_off, uint8_t digit); /** Unit test cases. */ extern const bs_test_set_t wlm_cairo_segment_display_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLM_SEGMENT_DISPLAY_H__ */ /* == End of segment_display.h ============================================= */ wlmaker-0.8/apps/primitives/segment_display_16x24.png0000644000175100017510000000117415203543557022344 0ustar runnerrunnerPNG  IHDR}V5bKGD1IDATh홱j@+4ج\%1IWs66k@7w9?ؓF|p+a?hbAXWfl,Yό8` kgt7ͿKKdx fq8~3Q~?89<ظY̊oc&ɅRh<i93 ρ,;Qrhd}> ?8xQjO.3:iʭ77gg_o{U  hdƢoc(N۟L~\gk֫b8W4弜^-Hȯ4ko;܄/7!"(8EPp @)r"ON&Ť~E1z~x=`ƻD3m6W_~Ӱ#IENDB`wlmaker-0.8/apps/primitives/segment_display_test.c0000644000175100017510000000264215203543557022176 0ustar runnerrunner/* ========================================================================= */ /** * @file cairo_segment_display_test.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 #include #include "segment_display.h" /** Unit tests. */ const bs_test_set_t *test_sets[] = { &wlm_cairo_segment_display_test_set, NULL, }; #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(int argc, const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; return bs_test_sets(test_sets, argc, argv, ¶ms); } /* == End of segment_display_test.c ======================================== */ wlmaker-0.8/apps/primitives/CMakeLists.txt0000644000175100017510000000310215203543557020334 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) add_library(primitives STATIC) target_sources( primitives PRIVATE primitives.h primitives.c segment_display.h segment_display.c) target_include_directories( primitives PRIVATE) target_link_libraries( primitives libbase) add_executable(segment_display_test segment_display_test.c segment_display.c segment_display.h) target_compile_definitions( segment_display_test PRIVATE "TEST_DATA_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"") target_include_directories( segment_display_test PRIVATE "${CAIRO_INCLUDE_DIRS}") target_link_libraries( segment_display_test PRIVATE libbase PkgConfig::CAIRO) add_test( NAME segment_display_test COMMAND segment_display_test) if(iwyu_path_and_options) set_target_properties( primitives PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( segment_display_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8/apps/primitives/segment_display_7x10.png0000644000175100017510000000063415203543557022257 0ustar runnerrunnerPNG  IHDRF  (bKGDQIDATH1k1 J&P\J_Mp?Z"nQ9&.VLf:޼_B_Ŕ2XVZCtll^G,3j8|h=]fo"MIFQoZ[sr/+5m.s1V+4@;LF幘|ZC,ZJ}%ɋlov8[@s'3|#s|# ,tR{)SC& fLݞ1ZC`ǽ;Է^ ޽{R>u>*32+IENDB`wlmaker-0.8/apps/wlm_graph_shared.c0000644000175100017510000015334115203543557017066 0ustar runnerrunner/* ========================================================================= */ /** * @file wlm_graph_shared.c * * Shared graph rendering utilities for wlmaker dock-apps. * * @copyright * 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. */ #include "wlm_graph_shared.h" #include #include #include #include #include #include #include #include #include #include "libwlclient/icon.h" /* == Internal definitions ================================================= */ /** Base icon size for scaling calculations. */ #define WLM_GRAPH_BASE_ICON_SIZE 56 /** Base font size for label (at 64px icon size). */ #define WLM_GRAPH_LABEL_FONT_SIZE_BASE 8 /** Label text color (light grey). */ #define WLM_GRAPH_LABEL_COLOR 0xffc4c4c4 /** Label font face. */ #define WLM_GRAPH_LABEL_FONT_FACE "Monospace" /** Single sample in the circular buffer. */ typedef struct wlm_graph_sample_t { /** Previous sample (older). Traversing via prev walks back in time. */ struct wlm_graph_sample_t *prev; /** Next sample (newer). */ struct wlm_graph_sample_t *next; /** Per-category usage values (0-255 each). */ wlm_graph_values_t values; /** Peak usage value: 0 = no activity, 255 = max activity. */ uint8_t value_peak; } wlm_graph_sample_t; /** Common graph state (managed internally by wlm_graph_app_run). */ typedef struct { // -- Sample management -- /** Allocated sample buffer (graph_size[0] entries). */ wlm_graph_sample_t *samples_alloc; /** Current sample (newest in circular buffer). */ wlm_graph_sample_t *sample_current; // -- Dimensions -- /** Current icon size [width, height] (for detecting size changes). */ uint32_t icon_size[2]; /** Graph size [width, height] in pixels (inner area). */ uint32_t graph_size[2]; /** Scaled margin for current icon dimensions. */ uint32_t margin_px; // -- Rendering -- /** Lookup table: pixel color for each value (0-255). */ uint32_t pixel_lut[256]; /** Pixel color for the line (top of usage). */ uint32_t pixel_line; /** * Scratch buffer for per-row counts during rendering. * * Each value's usage fills a vertical bar from bottom to top. row_counts[y] * accumulates how many values have bars extending to row y. This count is * then mapped to pixel intensity. */ uint32_t *row_counts; /** Pre-rendered graph buffer (graph_size[0] * graph_size[1] pixels). */ uint32_t *graph_pixels; /** Minimum y (highest peak) across rendered samples for partial scroll. */ uint32_t y_min; /** Previous y_min value (used for scroll bounds). */ uint32_t y_min_prev; /** Sample with highest peak (defines y_min). */ wlm_graph_sample_t *sample_peak; } wlm_graph_state_t; /** Minimum brightness for solid area (for WLM_GRAPH_COLOR_MODE_ALPHA). */ #define WLM_GRAPH_SOLID_BRIGHTNESS_MIN 32 /** Maximum brightness for solid area (for WLM_GRAPH_COLOR_MODE_ALPHA). */ #define WLM_GRAPH_SOLID_BRIGHTNESS_MAX 128 /** Black pixel (fully opaque). */ #define WLM_GRAPH_PIXEL_BLACK 0xff000000 /** Default line pixel color (green). */ #define WLM_GRAPH_PIXEL_LINE_DEFAULT 0xff008000 /** Constructs a grayscale ARGB pixel from brightness value. */ #define WLM_GRAPH_PIXEL_GRAY(b) (WLM_GRAPH_PIXEL_BLACK | ((uint32_t)(b) << 16) | \ ((uint32_t)(b) << 8) | (uint32_t)(b)) /** Color modes for the graph. */ typedef enum { /** Heat map from blue (cold) to red (hot). */ WLM_GRAPH_COLOR_MODE_HEAT, /** Gray-scale with alpha-like intensity. */ WLM_GRAPH_COLOR_MODE_ALPHA, } wlm_graph_color_mode_t; /** Maximum length of font face name. */ #define WLM_GRAPH_FONT_FACE_MAX 64 /** Font specification (XFT-style). */ typedef struct { /** Font face name. */ char face[WLM_GRAPH_FONT_FACE_MAX]; /** Font size (at base icon size). */ uint32_t size; /** Font weight (CAIRO_FONT_WEIGHT_NORMAL or CAIRO_FONT_WEIGHT_BOLD). */ cairo_font_weight_t weight; /** Font slant (CAIRO_FONT_SLANT_NORMAL, _ITALIC, or _OBLIQUE). */ cairo_font_slant_t slant; } wlm_graph_font_spec_t; /** Common user preferences (from command line arguments). */ typedef struct { /** Update interval in microseconds. */ uint64_t interval_usec; /** Bezel margin in logical pixels. */ uint32_t margin_logical_px; /** Color mode for the graph. */ wlm_graph_color_mode_t color_mode; /** Label font specification. */ wlm_graph_font_spec_t font; /** Whether to show label. True implies label_fn is set. */ bool show_label; } wlm_graph_prefs_t; /** * Common handle for graph app callbacks. * * Contains all data needed by the shared icon render and timer callbacks. */ typedef struct { /** Pointer to the common graph state. */ wlm_graph_state_t *graph_state; /** User preferences (immutable). */ const wlm_graph_prefs_t *prefs; /** Icon handle. */ wlclient_icon_t *icon_ptr; /** Application configuration. */ const wlm_graph_app_config_t *config; } wlm_graph_handle_t; /* == Forward declarations ================================================= */ static void _wlm_graph_sample_values_free(wlm_graph_sample_t *samples, const uint32_t count); static void _wlm_graph_samples_init(wlm_graph_sample_t *samples, const uint32_t count); static void _wlm_graph_sample_compute_peak( wlm_graph_sample_t *sample, const wlm_graph_mode_t accumulate_mode); static uint32_t _wlm_graph_usage_to_y(const uint8_t usage, const uint32_t height); static uint32_t _wlm_graph_usage_to_y_with_zero_check(const uint8_t usage, const uint32_t height); static uint32_t _wlm_graph_column_render( wlm_graph_state_t *graph_state, const wlm_graph_sample_t *sample, const uint32_t column, const wlm_graph_mode_t accumulate_mode); static void _wlm_graph_peak_connector_draw( uint32_t *graph_pixels, const uint32_t graph_width, const uint32_t pixel_line, const uint32_t x, const uint32_t y_range[2]); static void _wlm_graph_peak_connector_draw_between( uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t pixel_line, const uint8_t usage_curr, const uint8_t usage_prev, const uint32_t column_curr, const uint32_t column_prev); static void _wlm_graph_fill_columns_black( uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t column_end); static void _wlm_graph_scroll_left( uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t y_start); static void _wlm_graph_rebuild_from_samples( wlm_graph_state_t *graph_state, const wlm_graph_mode_t accumulate_mode); static void _wlm_graph_update_with_sample( wlm_graph_state_t *graph_state, wlm_graph_sample_t *new_sample, const wlm_graph_mode_t accumulate_mode); static uint64_t _wlm_graph_time_next_update(const uint64_t interval_usec); static bool _wlm_graph_icon_render_callback(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr); static void _wlm_graph_sample_update(wlm_graph_handle_t *handle); static void _wlm_graph_timer_callback(wlclient_t *client_ptr, void *ud_ptr); /* == Utility functions ==================================================== */ /* ------------------------------------------------------------------------- */ /** * Parses a string as a 32-bit integer within a specified range. * * @param opt_name Option name for error messages. * @param str String to parse. * @param val_min Minimum allowed value. * @param val_max Maximum allowed value. * @param result_ptr Output: parsed value. * * @return true on success. */ static bool _wlm_graph_arg_parse_i32( const char *opt_name, const char *str, const int32_t val_min, const int32_t val_max, int32_t *result_ptr) { char *endptr; errno = 0; const long val = strtol(str, &endptr, 10); if ('\0' != *endptr || 0 != errno || val < val_min || val > val_max) { bs_log(BS_ERROR, "Error: %s value '%s' must be %d-%d\n", opt_name, str, val_min, val_max); return false; } *result_ptr = (int32_t)val; return true; } /* ------------------------------------------------------------------------- */ /** * Parses a string as a double within a specified range. * * @param opt_name Option name for error messages. * @param str String to parse. * @param val_min Minimum allowed value. * @param val_max Maximum allowed value. * @param result_ptr Output: parsed value. * * @return true on success. */ static bool _wlm_graph_arg_parse_f64( const char *opt_name, const char *str, const double val_min, const double val_max, double *result_ptr) { char *endptr; errno = 0; const double val = strtod(str, &endptr); if ('\0' != *endptr || 0 != errno || val < val_min || val > val_max) { bs_log(BS_ERROR, "%s value '%s' must be %g-%g", opt_name, str, val_min, val_max); return false; } *result_ptr = val; return true; } /* ------------------------------------------------------------------------- */ /** Wrapper for _str_match_prefix_n using sizeof for string literal prefix. */ #define _str_match_prefix(str_ptr, len_ptr, prefix) \ _str_match_prefix_n(str_ptr, len_ptr, prefix, sizeof(prefix) - 1) /** * Checks if string starts with prefix; on match advances string and adjusts length. * * @param str_ptr Pointer to string pointer (advanced on match). * @param len_ptr Pointer to remaining length (decremented on match). * @param prefix Prefix to match. * @param prefix_len Length of prefix. * * @return true if prefix matched and consumed, false otherwise. */ static bool _str_match_prefix_n( const char **str_ptr, size_t *len_ptr, const char *prefix, const size_t prefix_len) { if (*len_ptr >= prefix_len && 0 == strncmp(*str_ptr, prefix, prefix_len)) { *str_ptr += prefix_len; *len_ptr -= prefix_len; return true; } return false; } /* ------------------------------------------------------------------------- */ /** * Parses XFT-style font specification: "Name:size=N:weight=W:slant=S". * * @param opt_name Option name for error messages (e.g., "--font"). * @param str Font specification string. * @param font Font spec struct to populate. * * @return true on success, false on error (message printed to stderr). */ static bool _wlm_graph_arg_parse_font( const char *opt_name, const char *str, wlm_graph_font_spec_t *font) { const char *colon = strchr(str, ':'); // Extract font name (everything before first colon, or entire string). const size_t name_len = (NULL != colon) ? (size_t)(colon - str) : strlen(str); if (0 == name_len || name_len >= WLM_GRAPH_FONT_FACE_MAX) { bs_log(BS_ERROR, "%s font name too long or empty", opt_name); return false; } memcpy(font->face, str, name_len); font->face[name_len] = '\0'; // Parse optional key=value pairs after font name. while (NULL != colon) { const char *pos = colon + 1; colon = strchr(pos, ':'); size_t len = (NULL != colon) ? (size_t)(colon - pos) : strlen(pos); if (_str_match_prefix(&pos, &len, "size=")) { char size_buf[16]; if (len >= sizeof(size_buf)) { bs_log(BS_ERROR, "%s size value too long", opt_name); return false; } memcpy(size_buf, pos, len); size_buf[len] = '\0'; int32_t size; if (!_wlm_graph_arg_parse_i32(opt_name, size_buf, 4, 72, &size)) { return false; } font->size = (uint32_t)size; } else if (_str_match_prefix(&pos, &len, "weight=")) { if (_str_match_prefix(&pos, &len, "normal") && 0 == len) { font->weight = CAIRO_FONT_WEIGHT_NORMAL; } else if (_str_match_prefix(&pos, &len, "bold") && 0 == len) { font->weight = CAIRO_FONT_WEIGHT_BOLD; } else { bs_log(BS_ERROR, "%s weight must be 'normal' or 'bold'", opt_name); return false; } } else if (_str_match_prefix(&pos, &len, "slant=")) { if (_str_match_prefix(&pos, &len, "normal") && 0 == len) { font->slant = CAIRO_FONT_SLANT_NORMAL; } else if (_str_match_prefix(&pos, &len, "italic") && 0 == len) { font->slant = CAIRO_FONT_SLANT_ITALIC; } else if (_str_match_prefix(&pos, &len, "oblique") && 0 == len) { font->slant = CAIRO_FONT_SLANT_OBLIQUE; } else { bs_log(BS_ERROR, "%s slant must be 'normal', 'italic', or 'oblique'", opt_name); return false; } } else if (len > 0) { bs_log(BS_ERROR, "Unknown %s option starting with '%.20s'", opt_name, pos); return false; } } return true; } /* == Initialization ======================================================= */ /* ------------------------------------------------------------------------- */ /** * Initializes the pixel lookup table for graph coloring. * * @param pixel_lut Output: 256-entry lookup table for value-to-color mapping. * @param pixel_line_ptr Output: color for the line/peak indicator. * @param color_mode Color mode (heat map or alpha/grayscale). */ static void _wlm_graph_pixel_lut_init( uint32_t pixel_lut[256], uint32_t *pixel_line_ptr, const wlm_graph_color_mode_t color_mode) { *pixel_line_ptr = WLM_GRAPH_PIXEL_LINE_DEFAULT; if (WLM_GRAPH_COLOR_MODE_ALPHA == color_mode) { // Grayscale: values 0-255 scaled from SOLID_BRIGHTNESS_MIN to MAX. const uint32_t range = WLM_GRAPH_SOLID_BRIGHTNESS_MAX - WLM_GRAPH_SOLID_BRIGHTNESS_MIN; for (uint32_t i = 0; i < 256; i++) { const uint8_t b = (uint8_t)(WLM_GRAPH_SOLID_BRIGHTNESS_MIN + ((i * range) / 255)); pixel_lut[i] = WLM_GRAPH_PIXEL_GRAY(b); } } else { // Heat map: blue (cold) -> green -> yellow -> red (hot). // Divide the 0-255 range into 3 color bands. const uint32_t band1_start = 256 / 3; // 85 const uint32_t band2_start = (2 * 256) / 3; // 170 for (uint32_t i = 0; i < 256; i++) { uint8_t r, g, b; if (i < band1_start) { // Blue to Green: B decreases, G increases. r = 0; g = (uint8_t)((i * 255) / (band1_start - 1)); b = (uint8_t)(255 - ((i * 255) / (band1_start - 1))); } else if (i < band2_start) { // Green to Yellow: R increases, G stays max. const uint32_t band_pos = i - band1_start; r = (uint8_t)((band_pos * 255) / (band2_start - band1_start - 1)); g = 255; b = 0; } else { // Yellow to Red: G decreases, R stays max. const uint32_t band_pos = i - band2_start; r = 255; g = (uint8_t)(255 - ((band_pos * 255) / (255 - band2_start))); b = 0; } pixel_lut[i] = WLM_GRAPH_PIXEL_BLACK | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; } } } /* == Buffer management ==================================================== */ /* ------------------------------------------------------------------------- */ /** * Resizes graph buffers when icon dimensions change. * * @param graph_state Graph state to resize. * @param size New icon dimensions [width, height]. * @param margin_logical_px Margin in logical pixels (at base icon size). * * @return true if buffers were resized, false if dimensions are too small. */ static bool _wlm_graph_buffers_resize( wlm_graph_state_t *graph_state, const uint32_t size[2], const uint32_t margin_logical_px) { // Calculate inner dimensions (graph area inside bezel). const uint32_t margin_px = (margin_logical_px * size[0]) / WLM_GRAPH_BASE_ICON_SIZE; const uint32_t inner_size[2] = { (size[0] > (2 * margin_px)) ? size[0] - (2 * margin_px) : 0, (size[1] > (2 * margin_px)) ? size[1] - (2 * margin_px) : 0, }; if (0 == inner_size[0] || 0 == inner_size[1]) { return false; } // Check if dimensions changed. const bool size_changed[2] = { inner_size[0] != graph_state->graph_size[0], inner_size[1] != graph_state->graph_size[1], }; // Reallocate samples if width changed. if (size_changed[0]) { _wlm_graph_sample_values_free(graph_state->samples_alloc, graph_state->graph_size[0]); free(graph_state->samples_alloc); graph_state->samples_alloc = NULL; graph_state->sample_current = NULL; // Allocate sample buffer (values allocated lazily during capture). graph_state->samples_alloc = calloc(inner_size[0], sizeof(wlm_graph_sample_t)); if (NULL == graph_state->samples_alloc) { bs_log(BS_FATAL | BS_ERRNO, "Failed to allocate sample buffer"); } // Initialize circular doubly-linked list. _wlm_graph_samples_init(graph_state->samples_alloc, inner_size[0]); graph_state->sample_current = &graph_state->samples_alloc[0]; } // Reallocate graph buffers if dimensions changed. if (size_changed[0] || size_changed[1]) { free(graph_state->graph_pixels); free(graph_state->row_counts); graph_state->graph_pixels = NULL; graph_state->row_counts = NULL; // Allocate graph data buffer. graph_state->graph_pixels = malloc(sizeof(uint32_t) * inner_size[0] * inner_size[1]); if (NULL == graph_state->graph_pixels) { bs_log(BS_FATAL | BS_ERRNO, "Failed to allocate graph data buffer"); } // Allocate row counts scratch buffer. graph_state->row_counts = malloc(sizeof(uint32_t) * inner_size[1]); if (NULL == graph_state->row_counts) { bs_log(BS_FATAL | BS_ERRNO, "Failed to allocate row counts buffer"); } graph_state->graph_size[0] = inner_size[0]; graph_state->graph_size[1] = inner_size[1]; } graph_state->icon_size[0] = size[0]; graph_state->icon_size[1] = size[1]; graph_state->margin_px = margin_px; return true; } /* == Rendering ============================================================ */ /* ------------------------------------------------------------------------- */ /** * Finds sample with highest peak, sets y_min and sample_peak in graph_state. * * Iterates newest-to-oldest and uses strict `<` so the newest sample wins ties. * This keeps sample_peak in the buffer longer, reducing rescan frequency. */ static void _wlm_graph_y_min_from_samples( wlm_graph_state_t *graph_state, wlm_graph_sample_t *current_sample) { const uint32_t height = graph_state->graph_size[1]; uint32_t y_min = height; wlm_graph_sample_t *sample_peak = NULL; wlm_graph_sample_t *sample = current_sample; do { if (0 < sample->value_peak) { const uint32_t y = _wlm_graph_usage_to_y(sample->value_peak, height); // Strict `<` so first (newest) sample at this y wins. if (y < y_min) { y_min = y; sample_peak = sample; } } sample = sample->prev; } while (sample != current_sample); graph_state->y_min = y_min; graph_state->sample_peak = sample_peak; } /* ------------------------------------------------------------------------- */ /** * Rebuilds the entire graph from stored samples. * * Used after resize to re-render all columns from sample history. * * @param graph_state Graph state containing samples and pixel buffer. * @param accumulate_mode Sample accumulation mode. */ static void _wlm_graph_rebuild_from_samples( wlm_graph_state_t *graph_state, const wlm_graph_mode_t accumulate_mode) { // All callers verify graph_size is non-zero. BS_ASSERT(0 < graph_state->graph_size[0]); const uint32_t *graph_size = graph_state->graph_size; if (NULL == graph_state->sample_current || 0 == graph_state->sample_current->values.num) { // No samples captured yet, fill with black. graph_state->y_min = graph_size[1]; graph_state->y_min_prev = graph_size[1]; graph_state->sample_peak = NULL; for (uint32_t i = 0; i < graph_size[0] * graph_size[1]; i++) { graph_state->graph_pixels[i] = WLM_GRAPH_PIXEL_BLACK; } return; } // Walk backward from sample_current to render samples right-to-left. // sample_current is the newest sample, goes in rightmost column. // Walk until we wrap around to sample_current. wlm_graph_sample_t *sample = graph_state->sample_current; uint32_t column = graph_size[0]; do { column--; _wlm_graph_column_render(graph_state, sample, column, accumulate_mode); // Draw vertical line to previous sample if needed. const uint8_t usage_curr = sample->value_peak; // Advance sample. sample = sample->prev; if (sample != graph_state->sample_current && 0 < column) { _wlm_graph_peak_connector_draw_between( graph_state->graph_pixels, graph_state->graph_size, graph_state->pixel_line, usage_curr, sample->value_peak, column, column - 1); } } while (0 < column && sample != graph_state->sample_current); // Fill remaining columns with black. if (0 < column) { _wlm_graph_fill_columns_black(graph_state->graph_pixels, graph_size, column); } // Compute y_min/sample_peak via rescan rather than tracking inline above. // This keeps peak tracking logic in one place (_wlm_graph_y_min_from_samples). _wlm_graph_y_min_from_samples(graph_state, graph_state->sample_current); graph_state->y_min_prev = graph_state->y_min; } /* ------------------------------------------------------------------------- */ /** * Renders a single column of the graph from sample data. * * @param graph_state Graph state containing pixel buffer and LUT. * @param sample Sample data for this column. * @param column Column index to render. * @param accumulate_mode Sample accumulation mode. * * @return Y coordinate of the peak line for this column. */ static uint32_t _wlm_graph_column_render( wlm_graph_state_t *graph_state, const wlm_graph_sample_t *sample, const uint32_t column, const wlm_graph_mode_t accumulate_mode) { // All callers verify graph_size is non-zero. BS_ASSERT(0 < graph_state->graph_size[1]); const uint32_t graph_size[2] = {graph_state->graph_size[0], graph_state->graph_size[1]}; uint32_t * const pixels = graph_state->graph_pixels; uint32_t * const row_counts = graph_state->row_counts; // Clear row counts scratch buffer. memset(row_counts, 0, sizeof(*row_counts) * graph_size[1]); const uint32_t values_num = sample->values.num; const uint8_t * const values = sample->values.data; // Accumulate row counts based on mode. if (WLM_GRAPH_ACCUMULATE_MODE_STACKED == accumulate_mode) { // Stacked: values stack on top of each other cumulatively. uint32_t cumulative = 0; for (uint32_t i = 0; i < values_num; i++) { const uint8_t usage = values[i]; if (0 == usage) continue; cumulative += usage; const uint8_t level = (uint8_t)BS_MIN(cumulative, 255U); // Calculate topmost line from cumulative usage. const uint32_t y_top = _wlm_graph_usage_to_y(level, graph_size[1]); // Skip if usage maps outside visible area. if (y_top >= graph_size[1]) continue; // Mark all lines from top to bottom as solid coverage. uint32_t *rc = &row_counts[y_top]; for (uint32_t y = y_top; y < graph_size[1]; y++) { (*rc++)++; } } } else { // Independent: each value fills from its level to bottom independently. for (uint32_t i = 0; i < values_num; i++) { const uint8_t usage = values[i]; if (0 == usage) continue; // Calculate topmost line from usage: higher usage = lower y. const uint32_t y_top = _wlm_graph_usage_to_y(usage, graph_size[1]); // Skip if usage maps outside visible area. if (y_top >= graph_size[1]) continue; // Mark all lines from top to bottom as solid coverage. uint32_t *rc = &row_counts[y_top]; for (uint32_t y = y_top; y < graph_size[1]; y++) { (*rc++)++; } } } // Convert peak usage to y coordinate for drawing. const uint32_t y_line = _wlm_graph_usage_to_y_with_zero_check(sample->value_peak, graph_size[1]); // Cache LUT pointer for inner loop. const uint32_t * const pixel_lut = graph_state->pixel_lut; // Clear stale pixels above peak, scanning up until we hit black. // Works because non-black pixels are contiguous from y_min downward. uint32_t *pixel = &pixels[(y_line * graph_size[0]) + column]; for (uint32_t y = y_line; y > 0; ) { y--; pixel -= graph_size[0]; if (WLM_GRAPH_PIXEL_BLACK == *pixel) break; *pixel = WLM_GRAPH_PIXEL_BLACK; } // Rows from peak onwards: render intensity based on count at each row. pixel = &pixels[(y_line * graph_size[0]) + column]; const uint32_t *row_count = &row_counts[y_line]; if (1 >= values_num) { // Single value: all non-zero counts use max LUT index. const uint32_t pixel_max = pixel_lut[255]; for (uint32_t y = y_line; y < graph_size[1]; y++) { *pixel = (0 < *row_count++) ? pixel_max : WLM_GRAPH_PIXEL_BLACK; pixel += graph_size[0]; } } else { // Multiple values: map count (1..values_num) to LUT index (0..255). const uint32_t divisor = values_num - 1; for (uint32_t y = y_line; y < graph_size[1]; y++) { const uint32_t count = *row_count++; *pixel = (0 == count) ? WLM_GRAPH_PIXEL_BLACK : pixel_lut[((count - 1) * 255) / divisor]; pixel += graph_size[0]; } } // Draw line pixel at peak position. if (y_line < graph_size[1]) { pixels[(y_line * graph_size[0]) + column] = graph_state->pixel_line; } return y_line; } /* ------------------------------------------------------------------------- */ /** Maps usage (0-255) to y coordinate: y=0 is top, higher usage = higher on screen. */ static uint32_t _wlm_graph_usage_to_y(const uint8_t usage, const uint32_t height) { return (height - 1) - ((usage * (height - 1)) / 255); } /* ------------------------------------------------------------------------- */ /** Maps usage (0-255) to y coordinate. Returns height for usage=0 (no bar). */ static uint32_t _wlm_graph_usage_to_y_with_zero_check(const uint8_t usage, const uint32_t height) { return (0 == usage) ? height : _wlm_graph_usage_to_y(usage, height); } /* ------------------------------------------------------------------------- */ /** * Draws a vertical connector line between y coordinates. * * @param graph_pixels Pixel buffer to draw into. * @param graph_width Width of the graph in pixels. * @param pixel_line Color for the connector line. * @param x X coordinate (column) to draw at. * @param y_range Y range [start, end) to draw. */ static void _wlm_graph_peak_connector_draw( uint32_t *graph_pixels, const uint32_t graph_width, const uint32_t pixel_line, const uint32_t x, const uint32_t y_range[2]) { uint32_t *pixel = &graph_pixels[(y_range[0] * graph_width) + x]; for (uint32_t y = y_range[0]; y < y_range[1]; y++) { *pixel = pixel_line; pixel += graph_width; } } /* ------------------------------------------------------------------------- */ /** * Draws a vertical connector between adjacent columns when peaks differ. * * @param graph_pixels Pixel buffer to draw into. * @param graph_size Graph dimensions [width, height]. * @param pixel_line Color for the connector line. * @param usage_curr Usage value for current column. * @param usage_prev Usage value for previous column. * @param column_curr Current column index. * @param column_prev Previous column index. */ static void _wlm_graph_peak_connector_draw_between( uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t pixel_line, const uint8_t usage_curr, const uint8_t usage_prev, const uint32_t column_curr, const uint32_t column_prev) { if (usage_curr == usage_prev) { return; } const uint32_t height = graph_size[1]; const uint32_t y_curr = _wlm_graph_usage_to_y_with_zero_check(usage_curr, height); const uint32_t y_prev = _wlm_graph_usage_to_y_with_zero_check(usage_prev, height); uint32_t x; uint32_t y_range[2]; if (usage_curr < usage_prev) { // Current usage is lower (higher y), draw on current column. // Start below line pixel (+1) to avoid overwriting it, unless zero (no line). x = column_curr; y_range[0] = (0 < usage_prev) ? y_prev + 1 : y_prev; y_range[1] = y_curr; } else { // Previous usage is lower (higher y), draw on previous column. // Start below line pixel (+1) to avoid overwriting it, unless zero (no line). x = column_prev; y_range[0] = (0 < usage_curr) ? y_curr + 1 : y_curr; y_range[1] = y_prev; } _wlm_graph_peak_connector_draw(graph_pixels, graph_size[0], pixel_line, x, y_range); } /* ------------------------------------------------------------------------- */ /** * Copies graph pixels to the destination graphics buffer. * * @param gfxbuf_ptr Destination graphics buffer. * @param graph_pixels Source pixel data. * @param graph_size Graph dimensions [width, height]. * @param offset Offset in destination buffer [x, y]. */ static void _wlm_graph_blit_to_buffer( bs_gfxbuf_t *gfxbuf_ptr, const uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t offset[2]) { const uint32_t stride_dst = gfxbuf_ptr->pixels_per_line; const uint32_t width = graph_size[0]; const size_t copy_bytes = sizeof(*graph_pixels) * width; uint32_t *row_dst = &gfxbuf_ptr->data_ptr[(offset[1] * stride_dst) + offset[0]]; const uint32_t *row_src = graph_pixels; // Copy each row from graph_pixels to the destination buffer. for (uint32_t y = 0; y < graph_size[1]; y++) { memcpy(row_dst, row_src, copy_bytes); row_dst += stride_dst; row_src += width; } } /* ------------------------------------------------------------------------- */ /** * Draws the bezel frame around the graph area. * * @param gfxbuf_ptr Graphics buffer to draw into. * @param margin_logical_px Margin in logical pixels (at base icon size). * * @return true on success. */ static bool _wlm_graph_bezel_draw(bs_gfxbuf_t *gfxbuf_ptr, const uint32_t margin_logical_px) { BS_ASSERT(0 < margin_logical_px); cairo_t * const cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { bs_log(BS_ERROR, "Failed cairo_create_from_bs_gfxbuf(%p)", gfxbuf_ptr); return false; } // Scale bezel relative to icon size, like wlmclock. const uint32_t size[2] = {gfxbuf_ptr->width, gfxbuf_ptr->height}; // Offset from edge to bezel position (scales with icon size). const uint32_t bezel_offset = ((margin_logical_px - 1) * size[0]) / WLM_GRAPH_BASE_ICON_SIZE; const uint32_t bezel_line_width = BS_MAX(size[0] / WLM_GRAPH_BASE_ICON_SIZE, 1U); wlm_primitives_draw_bezel_at( cairo_ptr, bezel_offset, bezel_offset, size[0] - (2 * bezel_offset), size[1] - (2 * bezel_offset), bezel_line_width, false); cairo_destroy(cairo_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Draws the label text in the top-left corner of the graph. * * @param gfxbuf_ptr Graphics buffer to draw into. * @param margin_px Margin in pixels (scaled to current icon size). * @param label Label text to draw. * @param prefs Preferences containing font settings. */ static void _wlm_graph_label_draw( bs_gfxbuf_t *gfxbuf_ptr, const uint32_t margin_px, const char *label, const wlm_graph_prefs_t *prefs) { if ('\0' == *label) { return; } cairo_t * const cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { return; } const uint32_t size[2] = {gfxbuf_ptr->width, gfxbuf_ptr->height}; // Compute scale factor relative to base icon size. const uint32_t scale = size[0]; const uint32_t scale_base = WLM_GRAPH_BASE_ICON_SIZE; const uint32_t font_size = (prefs->font.size * scale) / scale_base; if (0 == font_size) { cairo_destroy(cairo_ptr); return; } const uint32_t padding = (2 * scale) / scale_base; cairo_select_font_face( cairo_ptr, prefs->font.face, prefs->font.slant, prefs->font.weight); cairo_set_font_size(cairo_ptr, font_size); // Position: top-left corner, inside margin. const double x = margin_px + padding; const double y = margin_px + padding + font_size; // Draw text with black outline for readability. cairo_move_to(cairo_ptr, x, y); cairo_text_path(cairo_ptr, label); // Stroke outline (black). cairo_set_source_argb8888(cairo_ptr, WLM_GRAPH_PIXEL_BLACK); cairo_set_line_width(cairo_ptr, (2.0 * scale) / scale_base); cairo_stroke_preserve(cairo_ptr); // Fill text (white). cairo_set_source_argb8888(cairo_ptr, WLM_GRAPH_LABEL_COLOR); cairo_fill(cairo_ptr); cairo_destroy(cairo_ptr); } /* ------------------------------------------------------------------------- */ /** * Scrolls graph pixels left by one column. * * @param graph_pixels Pixel buffer to scroll. * @param graph_size Graph dimensions [width, height]. * @param y_start Starting Y coordinate (skip rows above this). */ static void _wlm_graph_scroll_left( uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t y_start) { const uint32_t width = graph_size[0]; const size_t move_bytes = sizeof(*graph_pixels) * (width - 1); uint32_t *row = &graph_pixels[y_start * width]; for (uint32_t y = y_start; y < graph_size[1]; y++) { memmove(row, row + 1, move_bytes); row += width; } } /* ------------------------------------------------------------------------- */ /** * Fills columns with black pixels. * * @param graph_pixels Pixel buffer to fill. * @param graph_size Graph dimensions [width, height]. * @param column_end Number of columns to fill (0 to column_end-1). */ static void _wlm_graph_fill_columns_black( uint32_t *graph_pixels, const uint32_t graph_size[2], const uint32_t column_end) { const uint32_t width = graph_size[0]; uint32_t *row = graph_pixels; for (uint32_t y = 0; y < graph_size[1]; y++) { for (uint32_t x = 0; x < column_end; x++) { row[x] = WLM_GRAPH_PIXEL_BLACK; } row += width; } } /* ------------------------------------------------------------------------- */ /** * Frees the values arrays within each sample. * * @param samples Array of samples to free values from. * @param count Number of samples in the array. */ static void _wlm_graph_sample_values_free(wlm_graph_sample_t *samples, const uint32_t count) { if (NULL == samples) return; for (uint32_t i = 0; i < count; i++) { free(samples[i].values.data); samples[i].values.data = NULL; samples[i].values.num = 0; } } /* ------------------------------------------------------------------------- */ /** * Computes the peak value for a sample from its values array. * * @param sample Sample with values already populated. * @param accumulate_mode Sample accumulation mode. */ static void _wlm_graph_sample_compute_peak( wlm_graph_sample_t *sample, const wlm_graph_mode_t accumulate_mode) { const uint32_t values_num = sample->values.num; const uint8_t *values = sample->values.data; if (WLM_GRAPH_ACCUMULATE_MODE_STACKED == accumulate_mode) { // Stacked: peak is sum of all values, clamped to 255. uint32_t total = 0; for (uint32_t i = 0; i < values_num; i++) { total += values[i]; } sample->value_peak = (uint8_t)BS_MIN(total, 255U); } else { // Independent: peak is maximum of all values. uint8_t max_val = 0; for (uint32_t i = 0; i < values_num; i++) { max_val = BS_MAX(max_val, values[i]); } sample->value_peak = max_val; } } /* ------------------------------------------------------------------------- */ /** * Initializes sample array as a circular doubly-linked list. * * @param samples Array of samples to initialize. * @param count Number of samples in the array. */ static void _wlm_graph_samples_init(wlm_graph_sample_t *samples, const uint32_t count) { for (uint32_t i = 0; i < count; i++) { const uint32_t i_prev = (i + count - 1) % count; const uint32_t i_next = (i + 1) % count; samples[i].prev = &samples[i_prev]; samples[i].next = &samples[i_next]; samples[i].values.data = NULL; samples[i].values.num = 0; samples[i].value_peak = 0; } } /* ------------------------------------------------------------------------- */ /** * Calculates the timestamp for the next update. * * @param interval_usec Update interval in microseconds. * * @return Timestamp in microseconds for the next update. */ static uint64_t _wlm_graph_time_next_update(const uint64_t interval_usec) { return bs_usec() + interval_usec; } /* ------------------------------------------------------------------------- */ /** * Updates the graph with a new sample, scrolling and rendering. * * @param graph_state Graph state to update. * @param new_sample Sample containing the new data. * @param accumulate_mode Sample accumulation mode. */ static void _wlm_graph_update_with_sample( wlm_graph_state_t *graph_state, wlm_graph_sample_t *new_sample, const wlm_graph_mode_t accumulate_mode) { const uint32_t *graph_size = graph_state->graph_size; // Check if sample being overwritten is the peak sample (before overwriting). const bool need_rescan = (new_sample == graph_state->sample_peak); // Scroll from previous y_min. _wlm_graph_scroll_left(graph_state->graph_pixels, graph_size, graph_state->y_min_prev); const uint32_t y_line = _wlm_graph_column_render(graph_state, new_sample, graph_size[0] - 1, accumulate_mode); // Update y_min: if peak sample scrolled out, rescan; else check new sample. if (need_rescan) { _wlm_graph_y_min_from_samples(graph_state, new_sample); } else if (y_line <= graph_state->y_min) { // Use `<=` so newest sample wins ties, reducing future rescans. graph_state->y_min = y_line; graph_state->sample_peak = new_sample; } graph_state->y_min_prev = graph_state->y_min; // Advance sample_current to the sample we just wrote. graph_state->sample_current = new_sample; // Draw vertical line connecting consecutive peaks. _wlm_graph_peak_connector_draw_between( graph_state->graph_pixels, graph_state->graph_size, graph_state->pixel_line, new_sample->value_peak, new_sample->prev->value_peak, graph_size[0] - 1, graph_size[0] - 2); } /* ------------------------------------------------------------------------- */ /** * Parses command-line arguments into preferences. * * @param argc Argument count. * @param argv Argument vector. * @param app_name Application name for error messages. * @param app_help Application help string for --help output. * @param has_custom_lut Whether app supports custom color lookup table. * @param has_label Whether app supports label display. * @param prefs Output: parsed preferences. * * @return 0 to continue, 1 if --help was shown, -1 on error. */ static int _wlm_graph_args_parse( const int argc, const char **argv, const char *app_name, const char *app_help, const bool has_custom_lut, const bool has_label, wlm_graph_prefs_t *prefs) { // Set defaults. prefs->interval_usec = 1000000; // 1 second. prefs->margin_logical_px = 2; prefs->color_mode = WLM_GRAPH_COLOR_MODE_HEAT; snprintf(prefs->font.face, sizeof(prefs->font.face), "%s", WLM_GRAPH_LABEL_FONT_FACE); prefs->font.size = WLM_GRAPH_LABEL_FONT_SIZE_BASE; prefs->font.weight = CAIRO_FONT_WEIGHT_NORMAL; prefs->font.slant = CAIRO_FONT_SLANT_NORMAL; prefs->show_label = has_label; for (int i = 1; i < argc; i++) { if (0 == strcmp(argv[i], "--interval") && i + 1 < argc) { double secs; if (!_wlm_graph_arg_parse_f64(argv[i], argv[i + 1], 0.01, 3600.0, &secs)) { return -1; } prefs->interval_usec = (uint64_t)(secs * 1000000.0); i++; } else if (0 == strcmp(argv[i], "--bezel-margin") && i + 1 < argc) { int32_t val; if (!_wlm_graph_arg_parse_i32(argv[i], argv[i + 1], 0, WLM_GRAPH_BASE_ICON_SIZE / 2, &val)) { return -1; } prefs->margin_logical_px = (uint32_t)val; i++; } else if (!has_custom_lut && 0 == strcmp(argv[i], "--color-mode") && i + 1 < argc) { if (0 == strcmp(argv[i + 1], "alpha")) { prefs->color_mode = WLM_GRAPH_COLOR_MODE_ALPHA; } else if (0 == strcmp(argv[i + 1], "heat")) { prefs->color_mode = WLM_GRAPH_COLOR_MODE_HEAT; } else { bs_log(BS_ERROR, "%s value '%s' must be 'alpha' or 'heat'", argv[i], argv[i + 1]); return -1; } i++; } else if (has_label && 0 == strcmp(argv[i], "--font") && i + 1 < argc) { if (!_wlm_graph_arg_parse_font(argv[i], argv[i + 1], &prefs->font)) { return -1; } i++; } else if (has_label && 0 == strcmp(argv[i], "--no-label")) { prefs->show_label = false; } else if (0 == strcmp(argv[i], "--help") || 0 == strcmp(argv[i], "-h")) { printf("%s\n\n", app_help); printf("Usage: %s [OPTIONS]\n", app_name); printf(" --interval SECS Update interval 0.01-3600 seconds (default: 1.0)\n"); printf(" --bezel-margin N Bezel margin in logical pixels (default: 5)\n"); if (!has_custom_lut) { printf(" --color-mode MODE Color mode: heat, alpha (default: heat)\n"); } if (has_label) { printf(" --no-label Disable label display\n"); printf(" --font SPEC XFT-style font (default: Monospace:size=8)\n"); printf(" (weight: normal|bold, slant: normal|italic|oblique)\n"); } printf(" --help, -h Show this help\n"); return 1; } else { bs_log(BS_ERROR, "Unknown argument '%s'", argv[i]); bs_log(BS_ERROR, "Try '%s --help' for usage.", app_name); return -1; } } return 0; } /* == Callbacks ============================================================ */ /* ------------------------------------------------------------------------- */ /** * Callback invoked when the icon needs to be rendered. * * @param gfxbuf_ptr Graphics buffer to render into. * @param ud_ptr User data pointer (wlm_graph_handle_t). * * @return true on success. */ static bool _wlm_graph_icon_render_callback(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr) { wlm_graph_handle_t * const handle = ud_ptr; wlm_graph_state_t * const gs = handle->graph_state; const uint32_t margin_logical_px = handle->prefs->margin_logical_px; const uint32_t size[2] = {gfxbuf_ptr->width, gfxbuf_ptr->height}; // Check if dimensions changed. const bool size_changed = (size[0] != gs->icon_size[0] || size[1] != gs->icon_size[1]); // Reset graph buffers if icon size changed. if (size_changed) { if (!_wlm_graph_buffers_resize(gs, size, margin_logical_px)) { bs_log(BS_ERROR, "Failed to reset graph buffers for %ux%u icon", size[0], size[1]); return false; } // Re-render graph from samples at new resolution. _wlm_graph_rebuild_from_samples(gs, handle->config->accumulate_mode); } // Clear to transparent (so bezel margin shows dock background). bs_gfxbuf_clear(gfxbuf_ptr, 0); // Draw beveled bezel. if (0 < margin_logical_px) { _wlm_graph_bezel_draw(gfxbuf_ptr, margin_logical_px); } if (0 == gs->graph_size[0] || 0 == gs->graph_size[1]) { return true; // No room for graph. } // Use pre-calculated margin offset from _wlm_graph_buffers_resize. const uint32_t offset[2] = {gs->margin_px, gs->margin_px}; _wlm_graph_blit_to_buffer(gfxbuf_ptr, gs->graph_pixels, gs->graph_size, offset); // Draw label if callback provided. if (handle->prefs->show_label) { const char *label = handle->config->label_fn(handle->config->app_state); if (NULL != label) { _wlm_graph_label_draw(gfxbuf_ptr, gs->margin_px, label, handle->prefs); } } return true; } /* ------------------------------------------------------------------------- */ /** * Reads stats and updates the graph with a new sample. * * @param handle Graph handle containing state and config. */ static void _wlm_graph_sample_update(wlm_graph_handle_t *handle) { wlm_graph_state_t * const gs = handle->graph_state; const wlm_graph_app_config_t * const config = handle->config; // Need at least 2 columns for meaningful graph updates. if (NULL == gs->sample_current || gs->graph_size[0] < 2) { return; } // Reuse oldest sample for new data. wlm_graph_sample_t * const sample = gs->sample_current->next; // Read stats into sample's values buffer (callback handles reallocation). const wlm_graph_read_result_t read_result = config->stats_read_fn(config->app_state, &sample->values); if (WLM_GRAPH_READ_ERROR == read_result) { return; } // Handle regeneration request (scale changed). if (WLM_GRAPH_READ_OK_AND_REGENERATE == read_result) { BS_ASSERT(NULL != config->regenerate_fn); // Count historical samples (excluding the current one we just filled). uint32_t sample_count = 0; for (wlm_graph_sample_t *s = sample->prev; s != sample && sample_count < gs->graph_size[0]; s = s->prev) { sample_count++; } if (sample_count > 0) { // Build temporary array of value buffers for regeneration. wlm_graph_values_t *samples = calloc(sample_count, sizeof(wlm_graph_values_t)); if (NULL != samples) { wlm_graph_sample_t *s = sample->prev; for (uint32_t i = 0; i < sample_count; i++) { samples[i] = s->values; s = s->prev; } // Regenerate historical samples at new scale. config->regenerate_fn(config->app_state, samples, sample_count); // Copy regenerated values back and recompute peaks. s = sample->prev; for (uint32_t i = 0; i < sample_count; i++) { s->values = samples[i]; _wlm_graph_sample_compute_peak(s, config->accumulate_mode); s = s->prev; } free(samples); } } // Rebuild entire graph with regenerated samples. _wlm_graph_sample_compute_peak(sample, config->accumulate_mode); gs->sample_current = sample; _wlm_graph_rebuild_from_samples(gs, config->accumulate_mode); return; } // Compute peak value from the sample. _wlm_graph_sample_compute_peak(sample, config->accumulate_mode); // Scroll, render, and advance. _wlm_graph_update_with_sample(gs, sample, config->accumulate_mode); } /* ------------------------------------------------------------------------- */ /** * Timer callback for periodic graph updates. * * @param client_ptr Wayland client instance. * @param ud_ptr User data pointer (wlm_graph_handle_t). */ static void _wlm_graph_timer_callback(wlclient_t *client_ptr, void *ud_ptr) { wlm_graph_handle_t * const handle = ud_ptr; _wlm_graph_sample_update(handle); wlclient_icon_register_ready_callback( handle->icon_ptr, _wlm_graph_icon_render_callback, handle); wlclient_register_timer( client_ptr, _wlm_graph_time_next_update(handle->prefs->interval_usec), _wlm_graph_timer_callback, handle); } /* == Internal state management ============================================ */ /* ------------------------------------------------------------------------- */ /** * Frees all resources associated with graph state. * * @param graph_state Graph state to free, or NULL. */ static void _wlm_graph_state_free(wlm_graph_state_t *graph_state) { if (NULL == graph_state) return; _wlm_graph_sample_values_free(graph_state->samples_alloc, graph_state->graph_size[0]); free(graph_state->samples_alloc); free(graph_state->row_counts); free(graph_state->graph_pixels); free(graph_state); } /* == Public API =========================================================== */ /* ------------------------------------------------------------------------- */ int wlm_graph_app_run( int argc, const char **argv, const wlm_graph_app_config_t *config) { bs_log_severity = BS_INFO; // Parse command line arguments and initialize preferences. wlm_graph_prefs_t prefs; const bool has_custom_lut = (NULL != config->pixel_lut); const bool has_label = (NULL != config->label_fn); const int parse_result = _wlm_graph_args_parse( argc, argv, config->app_name, config->app_help, has_custom_lut, has_label, &prefs); if (0 != parse_result) { return (1 == parse_result) ? EXIT_SUCCESS : EXIT_FAILURE; } // Allocate graph state. wlm_graph_state_t *graph_state = calloc(1, sizeof(wlm_graph_state_t)); if (NULL == graph_state) { bs_log(BS_ERROR, "%s: Failed to allocate graph state", config->app_name); return EXIT_FAILURE; } // Initialize pixel lookup table. if (NULL != config->pixel_lut) { memcpy(graph_state->pixel_lut, config->pixel_lut, sizeof(graph_state->pixel_lut)); graph_state->pixel_line = WLM_GRAPH_PIXEL_LINE_DEFAULT; } else { _wlm_graph_pixel_lut_init(graph_state->pixel_lut, &graph_state->pixel_line, prefs.color_mode); } // Initialize graph buffers with default icon size. const uint32_t size_default[2] = {WLM_GRAPH_BASE_ICON_SIZE, WLM_GRAPH_BASE_ICON_SIZE}; if (!_wlm_graph_buffers_resize(graph_state, size_default, prefs.margin_logical_px)) { bs_log(BS_ERROR, "Icon dimensions too small for graph"); _wlm_graph_state_free(graph_state); return EXIT_FAILURE; } // Initialize graph pixels (malloc leaves garbage, need opaque black). _wlm_graph_rebuild_from_samples(graph_state, config->accumulate_mode); // Build wlclient app ID: "wlmaker.". char wlclient_app_id[64]; snprintf(wlclient_app_id, sizeof(wlclient_app_id), "wlmaker.%s", config->app_name); wlclient_t * const wlclient_ptr = wlclient_create(wlclient_app_id); if (NULL == wlclient_ptr) { _wlm_graph_state_free(graph_state); config->state_free_fn(config->app_state); return EXIT_FAILURE; } if (!wlclient_icon_supported(wlclient_ptr)) { bs_log(BS_ERROR, "Icon protocol is not supported."); _wlm_graph_state_free(graph_state); config->state_free_fn(config->app_state); wlclient_destroy(wlclient_ptr); return EXIT_FAILURE; } wlclient_icon_t * const icon_ptr = wlclient_icon_create(wlclient_ptr); if (NULL == icon_ptr) { bs_log(BS_ERROR, "Failed to create icon."); _wlm_graph_state_free(graph_state); config->state_free_fn(config->app_state); wlclient_destroy(wlclient_ptr); return EXIT_FAILURE; } // Create handle for callbacks. wlm_graph_handle_t handle = { .graph_state = graph_state, .prefs = &prefs, .icon_ptr = icon_ptr, .config = config, }; wlclient_icon_register_ready_callback( icon_ptr, _wlm_graph_icon_render_callback, &handle); wlclient_register_timer( wlclient_ptr, _wlm_graph_time_next_update(prefs.interval_usec), _wlm_graph_timer_callback, &handle); wlclient_run(wlclient_ptr); wlclient_icon_destroy(icon_ptr); _wlm_graph_state_free(graph_state); config->state_free_fn(config->app_state); wlclient_destroy(wlclient_ptr); return EXIT_SUCCESS; } /* == End of wlm_graph_shared.c ============================================ */ wlmaker-0.8/apps/wlm_graph_shared.h0000644000175100017510000001433615203543557017073 0ustar runnerrunner/* ========================================================================= */ /** * @file wlm_graph_shared.h * * Shared graph rendering utilities for wlmaker dock-apps. * * @copyright * 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. */ #ifndef WLM_GRAPH_SHARED_H #define WLM_GRAPH_SHARED_H #include /* == Definitions ========================================================== */ /** * Recommended history size for apps implementing regenerate_fn. * Apps storing raw values for regeneration can use this as their buffer size. * 512 supports up to 4x HiDPI with no bezel (64 suffices for 1x). */ #define WLM_GRAPH_REGENERATE_HISTORY_MAX 512 /** * Sample accumulation mode: method of accumulating samples for display. */ typedef enum { /** * Each value fills from bottom independently, overlapping to create * heat-map colors where multiple values coincide. The peak line shows * the maximum value across all categories. */ WLM_GRAPH_ACCUMULATE_MODE_INDEPENDENT, /** * Values stack cumulatively on top of each other, with each category * rendered as a distinct layer. The peak line shows the sum of all * values (clamped to 255). */ WLM_GRAPH_ACCUMULATE_MODE_STACKED, } wlm_graph_mode_t; /** * Buffer for graph sample values. * * Used by stats_read_fn to store per-category usage values (0-255 each). * The callback may reallocate the buffer if the size doesn't match. */ typedef struct { /** Pointer to values buffer (may be NULL initially). */ uint8_t *data; /** Number of elements in the buffer. */ uint32_t num; } wlm_graph_values_t; /** * Return value for stats_read_fn callback. */ typedef enum { /** Error reading stats; sample will be skipped. */ WLM_GRAPH_READ_ERROR = -1, /** Success; sample filled normally. */ WLM_GRAPH_READ_OK = 0, /** Success; also regenerate historical samples (scale changed). */ WLM_GRAPH_READ_OK_AND_REGENERATE = 1, } wlm_graph_read_result_t; /** * Configuration for a graph application. * * Pass to wlm_graph_app_run() to configure the graph behavior. */ typedef struct { /** Application name (e.g., "wlmcpugraph"). Used for error messages. */ const char *app_name; /** Application help string. Shown in --help output. */ const char *app_help; /** Sample accumulation mode. */ wlm_graph_mode_t accumulate_mode; /** * Callback to read stats from the system. * * Called periodically to fill the values buffer. The callback owns buffer * reallocation: if values->num doesn't match the required count, * reallocate values->data and update values->num before filling. * * @param app_state The app_state pointer from this config. * @param values Buffer to fill (may reallocate data/num). * @return WLM_GRAPH_READ_OK on success, WLM_GRAPH_READ_ERROR to skip, * or WLM_GRAPH_READ_OK_AND_REGENERATE to regenerate historical samples. */ wlm_graph_read_result_t (*stats_read_fn)(void *app_state, wlm_graph_values_t *values); /** * Optional callback to regenerate historical samples after scale change. * * Called when stats_read_fn returns WLM_GRAPH_READ_OK_AND_REGENERATE. * The callback should fill the samples array with regenerated values at the * new scale. The current sample (that requested regeneration) is NOT included; * it was already filled at the new scale by stats_read_fn. Samples are ordered * newest to oldest (index 0 is the sample just before the current one). * Samples without available history must have their data cleared. * * @param app_state The app_state pointer from this config. * @param samples Array of sample buffers to regenerate (excludes current). * @param sample_count Number of samples in the array. */ void (*regenerate_fn)(void *app_state, wlm_graph_values_t *samples, uint32_t sample_count); /** App-specific state pointer, passed to callbacks. */ void *app_state; /** * Callback to free app-specific state. * * Called during cleanup to free resources in app_state. * * @param app_state The app_state pointer from this config. */ void (*state_free_fn)(void *app_state); /** * Optional custom pixel lookup table (256 entries, ARGB32 format). * * If non-NULL, overrides the default heat-map LUT. Index 0 maps to lowest * intensity (single value active), index 255 to highest (all values active). * Each entry should be fully opaque (0xff000000 | color). */ const uint32_t *pixel_lut; /** * Optional callback to get a label string for display. * * Called during rendering. The returned string is displayed in the * bottom-left corner of the graph. Return NULL to display no label. * * @param app_state The app_state pointer from this config. * @return Label string (caller retains ownership), or NULL. */ const char *(*label_fn)(void *app_state); } wlm_graph_app_config_t; /* == Public API =========================================================== */ /** * Runs a graph application. * * Handles argument parsing, wlclient setup, icon creation, callback * registration, main loop, and cleanup. Apps just need to initialize their * state and provide a configuration. * * Graph state is managed internally by this function. * * @param argc Argument count. * @param argv Argument vector. * @param config Application configuration. * * @return EXIT_SUCCESS or EXIT_FAILURE. */ int wlm_graph_app_run( int argc, const char **argv, const wlm_graph_app_config_t *config); #endif /* WLM_GRAPH_SHARED_H */ wlmaker-0.8/apps/wlmclock.c0000644000175100017510000001706215203543557015372 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmclock.c * * Demonstrator for using the icon protocol. * * @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 #include #include #include #include #include #include #include #include #include #include #include #include #include "libwlclient/icon.h" /** Foreground color of a LED in the VFD-style display. */ static const uint32_t color_led = 0xff55ffff; /** Color of a turned-off element in the VFD-style display. */ static const uint32_t color_off = 0xff114444; /** Background color in the VFD-style display. */ static const uint32_t color_background = 0xff111111; /* ------------------------------------------------------------------------- */ /** Returns the next full second for when to draw the clock. */ uint64_t next_draw_time(void) { return (bs_usec() / 1000000 + 1) * 1000000; } /* ------------------------------------------------------------------------- */ /** * Draws contents into the icon buffer. * * @param gfxbuf_ptr * @param ud_ptr */ bool icon_callback( bs_gfxbuf_t *gfxbuf_ptr, __UNUSED__ void *ud_ptr) { bs_gfxbuf_clear(gfxbuf_ptr, 0); if (gfxbuf_ptr->width != gfxbuf_ptr->height) { bs_log(BS_ERROR, "Requiring a square buffer, width %u != height %u", gfxbuf_ptr->width, gfxbuf_ptr->height); return false; } unsigned width = gfxbuf_ptr->width; 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(%p)", gfxbuf_ptr); return false; } float r, g, b, alpha; bs_gfxbuf_argb8888_to_floats(color_background, &r, &g, &b, &alpha); cairo_pattern_t *pattern_ptr = cairo_pattern_create_rgba(r, g, b, alpha); BS_ASSERT(NULL != pattern_ptr); cairo_set_source(cairo_ptr, pattern_ptr); cairo_pattern_destroy(pattern_ptr); cairo_rectangle( cairo_ptr, 1, width - 14, width - 2, 14); cairo_fill(cairo_ptr); wlm_primitives_draw_bezel_at( cairo_ptr, 0, width - 15, width, 15, 1.0, false); struct timeval tv; if (0 != gettimeofday(&tv, NULL)) { memset(&tv, 0, sizeof(tv)); } struct tm *tm_ptr = localtime(&tv.tv_sec); char time_buf[7]; snprintf(time_buf, sizeof(time_buf), "%02d%02d%02d", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec); for (int i = 0; i < 6; ++i) { wlm_cairo_7segment_display_digit( cairo_ptr, &wlm_cairo_7segment_param_8x12, width / 2 - 26 + i * 8 + (i / 2) * 2, width - 2, color_led, color_off, time_buf[i] - '0'); } // The dots between "HH:MM:SS" cairo_set_source_argb8888(cairo_ptr, color_led); cairo_rectangle(cairo_ptr, width / 2 - 10, width - 10, 1, 1.25); cairo_rectangle(cairo_ptr, width / 2 - 10, width - 6, 1, 1.25); cairo_rectangle(cairo_ptr, width / 2 + 8, width - 10, 1, 1.25); cairo_rectangle(cairo_ptr, width / 2 + 8, width - 6, 1, 1.25); cairo_fill(cairo_ptr); // Draws a clock face, with small ticks every hour. double center_x = 27.5 * width / 56.0; double center_y = 19.5 * width / 56.0; double radius = 18 * width / 56.0; wlm_primitives_draw_bezel_at( cairo_ptr, 0, 0, width, 40.0 * width / 56.0, 1, false); cairo_set_source_argb8888(cairo_ptr, color_background); cairo_rectangle( cairo_ptr, 1, 1, width - 2, 38.0 * width / 56.0); cairo_fill(cairo_ptr); cairo_set_source_argb8888(cairo_ptr, color_led); for (int i = 0; i < 12; ++i) { // ... and larger ticks every 3 hours. double ratio = 0.9; if (i % 3 == 0) { ratio = 0.85; cairo_set_line_width(cairo_ptr, 2.0); } else { cairo_set_line_width(cairo_ptr, 1.0); } cairo_move_to(cairo_ptr, center_x + ratio * radius * sin(i * 2*M_PI / 12.0), center_y - ratio * radius * cos(i * 2*M_PI / 12.0)); cairo_line_to(cairo_ptr, center_x + radius * sin(i * 2*M_PI / 12.0), center_y - radius * cos(i * 2*M_PI / 12.0)); cairo_stroke(cairo_ptr); } // Seconds pointer. double angle = tm_ptr->tm_sec * 2*M_PI / 60.0; cairo_set_line_width(cairo_ptr, 0.5); cairo_move_to(cairo_ptr, center_x, center_y); cairo_line_to(cairo_ptr, center_x + 0.7 * radius * sin(angle), center_y - 0.7 * radius * cos(angle)); cairo_stroke(cairo_ptr); // Minutes pointer. angle = tm_ptr->tm_min * 2*M_PI / 60.0 + ( tm_ptr->tm_sec / 60.0 * 2*M_PI / 60.0); cairo_set_line_width(cairo_ptr, 1.0); cairo_move_to(cairo_ptr, center_x, center_y); cairo_line_to(cairo_ptr, center_x + 0.7 * radius * sin(angle), center_y - 0.7 * radius * cos(angle)); cairo_stroke(cairo_ptr); // Hours pointer. angle = tm_ptr->tm_hour * 2*M_PI / 12.0 + ( tm_ptr->tm_min / 60.0 * 2*M_PI / 12.0); cairo_set_line_width(cairo_ptr, 2.0); cairo_move_to(cairo_ptr, center_x, center_y); cairo_line_to(cairo_ptr, center_x + 0.5 * radius * sin(angle), center_y - 0.5 * radius * cos(angle)); cairo_stroke(cairo_ptr); cairo_destroy(cairo_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Called once per second. */ void timer_callback(wlclient_t *client_ptr, void *ud_ptr) { wlclient_icon_t *icon_ptr = ud_ptr; wlclient_icon_register_ready_callback(icon_ptr, icon_callback, NULL); wlclient_register_timer( client_ptr, next_draw_time(), timer_callback, icon_ptr); } /* == Main program ========================================================= */ /** Main program. */ int main(__UNUSED__ int argc, __UNUSED__ char **argv) { bs_log_severity = BS_DEBUG; wlclient_t *wlclient_ptr = wlclient_create("wlmaker.wlmeyes"); if (NULL == wlclient_ptr) return EXIT_FAILURE; if (wlclient_icon_supported(wlclient_ptr)) { wlclient_icon_t *icon_ptr = wlclient_icon_create(wlclient_ptr); if (NULL == icon_ptr) { bs_log(BS_ERROR, "Failed wlclient_icon_create(%p)", wlclient_ptr); } else { wlclient_icon_register_ready_callback( icon_ptr, icon_callback, NULL); wlclient_register_timer( wlclient_ptr, next_draw_time(), timer_callback, icon_ptr); wlclient_run(wlclient_ptr); wlclient_icon_destroy(icon_ptr); } } else { bs_log(BS_ERROR, "icon protocol is not supported."); } wlclient_destroy(wlclient_ptr); return EXIT_SUCCESS; } /* == End of wlmclock.c ==================================================== */ wlmaker-0.8/apps/example_toplevel.c0000644000175100017510000001330215203543557017115 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmclock.c * * Example app for libwlclient. * * @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 #include #include #include #include #include #include #include "libwlclient/xdg_toplevel.h" #include "libwlclient/libwlclient.h" /** State of the client. */ wlclient_t *wlclient_ptr; /** Listener for key events. */ static struct wl_listener _key_listener; /** A colorful background. */ static bs_gfxbuf_t *background_colors; /* ------------------------------------------------------------------------- */ /** Handles key events. */ static void _handle_key(__UNUSED__ struct wl_listener *listener_ptr, void *data_ptr) { wlclient_key_event_t *event_ptr = data_ptr; if (!event_ptr->pressed) return; char name[128]; if (0 <= xkb_keysym_get_name(event_ptr->keysym, name, sizeof(name))) { bs_log(BS_INFO, "Key press received: %s", name); } if (XKB_KEY_Escape == event_ptr->keysym || XKB_KEY_q == event_ptr->keysym || XKB_KEY_Q == event_ptr->keysym) { wlclient_request_terminate(wlclient_ptr); } } /* ------------------------------------------------------------------------- */ /** Draws something into the buffer. */ static bool _callback(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr) { static uint64_t ns_base = 0; wlclient_xdg_toplevel_t *toplevel_ptr = ud_ptr; bs_log(BS_DEBUG, "Callback gfxbuf %p", gfxbuf_ptr); bs_gfxbuf_copy(gfxbuf_ptr, background_colors); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) return false; cairo_select_font_face( cairo_ptr, "Helvetica", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cairo_ptr, 32); cairo_set_source_argb8888(cairo_ptr, 0xffffffff); if (0 == ns_base) ns_base = bs_mono_nsec(); cairo_move_to( cairo_ptr, 100, 200 + 100 * sin(1.5e-9 * bs_mono_nsec() - ns_base)); cairo_show_text(cairo_ptr, "wlmaker Toplevel Example"); cairo_destroy(cairo_ptr); wlclient_xdg_toplevel_register_ready_callback( toplevel_ptr, _callback, toplevel_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Creates a colorful background. */ bs_gfxbuf_t *_create_background(unsigned w, unsigned h) { bs_gfxbuf_t *b = bs_gfxbuf_create(w, h); if (NULL == b) return NULL; uint32_t t = 0xc0000000; // Transparency. for (unsigned y = 0; y < h / 2; ++y) { for (unsigned x = 0; x < w / 2; ++x) { unsigned rel_y = y * 2 * 256 / h; unsigned rel_x = x * 2 * 256 / w; // Upper left: red (horizontal), green (vertical). b->data_ptr[y * b->pixels_per_line + x] = (rel_x << 16) + (rel_y << 8) + t; // Upper right: green (horizontal), blue (vertical). b->data_ptr[y * b->pixels_per_line + x + w / 2] = (rel_x << 8) + rel_y + t; // Bottom left: blue (horizontal), red (vertical). b->data_ptr[(y + h / 2)* b->pixels_per_line + x] = (rel_y << 16) + rel_x + t; // Bottom left: rgb (both horizontal + vertical) b->data_ptr[(y + h / 2) * b->pixels_per_line + x + w / 2] = (((rel_x + rel_y) << 15) & 0xff0000) + (((rel_x + rel_y) << 7) & 0x00ff00) + (((rel_x + rel_y) >> 1) & 0x0000ff) + t; } } return b; } /* == Main program ========================================================= */ /** Main program. */ int main(__UNUSED__ int argc, __UNUSED__ char **argv) { bs_log_severity = BS_INFO; background_colors = _create_background(640, 400); if (NULL == background_colors) return EXIT_FAILURE; wlclient_ptr = wlclient_create("example_toplevel"); if (NULL == wlclient_ptr) return EXIT_FAILURE; _key_listener.notify = _handle_key; wl_signal_add(&wlclient_events(wlclient_ptr)->key, &_key_listener); if (wlclient_xdg_supported(wlclient_ptr)) { wlclient_xdg_toplevel_t *toplevel_ptr = wlclient_xdg_toplevel_create( wlclient_ptr, "wlmaker Toplevel Example", 640, 400); wlclient_xdg_decoration_set_server_side(toplevel_ptr, true); wlclient_xdg_toplevel_register_ready_callback( toplevel_ptr, _callback, toplevel_ptr); if (NULL != toplevel_ptr) { wlclient_run(wlclient_ptr); wlclient_xdg_toplevel_destroy(toplevel_ptr); } else { bs_log(BS_ERROR, "Failed wlclient_xdg_toplevel_create(%p)", wlclient_ptr); } } else { bs_log(BS_ERROR, "XDG shell is not supported."); } wl_list_remove(&_key_listener.link); wlclient_destroy(wlclient_ptr); return EXIT_SUCCESS; } /* == End of example_toplevel.c ============================================ */ wlmaker-0.8/apps/wlmcpugraph.c0000644000175100017510000001726515203543557016115 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmcpugraph.c * * CPU usage graph dock-app for wlmaker. * * @copyright * 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. */ #include "wlm_graph_shared.h" #include #include #include #include #include #include #include /** Application name. */ static const char _app_name[] = "wlmcpugraph"; /** Application help. */ static const char _app_help[] = "Displays CPU usage as a scrolling graph.\n" "\n" "The peak shows maximum CPU usage.\n" "\n" "Colors below indicate multi-core activity:\n" " - 1 core active (blue)\n" " - 1/4 cores active (cyan)\n" " - 1/2 cores active (green)\n" " - 3/4 cores active (yellow)\n" " - All cores active (red)"; /** Line buffer size for /proc/stat parsing. */ #define LINE_BUFFER_SIZE 256 /* == Definitions ========================================================== */ /** * Absolute CPU time values from `/proc/stat`. * * Uses unsigned long to match `/proc/stat` format and avoid overflow * on systems with high uptime. */ typedef struct { /** Sum of all CPU time fields. */ unsigned long total; /** Idle time (idle + iowait). */ unsigned long idle; } cpu_times_t; /** State for the CPU graph (mutable runtime data). */ typedef struct { /** Open file handle to /proc/stat. */ FILE *proc_fp; /** Previous absolute CPU values for computing usage (dynamically allocated). */ cpu_times_t *cpu_times_prev; /** Number of elements in cpu_times_prev. */ uint32_t cpu_usage_num; } cpugraph_state_t; /* == Memory management ==================================================== */ /* ------------------------------------------------------------------------- */ /** * Allocates state arrays for the given number of CPUs. * * @param state CPU graph state. * @param cpu_usage_num Number of CPUs. * * @return true on success, false on allocation failure. */ static bool _cpu_state_arrays_alloc(cpugraph_state_t *state, const uint32_t cpu_usage_num) { // Free old allocation. free(state->cpu_times_prev); state->cpu_times_prev = NULL; state->cpu_usage_num = 0; state->cpu_times_prev = calloc(cpu_usage_num, sizeof(cpu_times_t)); if (NULL == state->cpu_times_prev) { return false; } state->cpu_usage_num = cpu_usage_num; return true; } /* ------------------------------------------------------------------------- */ /** Frees all allocated state. */ static void _state_free(void *app_state) { cpugraph_state_t *state = app_state; if (NULL != state->proc_fp) { fclose(state->proc_fp); state->proc_fp = NULL; } free(state->cpu_times_prev); state->cpu_times_prev = NULL; state->cpu_usage_num = 0; } /* == CPU statistics ======================================================= */ /* ------------------------------------------------------------------------- */ /** * Reads CPU statistics from /proc/stat. * * Uses the pre-opened file handle, counts CPUs, reallocates the buffer if * needed, and fills it with per-CPU usage values. * * @param app_state App state (cpugraph_state_t pointer). * @param values Buffer to fill (may reallocate data/num). * * @return WLM_GRAPH_READ_OK on success, WLM_GRAPH_READ_ERROR on error. */ static wlm_graph_read_result_t _stats_read_fn(void *app_state, wlm_graph_values_t *values) { cpugraph_state_t * const state = app_state; FILE * const fp = state->proc_fp; if (NULL == fp) { return WLM_GRAPH_READ_ERROR; } rewind(fp); char line[LINE_BUFFER_SIZE]; uint32_t cpu_count = 0; // First pass: count CPUs. while (NULL != fgets(line, sizeof(line), fp)) { if (0 != strncmp(line, "cpu", 3)) continue; if (isdigit(line[3])) { cpu_count++; } } if (0 == cpu_count) { return WLM_GRAPH_READ_ERROR; } // Reallocate buffer if size doesn't match. if (cpu_count != values->num) { uint8_t *new_buf = realloc(values->data, cpu_count); if (NULL == new_buf) { return WLM_GRAPH_READ_ERROR; } values->data = new_buf; values->num = cpu_count; } // Reallocate internal state arrays if CPU count changed. if (cpu_count != state->cpu_usage_num) { if (!_cpu_state_arrays_alloc(state, cpu_count)) { return WLM_GRAPH_READ_ERROR; } } // Rewind and read stats, computing usage from previous absolute values. rewind(fp); uint32_t read_count = 0; while (NULL != fgets(line, sizeof(line), fp) && read_count < cpu_count) { // Skip the aggregate "cpu" line, look for "cpu0", "cpu1", etc. if (0 != strncmp(line, "cpu", 3)) continue; if (!isdigit(line[3])) continue; unsigned long user, nice, system, idle, iowait, irq, softirq; const int fields_parsed = sscanf( line, "%*s %lu %lu %lu %lu %lu %lu %lu", &user, &nice, &system, &idle, &iowait, &irq, &softirq); if (7 == fields_parsed) { const unsigned long abs_total = user + nice + system + idle + iowait + irq + softirq; const unsigned long abs_idle = idle + iowait; cpu_times_t * const prev = &state->cpu_times_prev[read_count]; uint8_t usage = 0; // Compute usage percentage, handling wraparound as zero. // Skip if prev is uninitialized (zero) after CPU hotplug realloc. if (0 != prev->total && abs_total > prev->total && abs_idle >= prev->idle) { const unsigned long total_diff = abs_total - prev->total; const unsigned long idle_diff = BS_MIN(abs_idle - prev->idle, total_diff); usage = (uint8_t)(((total_diff - idle_diff) * 255) / total_diff); } values->data[read_count] = usage; // Update previous absolute values for next iteration. prev->total = abs_total; prev->idle = abs_idle; read_count++; } } return (read_count == cpu_count) ? WLM_GRAPH_READ_OK : WLM_GRAPH_READ_ERROR; } /* == Main program ========================================================= */ /** Main program. */ int main(const int argc, const char **argv) { cpugraph_state_t state = {}; state.proc_fp = fopen("/proc/stat", "r"); if (NULL == state.proc_fp) { bs_log(BS_ERROR | BS_ERRNO, "Failed to open /proc/stat"); return EXIT_FAILURE; } // Prime prev values so first real sample computes proper delta. { wlm_graph_values_t values = {}; _stats_read_fn(&state, &values); free(values.data); } const wlm_graph_app_config_t config = { .app_name = _app_name, .app_help = _app_help, .accumulate_mode = WLM_GRAPH_ACCUMULATE_MODE_INDEPENDENT, .stats_read_fn = _stats_read_fn, .app_state = &state, .state_free_fn = _state_free, }; return wlm_graph_app_run(argc, argv, &config); } /* == End of wlmcpugraph.c ================================================= */ wlmaker-0.8/apps/wlmnetgraph.c0000644000175100017510000003302115203543557016100 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmnetgraph.c * * Network usage graph dock-app for wlmaker. * * @copyright * 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. */ #include "wlm_graph_shared.h" #include #include #include #include #include /** Application name. */ static const char _app_name[] = "wlmnetgraph"; /** Application help. */ static const char _app_help[] = "Displays network activity as a scrolling graph.\n" "\n" "Shows three activity categories:\n" " - Receive: incoming traffic (blue)\n" " - Transmit: outgoing traffic (cyan)\n" " - Bidirectional: combined traffic (red)\n" "\n" "The label displays current throughput. Scale auto-adjusts to peak rate."; /* == Definitions ========================================================== */ /** Number of network categories tracked. */ #define NET_CATEGORY_COUNT 3 /** Minimum peak rate for scaling (1 MB/s in bytes). */ #define PEAK_RATE_MIN (1024ULL * 1024ULL) /** Peak rate decay divisor (~1% decay per sample). */ #define PEAK_DECAY_DIVISOR 128 /** Threshold below which peak rate doesn't decay. */ #define PEAK_DECAY_THRESHOLD 1024 /** Line buffer size for /proc/net/dev parsing. */ #define LINE_BUFFER_SIZE 512 /** Number of header lines to skip in /proc/net/dev. */ #define HEADER_LINE_COUNT 2 /** Maximum length of the label string. */ #define LABEL_BUFFER_SIZE 16 /** Bytes per kilobyte. */ #define BYTES_PER_KB 1024ULL /** Bytes per megabyte. */ #define BYTES_PER_MB (1024ULL * 1024ULL) /** Bytes per gigabyte. */ #define BYTES_PER_GB (1024ULL * 1024ULL * 1024ULL) /** Network category indices (maps to heat-map colors). */ enum { /** Receive activity. */ NET_CATEGORY_IN = 0, /** Transmit activity. */ NET_CATEGORY_OUT = 1, /** Bidirectional activity. */ NET_CATEGORY_IN_OUT = 2, }; /** Scale entry pairing a byte value with its display label. */ typedef struct { /** Byte threshold for this scale. */ unsigned long long bytes; /** Display label (e.g., "1 MB/s"). */ const char *label; } scale_entry_t; /** Available scale values (1/10/100 × KB/MB/GB). */ static const scale_entry_t _scales[] = { { 1ULL * BYTES_PER_KB, "1 KB/s" }, { 10ULL * BYTES_PER_KB, "10 KB/s" }, { 100ULL * BYTES_PER_KB, "100 KB/s" }, { 1ULL * BYTES_PER_MB, "1 MB/s" }, { 10ULL * BYTES_PER_MB, "10 MB/s" }, { 100ULL * BYTES_PER_MB, "100 MB/s" }, { 1ULL * BYTES_PER_GB, "1 GB/s" }, { 10ULL * BYTES_PER_GB, "10 GB/s" }, { 100ULL * BYTES_PER_GB, "100 GB/s" }, }; /** Number of scale entries. */ #define SCALE_COUNT (sizeof(_scales) / sizeof(_scales[0])) /** Raw rate history entry for regeneration. */ typedef struct { /** Receive rate (bytes per interval). */ unsigned long long rx_rate; /** Transmit rate (bytes per interval). */ unsigned long long tx_rate; } rate_history_t; /** State for the network graph (mutable runtime data). */ typedef struct { /** Open file handle to /proc/net/dev. */ FILE *proc_fp; /** Previous absolute RX byte count for computing rate. */ unsigned long long prev_rx_bytes; /** Previous absolute TX byte count for computing rate. */ unsigned long long prev_tx_bytes; /** Peak observed rate (for auto-scaling). */ unsigned long long peak_rate; /** Index into _scales for current display scale. */ size_t scale_index; /** Raw rate history for regeneration (ring buffer, newest first). */ rate_history_t history[WLM_GRAPH_REGENERATE_HISTORY_MAX]; /** Current write position in history (next slot to write). */ uint32_t history_index; /** Number of valid entries in history. */ uint32_t history_num; } netgraph_state_t; /* == Label ================================================================ */ /* ------------------------------------------------------------------------- */ /** * Finds index of smallest scale >= val. * * @param val Value to find ceiling scale for. * @return Index into _scales array. */ static size_t _scale_index_ceil(const unsigned long long val) { for (size_t i = 0; i < SCALE_COUNT; i++) { if (_scales[i].bytes >= val) { return i; } } // Beyond largest scale, return last index. return SCALE_COUNT - 1; } /* ------------------------------------------------------------------------- */ /** Returns the scale label. */ static const char *_label_fn(void *app_state) { netgraph_state_t *state = app_state; return _scales[state->scale_index].label; } /* == Cleanup ============================================================== */ /* ------------------------------------------------------------------------- */ /** Frees all allocated state. */ static void _state_free(void *app_state) { netgraph_state_t *state = app_state; if (NULL != state->proc_fp) { fclose(state->proc_fp); state->proc_fp = NULL; } } /* == Network statistics =================================================== */ /* ------------------------------------------------------------------------- */ /** * Reads network statistics from /proc/net/dev. * * Parses all interfaces (excluding loopback) and sums RX/TX bytes. * Computes rate since last read and scales to 0-255 based on peak rate. * * @param app_state App state (netgraph_state_t pointer). * @param values Buffer to fill (may reallocate data/num). * * @return WLM_GRAPH_READ_OK on success, WLM_GRAPH_READ_ERROR on error. */ static wlm_graph_read_result_t _stats_read_fn(void *app_state, wlm_graph_values_t *values) { netgraph_state_t * const state = app_state; FILE * const fp = state->proc_fp; if (NULL == fp) { return WLM_GRAPH_READ_ERROR; } // Reallocate buffer if size doesn't match. if (NET_CATEGORY_COUNT != values->num) { uint8_t *new_buf = realloc(values->data, NET_CATEGORY_COUNT); if (NULL == new_buf) { return WLM_GRAPH_READ_ERROR; } values->data = new_buf; values->num = NET_CATEGORY_COUNT; } rewind(fp); unsigned long long total_rx_bytes = 0; unsigned long long total_tx_bytes = 0; // Skip header lines in /proc/net/dev. char line[LINE_BUFFER_SIZE]; for (int i = 0; i < HEADER_LINE_COUNT; i++) { if (NULL == fgets(line, sizeof(line), fp)) { return WLM_GRAPH_READ_ERROR; } } while (NULL != fgets(line, sizeof(line), fp)) { // Find interface name (ends with ':'). char * const colon = strchr(line, ':'); if (NULL == colon) { continue; } // Extract interface name and skip loopback. *colon = '\0'; char *iface = line; while (' ' == *iface) iface++; // Trim leading spaces. if (0 == strcmp(iface, "lo")) { continue; } // Parse stats after colon. // Format: rx_bytes rx_packets rx_errs rx_drop rx_fifo rx_frame // rx_compressed rx_multicast tx_bytes tx_packets ... unsigned long long rx_bytes, tx_bytes, skip; const int parsed = sscanf(colon + 1, "%llu %llu %llu %llu %llu %llu %llu %llu %llu", &rx_bytes, &skip, &skip, &skip, &skip, &skip, &skip, &skip, &tx_bytes); if (9 == parsed) { total_rx_bytes += rx_bytes; total_tx_bytes += tx_bytes; } } // Compute rate (bytes since last read). unsigned long long rx_rate = 0; unsigned long long tx_rate = 0; if (total_rx_bytes >= state->prev_rx_bytes) { rx_rate = total_rx_bytes - state->prev_rx_bytes; } if (total_tx_bytes >= state->prev_tx_bytes) { tx_rate = total_tx_bytes - state->prev_tx_bytes; } state->prev_rx_bytes = total_rx_bytes; state->prev_tx_bytes = total_tx_bytes; // Store raw rates in history for regeneration. state->history[state->history_index].rx_rate = rx_rate; state->history[state->history_index].tx_rate = tx_rate; state->history_index = (state->history_index + 1) % WLM_GRAPH_REGENERATE_HISTORY_MAX; if (state->history_num < WLM_GRAPH_REGENERATE_HISTORY_MAX) { state->history_num++; } // Update peak rate for auto-scaling (with decay to adapt to lower rates). const unsigned long long total_rate = rx_rate + tx_rate; const size_t prev_scale_index = state->scale_index; if (total_rate > state->peak_rate) { state->peak_rate = total_rate; state->scale_index = _scale_index_ceil(state->peak_rate); } else if (state->peak_rate > PEAK_DECAY_THRESHOLD) { state->peak_rate -= state->peak_rate / PEAK_DECAY_DIVISOR; state->scale_index = _scale_index_ceil(state->peak_rate); } // Ensure minimum peak to avoid division by zero. if (state->peak_rate < PEAK_RATE_MIN) { state->peak_rate = PEAK_RATE_MIN; state->scale_index = _scale_index_ceil(PEAK_RATE_MIN); } // Scale rates to 0-255 based on peak (clamped to peak). // IN_OUT uses min to show bidirectional activity (both directions active). const unsigned long long peak = state->peak_rate; values->data[NET_CATEGORY_IN] = (uint8_t)((BS_MIN(rx_rate, peak) * 255) / peak); values->data[NET_CATEGORY_OUT] = (uint8_t)((BS_MIN(tx_rate, peak) * 255) / peak); values->data[NET_CATEGORY_IN_OUT] = (uint8_t)((BS_MIN(rx_rate, tx_rate) * 255) / peak); // Request regeneration if scale changed. if (state->scale_index != prev_scale_index) { return WLM_GRAPH_READ_OK_AND_REGENERATE; } return WLM_GRAPH_READ_OK; } /* ------------------------------------------------------------------------- */ /** * Regenerates historical samples at the current scale. * * @param app_state App state (netgraph_state_t pointer). * @param samples Array of sample buffers to regenerate (newest first). * @param samples_num Number of samples to regenerate. */ static void _regenerate_fn(void *app_state, wlm_graph_values_t *samples, const uint32_t samples_num) { netgraph_state_t * const state = app_state; const unsigned long long peak = state->peak_rate; uint32_t samples_num_regenerate = 0; if (state->history_num > 1) { // Determine how many samples have history available (excludes current). const uint32_t samples_num_available = state->history_num - 1; samples_num_regenerate = BS_MIN(samples_num, samples_num_available); // Regenerate samples that have history. // samples[0] = newest historical (before current), samples[N-1] = oldest. // History index wraps at array size (WLM_GRAPH_REGENERATE_HISTORY_MAX), // not history_num. The samples_num_available limit ensures we don't // read invalid entries. // -2: convert from next-write to most-recent (-1), skip current sample (-1). // +WLM_GRAPH_REGENERATE_HISTORY_MAX: ensures modulo works when subtracting. const uint32_t history_offset = (state->history_index + WLM_GRAPH_REGENERATE_HISTORY_MAX) - 2; for (uint32_t i = 0; i < samples_num_regenerate; i++) { const uint32_t hist_index = (history_offset - i) % WLM_GRAPH_REGENERATE_HISTORY_MAX; const rate_history_t *h = &state->history[hist_index]; // Regenerate scaled values at current peak. samples[i].data[NET_CATEGORY_IN] = (uint8_t)((BS_MIN(h->rx_rate, peak) * 255) / peak); samples[i].data[NET_CATEGORY_OUT] = (uint8_t)((BS_MIN(h->tx_rate, peak) * 255) / peak); samples[i].data[NET_CATEGORY_IN_OUT] = (uint8_t)((BS_MIN(h->rx_rate, h->tx_rate) * 255) / peak); } } // Clear samples without available history. for (uint32_t i = samples_num_regenerate; i < samples_num; i++) { memset(samples[i].data, 0, samples[i].num); } } /* == Main program ========================================================= */ /** Main program. */ int main(const int argc, const char **argv) { netgraph_state_t state = {}; state.proc_fp = fopen("/proc/net/dev", "r"); if (NULL == state.proc_fp) { bs_log(BS_ERROR | BS_ERRNO, "Failed to open /proc/net/dev"); return EXIT_FAILURE; } // Prime prev values so first real sample computes proper delta. // Reset peak and history after priming (first read sets peak to total bytes). { wlm_graph_values_t values = {}; _stats_read_fn(&state, &values); free(values.data); state.peak_rate = 0; state.history_index = 0; state.history_num = 0; } const wlm_graph_app_config_t config = { .app_name = _app_name, .app_help = _app_help, .accumulate_mode = WLM_GRAPH_ACCUMULATE_MODE_INDEPENDENT, .stats_read_fn = _stats_read_fn, .regenerate_fn = _regenerate_fn, .app_state = &state, .state_free_fn = _state_free, .label_fn = _label_fn, }; return wlm_graph_app_run(argc, argv, &config); } /* == End of wlmnetgraph.c ================================================= */ wlmaker-0.8/apps/CMakeLists.txt0000644000175100017510000000621315203543557016147 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) add_subdirectory(libwlclient) add_subdirectory(primitives) add_library(wlm_graph_shared STATIC wlm_graph_shared.c) target_include_directories(wlm_graph_shared PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlm_graph_shared libwlclient primitives) add_executable( example_toplevel example_toplevel.c) target_include_directories(example_toplevel PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( example_toplevel m libwlclient) add_executable( wlmclock wlmclock.c) target_include_directories(wlmclock PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlmclock libwlclient primitives m) add_executable( wlmeyes wlmeyes.c) target_include_directories(wlmeyes PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlmeyes m libwlclient) add_executable( wlmcpugraph wlmcpugraph.c) target_include_directories(wlmcpugraph PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlmcpugraph wlm_graph_shared libwlclient primitives) add_executable( wlmmemgraph wlmmemgraph.c) target_include_directories(wlmmemgraph PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlmmemgraph wlm_graph_shared libwlclient primitives) add_executable( wlmnetgraph wlmnetgraph.c) target_include_directories(wlmnetgraph PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlmnetgraph wlm_graph_shared libwlclient primitives) add_executable( wlmbattery wlmbattery.c) target_include_directories(wlmbattery PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries( wlmbattery libbase libwlclient primitives) install( TARGETS wlmclock wlmeyes wlmcpugraph wlmmemgraph wlmnetgraph wlmbattery DESTINATION "${CMAKE_INSTALL_BINDIR}") if(iwyu_path_and_options) set_target_properties( wlm_graph_shared PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( example_toplevel PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmclock PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmeyes PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmcpugraph PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmmemgraph PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmnetgraph PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8/apps/wlmbattery.c0000644000175100017510000005440015203543557015746 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmbattery.c * * Displays battery capacity from /sys/class/power_supply in a DockApp. * * TODO(kaeser@gubbe.ch): * - Only update when the numbers have actually changed. * - Support more than one battery (eg. by clicking through?) * - Use graphics for visualization, this initial version is very crude. * - Permit dynamic size of the app, currently it's fixed to 64x64. * * @copyright * 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. */ #include #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** Sufficient for the typical paths, although there is no strict maximum. */ #define SYSFS_BATTERY_PATH_MAX_LEN 256 /** Small buffer for reading single values (like capacity). */ #define VAL_BUF_LEN 64 /** Internal battery status enum. */ enum battery_status { BATTERY_STATUS_UNKNOWN = 0, BATTERY_STATUS_CHARGING, BATTERY_STATUS_DISCHARGING, BATTERY_STATUS_NOT_CHARGING, BATTERY_STATUS_FULL }; /** Power supply state container. */ struct wlm_power_supply { /** List of batteries. Elements are @ref wlm_battery::dlnode. */ bs_dllist_t batteries; /** List of adapters. Elements are @ref wlm_power_adapter::dlnode. */ bs_dllist_t adapters; }; /** Power adapter state container. */ struct wlm_power_adapter { /** List node, member of @ref wlm_power_supply::adapters. */ bs_dllist_node_t dlnode; /** Adapter name (e.g. "AC0"). */ char *name; /** Absolute sysfs path. */ char *sysfs_path; /** Whether the power adapter is online. */ bool online; }; /** Battery state container. */ struct wlm_battery { /** List node, member of @ref wlm_power_supply::batteries. */ bs_dllist_node_t dlnode; /** Battery name (e.g. "BAT0"). */ char *name; /** Absolute sysfs path. */ char *sysfs_path; /** Loaded battery properties */ uint64_t capacity; /** Current battery status. */ enum battery_status status; /** Current energy. */ uint64_t energy_now; /** Energy when full */ uint64_t energy_full; /** power being added currently. */ uint64_t power_now; /** Current charge. */ uint64_t charge_now; /** Full charge. */ uint64_t charge_full; /** current adding charge currently. */ uint64_t current_now; /** Precalculated remaining time in minutes, based on availability of energy/charge records (-1 if unavailable). */ int time_remaining_min; }; static struct wlm_power_supply *wlm_power_supply_create(void); static void wlm_power_supply_destroy(struct wlm_power_supply *ps); static bool wlm_power_supply_read(struct wlm_power_supply *ps); static size_t wlm_power_supply_num_batteries(struct wlm_power_supply *ps); static struct wlm_battery *wlm_power_supply_battery( struct wlm_power_supply *ps, size_t index); static bool wlm_power_supply_connected(struct wlm_power_supply *ps); static struct wlm_battery *wlm_battery_create( const char *name_ptr, const char *power_supply_dir); static void wlm_battery_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool wlm_battery_read(struct wlm_battery *bat); static struct wlm_power_adapter *wlm_power_adapter_create( const char *name_ptr, const char *power_supply_dir); static void wlm_power_adapter_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool wlm_power_adapter_connected( bs_dllist_node_t *node_ptr, void *ud_ptr); static bool wlm_power_adapter_read(struct wlm_power_adapter *adapter); static bool wlm_vread_buffer( char *v, size_t v_size, const char *fmt_ptr, va_list ap); static bool wlm_read_buffer( char *v, size_t v_size, const char *fmt_ptr, ...); static bool wlm_read_uint64( uint64_t *u64_ptr, const char *fmt_ptr, ...); static enum battery_status parse_battery_status(const char *status_str); /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Iterates through /sys/class/power_supply and creates corresponding * battery and adapter items. * * @return Pointer to the power supply, or NULL on error. */ struct wlm_power_supply *wlm_power_supply_create(void) { struct wlm_power_supply *ps = logged_calloc(1, sizeof(*ps)); if (!ps) return NULL; static const char *power_supply_dir = "/sys/class/power_supply"; DIR *dir = opendir(power_supply_dir); if (!dir) { bs_log(BS_ERROR, "Failed to open %s", power_supply_dir); free(ps); return NULL; } struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (bs_str_startswith(ent->d_name, ".")) { continue; } if (bs_str_startswith(ent->d_name, "BAT")) { struct wlm_battery *bat = wlm_battery_create(ent->d_name, power_supply_dir); if (bat) { bs_dllist_push_back(&ps->batteries, &bat->dlnode); } } else { struct wlm_power_adapter *adapter = wlm_power_adapter_create(ent->d_name, power_supply_dir); if (adapter) { bs_dllist_push_back(&ps->adapters, &adapter->dlnode); } } } closedir(dir); return ps; } /* ------------------------------------------------------------------------- */ /** * Destroys the power supply container and internal batteries/adapters. * * @param ps Pointer to the target power supply. */ void wlm_power_supply_destroy(struct wlm_power_supply *ps) { if (!ps) return; bs_dllist_for_each(&ps->batteries, wlm_battery_destroy, NULL); bs_dllist_for_each(&ps->adapters, wlm_power_adapter_destroy, NULL); free(ps); } /* ------------------------------------------------------------------------- */ /** * Reads current state of the power supply (batteries and adapters). * * @param ps * * @return true on success. */ bool wlm_power_supply_read(struct wlm_power_supply *ps) { bs_dllist_node_t *dlnode_ptr; bool failure = false; for (dlnode_ptr = ps->batteries.head_ptr; NULL != dlnode_ptr; dlnode_ptr = bs_dllist_node_iterator_forward(dlnode_ptr)) { struct wlm_battery *bat = BS_CONTAINER_OF( dlnode_ptr, struct wlm_battery, dlnode); failure |= !(wlm_battery_read(bat)); } for (dlnode_ptr = ps->adapters.head_ptr; NULL != dlnode_ptr; dlnode_ptr = bs_dllist_node_iterator_forward(dlnode_ptr)) { struct wlm_power_adapter *a = BS_CONTAINER_OF( dlnode_ptr, struct wlm_power_adapter, dlnode); failure |= !(wlm_power_adapter_read(a)); } return !failure; } /* ------------------------------------------------------------------------- */ /** * Returns the tracked amount of batteries. * * @param ps Pointer to the target power supply. * * @return the tracked amount of batteries. */ size_t wlm_power_supply_num_batteries(struct wlm_power_supply *ps) { return bs_dllist_size(&ps->batteries); } /* ------------------------------------------------------------------------- */ /** * Extract battery handle at specified `index` linearly. * * @param ps Pointer to the structured system state container. * @param index Index integer for node traversal limit bounding. * * @return Sliced battery memory, NULL if invalid parameter passed. */ struct wlm_battery *wlm_power_supply_battery( struct wlm_power_supply *ps, size_t index) { bs_dllist_node_t *node = ps->batteries.head_ptr; for (size_t i = 0; i < index && node != NULL; ++i) { node = bs_dllist_node_iterator_forward(node); } if (!node) return NULL; return BS_CONTAINER_OF(node, struct wlm_battery, dlnode); } /* ------------------------------------------------------------------------- */ /** * Indicates if any of the power adapters is online. * * @param ps Pointer to the target power supply. * * @return True if at least one adapter is connected and online, otherwise false. */ bool wlm_power_supply_connected(struct wlm_power_supply *ps) { return bs_dllist_any(&ps->adapters, wlm_power_adapter_connected, NULL); } /* ------------------------------------------------------------------------- */ /** * Creates battery struct spanning the specific `name_ptr`. * * @param name_ptr Null-terminated property name segment (e.g. "BAT0"). * @param power_supply_dir The root directory where sysfs devices live. * * @return Pointer to allocated battery structure, or NULL on error. */ struct wlm_battery *wlm_battery_create( const char *name_ptr, const char *power_supply_dir) { struct wlm_battery *bat = logged_calloc(1, sizeof(*bat)); if (!bat) return NULL; bat->name = logged_strdup(name_ptr); if (!bat->name) { wlm_battery_destroy(&bat->dlnode, NULL); return NULL; } bat->sysfs_path = bs_strdupf("%s/%s", power_supply_dir, name_ptr); if (!bat->sysfs_path) { wlm_battery_destroy(&bat->dlnode, NULL); return NULL; } return bat; } /* ------------------------------------------------------------------------- */ /** * Destroys battery struct. * * @param dlnode_ptr * @param ud_ptr */ void wlm_battery_destroy( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { struct wlm_battery *bat = BS_CONTAINER_OF( dlnode_ptr, struct wlm_battery, dlnode); if (bat->name) { free(bat->name); } if (bat->sysfs_path) { free(bat->sysfs_path); } free(bat); } /* ------------------------------------------------------------------------- */ /** * Helper to pull all readings into the structural cache representation. * * @param bat Pointer to the target battery object tracking the metric states. * * @return True on success. */ bool wlm_battery_read(struct wlm_battery *bat) { // Capacity if (!wlm_read_uint64(&bat->capacity, "%s/capacity", bat->sysfs_path)) { return false; } // Status char buf[VAL_BUF_LEN]; if (!wlm_read_buffer(buf, sizeof(buf), "%s/status", bat->sysfs_path)) { return false; } else { size_t len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; } } bat->status = parse_battery_status(buf); // Advanced hardware stats gathering bat->time_remaining_min = -1; bat->charge_now = bat->charge_full = bat->current_now = 0; bat->energy_now = bat->energy_full = bat->power_now = 0; uint64_t val_now = 0, val_full = 0, rate = 0; /* * Linux power_supply exposes either charge-based (µAh) or energy-based (µWh) * parameters depending on the specific hardware controller. Reading charge_now * will naturally fail on systems utilizing energy parameters. */ if (wlm_read_uint64(&bat->charge_now, "%s/charge_now", bat->sysfs_path)) { val_now = bat->charge_now; if (wlm_read_uint64(&bat->charge_full, "%s/charge_full", bat->sysfs_path)) { val_full = bat->charge_full; } if (wlm_read_uint64(&bat->current_now, "%s/current_now", bat->sysfs_path)) { rate = bat->current_now; } } else { if (wlm_read_uint64(&bat->energy_now, "%s/energy_now", bat->sysfs_path)) { val_now = bat->energy_now; if (wlm_read_uint64(&bat->energy_full, "%s/energy_full", bat->sysfs_path)) { val_full = bat->energy_full; } if (wlm_read_uint64(&bat->power_now, "%s/power_now", bat->sysfs_path)) { rate = bat->power_now; } } } if (rate > 0 && (bat->status == BATTERY_STATUS_CHARGING || bat->status == BATTERY_STATUS_DISCHARGING)) { double hours = 0; if (bat->status == BATTERY_STATUS_DISCHARGING) { hours = (double)val_now / rate; } else if (bat->status == BATTERY_STATUS_CHARGING) { hours = (double)(val_full > val_now ? val_full - val_now : 0) / rate; } bat->time_remaining_min = (int)(hours * 60.0); } return true; } /* ------------------------------------------------------------------------- */ /** * Creates adapter struct spanning the specific `name_ptr`. * * @param name_ptr Null-terminated property name segment (e.g. "AC0"). * @param power_supply_dir The root directory where sysfs devices live. * * @return Pointer to allocated adapter structure, or NULL on error. */ struct wlm_power_adapter *wlm_power_adapter_create( const char *name_ptr, const char *power_supply_dir) { struct wlm_power_adapter *adapter = logged_calloc(1, sizeof(*adapter)); if (!adapter) return NULL; adapter->name = logged_strdup(name_ptr); if (!adapter->name) { wlm_power_adapter_destroy(&adapter->dlnode, NULL); return NULL; } adapter->sysfs_path = bs_strdupf("%s/%s", power_supply_dir, name_ptr); if (!adapter->sysfs_path) { wlm_power_adapter_destroy(&adapter->dlnode, NULL); return NULL; } return adapter; } /* ------------------------------------------------------------------------- */ /** * Destroys power adapter struct. * * @param dlnode_ptr The list node of the power adapter to destroy. * @param ud_ptr */ void wlm_power_adapter_destroy( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { struct wlm_power_adapter *adapter = BS_CONTAINER_OF( dlnode_ptr, struct wlm_power_adapter, dlnode); if (adapter->name) { free(adapter->name); } if (adapter->sysfs_path) { free(adapter->sysfs_path); } free(adapter); } /** * Iterator function to verify whether a power adapter node is connected. * * @param node_ptr The doubly-linked list node embedded in a wlm_power_adapter. * @param ud_ptr Ignored user data. * * @return True if the adapter is online. */ bool wlm_power_adapter_connected( bs_dllist_node_t *node_ptr, __UNUSED__ void *ud_ptr) { struct wlm_power_adapter *adapter = BS_CONTAINER_OF(node_ptr, struct wlm_power_adapter, dlnode); return adapter->online; } /* ------------------------------------------------------------------------- */ /** * Reads power adapter properties into the structural cache representation. * * @param adapter Pointer to the target power adapter object. * * @return True on success. */ bool wlm_power_adapter_read(struct wlm_power_adapter *adapter) { uint64_t v; if (!wlm_read_uint64(&v, "%s/online", adapter->sysfs_path)) { return false; } adapter->online = (v != 0); return true; } /* ------------------------------------------------------------------------- */ /** * Reads content of file named by the formatted path into a buffer. * * @param v Output buffer. * @param v_size Length boundary for `v`. * @param fmt_ptr Format string for the path. * @param ap Variadic arguments list. * * @return True if read safely, otherwise false. */ bool wlm_vread_buffer(char *v, size_t v_size, const char *fmt_ptr, va_list ap) { char path[SYSFS_BATTERY_PATH_MAX_LEN]; int len = vsnprintf(path, sizeof(path), fmt_ptr, ap); if (len < 0 || (size_t)len >= sizeof(path)) { bs_log(BS_ERROR, "Path too long or format error"); return false; } if (bs_file_read_buffer(path, v, v_size) < 0) { return false; } return true; } /* ------------------------------------------------------------------------- */ /** * Reads content of file named by the formatted path into the buffer. * * @param v Output buffer. * @param v_size Length boundary for `v`. * @param fmt_ptr Format string for the path. * @param ... Arguments for the format string. * * @return True if read safely, otherwise false. */ bool wlm_read_buffer(char *v, size_t v_size, const char *fmt_ptr, ...) { va_list ap; va_start(ap, fmt_ptr); bool result = wlm_vread_buffer(v, v_size, fmt_ptr, ap); va_end(ap); return result; } /* ------------------------------------------------------------------------- */ /** * Reads content of file named by the formatted path and parses it as uint64. * * @param u64_ptr Output pointer for the parsed integer. * @param fmt_ptr Format string for the path. * @param ... Arguments for the format string. * * @return True on successful parse, otherwise false. */ bool wlm_read_uint64(uint64_t *u64_ptr, const char *fmt_ptr, ...) { char buf[VAL_BUF_LEN]; va_list ap; va_start(ap, fmt_ptr); bool result = wlm_vread_buffer(buf, sizeof(buf), fmt_ptr, ap); va_end(ap); return result && bs_strconvert_uint64(buf, u64_ptr, 10); } /* ------------------------------------------------------------------------- */ /** * Maps standard sysfs status string to internal enum. * * @param status_str String literal describing the battery status. * * @return The corresponding parsed internal enum status. */ static enum battery_status parse_battery_status(const char *status_str) { if (strcmp(status_str, "Charging") == 0) return BATTERY_STATUS_CHARGING; if (strcmp(status_str, "Discharging") == 0) return BATTERY_STATUS_DISCHARGING; if (strcmp(status_str, "Not charging") == 0) return BATTERY_STATUS_NOT_CHARGING; if (strcmp(status_str, "Full") == 0) return BATTERY_STATUS_FULL; return BATTERY_STATUS_UNKNOWN; } /* ------------------------------------------------------------------------- */ /** Argument to @ref icon_callback and @ref timer_callback. */ struct callback_arg { /** The icon handle */ wlclient_icon_t *icon_ptr; /** Power supply handle */ struct wlm_power_supply *ps; }; /* ------------------------------------------------------------------------- */ /** * Draws current byttery status into the icon buffer. * * @param gfxbuf_ptr * @param ud_ptr */ bool icon_callback( bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr) { struct callback_arg *arg_ptr = ud_ptr; bs_gfxbuf_clear(gfxbuf_ptr, 0); 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(%p)", gfxbuf_ptr); return false; } float r, g, b, alpha; bs_gfxbuf_argb8888_to_floats(0xff111111, &r, &g, &b, &alpha); cairo_pattern_t *pattern_ptr = cairo_pattern_create_rgba(r, g, b, alpha); BS_ASSERT(NULL != pattern_ptr); cairo_set_source(cairo_ptr, pattern_ptr); cairo_pattern_destroy(pattern_ptr); cairo_rectangle(cairo_ptr, 0, 0, 56, 56); cairo_fill(cairo_ptr); wlm_primitives_draw_bezel_at(cairo_ptr, 0, 0, 56, 56, 1.0, false); wlm_power_supply_read(arg_ptr->ps); cairo_select_font_face( cairo_ptr, "Helvetica", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cairo_ptr, 12); cairo_set_source_argb8888(cairo_ptr, 0xffffffff); if (0 < wlm_power_supply_num_batteries(arg_ptr->ps)) { struct wlm_battery *bat = wlm_power_supply_battery(arg_ptr->ps, 0); char buf[16]; cairo_move_to(cairo_ptr, 8, 15); snprintf(buf, sizeof(buf), "%3"PRIu64"%%", bat->capacity); cairo_show_text(cairo_ptr, buf); cairo_move_to(cairo_ptr, 8, 27); switch (bat->status) { case BATTERY_STATUS_CHARGING: cairo_show_text(cairo_ptr, "CHRG"); break; case BATTERY_STATUS_DISCHARGING: cairo_show_text(cairo_ptr, "DISC"); break; case BATTERY_STATUS_NOT_CHARGING: cairo_show_text(cairo_ptr, "----"); break; case BATTERY_STATUS_FULL: cairo_show_text(cairo_ptr, "FULL"); break; default: cairo_show_text(cairo_ptr, "UNKN"); break; } cairo_move_to(cairo_ptr, 8, 39); if (0 <= bat->time_remaining_min) { snprintf(buf, sizeof(buf), "% 2d:%02d", bat->time_remaining_min / 60, bat->time_remaining_min % 60); cairo_show_text(cairo_ptr, buf); } } cairo_move_to(cairo_ptr, 8, 51); if (wlm_power_supply_connected(arg_ptr->ps)) { cairo_show_text(cairo_ptr, " AC "); } else { cairo_show_text(cairo_ptr, "no pwr"); } cairo_destroy(cairo_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Called once per second. */ void timer_callback(wlclient_t *client_ptr, void *ud_ptr) { struct callback_arg *arg_ptr = ud_ptr; wlclient_icon_register_ready_callback( arg_ptr->icon_ptr, icon_callback, arg_ptr); wlclient_register_timer( client_ptr, bs_usec() + 1000000, timer_callback, arg_ptr); } /* == Main program ========================================================= */ /** @return `EXIT_SUCCESS` on success. */ int main(void) { struct wlm_power_supply *ps = wlm_power_supply_create(); if (!ps) { return EXIT_FAILURE; } wlclient_t *wlclient_ptr = wlclient_create("wlmaker.wlmbattery"); if (NULL == wlclient_ptr) { wlm_power_supply_destroy(ps); return EXIT_FAILURE; } if (wlclient_icon_supported(wlclient_ptr)) { wlclient_icon_t *icon_ptr = wlclient_icon_create(wlclient_ptr); struct callback_arg arg = { .ps = ps, .icon_ptr = icon_ptr }; if (NULL == icon_ptr) { bs_log(BS_ERROR, "Failed wlclient_icon_create(%p)", wlclient_ptr); } else { wlclient_icon_register_ready_callback( icon_ptr, icon_callback, &arg); wlclient_register_timer( wlclient_ptr, bs_usec() + 1000000, timer_callback, &arg); wlclient_run(wlclient_ptr); wlclient_icon_destroy(icon_ptr); } } else { bs_log(BS_ERROR, "icon protocol is not supported."); } wlclient_destroy(wlclient_ptr); wlm_power_supply_destroy(ps); return EXIT_SUCCESS; } /* == End of wlmbattery.c ================================================== */ wlmaker-0.8/apps/wlmeyes.c0000644000175100017510000002334515203543557015245 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmeyes.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 #include #include #include #include #include #include #include #include #include #include #include "libwlclient/xdg_toplevel.h" #include "libwlclient/libwlclient.h" #include "libwlclient/icon.h" /* == Data ================================================================= */ /** State of the client. */ wlclient_t *wlclient_ptr; /** Listener for key events. */ static struct wl_listener _key_listener; /** Most recent X position of the pointer. */ double pointer_x; /** Most recent Y position of the pointer. */ double pointer_y; /** Most recent X position of the pointer relative to the ivon. */ double icon_pointer_x; /** Most recent Y position of the pointer relative to the ivon. */ double icon_pointer_y; /** Desired width of the toplevel, in pixels. */ uint32_t toplevel_width; /** Desired height of the toplevel, in pixels. */ uint32_t toplevel_height; /** Commandline arguments. */ static const bs_arg_t _wlmeyes_args[] = { BS_ARG_UINT32( "width", "Desired width of the XDG toplevel window, in pixels.", 512, 1, INT32_MAX, &toplevel_width), BS_ARG_UINT32( "height", "Desired height of the XDG toplevel window, in pixels.", 384, 1, INT32_MAX, &toplevel_height), BS_ARG_SENTINEL(), }; /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Handles key events. */ static void _handle_key(__UNUSED__ struct wl_listener *listener_ptr, void *data_ptr) { wlclient_key_event_t *event_ptr = data_ptr; if (!event_ptr->pressed) return; char name[128]; if (0 <= xkb_keysym_get_name(event_ptr->keysym, name, sizeof(name))) { bs_log(BS_INFO, "Key press received: %s", name); } if (XKB_KEY_Escape == event_ptr->keysym || XKB_KEY_q == event_ptr->keysym || XKB_KEY_Q == event_ptr->keysym) { wlclient_request_terminate(wlclient_ptr); } } /* ------------------------------------------------------------------------- */ /** @return x * x. */ static inline double sqr(double x) { return x * x; } /* ------------------------------------------------------------------------- */ /** Draws the white + border of the eye. */ void _draw_around(cairo_t *cairo_ptr, double x, double y, int width, int height) { double diag = sqrt(width * width + height * height); cairo_save(cairo_ptr); cairo_translate(cairo_ptr, x * width, y * height); cairo_scale(cairo_ptr, 0.2 * width / diag, 0.4 * height / diag); cairo_set_line_width(cairo_ptr, 0); cairo_set_source_rgb(cairo_ptr, 1.0, 1.0, 1.0); cairo_arc(cairo_ptr, 0.0, 0.0, diag, 0, 2 * M_PI); cairo_fill(cairo_ptr); cairo_set_line_width(cairo_ptr, sqrt(width * width + height * height) / 10); cairo_set_source_rgb(cairo_ptr, 0.0, 0.0, 0.0); cairo_arc(cairo_ptr, 0.0, 0.0, diag, 0, 2 * M_PI); cairo_stroke(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ /** Draws the eyes' pupil, in relative coordinates. */ void _draw_pupil(cairo_t *cairo_ptr, double pointer_x, double pointer_y, double px, double py, double w, double h, int width, int height) { double rel_x = pointer_x - px; double rel_y = pointer_y - py; // Scale the position back to the ellipsis. double ratio = sqr(rel_x / w) + sqr(rel_y / h); if (ratio > 1.0) { rel_x = rel_x / sqrt(ratio); rel_y = rel_y / sqrt(ratio); } int x = width * (rel_x + px); int y = height * (rel_y + py); cairo_save(cairo_ptr); cairo_set_source_rgb(cairo_ptr, 0.0, 0.0, 0.0); cairo_set_line_width(cairo_ptr, sqrt(width * width + height * height) / 15); cairo_set_line_cap(cairo_ptr, CAIRO_LINE_CAP_ROUND); cairo_move_to(cairo_ptr, x, y); cairo_line_to(cairo_ptr, x, y); cairo_stroke(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ /** Draws something into the buffer. */ static bool _callback(bs_gfxbuf_t *gfxbuf_ptr, __UNUSED__ void *ud_ptr) { bs_gfxbuf_clear(gfxbuf_ptr, 0); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) return false; _draw_around(cairo_ptr, 0.25, 0.5, gfxbuf_ptr->width, gfxbuf_ptr->height); _draw_around(cairo_ptr, 0.75, 0.5, gfxbuf_ptr->width, gfxbuf_ptr->height); _draw_pupil(cairo_ptr, pointer_x, pointer_y, 0.25, 0.5, 0.13, 0.3, gfxbuf_ptr->width, gfxbuf_ptr->height); _draw_pupil(cairo_ptr, pointer_x, pointer_y, 0.75, 0.5, 0.13, 0.3, gfxbuf_ptr->width, gfxbuf_ptr->height); return true; } /* ------------------------------------------------------------------------- */ /** Updates pointer position. */ static void _position_callback(double x, double y, void *ud_ptr) { wlclient_xdg_toplevel_t *toplevel_ptr = ud_ptr; pointer_x = x; pointer_y = y; wlclient_xdg_toplevel_register_ready_callback( toplevel_ptr, _callback, toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** Called when the icon is ready to refresh. */ static bool _icon_callback(bs_gfxbuf_t *gfxbuf_ptr, __UNUSED__ void *ud_ptr) { bs_gfxbuf_clear(gfxbuf_ptr, 0); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) return false; _draw_around(cairo_ptr, 0.25, 0.5, gfxbuf_ptr->width, gfxbuf_ptr->height); _draw_around(cairo_ptr, 0.75, 0.5, gfxbuf_ptr->width, gfxbuf_ptr->height); _draw_pupil(cairo_ptr, icon_pointer_x, icon_pointer_y, 0.25, 0.5, 0.13, 0.3, gfxbuf_ptr->width, gfxbuf_ptr->height); _draw_pupil(cairo_ptr, icon_pointer_x, icon_pointer_y, 0.75, 0.5, 0.13, 0.3, gfxbuf_ptr->width, gfxbuf_ptr->height); return true; } /* ------------------------------------------------------------------------- */ /** Updates pointer position for the icon. */ static void _icon_position_callback(double x, double y, void *ud_ptr) { wlclient_icon_t *icon_ptr = ud_ptr; icon_pointer_x = x; icon_pointer_y = y; wlclient_icon_register_ready_callback( icon_ptr, _icon_callback, icon_ptr); } /* == Main program ========================================================= */ /** Main program. */ int main(int argc, const char **argv) { bs_log_severity = BS_INFO; if (!bs_arg_parse(_wlmeyes_args, BS_ARG_MODE_NO_EXTRA, &argc, argv)) { bs_arg_print_usage(stderr, _wlmeyes_args); return EXIT_FAILURE; } wlclient_ptr = wlclient_create("wlmaker.wlmeyes"); if (NULL == wlclient_ptr) return EXIT_FAILURE; _key_listener.notify = _handle_key; wl_signal_add(&wlclient_events(wlclient_ptr)->key, &_key_listener); if (wlclient_xdg_supported(wlclient_ptr)) { wlclient_xdg_toplevel_t *toplevel_ptr = wlclient_xdg_toplevel_create( wlclient_ptr, "wlmaker Toplevel Example", toplevel_width, toplevel_height); if (NULL == toplevel_ptr) { bs_log(BS_ERROR, "Failed wlclient_xdg_toplevel_create(%p)", wlclient_ptr); } else { wlclient_xdg_decoration_set_server_side(toplevel_ptr, false); wlclient_xdg_toplevel_register_ready_callback( toplevel_ptr, _callback, toplevel_ptr); wlclient_xdg_toplevel_register_position_callback( toplevel_ptr, _position_callback, toplevel_ptr); wlclient_icon_t *icon_ptr = wlclient_icon_create(wlclient_ptr); if (NULL != icon_ptr) { wlclient_icon_register_ready_callback( icon_ptr, _icon_callback, icon_ptr); wlclient_icon_register_position_callback( icon_ptr, _icon_position_callback, icon_ptr); } wlclient_run(wlclient_ptr); wlclient_xdg_toplevel_destroy(toplevel_ptr); } } else { bs_log(BS_ERROR, "XDG shell is not supported."); } wl_list_remove(&_key_listener.link); wlclient_destroy(wlclient_ptr); return EXIT_SUCCESS; } /* == End of wlmeyes.c ===================================================== */ wlmaker-0.8/apps/wlmmemgraph.c0000644000175100017510000002405115203543557016073 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmmemgraph.c * * Memory usage graph dock-app for wlmaker. * * @copyright * 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. */ #include "wlm_graph_shared.h" #include #include #include #include #include /** Application name. */ static const char _app_name[] = "wlmmemgraph"; /** Application help. */ static const char _app_help[] = "Displays memory usage as a scrolling graph.\n" "\n" "Shows stacked memory categories:\n" " - Cached: top of graph (dark blue)\n" " - Buffers: middle of graph (dark cyan)\n" " - Used: bottom of graph (green)\n" "\n" "The label displays total memory usage."; /** Line buffer size for /proc/meminfo parsing. */ #define LINE_BUFFER_SIZE 256 /** Number of fields to parse from /proc/meminfo. */ #define MEMINFO_FIELD_COUNT 5 /* == Definitions ========================================================== */ /** Number of memory categories tracked. */ #define MEM_CATEGORY_COUNT 3 /** Memory category indices. */ enum { /** Cached (including SReclaimable). */ MEM_CATEGORY_CACHED = 0, /** Buffers. */ MEM_CATEGORY_BUFFERS = 1, /** Used (non-reclaimable). */ MEM_CATEGORY_USED = 2, }; /* ------------------------------------------------------------------------- */ /** * Generates blue-to-green gradient LUT (256 entries). * * With 3 memory categories, only indices 0, 127, and 255 are sampled * (mapping count 1, 2, 3 respectively). The full 256 entries are required * by the API but only 3 discrete colors matter for this use case. * * @param lut Output LUT array (256 entries). */ static void _memgraph_lut_init(uint32_t lut[256]) { for (uint32_t i = 0; i < 256; i++) { // Blue to green gradient, with lower values (cached/buffers) darker. // Cached and buffers are darker as this memory is technically free. // Brightness scales from 1/3 at i=0 to 2/3 at i=255. const uint32_t brightness = 85 + (i * 85) / 255; // 85-170 (1/3-2/3) const uint8_t g = (uint8_t)((i * brightness) / 255); const uint8_t b = (uint8_t)(((255 - i) * brightness) / 255); lut[i] = 0xff000000 | ((uint32_t)g << 8) | (uint32_t)b; } } /** Maximum length of the label string. */ #define LABEL_BUFFER_SIZE 16 /** Kilobytes per megabyte. */ #define KB_PER_MB 1024UL /** Kilobytes per gigabyte. */ #define KB_PER_GB (1024UL * 1024) /** Kilobytes per terabyte. */ #define KB_PER_TB (1024UL * 1024 * 1024) /** State for the memory graph (mutable runtime data). */ typedef struct { /** Open file handle to /proc/meminfo. */ FILE *proc_fp; /** Total memory in kB (from /proc/meminfo). */ unsigned long mem_total_kb; /** Used memory in kB (non-reclaimable). */ unsigned long mem_used_kb; /** Formatted label string. */ char label[LABEL_BUFFER_SIZE]; } memgraph_state_t; /* == Cleanup ============================================================== */ /* ------------------------------------------------------------------------- */ /** Frees all allocated state. */ static void _state_free(void *app_state) { memgraph_state_t *state = app_state; if (NULL != state->proc_fp) { fclose(state->proc_fp); state->proc_fp = NULL; } } /* == Label ================================================================ */ /* ------------------------------------------------------------------------- */ /** * Formats memory size with appropriate suffix (GB, MB, kB). * * @param buf Output buffer. * @param buf_size Size of output buffer. * @param kb Memory size in kilobytes. */ static void _format_memory_size(char *buf, const size_t buf_size, const unsigned long kb) { BS_ASSERT(0 < buf_size); if (kb >= KB_PER_TB) { // TB range: X.XXXX TB (4 decimal places). const unsigned long scale = 10000; const unsigned long val = (kb * scale) / KB_PER_TB; snprintf(buf, buf_size, "%lu.%04lu TB", val / scale, val % scale); } else if (kb >= KB_PER_GB) { // GB range: X.XX GB (2 decimal places). const unsigned long scale = 100; const unsigned long val = (kb * scale) / KB_PER_GB; snprintf(buf, buf_size, "%lu.%02lu GB", val / scale, val % scale); } else if (kb >= KB_PER_MB) { // MB range: X.X MB (1 decimal place). const unsigned long scale = 10; const unsigned long val = (kb * scale) / KB_PER_MB; snprintf(buf, buf_size, "%lu.%lu MB", val / scale, val % scale); } else { // kB range. snprintf(buf, buf_size, "%lu kB", kb); } buf[buf_size - 1] = '\0'; } /* ------------------------------------------------------------------------- */ /** Returns the formatted memory usage label. */ static const char *_label_fn(void *app_state) { memgraph_state_t *state = app_state; return state->label; } /* == Memory statistics ==================================================== */ /** * Matches line label against a string literal. * * Compares `label_len` bytes of `line` against the literal. Used for parsing * /proc/meminfo where each line has format "Label: value kB". * * @param literal String literal to match against. */ #define LABEL_MATCH(literal) \ (sizeof(literal) - 1 == label_len && 0 == memcmp(line, literal, sizeof(literal) - 1)) /* ------------------------------------------------------------------------- */ /** * Reads memory statistics from /proc/meminfo. * * Parses MemTotal, MemFree, Buffers, Cached, and SReclaimable to compute * per-category usage percentages. * * @param app_state App state (memgraph_state_t pointer). * @param values Buffer to fill (may reallocate data/num). * * @return WLM_GRAPH_READ_OK on success, WLM_GRAPH_READ_ERROR on error. */ static wlm_graph_read_result_t _stats_read_fn(void *app_state, wlm_graph_values_t *values) { memgraph_state_t * const state = app_state; FILE * const fp = state->proc_fp; if (NULL == fp) { return WLM_GRAPH_READ_ERROR; } // Reallocate buffer if size doesn't match. if (MEM_CATEGORY_COUNT != values->num) { uint8_t *new_buf = realloc(values->data, MEM_CATEGORY_COUNT); if (NULL == new_buf) { return WLM_GRAPH_READ_ERROR; } values->data = new_buf; values->num = MEM_CATEGORY_COUNT; } rewind(fp); unsigned long mem_total = 0; unsigned long mem_free = 0; unsigned long buffers = 0; unsigned long cached = 0; unsigned long sreclaimable = 0; char line[LINE_BUFFER_SIZE]; size_t label_len = 0; int fields_found = 0; while (NULL != fgets(line, sizeof(line), fp) && fields_found < MEMINFO_FIELD_COUNT) { // Find the colon to extract the value efficiently. char * const colon = strchr(line, ':'); if (NULL == colon) { continue; } unsigned long value; if (1 != sscanf(colon + 1, "%lu", &value)) { continue; } // Match label by prefix length (colon position). label_len = colon - line; if (LABEL_MATCH("MemTotal")) { mem_total = value; fields_found++; } else if (LABEL_MATCH("MemFree")) { mem_free = value; fields_found++; } else if (LABEL_MATCH("Buffers")) { buffers = value; fields_found++; } else if (LABEL_MATCH("Cached")) { cached = value; fields_found++; } else if (LABEL_MATCH("SReclaimable")) { sreclaimable = value; fields_found++; } } if (0 == mem_total) { return WLM_GRAPH_READ_ERROR; } // Calculate usage percentages (0-255). // Used = MemTotal - MemFree - Buffers - Cached - SReclaimable const unsigned long reclaimable = buffers + cached + sreclaimable; unsigned long used = 0; if (mem_total > mem_free + reclaimable) { used = mem_total - mem_free - reclaimable; } // Scale each category to 0-255 based on total memory. values->data[MEM_CATEGORY_USED] = (uint8_t)((used * 255) / mem_total); values->data[MEM_CATEGORY_BUFFERS] = (uint8_t)((buffers * 255) / mem_total); values->data[MEM_CATEGORY_CACHED] = (uint8_t)(((cached + sreclaimable) * 255) / mem_total); // Store values and format label for display. state->mem_total_kb = mem_total; state->mem_used_kb = used; _format_memory_size(state->label, sizeof(state->label), used); return WLM_GRAPH_READ_OK; } #undef LABEL_MATCH /* == Main program ========================================================= */ /** Main program. */ int main(const int argc, const char **argv) { memgraph_state_t state = {}; state.proc_fp = fopen("/proc/meminfo", "r"); if (NULL == state.proc_fp) { bs_log(BS_ERROR | BS_ERRNO, "Failed to open /proc/meminfo"); return EXIT_FAILURE; } // Initialize custom LUT (blue-to-green gradient). uint32_t pixel_lut[256]; _memgraph_lut_init(pixel_lut); const wlm_graph_app_config_t config = { .app_name = _app_name, .app_help = _app_help, .accumulate_mode = WLM_GRAPH_ACCUMULATE_MODE_STACKED, .stats_read_fn = _stats_read_fn, .app_state = &state, .state_free_fn = _state_free, .pixel_lut = pixel_lut, .label_fn = _label_fn, }; return wlm_graph_app_run(argc, argv, &config); } /* == End of wlmmemgraph.c ================================================= */ wlmaker-0.8/apps/libwlclient/0000755000175100017510000000000015203543557015715 5ustar runnerrunnerwlmaker-0.8/apps/libwlclient/dblbuf.c0000644000175100017510000003104215203543557017317 0ustar runnerrunner/* ========================================================================= */ /** * @file dblbuf.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 "dblbuf.h" #include #include #include #include #include #include #include #include #include #include struct wl_buffer; struct wl_callback; struct wl_shm_pool; /* == Declarations ========================================================= */ /** How many buffers to hold for the double buffer: Two. */ #define _WLCL_DBLBUF_NUM 2 /** A single buffer. Two of these are backing the double-buffer. */ typedef struct { /** The wayland buffer structure. */ struct wl_buffer *wl_buffer_ptr; /** Pixel buffer we're using for clients. */ bs_gfxbuf_t *gfxbuf_ptr; /** Back-link to the double-buffer. */ wlcl_dblbuf_t *dblbuf_ptr; } wlcl_buffer_t; /** State of double-buffered shared memory. */ struct _wlcl_dblbuf_t { /** With of the buffer, in pixels. */ unsigned width; /** Height of the buffer, in pixels. */ unsigned height; /** Holds the two @ref wlcl_buffer_t backing this double buffer. */ wlcl_buffer_t buffers[_WLCL_DBLBUF_NUM]; /** Holds @ref wlcl_dblbuf_t::buffers items that are released. */ wlcl_buffer_t *released_buffer_ptrs[_WLCL_DBLBUF_NUM]; /** Number of items in @ref wlcl_dblbuf_t::released_buffer_ptrs. */ int released; /** Indicates that a frame is due to be drawn. */ bool frame_is_due; /** Blob of memory-mapped buffer data. */ void *data_ptr; /** Size of @ref wlcl_dblbuf_t::data_ptr. */ size_t data_size; /** Will be called when the buffer is ready to draw into. */ wlcl_dblbuf_ready_callback_t callback; /** Argument to @ref wlcl_dblbuf_t::callback. */ void *callback_ud_ptr; /** Surface that this double buffer is operating on. */ struct wl_surface *wl_surface_ptr; }; static void _wlcl_dblbuf_callback_if_ready(wlcl_dblbuf_t *dblbuf_ptr); static void _wlcl_dblbuf_handle_frame_done( void *data_ptr, struct wl_callback *callback, __UNUSED__ uint32_t time); static bool _wlcl_dblbuf_create_buffer( wlcl_buffer_t *buffer_ptr, wlcl_dblbuf_t *dblbuf_ptr, struct wl_shm_pool *wl_shm_pool_ptr, unsigned page, unsigned width, unsigned height); static void _wlcl_dblbuf_handle_wl_buffer_release( void *data_ptr, struct wl_buffer *wl_buffer_ptr); static int _wlcl_dblbuf_shm_create(const char *app_id_ptr, size_t size); /* == Data ================================================================= */ /** How many attempts to try shm_open before giving up. */ static const uint32_t SHM_OPEN_RETRIES = 256; /** Listener implementation for the `wl_buffer`. */ static const struct wl_buffer_listener _wlcl_dblbuf_wl_buffer_listener = { .release = _wlcl_dblbuf_handle_wl_buffer_release, }; /** Listener implementation for the frame. */ static const struct wl_callback_listener _wlcl_dblbuf_frame_listener = { .done = _wlcl_dblbuf_handle_frame_done }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlcl_dblbuf_t *wlcl_dblbuf_create( const char *app_id_ptr, struct wl_surface *wl_surface_ptr, struct wl_shm *wl_shm_ptr, unsigned width, unsigned height) { wlcl_dblbuf_t *dblbuf_ptr = logged_calloc(1, sizeof(wlcl_dblbuf_t)); if (NULL == dblbuf_ptr) return NULL; dblbuf_ptr->width = width; dblbuf_ptr->height = height; dblbuf_ptr->wl_surface_ptr = BS_ASSERT_NOTNULL(wl_surface_ptr); dblbuf_ptr->data_size = 2 * width * height * sizeof(uint32_t); int fd = _wlcl_dblbuf_shm_create(app_id_ptr, dblbuf_ptr->data_size); if (0 >= fd) goto error; dblbuf_ptr->data_ptr = mmap( NULL, dblbuf_ptr->data_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (MAP_FAILED == dblbuf_ptr->data_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed mmap(NULL, %zu, " "PROT_READ|PROT_WRITE, MAP_SHARED, %d, 0)", dblbuf_ptr->data_size, fd); close(fd); goto error; } struct wl_shm_pool *wl_shm_pool_ptr = wl_shm_create_pool( wl_shm_ptr, fd, dblbuf_ptr->data_size); close(fd); if (NULL == wl_shm_pool_ptr) { bs_log(BS_ERROR, "Failed wl_shm_create_pool(%p, %d, %zu)", wl_shm_ptr, fd, dblbuf_ptr->data_size); goto error; } for (int i = 0; i < _WLCL_DBLBUF_NUM; ++i) { if (_wlcl_dblbuf_create_buffer( &dblbuf_ptr->buffers[i], dblbuf_ptr, wl_shm_pool_ptr, i, width, height)) { _wlcl_dblbuf_handle_wl_buffer_release( &dblbuf_ptr->buffers[i], dblbuf_ptr->buffers[i].wl_buffer_ptr); } } if (dblbuf_ptr->released != _WLCL_DBLBUF_NUM) goto error; dblbuf_ptr->frame_is_due = true; return dblbuf_ptr; error: wlcl_dblbuf_destroy(dblbuf_ptr); return NULL; } /* ------------------------------------------------------------------------- */ void wlcl_dblbuf_destroy(wlcl_dblbuf_t *dblbuf_ptr) { for (int i = 0; i < _WLCL_DBLBUF_NUM; ++i) { wlcl_buffer_t *buffer_ptr = &dblbuf_ptr->buffers[i]; if (NULL != buffer_ptr->wl_buffer_ptr) { wl_buffer_destroy(buffer_ptr->wl_buffer_ptr); buffer_ptr->wl_buffer_ptr = NULL; } if (NULL != buffer_ptr->gfxbuf_ptr) { bs_gfxbuf_destroy(buffer_ptr->gfxbuf_ptr); buffer_ptr->gfxbuf_ptr = NULL; } } if (NULL != dblbuf_ptr->data_ptr) { munmap(dblbuf_ptr->data_ptr, dblbuf_ptr->data_size); dblbuf_ptr->data_ptr = NULL; } free(dblbuf_ptr); } /* ------------------------------------------------------------------------- */ void wlcl_dblbuf_register_ready_callback( wlcl_dblbuf_t *dblbuf_ptr, wlcl_dblbuf_ready_callback_t callback, void *callback_ud_ptr) { dblbuf_ptr->callback = callback; dblbuf_ptr->callback_ud_ptr = callback_ud_ptr; _wlcl_dblbuf_callback_if_ready(dblbuf_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Calls @ref wlcl_dblbuf_t::callback, if it is registered, a frame is due, * and if there are available buffers. If so, and if the callback returns * true, the corresponding buffer will be attached to the surface and the * surface is committed. * * @param dblbuf_ptr */ void _wlcl_dblbuf_callback_if_ready(wlcl_dblbuf_t *dblbuf_ptr) { // Only proceed a frame is due, the client asked, and we have a buffer. if (!dblbuf_ptr->callback || !dblbuf_ptr->frame_is_due || 0 >= dblbuf_ptr->released) return; wlcl_buffer_t *buffer_ptr = dblbuf_ptr->released_buffer_ptrs[--dblbuf_ptr->released]; dblbuf_ptr->frame_is_due = false; wlcl_dblbuf_ready_callback_t callback = dblbuf_ptr->callback; dblbuf_ptr->callback = NULL; if (!callback( buffer_ptr->gfxbuf_ptr, dblbuf_ptr->callback_ud_ptr)) { dblbuf_ptr->released_buffer_ptrs[dblbuf_ptr->released++] = buffer_ptr; dblbuf_ptr->frame_is_due = true; return; } wl_surface_damage_buffer( dblbuf_ptr->wl_surface_ptr, 0, 0, INT32_MAX, INT32_MAX); struct wl_callback *wl_callback = wl_surface_frame( dblbuf_ptr->wl_surface_ptr); wl_callback_add_listener( wl_callback, &_wlcl_dblbuf_frame_listener, dblbuf_ptr); dblbuf_ptr->frame_is_due = false; wl_surface_attach( dblbuf_ptr->wl_surface_ptr, buffer_ptr->wl_buffer_ptr, 0, 0); wl_surface_commit(dblbuf_ptr->wl_surface_ptr); } /* ------------------------------------------------------------------------- */ /** Callback for when the compositor indicates a frame is due. */ void _wlcl_dblbuf_handle_frame_done( void *data_ptr, struct wl_callback *callback, __UNUSED__ uint32_t time) { wl_callback_destroy(callback); wlcl_dblbuf_t *dblbuf_ptr = data_ptr; dblbuf_ptr->frame_is_due = true; _wlcl_dblbuf_callback_if_ready(dblbuf_ptr); } /* ------------------------------------------------------------------------- */ /** * Helper: Creates a `struct wl_buffer` from the `wl_shm_pool_ptr` at given * dimensions and page number, and stores all into `buffer_ptr`. * * @param buffer_ptr * @param dblbuf_ptr * @param wl_shm_pool_ptr * @param page * @param width * @param height * * @return true on success. */ bool _wlcl_dblbuf_create_buffer( wlcl_buffer_t *buffer_ptr, wlcl_dblbuf_t *dblbuf_ptr, struct wl_shm_pool *wl_shm_pool_ptr, unsigned page, unsigned width, unsigned height) { buffer_ptr->dblbuf_ptr = dblbuf_ptr; buffer_ptr->wl_buffer_ptr = wl_shm_pool_create_buffer( wl_shm_pool_ptr, page * width * height * sizeof(uint32_t), width, height, width * sizeof(uint32_t), WL_SHM_FORMAT_ARGB8888); if (NULL == buffer_ptr->wl_buffer_ptr) { bs_log(BS_ERROR, "Failed wl_shm_pool_create_buffer(%p, %zu, %u, %u, " "%zu, WL_SHM_FORMAT_ARGB8888)", wl_shm_pool_ptr, page * width * height * sizeof(uint32_t), width, height, width * sizeof(uint32_t)); return false; } buffer_ptr->gfxbuf_ptr = bs_gfxbuf_create_unmanaged( width, height, width, (uint32_t*)dblbuf_ptr->data_ptr + page * width * height); if (NULL == buffer_ptr->gfxbuf_ptr) return false; wl_buffer_add_listener( buffer_ptr->wl_buffer_ptr, &_wlcl_dblbuf_wl_buffer_listener, buffer_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Handles the `release` notification of the wl_buffer interface. * * @param data_ptr * @param wl_buffer_ptr */ static void _wlcl_dblbuf_handle_wl_buffer_release( void *data_ptr, struct wl_buffer *wl_buffer_ptr) { wlcl_buffer_t *buffer_ptr = data_ptr; BS_ASSERT(buffer_ptr->wl_buffer_ptr == wl_buffer_ptr); wlcl_dblbuf_t *dblbuf_ptr = buffer_ptr->dblbuf_ptr; dblbuf_ptr->released_buffer_ptrs[dblbuf_ptr->released++] = buffer_ptr; _wlcl_dblbuf_callback_if_ready(dblbuf_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates a POSIX shared memory object and allocates `size` bytes to it. * * @param app_id_ptr * @param size * * @return The file descriptor (a non-negative integer) on success, or -1 on * failure. The file descriptor must be closed with close(2). */ int _wlcl_dblbuf_shm_create(const char *app_id_ptr, size_t size) { char shm_name[NAME_MAX]; int fd = -1; shm_name[0] = '\0'; for (uint32_t sequence = 0; sequence < SHM_OPEN_RETRIES; ++sequence) { snprintf(shm_name, NAME_MAX, "/%s_%"PRIdMAX"_shm_%"PRIx64"_%"PRIu32, app_id_ptr ? app_id_ptr : "wlclient", (intmax_t)getpid(), bs_usec(), sequence); fd = shm_open(shm_name, O_RDWR|O_CREAT|O_EXCL, 0600); if (0 > fd && errno == EEXIST) continue; if (0 < fd) break; bs_log(BS_WARNING | BS_ERRNO, "Failed shm_open(\"%s\", O_RDWR|O_CREAT|O_EXCL, 0600)", shm_name); return -1; } if (0 != shm_unlink(shm_name)) { bs_log(BS_ERROR | BS_ERRNO, "Failed shm_unlink(\"%s\")", shm_name); close(fd); return -1; } while (0 != ftruncate(fd, size)) { if (EINTR == errno) continue; // try again... bs_log(BS_ERROR | BS_ERRNO, "Failed ftruncate(%d, %zu)", fd, size); close(fd); return -1; } return fd; } /* == End of dblbuf.c ====================================================== */ wlmaker-0.8/apps/libwlclient/xdg_toplevel.h0000644000175100017510000000571715203543557020574 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_toplevel.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 __LIBWLCLIENT_XDG_TOPLEVEL_H__ #define __LIBWLCLIENT_XDG_TOPLEVEL_H__ #include #include #include "libwlclient.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: State of the toplevel. */ typedef struct _wlclient_xdg_toplevel_t wlclient_xdg_toplevel_t; /** * Creates a XDG toplevel. * * @param wlclient_ptr * @param title_ptr * @param width * @param height * * @return State of the toplevel or NULL on error. */ wlclient_xdg_toplevel_t *wlclient_xdg_toplevel_create( wlclient_t *wlclient_ptr, const char *title_ptr, unsigned width, unsigned height); /** * Destroys the XDG toplevel. * * @param toplevel_ptr */ void wlclient_xdg_toplevel_destroy(wlclient_xdg_toplevel_t *toplevel_ptr); /** * Returns whether the XDG shell protocol is supported on the client. * * @param wlclient_ptr */ bool wlclient_xdg_supported(wlclient_t *wlclient_ptr); /** * Sets XDG decoration mode to "server side". * * @param toplevel_ptr * @param enabled Whether to enable server-side decoration. If * false, will set client-side decoration. * * @return true if the XDG decoration protocol is supported. */ bool wlclient_xdg_decoration_set_server_side( wlclient_xdg_toplevel_t *toplevel_ptr, bool enabled); /** * Registers the callback to notify when the buffer is ready to draw into. * * @param toplevel_ptr * @param callback * @param callback_ud_ptr */ void wlclient_xdg_toplevel_register_ready_callback( wlclient_xdg_toplevel_t *toplevel_ptr, bool (*callback)(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr), void *callback_ud_ptr); /** * Registers the callback to notify the pointer position relative to the * toplevel's surface. * * @param toplevel_ptr * @param callback * @param callback_ud_ptr */ void wlclient_xdg_toplevel_register_position_callback( wlclient_xdg_toplevel_t *toplevel_ptr, void (*callback)(double x, double y, void *ud_ptr), void *callback_ud_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBWLCLIENT_XDG_TOPLEVEL_H__ */ /* == End of xdg_toplevel.h ================================================== */ wlmaker-0.8/apps/libwlclient/client.c0000644000175100017510000010265115203543557017344 0ustar runnerrunner/* ========================================================================= */ /** * @file client.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 "libwlclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wlmaker-icon-unstable-v1-client-protocol.h" #include "ext-input-observation-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "xdg-decoration-client-protocol.h" struct wl_keyboard; struct wl_pointer; struct wl_registry; struct wl_seat; struct wl_surface; /* == Declarations ========================================================= */ /** State of the wayland client. */ struct _wlclient_t { /** Shareable attributes. */ wlclient_attributes_t attributes; /** Events. */ wlclient_events_t events; /** XKB context. */ struct xkb_context *xkb_context_ptr; /** XKB keymap. */ struct xkb_keymap *xkb_keymap_ptr; /** XKB state. */ struct xkb_state *xkb_state_ptr; /** Registry singleton for the above connection. */ struct wl_registry *wl_registry_ptr; /** Pointer state, if & when the seat has the capability. */ struct wl_pointer *wl_pointer_ptr; /** Keyboard state, if & when the seat has the capability. */ struct wl_keyboard *wl_keyboard_ptr; /** List of registered timers. TODO(kaeser@gubbe.ch): Replace with HEAP. */ bs_dllist_t timers; /** File descriptor to monitor SIGINT. */ int signal_fd; /** Whether to keep the client running. */ volatile bool keep_running; }; /** State of a registered timer. */ typedef struct { /** Node within the list of timers, see `wlclient_t.timers`. */ bs_dllist_node_t dlnode; /** Target time, in usec since epoch. */ uint64_t target_usec; /** Callback once the timer is triggered. */ wlclient_callback_t callback; /** Argument to the callback. */ void *callback_ud_ptr; } wlclient_timer_t; /** Descriptor for a wayland object to bind to. */ typedef struct { /** The interface definition. */ const struct wl_interface *wl_interface_ptr; /** Version desired to bind to. */ uint32_t desired_version; /** Offset of the bound interface, relative to `wlclient_t`. */ size_t bound_ptr_offset; /** Additional setup for this wayland object. */ void (*setup)(wlclient_t *client_ptr); } object_t; static void wl_to_bs_log( const char *fmt, va_list args); static void handle_global_announce( void *data_ptr, struct wl_registry *wl_registry_ptr, uint32_t name, const char *interface_ptr, uint32_t version); static void handle_global_remove( void *data_ptr, struct wl_registry *registry, uint32_t name); static wlclient_timer_t *wlc_timer_create( wlclient_t *client_ptr, uint64_t target_usec, wlclient_callback_t callback, void *callback_ud_ptr); static void wlc_timer_destroy( wlclient_timer_t *timer_ptr); static void wlc_seat_setup(wlclient_t *client_ptr); static void wlc_seat_handle_capabilities( void *data_ptr, struct wl_seat *wl_seat_ptr, uint32_t capabilities); static void wlc_seat_handle_name( void *data_ptr, struct wl_seat *wl_seat_ptr, const char *name_ptr); static void wlc_pointer_handle_enter( void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y); static void wlc_pointer_handle_leave( void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface); static void wlc_pointer_handle_motion( void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); static void wlc_pointer_handle_button( void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); static void wlc_pointer_handle_axis( void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); static void wlc_pointer_handle_frame( void *data, struct wl_pointer *wl_pointer); static void wlc_pointer_handle_axis_source( void *data, struct wl_pointer *wl_pointer, uint32_t axis_source); static void wlc_pointer_handle_axis_stop( void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis); static void wlc_pointer_handle_axis_discrete( void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete); static void _wlc_keyboard_handle_keymap( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, uint32_t format, int32_t fd, uint32_t size); static void _wlc_keyboard_handle_enter( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, uint32_t serial, struct wl_surface *wl_surface_ptr, struct wl_array *keys); static void _wlc_keyboard_handle_leave( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, uint32_t serial, struct wl_surface *wl_surface_ptr); static void _wlc_keyboard_handle_key( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); static void _wlc_keyboard_handle_modifiers( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); static void _wlc_keyboard_handle_repeat_info( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, int32_t rate, int32_t delay); /* == Data ================================================================= */ /** Listener for the registry, taking note of registry updates. */ static const struct wl_registry_listener registry_listener = { .global = handle_global_announce, .global_remove = handle_global_remove, }; /** Listeners for the seat. */ static const struct wl_seat_listener wlc_seat_listener = { .capabilities = wlc_seat_handle_capabilities, .name = wlc_seat_handle_name, }; /** Listeners for the pointer. */ static const struct wl_pointer_listener wlc_pointer_listener = { .enter = wlc_pointer_handle_enter, .leave = wlc_pointer_handle_leave, .motion = wlc_pointer_handle_motion, .button = wlc_pointer_handle_button, .axis = wlc_pointer_handle_axis, .frame = wlc_pointer_handle_frame, .axis_source = wlc_pointer_handle_axis_source, .axis_stop = wlc_pointer_handle_axis_stop, .axis_discrete = wlc_pointer_handle_axis_discrete, }; /** Listeners for the keyboard. */ static const struct wl_keyboard_listener wlc_keyboard_listener = { .keymap = _wlc_keyboard_handle_keymap, .enter = _wlc_keyboard_handle_enter, .leave = _wlc_keyboard_handle_leave, .key = _wlc_keyboard_handle_key, .modifiers = _wlc_keyboard_handle_modifiers, .repeat_info = _wlc_keyboard_handle_repeat_info }; /** List of wayland objects we want to bind to. */ static const object_t objects[] = { { &wl_compositor_interface, 4, offsetof(wlclient_attributes_t, wl_compositor_ptr), NULL }, { &wl_shm_interface, 1, offsetof(wlclient_attributes_t, wl_shm_ptr), NULL }, { &xdg_wm_base_interface, 1, offsetof(wlclient_attributes_t, xdg_wm_base_ptr), NULL }, { &wl_seat_interface, 5, offsetof(wlclient_attributes_t, wl_seat_ptr), wlc_seat_setup }, { &zwlmaker_icon_manager_v1_interface, 1, offsetof(wlclient_attributes_t, icon_manager_ptr), NULL }, { &zxdg_decoration_manager_v1_interface, 1, offsetof(wlclient_attributes_t, xdg_decoration_manager_ptr), NULL }, { &ext_input_observation_manager_v1_interface, 1, offsetof(wlclient_attributes_t, input_observation_manager_ptr), NULL }, { NULL, 0, 0, NULL } // sentinel. }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlclient_t *wlclient_create(const char *app_id_ptr) { wlclient_t *wlclient_ptr = logged_calloc(1, sizeof(wlclient_t)); if (NULL == wlclient_ptr) return NULL; wl_log_set_handler_client(wl_to_bs_log); wl_signal_init(&wlclient_ptr->events.key); if (NULL != app_id_ptr) { wlclient_ptr->attributes.app_id_ptr = logged_strdup(app_id_ptr); if (NULL == wlclient_ptr->attributes.app_id_ptr) { wlclient_destroy(wlclient_ptr); return NULL; } } sigset_t signal_set; if (sigemptyset(&signal_set)) { bs_log(BS_ERROR | BS_ERRNO, "Failed sigemptyset(%p)", &signal_set); wlclient_destroy(wlclient_ptr); return NULL; } if (sigaddset(&signal_set, SIGINT)) { bs_log(BS_ERROR | BS_ERRNO, "Failed sigemptyset(%p, %d)", &signal_set, SIGINT); wlclient_destroy(wlclient_ptr); return NULL; } if (sigprocmask(SIG_BLOCK, &signal_set, NULL) == -1) { bs_log(BS_ERROR | BS_ERRNO, "Failed sigprocmask(SIG_BLOCK, %p, NULL)", &signal_set); wlclient_destroy(wlclient_ptr); return NULL; } wlclient_ptr->signal_fd = signalfd(-1, &signal_set, SFD_NONBLOCK); if (0 >= wlclient_ptr->signal_fd) { bs_log(BS_ERROR | BS_ERRNO, "Failed signalfd(-1, %p, SFD_NONBLOCK)", &signal_set); wlclient_destroy(wlclient_ptr); return NULL; } wlclient_ptr->xkb_context_ptr = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (NULL == wlclient_ptr->xkb_context_ptr) { bs_log(BS_ERROR, "Failex xkb_context_new(XKB_CONTEXT_NO_FLAGS)"); wlclient_destroy(wlclient_ptr); return NULL; } wlclient_ptr->attributes.wl_display_ptr = wl_display_connect(NULL); if (NULL == wlclient_ptr->attributes.wl_display_ptr) { bs_log(BS_ERROR, "Failed wl_display_connect(NULL)."); wlclient_destroy(wlclient_ptr); return NULL; } wlclient_ptr->wl_registry_ptr = wl_display_get_registry( wlclient_ptr->attributes.wl_display_ptr); if (NULL == wlclient_ptr->wl_registry_ptr) { bs_log(BS_ERROR, "Failed wl_display_get_registry(%p).", wlclient_ptr->wl_registry_ptr); wlclient_destroy(wlclient_ptr); return NULL; } if (0 != wl_registry_add_listener( wlclient_ptr->wl_registry_ptr, ®istry_listener, &wlclient_ptr->attributes)) { bs_log(BS_ERROR, "Failed wl_registry_add_listener(%p, %p, %p).", wlclient_ptr->wl_registry_ptr, ®istry_listener, &wlclient_ptr->attributes); wlclient_destroy(wlclient_ptr); return NULL; } wl_display_roundtrip(wlclient_ptr->attributes.wl_display_ptr); if (NULL == wlclient_ptr->attributes.wl_compositor_ptr) { bs_log(BS_ERROR, "'wl_compositor' interface not found on Wayland."); wlclient_destroy(wlclient_ptr); return NULL; } if (NULL == wlclient_ptr->attributes.wl_shm_ptr) { bs_log(BS_ERROR, "'wl_shm' interface not found on Wayland."); wlclient_destroy(wlclient_ptr); return NULL; } if (NULL == wlclient_ptr->attributes.xdg_wm_base_ptr) { bs_log(BS_ERROR, "'xdg_wm_base' interface not found on Wayland."); wlclient_destroy(wlclient_ptr); return NULL; } // Hack: Somehow this propagates the protool far enough for getting // the pointer registered. wl_display_roundtrip(wlclient_ptr->attributes.wl_display_ptr); wl_display_roundtrip(wlclient_ptr->attributes.wl_display_ptr); wl_display_roundtrip(wlclient_ptr->attributes.wl_display_ptr); return wlclient_ptr; } /* ------------------------------------------------------------------------- */ void wlclient_destroy(wlclient_t *wlclient_ptr) { bs_dllist_node_t *dlnode_ptr; while (NULL != (dlnode_ptr = bs_dllist_pop_front(&wlclient_ptr->timers))) { wlc_timer_destroy((wlclient_timer_t*)dlnode_ptr); } if (NULL != wlclient_ptr->wl_registry_ptr) { wl_registry_destroy(wlclient_ptr->wl_registry_ptr); wlclient_ptr->wl_registry_ptr = NULL; } if (NULL != wlclient_ptr->attributes.wl_display_ptr) { wl_display_disconnect(wlclient_ptr->attributes.wl_display_ptr); wlclient_ptr->attributes.wl_display_ptr = NULL; } if (NULL != wlclient_ptr->xkb_state_ptr) { xkb_state_unref(wlclient_ptr->xkb_state_ptr); wlclient_ptr->xkb_state_ptr = NULL; } if (NULL != wlclient_ptr->xkb_keymap_ptr) { xkb_keymap_unref(wlclient_ptr->xkb_keymap_ptr); wlclient_ptr->xkb_keymap_ptr = NULL; } if (NULL != wlclient_ptr->xkb_context_ptr) { xkb_context_unref(wlclient_ptr->xkb_context_ptr); wlclient_ptr->xkb_context_ptr = NULL; } if (0 < wlclient_ptr->signal_fd) { close(wlclient_ptr->signal_fd); wlclient_ptr->signal_fd = 0; } if (NULL != wlclient_ptr->attributes.app_id_ptr) { // Cheated when saying it's const... free((char*)wlclient_ptr->attributes.app_id_ptr); wlclient_ptr->attributes.app_id_ptr = NULL; } free(wlclient_ptr); } /* ------------------------------------------------------------------------- */ const wlclient_attributes_t *wlclient_attributes( const wlclient_t *wlclient_ptr) { return &wlclient_ptr->attributes; } /* ------------------------------------------------------------------------- */ wlclient_events_t *wlclient_events(wlclient_t *wlclient_ptr) { return &wlclient_ptr->events; } /* ------------------------------------------------------------------------- */ // TODO(kaeser@gubbe.ch): Clean up. void wlclient_run(wlclient_t *wlclient_ptr) { wlclient_ptr->keep_running = true; do { while (0 != wl_display_prepare_read(wlclient_ptr->attributes.wl_display_ptr)) { if (0 > wl_display_dispatch_pending(wlclient_ptr->attributes.wl_display_ptr)) { bs_log(BS_ERROR | BS_ERRNO, "Failed wl_display_dispatch_pending(%p)", wlclient_ptr->attributes.wl_display_ptr); break; // Error (?) } } if (0 > wl_display_flush(wlclient_ptr->attributes.wl_display_ptr)) { if (EAGAIN != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed wl_display_flush(%p)", wlclient_ptr->attributes.wl_display_ptr); wl_display_cancel_read(wlclient_ptr->attributes.wl_display_ptr); break; // Error! } } struct pollfd pollfds[2]; pollfds[0].fd = wl_display_get_fd(wlclient_ptr->attributes.wl_display_ptr); pollfds[0].events = POLLIN; pollfds[0].revents = 0; pollfds[1].fd = wlclient_ptr->signal_fd; pollfds[1].events = POLLIN; pollfds[1].revents = 0; int rv = poll(&pollfds[0], 2, 100); if (0 > rv && EINTR != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed poll(%p, 1, 100)", &pollfds); wl_display_cancel_read(wlclient_ptr->attributes.wl_display_ptr); break; // Error! } if (pollfds[0].revents & POLLIN) { if (0 > wl_display_read_events(wlclient_ptr->attributes.wl_display_ptr)) { bs_log(BS_ERROR | BS_ERRNO, "Failed wl_display_read_events(%p)", wlclient_ptr->attributes.wl_display_ptr); break; // Error! } } else { wl_display_cancel_read(wlclient_ptr->attributes.wl_display_ptr); } if (pollfds[1].revents & POLLIN) { struct signalfd_siginfo siginfo; ssize_t rd = read(wlclient_ptr->signal_fd, &siginfo, sizeof(siginfo)); if (0 > rd) { bs_log(BS_ERROR, "Failed read(%d, %p, %zu)", wlclient_ptr->signal_fd, &siginfo, sizeof(siginfo)); break; } else if ((size_t)rd != sizeof(siginfo)) { bs_log(BS_ERROR, "Bytes read from signal_fd %zu != %zd", sizeof(siginfo), rd); break; } bs_log(BS_ERROR, "Signal caught: %d", siginfo.ssi_signo); wlclient_ptr->keep_running = false; } if (0 > wl_display_dispatch_pending(wlclient_ptr->attributes.wl_display_ptr)) { bs_log(BS_ERROR | BS_ERRNO, "Failed wl_display_dispatch_queue_pending(%p)", wlclient_ptr->attributes.wl_display_ptr); int err = wl_display_get_error(wlclient_ptr->attributes.wl_display_ptr); if (0 != err) { bs_log(BS_ERROR, "Display error %d", err); } uint32_t id; const struct wl_interface *wl_interface_ptr; uint32_t perr = wl_display_get_protocol_error( wlclient_ptr->attributes.wl_display_ptr, &wl_interface_ptr, &id); if (0 != perr) { bs_log(BS_ERROR, "Protocol error %"PRIu32", interface %s id %"PRIu32, perr, wl_interface_ptr->name, id); } break; // Error! } // Flush the timer queue. uint64_t current_usec = bs_usec(); bs_dllist_node_t *dlnode_ptr; while (NULL != (dlnode_ptr = wlclient_ptr->timers.head_ptr) && ((wlclient_timer_t*)dlnode_ptr)->target_usec <= current_usec) { bs_dllist_pop_front(&wlclient_ptr->timers); wlclient_timer_t *timer_ptr = (wlclient_timer_t*)dlnode_ptr; timer_ptr->callback(wlclient_ptr, timer_ptr->callback_ud_ptr); wlc_timer_destroy(timer_ptr); } } while (wlclient_ptr->keep_running); } /* ------------------------------------------------------------------------- */ void wlclient_request_terminate(wlclient_t *wlclient_ptr) { wlclient_ptr->keep_running = false; } /* ------------------------------------------------------------------------- */ bool wlclient_register_timer( wlclient_t *wlclient_ptr, uint64_t target_usec, wlclient_callback_t callback, void *callback_ud_ptr) { wlclient_timer_t *timer_ptr = wlc_timer_create( wlclient_ptr, target_usec, callback, callback_ud_ptr); return (timer_ptr != NULL); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Redirects a wayland log call into s_log. * * @param fmt_ptr * @param args */ void wl_to_bs_log( const char *fmt_ptr, va_list args) { bs_log_vwrite(BS_ERROR, __FILE__, __LINE__, fmt_ptr, args); } /* ------------------------------------------------------------------------- */ /** * Handles the announcement of a global object. * * Called by `struct wl_registry_listener` `global` callback, invoked to notify * clients of global objects. * * @param data_ptr Points to a @ref wlclient_t. * @param wl_registry_ptr The `struct wl_registry` this is invoked for. * @param name Numeric name of the global object. * @param interface_name_ptr Name of the interface implemented by the object. * @param version Interface version. */ void handle_global_announce( void *data_ptr, struct wl_registry *wl_registry_ptr, uint32_t name, const char *interface_name_ptr, uint32_t version) { for (const object_t *object_ptr = &objects[0]; NULL != object_ptr->wl_interface_ptr; ++object_ptr) { // Proceed, if the interface name doesn't match. if (0 != strcmp(interface_name_ptr, object_ptr->wl_interface_ptr->name)) { continue; } void *bound_ptr = wl_registry_bind( wl_registry_ptr, name, object_ptr->wl_interface_ptr, object_ptr->desired_version); if (NULL == bound_ptr) { bs_log(BS_ERROR, "Failed wl_registry_bind(%p, %"PRIu32", %p, %"PRIu32") " "for interface %s, version %"PRIu32".", wl_registry_ptr, name, object_ptr->wl_interface_ptr, object_ptr->desired_version, interface_name_ptr, version); continue; } ((void**)((uint8_t*)data_ptr + object_ptr->bound_ptr_offset))[0] = bound_ptr; bs_log(BS_INFO, "Bound interface %s to %p", interface_name_ptr, bound_ptr); if (NULL != object_ptr->setup) object_ptr->setup(data_ptr); } } /* ------------------------------------------------------------------------- */ /** * Handles the removal of a wayland global object. * * Called by `struct wl_registry_listener` `global_remove`, invoked to notify * clients of removed global objects. * * @param data_ptr Points to a @ref wlclient_t. * @param wl_registry_ptr The `struct wl_registry` this is invoked for. * @param name Numeric name of the global object. */ void handle_global_remove( void *data_ptr, struct wl_registry *wl_registry_ptr, uint32_t name) { // TODO(kaeser@gubbe.ch): Add implementation. bs_log(BS_INFO, "handle_global_remove(%p, %p, %"PRIu32").", data_ptr, wl_registry_ptr, name); } /* ------------------------------------------------------------------------- */ /** * Creates a timer and registers it with the client. * * @param client_ptr * @param target_usec * @param callback * @param callback_ud_ptr * * @return A pointer to the created timer, or NULL on error. The pointer must * be destroyed by @ref wlc_timer_destroy. */ wlclient_timer_t *wlc_timer_create( wlclient_t *client_ptr, uint64_t target_usec, wlclient_callback_t callback, void *callback_ud_ptr) { wlclient_timer_t *timer_ptr = logged_calloc(1, sizeof(wlclient_timer_t)); if (NULL == timer_ptr) return NULL; timer_ptr->target_usec = target_usec; timer_ptr->callback = callback; timer_ptr->callback_ud_ptr = callback_ud_ptr; // TODO(kaeser@gubbe.ch): This should be a HEAP. bs_dllist_node_t *dlnode_ptr = client_ptr->timers.head_ptr; for (; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlclient_timer_t *ref_timer_ptr = (wlclient_timer_t *)dlnode_ptr; if (timer_ptr->target_usec > ref_timer_ptr->target_usec) continue; bs_dllist_insert_node_before( &client_ptr->timers, dlnode_ptr, &timer_ptr->dlnode); } if (NULL == dlnode_ptr) { bs_dllist_push_back(&client_ptr->timers, &timer_ptr->dlnode); } return timer_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the timer. Note: The timer will NOT be unregistered first. * * @param timer_ptr */ void wlc_timer_destroy(wlclient_timer_t *timer_ptr) { free(timer_ptr); } /* ------------------------------------------------------------------------- */ /** Set up the seat: Registers the client's seat listeners. */ void wlc_seat_setup(wlclient_t *client_ptr) { wl_seat_add_listener( client_ptr->attributes.wl_seat_ptr, &wlc_seat_listener, client_ptr); } /* ------------------------------------------------------------------------- */ /** * Handles the seat's capability updates. * * Un-/Registers listeners for the pointer, if the capability is available. * * @param data_ptr * @param wl_seat_ptr * @param capabilities */ void wlc_seat_handle_capabilities( void *data_ptr, struct wl_seat *wl_seat_ptr, uint32_t capabilities) { wlclient_t *client_ptr = data_ptr; bool supports_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER; if (supports_pointer && NULL == client_ptr->attributes.wl_pointer_ptr) { client_ptr->attributes.wl_pointer_ptr = wl_seat_get_pointer(wl_seat_ptr); wl_pointer_add_listener( client_ptr->attributes.wl_pointer_ptr, &wlc_pointer_listener, client_ptr); } else if (!supports_pointer && NULL != client_ptr->attributes.wl_pointer_ptr) { wl_pointer_release(client_ptr->attributes.wl_pointer_ptr); client_ptr->attributes.wl_pointer_ptr = NULL; } bool supports_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; if (supports_keyboard && NULL == client_ptr->wl_keyboard_ptr) { client_ptr->wl_keyboard_ptr = wl_seat_get_keyboard(wl_seat_ptr); wl_keyboard_add_listener( client_ptr->wl_keyboard_ptr, &wlc_keyboard_listener, client_ptr); } else if (!supports_keyboard && NULL != client_ptr->wl_pointer_ptr) { wl_keyboard_release(client_ptr->wl_keyboard_ptr); client_ptr->wl_keyboard_ptr = NULL; } } /* ------------------------------------------------------------------------- */ /** Handles the unique identifier callback. */ void wlc_seat_handle_name( void *data_ptr, struct wl_seat *wl_seat_ptr, const char *name_ptr) { bs_log(BS_DEBUG, "Client %p bound to seat %p: %s", data_ptr, wl_seat_ptr, name_ptr); } /* ------------------------------------------------------------------------- */ /** Called when the client obtains pointer focus. */ void wlc_pointer_handle_enter( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t serial, __UNUSED__ struct wl_surface *surface, __UNUSED__ wl_fixed_t surface_x, __UNUSED__ wl_fixed_t surface_y) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called when the client looses pointer focus. */ void wlc_pointer_handle_leave( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t serial, __UNUSED__ struct wl_surface *surface) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called upon pointer motion. */ void wlc_pointer_handle_motion( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t time, __UNUSED__ wl_fixed_t surface_x, __UNUSED__ wl_fixed_t surface_y) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called upon pointer button events. */ void wlc_pointer_handle_button( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t serial, __UNUSED__ uint32_t time, __UNUSED__ uint32_t button, __UNUSED__ uint32_t state) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called upon axis events. */ void wlc_pointer_handle_axis( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t time, __UNUSED__ uint32_t axis, __UNUSED__ wl_fixed_t value) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called upon frame events. */ void wlc_pointer_handle_frame( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called upon axis source events. */ void wlc_pointer_handle_axis_source( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t axis_source) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Axis stop events. */ void wlc_pointer_handle_axis_stop( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t time, __UNUSED__ uint32_t axis) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called upon axis click events. */ void wlc_pointer_handle_axis_discrete( __UNUSED__ void *data, __UNUSED__ struct wl_pointer *wl_pointer, __UNUSED__ uint32_t axis, __UNUSED__ int32_t discrete) { /* Currently nothing done. */ } /* ------------------------------------------------------------------------- */ /** Called when compositor provides a keymap to memory-map. */ void _wlc_keyboard_handle_keymap( void *data_ptr, struct wl_keyboard *wl_keyboard_ptr, uint32_t format, int32_t fd, uint32_t size) { // Guard clause. So far, we only understand xkb maps. if (WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 != format) { bs_log(BS_FATAL, "Unsupported keymap: %"PRIu32, format); return; } wlclient_t *client_ptr = BS_ASSERT_NOTNULL(data_ptr); void *desc_ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (MAP_FAILED == desc_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed mmap(NULL, %"PRIu32", PROT_READ, MAP_PRIVATE, %d, 0)", size, fd); close(fd); return; } if (NULL != client_ptr->xkb_keymap_ptr) { xkb_keymap_unref(client_ptr->xkb_keymap_ptr); } client_ptr->xkb_keymap_ptr = xkb_keymap_new_from_string( client_ptr->xkb_context_ptr, desc_ptr, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(desc_ptr, size); close(fd); if (NULL == client_ptr->xkb_keymap_ptr) { bs_log(BS_FATAL, "Failed xkb_keymap_new_from_string()"); return; } if (NULL != client_ptr->xkb_state_ptr) { xkb_state_unref(client_ptr->xkb_state_ptr); } client_ptr->xkb_state_ptr = xkb_state_new(client_ptr->xkb_keymap_ptr); if (NULL == client_ptr->xkb_state_ptr) { bs_log(BS_FATAL, "Failed xkb_state_new()"); return; } bs_log(BS_DEBUG, "Keyboard %p with keymap \"%s\"", wl_keyboard_ptr, xkb_keymap_layout_get_name(client_ptr->xkb_keymap_ptr, 0)); } /* ------------------------------------------------------------------------- */ /** Called when the given surface gained keyboard focus. */ void _wlc_keyboard_handle_enter( __UNUSED__ void *data_ptr, __UNUSED__ struct wl_keyboard *wl_keyboard_ptr, __UNUSED__ uint32_t serial, __UNUSED__ struct wl_surface *wl_surface_ptr, __UNUSED__ struct wl_array *keys) { // Currently unused. } /* ------------------------------------------------------------------------- */ /** Called when the given surface lost keyboard focus. */ void _wlc_keyboard_handle_leave( __UNUSED__ void *data_ptr, __UNUSED__ struct wl_keyboard *wl_keyboard_ptr, __UNUSED__ uint32_t serial, __UNUSED__ struct wl_surface *wl_surface_ptr) { // Currently unused. } /* ------------------------------------------------------------------------- */ /** Called when a key was pressed or released. */ void _wlc_keyboard_handle_key( void *data_ptr, __UNUSED__ struct wl_keyboard *wl_keyboard_ptr, __UNUSED__ uint32_t serial, __UNUSED__ uint32_t time, uint32_t key, uint32_t state) { wlclient_t *client_ptr = data_ptr; const xkb_keysym_t *key_syms; int key_syms_count = xkb_state_key_get_syms( client_ptr->xkb_state_ptr, key + 8, &key_syms); for (int i = 0; i < key_syms_count; ++i) { wlclient_key_event_t event = { .keysym = key_syms[i], .pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED }; wl_signal_emit(&client_ptr->events.key, &event); } static const enum xkb_key_direction translate_state[2] = { [WL_KEYBOARD_KEY_STATE_RELEASED] = XKB_KEY_UP, [WL_KEYBOARD_KEY_STATE_PRESSED] = XKB_KEY_DOWN, }; BS_ASSERT(state < sizeof(translate_state)/sizeof(enum xkb_key_direction)); xkb_state_update_key(client_ptr->xkb_state_ptr, key + 8, state); } /* ------------------------------------------------------------------------- */ /** Called when the modifier or group state has changed. */ void _wlc_keyboard_handle_modifiers( void *data_ptr, __UNUSED__ struct wl_keyboard *wl_keyboard_ptr, __UNUSED__ uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { wlclient_t *client_ptr = data_ptr; xkb_state_update_mask( client_ptr->xkb_state_ptr, mods_depressed, mods_latched, mods_locked, 0, // depressesd_layout. 0, // latched_layout. group); } /* ------------------------------------------------------------------------- */ /** Called to configure repeat and delay settings. */ void _wlc_keyboard_handle_repeat_info( __UNUSED__ void *data_ptr, __UNUSED__ struct wl_keyboard *wl_keyboard_ptr, __UNUSED__ int32_t rate, __UNUSED__ int32_t delay) { // Currently unused. } /* == End of client.c ====================================================== */ wlmaker-0.8/apps/libwlclient/dblbuf.h0000644000175100017510000000574215203543557017334 0ustar runnerrunner/* ========================================================================= */ /** * @file dblbuf.h * * Functions for working with a double buffer on a wayland surface. * * @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 __WLCL_DBLBUF_H__ #define __WLCL_DBLBUF_H__ #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus struct wl_shm; struct wl_surface; /** Forward declaration: Double buffer state. */ typedef struct _wlcl_dblbuf_t wlcl_dblbuf_t; /** Callback that indicates the buffer is ready to draw into. */ typedef bool (*wlcl_dblbuf_ready_callback_t)( bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr); /** * Creates a double buffer for the surface with provided dimensions. * * @param app_id_ptr * @param wl_surface_ptr * @param wl_shm_ptr * @param width * @param height * * @return Pointer to state of the double buffer, or NULL on error. Call * @ref wlcl_dblbuf_destroy for freeing up the associated resources. */ wlcl_dblbuf_t *wlcl_dblbuf_create( const char *app_id_ptr, struct wl_surface *wl_surface_ptr, struct wl_shm *wl_shm_ptr, unsigned width, unsigned height); /** Destroys the double buffer. */ void wlcl_dblbuf_destroy(wlcl_dblbuf_t *dblbuf_ptr); /** * Registers a callback for when a frame can be drawn into the buffer. * * The frame can be drawn if (1) it is due, and (2) there is a back buffer * available ("released") for drawing into. If these conditions hold true, * `callback` will be called right away. Otherwise, it will be called once * these conditions are fulfilled. * * The callback will be called only once. If the client wishes further * notifications, they must call @ref wlcl_dblbuf_register_ready_callback * again. * * The callback must be registered only after the surface is ready. Eg. for * an XDG toplevel, after it has received & acknowledged `configure`. * * @param dblbuf_ptr * @param callback The callback function, or NULL to clear the * callback. * @param callback_ud_ptr Argument to use for `callback`. */ void wlcl_dblbuf_register_ready_callback( wlcl_dblbuf_t *dblbuf_ptr, wlcl_dblbuf_ready_callback_t callback, void *callback_ud_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLCL_DBLBUF_H__ */ /* == End of dblbuf_buffer.h =============================================== */ wlmaker-0.8/apps/libwlclient/CMakeLists.txt0000644000175100017510000000476515203543557020471 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) include(WaylandProtocol) pkg_check_modules(WAYLAND_CLIENT REQUIRED IMPORTED_TARGET wayland-client>=1.22.0) pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED IMPORTED_TARGET wayland-protocols>=1.32) pkg_get_variable(wayland_protocol_dir wayland-protocols pkgdatadir) add_library(libwlclient STATIC) set(public_header_files dblbuf.h icon.h libwlclient.h xdg_toplevel.h ) set_target_properties( libwlclient PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}") set(sources client.c dblbuf.c icon.c xdg_toplevel.c ) waylandprotocol_add( libwlclient BASE_NAME xdg-decoration PROTOCOL_FILE "${wayland_protocol_dir}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" SIDE client) waylandprotocol_add( libwlclient BASE_NAME xdg-shell PROTOCOL_FILE "${wayland_protocol_dir}/stable/xdg-shell/xdg-shell.xml" SIDE client) waylandprotocol_add( libwlclient BASE_NAME wlmaker-icon-unstable-v1 PROTOCOL_FILE "${PROJECT_SOURCE_DIR}/protocols/wlmaker-icon-unstable-v1.xml" SIDE client) waylandprotocol_add( libwlclient BASE_NAME ext-input-observation-v1 PROTOCOL_FILE "${PROJECT_SOURCE_DIR}/protocols/ext-input-observation-v1.xml" SIDE client) target_sources(libwlclient PRIVATE "${sources}") target_include_directories( libwlclient PRIVATE "${WAYLAND_CLIENT_INCLUDE_DIRS}" "${XKBCOMMON_INCLUDE_DIRS}" "${CMAKE_CURRENT_BINARY_DIR}") target_link_libraries( libwlclient libbase PkgConfig::WAYLAND_CLIENT PkgConfig::XKBCOMMON) include(CheckSymbolExists) check_symbol_exists(signalfd "sys/signalfd.h" HAVE_SIGNALFD) if(NOT HAVE_SIGNALFD) pkg_check_modules(EPOLL REQUIRED IMPORTED_TARGET epoll-shim) target_link_libraries( libwlclient PkgConfig::EPOLL) endif() if(iwyu_path_and_options) set_target_properties( libwlclient PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8/apps/libwlclient/icon.h0000644000175100017510000000461015203543557017017 0ustar runnerrunner/* ========================================================================= */ /** * @file icon.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 __LIBWLCLIENT_ICON_H__ #define __LIBWLCLIENT_ICON_H__ #include #include #include "libwlclient.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration of an icon's state. */ typedef struct _wlclient_icon_t wlclient_icon_t; /** * Creates an icon. * * @param wlclient_ptr * * @return An icon state or NULL on error. The state must be free'd by calling * @ref wlclient_icon_destroy. */ wlclient_icon_t *wlclient_icon_create( wlclient_t *wlclient_ptr); /** * Destroys the icon. * * @param icon_ptr */ void wlclient_icon_destroy( wlclient_icon_t *icon_ptr); /** * Returns whether the icon protocol is supported on the client. * * @param wlclient_ptr */ bool wlclient_icon_supported(wlclient_t *wlclient_ptr); /** * Sets a callback to invoke when the background buffer is ready for drawing. * * @see wlcl_dblbuf_register_ready_callback. * * @param icon_ptr * @param callback * @param ud_ptr */ void wlclient_icon_register_ready_callback( wlclient_icon_t *icon_ptr, bool (*callback)(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr), void *ud_ptr); /** * Registers the callback to notify the pointer position relative to the * icon's surface. * * @param icon_ptr * @param callback * @param callback_ud_ptr */ void wlclient_icon_register_position_callback( wlclient_icon_t *icon_ptr, void (*callback)(double x, double y, void *ud_ptr), void *callback_ud_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBWLCLIENT_ICON_H__ */ /* == End of icon.h ======================================================== */ wlmaker-0.8/apps/libwlclient/icon.c0000644000175100017510000002601615203543557017016 0ustar runnerrunner/* ========================================================================= */ /** * @file icon.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.h" #include #include #include #include #include "dblbuf.h" #include "ext-input-observation-v1-client-protocol.h" #include "wlmaker-icon-unstable-v1-client-protocol.h" struct ext_input_position_observer_v1; struct wl_surface; struct zwlmaker_toplevel_icon_v1; /* == Declarations ========================================================= */ /** State of the icon. */ typedef struct _wlclient_icon_t { /** Back-link to the client. */ wlclient_t *wlclient_ptr; /** Surface. */ struct wl_surface *wl_surface_ptr; /** The icon interface. */ struct zwlmaker_toplevel_icon_v1 *toplevel_icon_ptr; /** Width of the icon, once suggested by the server. */ unsigned width; /** Height of the icon, once suggested by the server. */ unsigned height; /** Callback for when the icon's buffer is ready to be drawn into. */ wlcl_dblbuf_ready_callback_t ready_callback; /** Argument to that callback. */ void *ready_callback_ud_ptr; /** Double-buffered state of the surface. */ wlcl_dblbuf_t *dblbuf_ptr; /** Callback for input position observation. */ void (*position_callback)(double x, double y, void *ud_ptr); /** Client-provided argument to @ref wlclient_xdg_toplevel_t::position_callback. */ void *position_callback_ud_ptr; /** Whether any position update had been received already. */ bool position_received; /** Last known reported input X position. */ int32_t last_position_x; /** Last known reported input Y position. */ int32_t last_position_y; /** Input observer. */ struct ext_input_position_observer_v1 *input_position_observer_ptr; } wlclient_icon_t; static void handle_toplevel_icon_configure( void *data_ptr, struct zwlmaker_toplevel_icon_v1 *zwlmaker_toplevel_icon_v1_ptr, int32_t width, int32_t height, uint32_t serial); static void _wlclient_icon_input_position_observer_position( void *data_ptr, struct ext_input_position_observer_v1 *input_position_observer_ptr, struct wl_surface *wl_surface_ptr, uint32_t instance, int32_t relative_x, int32_t relative_y); /* == Data ================================================================= */ /** Listener implementation for toplevel icon. */ static const struct zwlmaker_toplevel_icon_v1_listener toplevel_icon_listener={ .configure = handle_toplevel_icon_configure, }; /** Listeners for the icon's surface's pointer position Tracker. */ static const struct ext_input_position_observer_v1_listener _wlclient_icon_tracker_listener = { .position = _wlclient_icon_input_position_observer_position, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlclient_icon_t *wlclient_icon_create(wlclient_t *wlclient_ptr) { if (!wlclient_icon_supported(wlclient_ptr)) { bs_log(BS_ERROR, "Icon manager is not supported."); return NULL; } wlclient_icon_t *icon_ptr = logged_calloc(1, sizeof(wlclient_icon_t)); if (NULL == icon_ptr) return NULL; icon_ptr->wlclient_ptr = wlclient_ptr; icon_ptr->wl_surface_ptr = wl_compositor_create_surface( wlclient_attributes(wlclient_ptr)->wl_compositor_ptr); if (NULL == icon_ptr->wl_surface_ptr) { bs_log(BS_ERROR, "Failed wl_compositor_create_surface(%p).", wlclient_attributes(wlclient_ptr)->wl_compositor_ptr); wlclient_icon_destroy(icon_ptr); return NULL; } icon_ptr->toplevel_icon_ptr = zwlmaker_icon_manager_v1_get_toplevel_icon( wlclient_attributes(wlclient_ptr)->icon_manager_ptr, NULL, icon_ptr->wl_surface_ptr); if (NULL == icon_ptr->toplevel_icon_ptr) { bs_log(BS_ERROR, "Failed zwlmaker_icon_manager_v1_get_toplevel_icon" "(%p, NULL, %p).", wlclient_attributes(wlclient_ptr)->icon_manager_ptr, icon_ptr->wl_surface_ptr); wlclient_icon_destroy(icon_ptr); return NULL; } zwlmaker_toplevel_icon_v1_add_listener( icon_ptr->toplevel_icon_ptr, &toplevel_icon_listener, icon_ptr); wl_surface_commit(icon_ptr->wl_surface_ptr); if (NULL != wlclient_attributes(wlclient_ptr)->input_observation_manager_ptr) { icon_ptr->input_position_observer_ptr = ext_input_observation_manager_v1_create_pointer_observer( wlclient_attributes(wlclient_ptr)->input_observation_manager_ptr, wlclient_attributes(wlclient_ptr)->wl_pointer_ptr, icon_ptr->wl_surface_ptr); if (NULL == icon_ptr->input_position_observer_ptr) { bs_log(BS_ERROR, "Failed ext_input_observation_v1_pointer_position(%p, %p)", wlclient_attributes(wlclient_ptr)->input_observation_manager_ptr, icon_ptr->wl_surface_ptr); wlclient_icon_destroy(icon_ptr); return NULL; } ext_input_position_observer_v1_add_listener( icon_ptr->input_position_observer_ptr, &_wlclient_icon_tracker_listener, icon_ptr); bs_log(BS_INFO, "Created pointer tracker %p for wl_surface %p", icon_ptr->input_position_observer_ptr, icon_ptr->wl_surface_ptr); } return icon_ptr; } /* ------------------------------------------------------------------------- */ void wlclient_icon_destroy(wlclient_icon_t *icon_ptr) { if (NULL != icon_ptr->input_position_observer_ptr) { ext_input_position_observer_v1_destroy( icon_ptr->input_position_observer_ptr); icon_ptr->input_position_observer_ptr = NULL; } if (NULL != icon_ptr->toplevel_icon_ptr) { // TODO(kaeser@gubbe.ch): Destroy the icon! icon_ptr->toplevel_icon_ptr = NULL; } if (NULL != icon_ptr->dblbuf_ptr) { wlcl_dblbuf_destroy(icon_ptr->dblbuf_ptr); icon_ptr->dblbuf_ptr = NULL; } if (NULL != icon_ptr->wl_surface_ptr) { wl_surface_destroy(icon_ptr->wl_surface_ptr); icon_ptr->wl_surface_ptr = NULL; } free(icon_ptr); } /* ------------------------------------------------------------------------- */ bool wlclient_icon_supported( wlclient_t *wlclient_ptr) { return (NULL != wlclient_attributes(wlclient_ptr)->icon_manager_ptr); } /* ------------------------------------------------------------------------ */ void wlclient_icon_register_ready_callback( wlclient_icon_t *icon_ptr, bool (*callback)(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr), void *ud_ptr) { if (NULL != icon_ptr->dblbuf_ptr) { wlcl_dblbuf_register_ready_callback( icon_ptr->dblbuf_ptr, callback, ud_ptr); } else { icon_ptr->ready_callback = callback; icon_ptr->ready_callback_ud_ptr = ud_ptr; } } /* ------------------------------------------------------------------------- */ void wlclient_icon_register_position_callback( wlclient_icon_t *icon_ptr, void (*callback)(double x, double y, void *ud_ptr), void *callback_ud_ptr) { if (icon_ptr->position_received) { callback( icon_ptr->last_position_x / 256.0, icon_ptr->last_position_y / 256.0, callback_ud_ptr); } icon_ptr->position_callback = callback; icon_ptr->position_callback_ud_ptr = callback_ud_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handles the 'configure' event: Creates appropriately sized buffer. * * @param data_ptr * @param zwlmaker_toplevel_icon_v1_ptr * @param width * @param height * @param serial */ void handle_toplevel_icon_configure( void *data_ptr, struct zwlmaker_toplevel_icon_v1 *zwlmaker_toplevel_icon_v1_ptr, int32_t width, int32_t height, uint32_t serial) { wlclient_icon_t *icon_ptr = data_ptr; icon_ptr->width = width; icon_ptr->height = height; bs_log(BS_DEBUG, "Configured icon to %"PRId32" x %"PRId32, width, height); zwlmaker_toplevel_icon_v1_ack_configure( zwlmaker_toplevel_icon_v1_ptr, serial); wlclient_t *wlclient_ptr = icon_ptr->wlclient_ptr; icon_ptr->dblbuf_ptr = wlcl_dblbuf_create( wlclient_attributes(wlclient_ptr)->app_id_ptr, icon_ptr->wl_surface_ptr, wlclient_attributes(wlclient_ptr)->wl_shm_ptr, icon_ptr->width, icon_ptr->height); if (NULL == icon_ptr->dblbuf_ptr) { bs_log(BS_FATAL, "Failed wlcl_dblbuf_create(%p, %p, %u, %u)", icon_ptr->wl_surface_ptr, wlclient_attributes(wlclient_ptr)->wl_shm_ptr, icon_ptr->width, icon_ptr->height); // TODO(kaeser@gubbe.ch): Error handling. } wlcl_dblbuf_ready_callback_t callback = icon_ptr->ready_callback; if (NULL != callback) { icon_ptr->ready_callback = NULL; wlcl_dblbuf_register_ready_callback( icon_ptr->dblbuf_ptr, callback, icon_ptr->ready_callback_ud_ptr); } } /* ------------------------------------------------------------------------- */ /** Callback for when a `position` event is received. */ void _wlclient_icon_input_position_observer_position( void *data_ptr, __UNUSED__ struct ext_input_position_observer_v1 *input_position_observer_ptr, __UNUSED__ struct wl_surface *wl_surface_ptr, __UNUSED__ uint32_t instance, int32_t relative_x, int32_t relative_y) { wlclient_icon_t *icon_ptr = data_ptr; if (!icon_ptr->position_received || icon_ptr->last_position_x != relative_x || icon_ptr->last_position_y != relative_y) { icon_ptr->position_received = true; icon_ptr->last_position_x = relative_x; icon_ptr->last_position_y = relative_y; if (NULL != icon_ptr->position_callback) { icon_ptr->position_callback( icon_ptr->last_position_x / 256.0, icon_ptr->last_position_y / 256.0, icon_ptr->position_callback_ud_ptr); } } } /* == End of icon.c ======================================================== */ wlmaker-0.8/apps/libwlclient/libwlclient.h0000644000175100017510000001102615203543557020376 0ustar runnerrunner/* ========================================================================= */ /** * @file libwlclient.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 __LIBWLCLIENT_H__ #define __LIBWLCLIENT_H__ #include #include #include #include #include /** Forward declaration: Wayland client handle. */ typedef struct _wlclient_t wlclient_t; struct zxdg_toplevel_decoration_v1; #include "icon.h" #include "xdg_toplevel.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * A client's callback, as used in @ref wlclient_register_timer. * * @param wlclient_ptr * @param ud_ptr */ typedef void (*wlclient_callback_t)( wlclient_t *wlclient_ptr, void *ud_ptr); /** Accessor to 'public' client attributes. */ typedef struct { /** Wayland display connection. */ struct wl_display *wl_display_ptr; /** The bound compositor interface. */ struct wl_compositor *wl_compositor_ptr; /** The bound SHM interface. */ struct wl_shm *wl_shm_ptr; /** The bound XDG wm_base interface. */ struct xdg_wm_base *xdg_wm_base_ptr; /** The bound seat. */ struct wl_seat *wl_seat_ptr; /** Pointer state, if & when the seat has the capability. */ struct wl_pointer *wl_pointer_ptr; /** The bound Toplevel Icon Manager. Will be NULL if not supported. */ struct zwlmaker_icon_manager_v1 *icon_manager_ptr; /** The bound XDG decoration manager. NULL if not supported. */ struct zxdg_decoration_manager_v1 *xdg_decoration_manager_ptr; /** The bound Input Observation. */ struct ext_input_observation_manager_v1 *input_observation_manager_ptr; /** Application ID, as a string. Or NULL, if not set. */ const char *app_id_ptr; } wlclient_attributes_t; /** Events of the client. */ typedef struct { /** A key was pressed. */ struct wl_signal key; } wlclient_events_t; /** Key event. */ typedef struct{ /** The key. */ xkb_keysym_t keysym; /** Wheter it was pressed (true) or released. */ bool pressed; } wlclient_key_event_t; /** * Creates a wayland client for simple buffer interactions. * * @param app_id_ptr Application ID or NULL if not set. * * @return The client state, or NULL on error. The state needs to be free'd * via @ref wlclient_destroy. */ wlclient_t *wlclient_create(const char *app_id_ptr); /** * Destroys the wayland client, as created by @ref wlclient_create. * * @param wlclient_ptr */ void wlclient_destroy(wlclient_t *wlclient_ptr); /** * Gets the client attributes. * * @param wlclient_ptr * * @return A pointer to the attributes. */ const wlclient_attributes_t *wlclient_attributes( const wlclient_t *wlclient_ptr); /** @return A pointer to @ref wlclient_t::events. */ wlclient_events_t *wlclient_events(wlclient_t *wlclient_ptr); /** * Runs the client's mainloop. * * @param wlclient_ptr */ void wlclient_run(wlclient_t *wlclient_ptr); /** * Requests termination of the client-s mainloop. This takes effect only once * the mainloop wraps up an iteration. * * @param wlclient_ptr */ void wlclient_request_terminate(wlclient_t *wlclient_ptr); /** * Registers a timer with the client. * * Once the system clock reaches (or has passed) `target_usec`, `callback` will * be called with the provided arguments. This is a one-time registration. For * repeated calls, clients need to re-register. * * @param wlclient_ptr * @param target_usec * @param callback * @param callback_ud_ptr * * @return true on success. */ bool wlclient_register_timer( wlclient_t *wlclient_ptr, uint64_t target_usec, wlclient_callback_t callback, void *callback_ud_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBWLCLIENT_H__ */ /* == End of libwlclient.h ================================================= */ wlmaker-0.8/apps/libwlclient/xdg_toplevel.c0000644000175100017510000004544115203543557020565 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_toplevel.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 "xdg_toplevel.h" #include #include #include #include #include "dblbuf.h" #include "ext-input-observation-v1-client-protocol.h" #include "xdg-decoration-client-protocol.h" #include "xdg-shell-client-protocol.h" struct ext_input_position_observer_v1; struct wl_array; struct wl_surface; struct xdg_surface; struct xdg_toplevel; struct zxdg_toplevel_decoration_v1; /* == Declarations ========================================================= */ /** State of the XDG toplevel. */ struct _wlclient_xdg_toplevel_t { /** Back-link to the client. */ wlclient_t *wlclient_ptr; /** Window title of the toplevel. */ char *title_ptr; /** Surface. */ struct wl_surface *wl_surface_ptr; /** Wrapped as XDG surface. */ struct xdg_surface *xdg_surface_ptr; /** The XDG toplevel. */ struct xdg_toplevel *xdg_toplevel_ptr; /** The XDG toplevel'ss decoration handle. */ struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration_v1_ptr; /** Whether to request decoration on the server side. */ bool decorate_server_side; /** The double-buffer wrapper for the surface. */ wlcl_dblbuf_t *dblbuf_ptr; /** Whether the surface had been configured. Can only use after that. */ bool configured; /** Whether the decoration has gotten configured. */ bool decoration_configured; /** Callback for when the buffer is ready to draw into. */ wlcl_dblbuf_ready_callback_t callback; /** Client-provided argument to @ref wlclient_xdg_toplevel_t::callback. */ void *callback_ud_ptr; /** Callback for input position observation. */ void (*position_callback)(double x, double y, void *ud_ptr); /** Client-provided argument to @ref wlclient_xdg_toplevel_t::position_callback. */ void *position_callback_ud_ptr; /** Whether any position update had been received already. */ bool position_received; /** Last known reported input X position. */ int32_t last_position_x; /** Last known reported input Y position. */ int32_t last_position_y; /** Input observer. */ struct ext_input_position_observer_v1 *input_position_observer_ptr; }; static void _wlclient_xdg_configure_decoration( wlclient_xdg_toplevel_t *toplevel_ptr); static void _wlclient_xdg_surface_configure( void *data, struct xdg_surface *xdg_surface, uint32_t serial); static void _wlc_xdg_toplevel_decoration_v1_configure( void *data_ptr, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1_ptr, uint32_t mode); static void _xdg_toplevel_handle_configure( void *data_ptr, struct xdg_toplevel *xdg_toplevel_ptr, int32_t width, int32_t height, struct wl_array *states); static void _xdg_toplevel_handle_close( void *data_ptr, struct xdg_toplevel *xdg_toplevel_ptr); static void _xdg_toplevel_handle_configure_bounds( void *data_ptr, struct xdg_toplevel *xdg_toplevel_ptr, int32_t width, int32_t height); static void _xdg_toplevel_handle_wm_capabilities( void *data_ptr, struct xdg_toplevel *xdg_toplevel_ptr, struct wl_array *capabilities); static void _wlclient_input_position_observer_position( void *data_ptr, struct ext_input_position_observer_v1 *input_position_observer_ptr, struct wl_surface *wl_surface_ptr, uint32_t instance, int32_t relative_x, int32_t relative_y); /* == Data ================================================================= */ /** Listeners for the XDG toplevel. */ static const struct xdg_toplevel_listener _wlc_xdg_toplevel_listener = { .configure = _xdg_toplevel_handle_configure, .close = _xdg_toplevel_handle_close, .configure_bounds = _xdg_toplevel_handle_configure_bounds, .wm_capabilities = _xdg_toplevel_handle_wm_capabilities }; /** Listeners for the XDG surface. */ static const struct xdg_surface_listener _wlclient_xdg_surface_listener = { .configure = _wlclient_xdg_surface_configure, }; /** Listeners for the XDG decoration manager. */ static const struct zxdg_toplevel_decoration_v1_listener _wlc_xdg_toplevel_decoration_v1_listener = { .configure = _wlc_xdg_toplevel_decoration_v1_configure, }; /** Listeners for the Pointer position Tracker. */ static const struct ext_input_position_observer_v1_listener _wlclient_tracker_listener = { .position = _wlclient_input_position_observer_position, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlclient_xdg_toplevel_t *wlclient_xdg_toplevel_create( wlclient_t *wlclient_ptr, const char *title_ptr, unsigned width, unsigned height) { wlclient_xdg_toplevel_t *toplevel_ptr = logged_calloc( 1, sizeof(wlclient_xdg_toplevel_t)); if (NULL == toplevel_ptr) return NULL; toplevel_ptr->wlclient_ptr = wlclient_ptr; toplevel_ptr->title_ptr = logged_strdup(title_ptr); if (NULL == toplevel_ptr->title_ptr) { wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } toplevel_ptr->wl_surface_ptr = wl_compositor_create_surface( wlclient_attributes(wlclient_ptr)->wl_compositor_ptr); if (NULL == toplevel_ptr->wl_surface_ptr) { bs_log(BS_ERROR, "Failed wl_compositor_create_surface(%p).", wlclient_attributes(wlclient_ptr)->wl_compositor_ptr); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } toplevel_ptr->dblbuf_ptr = wlcl_dblbuf_create( wlclient_attributes(wlclient_ptr)->app_id_ptr, toplevel_ptr->wl_surface_ptr, wlclient_attributes(wlclient_ptr)->wl_shm_ptr, width, height); if (NULL == toplevel_ptr->dblbuf_ptr) { bs_log(BS_ERROR, "Failed wlcl_dblbuf_create(%p, %p, %u, %u)", toplevel_ptr->wl_surface_ptr, wlclient_attributes(wlclient_ptr)->wl_shm_ptr, width, height); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } toplevel_ptr->xdg_surface_ptr = xdg_wm_base_get_xdg_surface( wlclient_attributes(wlclient_ptr)->xdg_wm_base_ptr, toplevel_ptr->wl_surface_ptr); if (NULL == toplevel_ptr->xdg_surface_ptr) { bs_log(BS_ERROR, "Failed xdg_wm_base_get_xdg_surface(%p, %p)", wlclient_attributes(wlclient_ptr)->xdg_wm_base_ptr, toplevel_ptr->wl_surface_ptr); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } xdg_surface_add_listener( toplevel_ptr->xdg_surface_ptr, &_wlclient_xdg_surface_listener, toplevel_ptr); toplevel_ptr->xdg_toplevel_ptr = xdg_surface_get_toplevel( toplevel_ptr->xdg_surface_ptr); if (NULL == toplevel_ptr->xdg_toplevel_ptr) { bs_log(BS_ERROR, "Failed xdg_surface_get_toplevel(%p)", toplevel_ptr->xdg_surface_ptr); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } xdg_surface_set_window_geometry( toplevel_ptr->xdg_surface_ptr, 0, 0, width, height); if (0 != xdg_toplevel_add_listener( toplevel_ptr->xdg_toplevel_ptr, &_wlc_xdg_toplevel_listener, toplevel_ptr)) { wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } if (NULL != wlclient_attributes(wlclient_ptr)->xdg_decoration_manager_ptr) { toplevel_ptr->xdg_toplevel_decoration_v1_ptr = zxdg_decoration_manager_v1_get_toplevel_decoration( wlclient_attributes(wlclient_ptr)->xdg_decoration_manager_ptr, toplevel_ptr->xdg_toplevel_ptr); if (NULL == toplevel_ptr->xdg_toplevel_decoration_v1_ptr) { bs_log(BS_ERROR, "Failed " "zxdg_decoration_manager_v1_get_toplevel_decoration()"); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } if (0 != zxdg_toplevel_decoration_v1_add_listener( toplevel_ptr->xdg_toplevel_decoration_v1_ptr, &_wlc_xdg_toplevel_decoration_v1_listener, toplevel_ptr)) { bs_log(BS_ERROR, "Failed zxdg_toplevel_decoration_v1_add_listener"); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } } xdg_toplevel_set_title(toplevel_ptr->xdg_toplevel_ptr, toplevel_ptr->title_ptr); if (NULL != wlclient_attributes(wlclient_ptr)->app_id_ptr) { xdg_toplevel_set_app_id( toplevel_ptr->xdg_toplevel_ptr, wlclient_attributes(wlclient_ptr)->app_id_ptr); } if (NULL != wlclient_attributes(wlclient_ptr)->input_observation_manager_ptr) { toplevel_ptr->input_position_observer_ptr = ext_input_observation_manager_v1_create_pointer_observer( wlclient_attributes(wlclient_ptr)->input_observation_manager_ptr, wlclient_attributes(wlclient_ptr)->wl_pointer_ptr, toplevel_ptr->wl_surface_ptr); if (NULL == toplevel_ptr->input_position_observer_ptr) { bs_log(BS_ERROR, "Failed ext_input_observation_v1_pointer_position(%p, %p)", wlclient_attributes(wlclient_ptr)->input_observation_manager_ptr, toplevel_ptr->wl_surface_ptr); wlclient_xdg_toplevel_destroy(toplevel_ptr); return NULL; } ext_input_position_observer_v1_add_listener( toplevel_ptr->input_position_observer_ptr, &_wlclient_tracker_listener, toplevel_ptr); bs_log(BS_INFO, "Created pointer tracker %p for wl_surface %p", toplevel_ptr->input_position_observer_ptr, toplevel_ptr->wl_surface_ptr); } wl_surface_commit(toplevel_ptr->wl_surface_ptr); return toplevel_ptr; } /* ------------------------------------------------------------------------- */ void wlclient_xdg_toplevel_destroy(wlclient_xdg_toplevel_t *toplevel_ptr) { if (NULL != toplevel_ptr->xdg_toplevel_decoration_v1_ptr) { zxdg_toplevel_decoration_v1_destroy( toplevel_ptr->xdg_toplevel_decoration_v1_ptr); toplevel_ptr->xdg_toplevel_decoration_v1_ptr = NULL; } if (NULL != toplevel_ptr->xdg_toplevel_ptr) { xdg_toplevel_destroy(toplevel_ptr->xdg_toplevel_ptr); toplevel_ptr->xdg_toplevel_ptr = NULL; } if (NULL != toplevel_ptr->dblbuf_ptr) { wlcl_dblbuf_destroy(toplevel_ptr->dblbuf_ptr); toplevel_ptr->dblbuf_ptr = NULL; } if (NULL != toplevel_ptr->input_position_observer_ptr) { ext_input_position_observer_v1_destroy( toplevel_ptr->input_position_observer_ptr); toplevel_ptr->input_position_observer_ptr = NULL; } if (NULL != toplevel_ptr->wl_surface_ptr) { wl_surface_destroy(toplevel_ptr->wl_surface_ptr); toplevel_ptr->wl_surface_ptr = NULL; } if (NULL != toplevel_ptr->title_ptr) { free(toplevel_ptr->title_ptr); toplevel_ptr->title_ptr = NULL; } free(toplevel_ptr); } /* ------------------------------------------------------------------------- */ bool wlclient_xdg_supported(wlclient_t *wlclient_ptr) { return (NULL != wlclient_attributes(wlclient_ptr)->xdg_wm_base_ptr); } /* ------------------------------------------------------------------------- */ bool wlclient_xdg_decoration_set_server_side( wlclient_xdg_toplevel_t *toplevel_ptr, bool enabled) { // Guard clause. if (NULL == toplevel_ptr->xdg_toplevel_decoration_v1_ptr) return false; // Nothing to do. if (toplevel_ptr->decorate_server_side == enabled) return true; toplevel_ptr->decoration_configured = false; toplevel_ptr->decorate_server_side = enabled; _wlclient_xdg_configure_decoration(toplevel_ptr); return true; } /* ------------------------------------------------------------------------- */ void wlclient_xdg_toplevel_register_ready_callback( wlclient_xdg_toplevel_t *toplevel_ptr, bool (*callback)(bs_gfxbuf_t *gfxbuf_ptr, void *ud_ptr), void *callback_ud_ptr) { if (toplevel_ptr->configured) { wlcl_dblbuf_register_ready_callback( toplevel_ptr->dblbuf_ptr, callback, callback_ud_ptr); return; } toplevel_ptr->callback = callback; toplevel_ptr->callback_ud_ptr = callback_ud_ptr; } /* ------------------------------------------------------------------------- */ void wlclient_xdg_toplevel_register_position_callback( wlclient_xdg_toplevel_t *toplevel_ptr, void (*callback)(double x, double y, void *ud_ptr), void *callback_ud_ptr) { if (toplevel_ptr->position_received) { callback( toplevel_ptr->last_position_x / 256.0, toplevel_ptr->last_position_y / 256.0, callback_ud_ptr); } toplevel_ptr->position_callback = callback; toplevel_ptr->position_callback_ud_ptr = callback_ud_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Updates the server-side decoration mode. */ void _wlclient_xdg_configure_decoration(wlclient_xdg_toplevel_t *toplevel_ptr) { // Guard clauses. if (NULL == toplevel_ptr->xdg_toplevel_decoration_v1_ptr) return; if (toplevel_ptr->decoration_configured) return; zxdg_toplevel_decoration_v1_set_mode( toplevel_ptr->xdg_toplevel_decoration_v1_ptr, (toplevel_ptr->decorate_server_side ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE)); toplevel_ptr->decoration_configured = true; } /* ------------------------------------------------------------------------- */ /** * Handler for the `configure` event of the XDG surface. * * @param data_ptr Untyped pointer to @ref wlclient_xdg_toplevel_t. * @param xdg_surface_ptr * @param serial */ void _wlclient_xdg_surface_configure( void *data_ptr, struct xdg_surface *xdg_surface_ptr, uint32_t serial) { wlclient_xdg_toplevel_t *toplevel_ptr = data_ptr; xdg_surface_ack_configure(xdg_surface_ptr, serial); _wlclient_xdg_configure_decoration(toplevel_ptr); toplevel_ptr->configured = true; if (NULL != toplevel_ptr->callback) { wlcl_dblbuf_register_ready_callback( toplevel_ptr->dblbuf_ptr, toplevel_ptr->callback, toplevel_ptr->callback_ud_ptr); toplevel_ptr->callback = NULL; } } /* ------------------------------------------------------------------------- */ /** Handles the decoration mode change listener. */ void _wlc_xdg_toplevel_decoration_v1_configure( void *data_ptr, __UNUSED__ struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1_ptr, uint32_t mode) { wlclient_xdg_toplevel_t *toplevel_ptr = data_ptr; static const char* decoration_modes[3] = { [0] = "(unknown)", [ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE] = ( "ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE"), [ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE] = ( "ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE") }; if (mode >= sizeof(decoration_modes) / sizeof(char*)) mode = 0; bs_log(BS_INFO, "XDG toplevel %p configured decoration mode %s", toplevel_ptr->xdg_toplevel_ptr, decoration_modes[mode]); toplevel_ptr->decoration_configured = false; _wlclient_xdg_configure_decoration(toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** Handles XDG toplevel's configure event. */ void _xdg_toplevel_handle_configure( __UNUSED__ void *data_ptr, __UNUSED__ struct xdg_toplevel *xdg_toplevel_ptr, __UNUSED__ int32_t width, __UNUSED__ int32_t height, __UNUSED__ struct wl_array *states) { // Currently unused. } /* ------------------------------------------------------------------------- */ /** Handles the action of the 'close' button. */ void _xdg_toplevel_handle_close( void *data_ptr, __UNUSED__ struct xdg_toplevel *xdg_toplevel_ptr) { wlclient_xdg_toplevel_t *toplevel_ptr = data_ptr; wlclient_request_terminate(toplevel_ptr->wlclient_ptr); } /* ------------------------------------------------------------------------- */ /** Handles 'configure_bounds' of the toplevel. */ void _xdg_toplevel_handle_configure_bounds( __UNUSED__ void *data_ptr, __UNUSED__ struct xdg_toplevel *xdg_toplevel_ptr, __UNUSED__ int32_t width, __UNUSED__ int32_t height) { // Currently unused. } /* ------------------------------------------------------------------------- */ /** Handles the 'wm_capabilities' request. */ void _xdg_toplevel_handle_wm_capabilities( __UNUSED__ void *data_ptr, __UNUSED__ struct xdg_toplevel *xdg_toplevel_ptr, __UNUSED__ struct wl_array *capabilities) { // Currently unused. } /* ------------------------------------------------------------------------- */ /** Callback for when a `position` event is received. */ void _wlclient_input_position_observer_position( void *data_ptr, __UNUSED__ struct ext_input_position_observer_v1 *input_position_observer_ptr, __UNUSED__ struct wl_surface *wl_surface_ptr, __UNUSED__ uint32_t instance, int32_t relative_x, int32_t relative_y) { wlclient_xdg_toplevel_t *toplevel_ptr = data_ptr; if (!toplevel_ptr->position_received || toplevel_ptr->last_position_x != relative_x || toplevel_ptr->last_position_y != relative_y) { toplevel_ptr->position_received = true; toplevel_ptr->last_position_x = relative_x; toplevel_ptr->last_position_y = relative_y; if (NULL != toplevel_ptr->position_callback) { toplevel_ptr->position_callback( toplevel_ptr->last_position_x / 256.0, toplevel_ptr->last_position_y / 256.0, toplevel_ptr->position_callback_ud_ptr); } } } /* == End of xdg_toplevel.c ================================================== */ wlmaker-0.8/.gitmodules0000644000175100017510000000101315203543557014612 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/tests/0000755000175100017510000000000015203543557013604 5ustar runnerrunnerwlmaker-0.8/tests/wlmtool_test.c0000644000175100017510000000265115203543557016510 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmtool_test.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 #include #include "item.h" #include "menu.h" #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(int argc, const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; const bs_test_set_t *sets[] = { &wlmtool_item_test_set, &wlmtool_menu_test_set, NULL }; return bs_test_sets(sets, argc, argv, ¶ms); } /* == End of wlmtool_test.c ================================================ */ wlmaker-0.8/tests/backend_test.c0000644000175100017510000000242715203543557016403 0ustar runnerrunner/* ========================================================================= */ /** * @file backend_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 #include #include "backend/backend.h" #include "backend/output_config.h" /** Backend unit tests. */ const bs_test_set_t *backend_test_sets[] = { &wlmbe_backend_test_set, &wlmbe_output_config_test_set, NULL, }; /** Main program, runs the unit tests. */ int main(int argc, const char **argv) { const bs_test_param_t params = {}; return bs_test_sets(backend_test_sets, argc, argv, ¶ms); } /* == End of backend_test.c ================================================ */ wlmaker-0.8/tests/wlmaker_test.c0000644000175100017510000000452415203543557016456 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmaker_test.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 #include #include "action.h" #include "action_item.h" #include "wlmbacktrace.h" #include "clip.h" #include "config.h" #include "corner.h" #include "dock.h" #include "files.h" #include "launcher.h" #include "layer_panel.h" #include "lock_mgr.h" #include "root_menu.h" #include "xdg_decoration.h" #include "xdg_toplevel.h" #if defined(WLMAKER_HAVE_XWAYLAND) #include "xwl_surface.h" #endif // defined(WLMAKER_HAVE_XWAYLAND) #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(int argc, const char **argv) { if (!wlmaker_backtrace_setup(argv[0])) return EXIT_FAILURE; const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; const bs_test_set_t* sets[] = { &wlmaker_action_item_test_set, &wlmaker_action_test_set, &wlmaker_clip_test_set, &wlmaker_config_test_set, &wlmaker_corner_test_set, &wlmaker_dock_test_set, &wlmaker_files_test_set, &wlmaker_launcher_test_set, &wlmaker_layer_panel_test_set, &wlmaker_lock_mgr_test_set, &wlmaker_root_menu_test_set, &wlmaker_xdg_decoration_test_set, &wlmaker_xdg_toplevel_test_set, #if defined(WLMAKER_HAVE_XWAYLAND) &wlmaker_xwl_surface_test_set, #endif // defined(WLMAKER_HAVE_XWAYLAND) NULL }; return bs_test_sets(sets, argc, argv, ¶ms); } /* == End of wlmaker_test.c ================================================ */ wlmaker-0.8/tests/desktop_parser_test.c0000644000175100017510000000263015203543557020035 0ustar runnerrunner/* ========================================================================= */ /** * @file desktop_parser_test.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 #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(int argc, const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; const bs_test_set_t *sets[] = { &desktop_parser_test_set, NULL }; return bs_test_sets(sets, argc, argv, ¶ms); } /* == End of desktop_parser_test.c ========================================= */ wlmaker-0.8/tests/input_test.c0000644000175100017510000000302215203543557016143 0ustar runnerrunner/* ========================================================================= */ /** * @file input_test.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 #include #include "cursor.h" #include "manager.h" #include "pointer.h" #include "keyboard.h" #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(int argc, const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; const bs_test_set_t* sets[] = { &wlmim_cursor_test_set, &wlmim_keyboard_test_set, &wlmim_pointer_test_set, &wlmim_test_set, NULL }; return bs_test_sets(sets, argc, argv, ¶ms); } /* == End of input_test.c ================================================== */ wlmaker-0.8/tests/CMakeLists.txt0000644000175100017510000000573615203543557016357 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. add_executable(backend_test backend_test.c) add_dependencies(backend_test backend) target_link_libraries(backend_test backend) add_test(NAME backend_test COMMAND backend_test) add_executable(input_test input_test.c) add_dependencies(input_test libbase input) target_link_libraries(input_test libbase input) target_compile_definitions( input_test PUBLIC "TEST_DATA_DIR=\"${PROJECT_SOURCE_DIR}/tests/data\"") add_test(NAME input_test COMMAND input_test) add_executable(toolkit_test toolkit_test.c) target_link_libraries(toolkit_test toolkit) target_include_directories( toolkit_test PRIVATE "${PROJECT_SOURCE_DIR}/include/toolkit") target_compile_definitions( toolkit_test PUBLIC "TEST_DATA_DIR=\"${PROJECT_SOURCE_DIR}/tests/data\"") add_test(NAME toolkit_test COMMAND toolkit_test) add_executable(wlmaker_test wlmaker_test.c) add_dependencies(wlmaker_test wlmaker_lib) target_include_directories( wlmaker_test PRIVATE "${PROJECT_SOURCE_DIR}/src") target_link_libraries(wlmaker_test PRIVATE wlmaker_lib) target_compile_definitions( wlmaker_test PUBLIC "TEST_DATA_DIR=\"${PROJECT_SOURCE_DIR}/tests/data\"") add_test(NAME wlmaker_test COMMAND wlmaker_test) add_executable(desktop_parser_test desktop_parser_test.c) add_dependencies(desktop_parser_test libbase desktop-parser) target_link_libraries( desktop_parser_test desktop-parser libbase) target_compile_definitions( desktop_parser_test PUBLIC "TEST_DATA_DIR=\"${PROJECT_SOURCE_DIR}/tests/data\"") add_executable(wlmtool_test wlmtool_test.c) add_dependencies(wlmtool_test libbase libwlmtool) target_link_libraries(wlmtool_test libbase libwlmtool) target_compile_definitions( wlmtool_test PUBLIC "TEST_DATA_DIR=\"${PROJECT_SOURCE_DIR}/tests/data\"") if(iwyu_path_and_options) set_target_properties( backend_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( input_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( toolkit_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmaker_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( desktop_parser_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( wlmtool_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8/tests/data/0000755000175100017510000000000015203543557014515 5ustar runnerrunnerwlmaker-0.8/tests/data/keyboard0000644000175100017510000000025715203543557016244 0ustar runnerrunner# -*- conf -*- -- Keyboard configuration file, for wlmaker test. XKBMODEL="pc105" XKBLAYOUT= "us,ch" XKBVARIANT="intl," XKBOPTIONS="grp:shift_caps_toggle" BACKSPACE="guess" wlmaker-0.8/tests/data/toolkit/0000755000175100017510000000000015203543557016202 5ustar runnerrunnerwlmaker-0.8/tests/data/toolkit/primitive_fill_adgradient.png0000644000175100017510000000021315203543557024104 0ustar runnerrunnerPNG  IHDRbKGD@IDATcRdbfcdf#deafab"J)FP+~ 4`QKN(E@R vJBIENDB`wlmaker-0.8/tests/data/toolkit/menu_item_submenu_enabled.png0000644000175100017510000000357715203543557024116 0ustar runnerrunnerPNG  IHDRPxbKGD4IDATXWklTE>g-]B J PQTBTd(>P,! EABD0/ P*﷥ȣ@+iK;wI}D_3;3swsv~ _XHL!rd[Y7A1P@$@"$ ADk:@`֪ `ٍ_{kdf\w 1Xml+@_DyT7gNׇ挡>ed4Q`ðeG9G$5H L/@q$AXp6) ȱ>Zr4ٙ,G+KZ̏c,S"嫷z"HEBo'xNfR fCfD0VŠ6?%. [-$,]dkaq7a _>fЅ8Zn. eaÉcA պؑ QB`0 bXA$բHT\}PD4$^/eo{%#,fL9ESA0'٣*+s ˎ"wYFQpHs-WU]/&ʱ==9( 䱵c?x ˂+9흭_kѓ23{tofFcᖁ^Ow߁^iɡGP)VTGOC 2lc=ftlK_l$ͤ NUц&[ku]~dm(\=ĪuoJx7Rc!?W))'D3:^^NJ+w^w/UI:]0~\邍MMjzuLmZ!{Ÿ|{WI}sūsui+{[ɾi:ڋ5Jwe `쎲LƍTi7)).sCpB ի^tuESȔ[xM)ΖnT?;x:kC;kCHvG* iƎb\5O;|$ ؾ16jqk &9 lUW`X䝾dsUID0ʟӡCa]x* ( cGJi9УKTvk9}hW.[r5ap%+AfF6X_ªi[4Ӌz\$4nw?+hZvVG%pxڻu@`3ⱓҲ }zDGθVsQbCElؽRуpbCLk$zBjeMקfe\oRٯWdFf$еc=^A:⣑Ţ\8ٓ:}!ghᙂf]zE\+{m.X4% Wg?0ẴakYz62%n&OqEe&w=HQpѬe5fwۡ@ Hĸ"16=*`x:R1)]͇t?4qpL/^#Ks E@hZ۸jcΚ1NK#_fl-f""$B(\`I%f~,X@Q6 [c!ņ9jE Pyh։> Yւ;,n+ V,X jەnkX`#E\JtY།Kf ȢϿ ]戨 jSvBD Z/HXDˍ1r01!}CBb}&@IENDB`wlmaker-0.8/tests/data/toolkit/menu_item_disabled.png0000644000175100017510000000321415203543557022521 0ustar runnerrunnerPNG  IHDRPxbKGDAIDATXWoU33n)]"-l"jD#Dm 'cbbol|4cbT$ƈpr1m¶eanΒDݧ9}7g$PT!P-Dϫ⛐wё&xA A P(H}(grOl5!M C_Dm?y3"d>5?jE= k"`rzT6W&C5ZP 3 WF" :xZj$uMP2пHX:! ,Qj,Lc2ȃO5FƂnXypF$$)ĂfxʹVFԐAzjHْFږnZ[UGl zsjŴq1uTAZ^ª׃+ F1ޝpd6:0/ֶҌܱPdL%H.3ACPH,J"me,wtp?k\90zoצiÞo*TV2y~/^}u GT,ag3vȚGNe*ꏤC?9exAg3IJqwσ.k=WK7tfiE.eF_gas }9޷/c}oㅅP}o:$(w#g f;w:z~!{:BXmɥ9cz{X(fqKg5޿,Q'ҍq[z}݉uRwl:9Y֞Y{9H__i5EჂ C걦;D'2?sԀuĊE1\3rdO45&01+{.3)ŊlڵLR.6Mzb1S ]iT-6uRZ\UlZ}Zi+pGF]؜]զ#̏c7AS ڥUIZZw.0H俩P(L/ 9]DK!%'J@;YADn2v ؽ1& MY aӍ :NF4 vkIQżli`i涍{Q6TJLۺao6F JRS%Ҵx 76$KBȲ=ooxhθ=).2Q ui50 FkLLt _]Vڟn.XPJqj׆ņk^)+AV2x9a_84=sbwo:×{4G{D+?t7IlF!Ӂ>M|_~~Tץn+p?`9 2q&ya m9u=BZV{ͤRORI#U.X"8_)]mj,hi$@y*k劋ٖ%vRax4mmY6;, Ѽh q, O Jw5,z1T+tOCE?'1IDpOL "@M^PP7 oPpCDBĠ?l֨2IENDB`wlmaker-0.8/tests/data/toolkit/primitive_fill_hgradient.png0000644000175100017510000000014615203543557023754 0ustar runnerrunnerPNG  IHDRbKGDIDATcRvcf31F5zŔsIENDB`wlmaker-0.8/tests/data/toolkit/primitive_fill_dgradient.png0000644000175100017510000000025015203543557023744 0ustar runnerrunnerPNG  IHDRbKGD]IDAT @ ޱ/3CvHqa|)"%{zdKj7n2WIENDB`wlmaker-0.8/tests/data/toolkit/resizebar_area_pressed.png0000644000175100017510000000017115203543557023412 0ustar runnerrunnerPNG  IHDR ;sbKGD.IDATcRf <}XԤx(qrgo Y;~EJ?6IENDB`wlmaker-0.8/tests/data/toolkit/title_button_focussed_released.png0000644000175100017510000000060515203543557025164 0ustar runnerrunnerPNG  IHDRKlbKGD:IDAT8͓=0m CH>鑕$' is 蓂#D,P\ =vdo݈&KWf`>BsZ0z[!!A~Q}R(<)#]@lwPꦩg'4]P,[,$1\tM K%,HJ00k;9/'X c4,Z !z<7¶JJeY0lnAe?|:UE%@LBk:}Y,>cj5 F7Mo*p8r^A|jPIENDB`wlmaker-0.8/tests/data/toolkit/menu_item_highlighted.png0000644000175100017510000000203215203543557023227 0ustar runnerrunnerPNG  IHDRPxbKGDIDATXc|3Èga``@N3LzQwKKYG  /:H#|3`??Mlÿzȹˏi 6V o#vvͧ.<-8=,/z#8>QV# UvV0004HOncbbb``s;o?! >}lɫ7hihϿEyNKמ`j8?jWL`e``sƙ *r}a``sqQ[J A99e27kB23sH 6i 3~o"-B LF:r.?:w!rzf``XvY{Z;4)>yǑm@e+wݧ>jH#++ˋNw?]?sX3?s ?~*=-5oiIbVNK֞螾P)9̌ɑ66{4Rڸ·ᅡ< I `|3=?afbba6h۬nHF=<0cO&C3tIENDB`wlmaker-0.8/tests/data/toolkit/menu_item_submenu_highlighted.png0000644000175100017510000000225015203543557024767 0ustar runnerrunnerPNG  IHDRPxbKGD]IDATXWmL[U> ^hRUXdKLh25N8b ..cEh,FfiD_,ӍYXIR]Ki{{sQeۖ }|q8g::vםdkDtˍU+ aߡST5mN44Dޙ 2 , Kˠ#o$Yz\ cI#I:* 0&]U$I2"q?v+vפ=qEkB(`OBgSQAw>|[ًBGFpeܶ>"HDB #~*W!r>AQ.߬]F^"mi:rYsXD!=\I-x)a JǪΞޔ4]_IENDB`wlmaker-0.8/tests/data/toolkit/menu_item_submenu_disabled.png0000644000175100017510000000343115203543557024260 0ustar runnerrunnerPNG  IHDRPxbKGDIDATXW]lW=knvn\;I4Q6BPU> oE"UT@6׭J_^;;Rwws^&  I?`W5HY? .B>/.4 |{?$hB R ܆B%`(se uaDVӆ綳 S6,^XO |:Qmh7TF!pqh;\ٽG,jf%QXtTK2":E,@ P* яHX$+j,72/OMA+ʗ϶۟ S iHHR>i;-H!+%) մcEHQF14T0A~bibz>j9,^o'Rq! w*h1wmZu`lM ݱɍKOdn $GY0\C̮' A!Ah9(moɱp&s.`xr͹ӏ;\ܲ ;Fꃍ=]>PVzDg2}~vԚ689ܽj_EW%gq4+漀[K[_|W?`IqlʵLRnLzҫS^e!cT-?kN U zsm$61m6f'Yw-NU\iݖ؟:]Ֆ#6j毦󑛠)ҪΤ ,ZpUq^T(L!}/L>KV/'m"O!%J8Phѝ/'rȥdS"ZiM` Y6[:H2F9!^^ctV$АʗZXJ VỚdW/J,mUkBH19R4,%]T!M[Ŕ-fVNbh`JԄea깔3 %l˜°$^rDjkG~glzg8Sݙ 0Xf}aֹg7zo}`RNƳ z2RLU]: (ؑw^Ա_Τ/J#g3)K<{j iijT`^/~?{v>iHt`@.~jx/pve| qojr\Fxxygw^]s\Fx-f]z%I^RB\T`>5%5:/XjZQ; J肪ð}δbve!jaGz/XVg!jx= ;aADO\qG\2l;`*jzyLNAHqA@H ;/*. wj7K(P0ס!DBDjoh0IENDB`wlmaker-0.8/tests/data/toolkit/title_focussed.png0000644000175100017510000000104515203543557021724 0ustar runnerrunnerPNG  IHDRZPBbKGDIDATX혿KqRI̥+jjp)ZAȱI B)p !C$i;:J_ ɕDuw{/{~xxi3w@0X:::afg\Z6OOLLpw(ZCJH`ah  Z-\. Y?:"{{Ύ|zxxx'F%D <VyNO"Qs{ pxH2hY,B|TJ 15.iO#\]cc1Jc~,?af7i=Fi.:mKnn:ZMQL+@I*E*#N'ŧxyX^N=aeEj5h% tt"LN*꯯DlnvcˑHP, FI:!擓$ecPxЧq=IENDB`wlmaker-0.8/tests/data/toolkit/primitive_window_title.png0000644000175100017510000000066315203543557023515 0ustar runnerrunnerPNG  IHDRPWbKGDhIDATX햭KA &~p6Ѧ(l^m6M/LA$pA fAA_9ܭ坙gv@ LՕtIےF+UIt'䞁wקǵ)8LMt76 $ݹ|مgȸ20WI{%J:4VK˒*/T[ IJ>$e\ KlfXoT$"s1'I\mf,෋i,`-{jߺefy3Og=^7W%q, \7KAW|-ēfV& Lg]y@k9.`IENDB`wlmaker-0.8/tests/data/toolkit/primitive_close_icon.png0000644000175100017510000000030615203543557023114 0ustar runnerrunnerPNG  IHDRĴl;bKGD{IDAT8A W}11/<`D(A &J'\<'Ct']X՛݁'ؓ$s% *P+|^q+ ٕ0G:$IENDB`wlmaker-0.8/tests/data/toolkit/primitive_minimize_icon_large.png0000644000175100017510000000034315203543557025003 0ustar runnerrunnerPNG  IHDR22?bKGDIDATh1 1@џn^e"x7aX Z)Y $ImΡ]έBE˙4dG!Rͦ8e,mҬ\ęW_ ǐj Ɛj oBz_cq92)$IuyɅ*SIENDB`wlmaker-0.8/tests/data/toolkit/primitive_minimize_icon.png0000644000175100017510000000021115203543557023623 0ustar runnerrunnerPNG  IHDRĴl;bKGD>IDAT8c`0 `dj."hf0N@LPS3b`8`'Il&8wBIENDB`wlmaker-0.8/tests/data/toolkit/title_button_blurred.png0000644000175100017510000000057315203543557023150 0ustar runnerrunnerPNG  IHDRKlbKGD0IDAT8SJ@n+ 6B'. 8:{+ o?!6rXyrŕٽbaML472{ 2`@0(dYo ι3D|/i~vyQ-+p&JzQ.%4;ޥYoZH49?=>;9x!%exʋJ&BZu */*#μqZ\袈pvuBRJ&qr"=^?FKsZT(,Jf=2Ƭc}ߚ眷Eܝ?ckв,9C(>y%^jIENDB`wlmaker-0.8/tests/data/toolkit/primitive_fill_vgradient.png0000644000175100017510000000014615203543557023772 0ustar runnerrunnerPNG  IHDRbKGDIDATcVa H > 4;AtIENDB`wlmaker-0.8/tests/data/toolkit/title_button_focussed_pressed.png0000644000175100017510000000066415203543557025052 0ustar runnerrunnerPNG  IHDRKlbKGDiIDAT8͓n@Ǐ+wKOIh;0!q"}} > w1:Х`Pz[w|?Wp Z#t]XuQDQ^Ey+rR)) 0#JCc|!j\ji al1cfr^WUREۏiu`Suc$iYT= L3L!Щ?gȕYp'ɤO9ۖ+3PXFp!mUePuOi4YV{,{e 䗧iNY|e}|QA3 *0z.݂v^cIENDB`wlmaker-0.8/tests/data/toolkit/resizebar_area_released.png0000644000175100017510000000017315203543557023533 0ustar runnerrunnerPNG  IHDR ;sbKGD0IDATcܿy^y Uxz 1@gE,[֯3~E M#3IENDB`wlmaker-0.8/tests/data/toolkit/title_blurred.png0000644000175100017510000000105715203543557021553 0ustar runnerrunnerPNG  IHDRZPBbKGDIDATXߪ@LbET/ *utd]D!R!Pb@8sS{jXp%ah4`8Z[1f^(Pؑ#58!d2 Naum۞[Ώ;vA<pq!^$UU%Ir]cj ][y"ݝft(  |ܱ:BjZ7v@]%1>fUѬ鶙I%\~UabBR,cN_.β9Ԃ8MAXeX֕= #}e4HaK!@FTa#NN '}#_$L r, ZũI/)z_c|;N>Wz_k7:Z.;\OdJ}S׼8}U88Jpwoir>߮b pHYs.#.#x?vtIME.DtEXtCommentCreated with GIMPW IDATcph8tIENDB`wlmaker-0.8/tests/data/toolkit/menu_item_enabled.png0000644000175100017510000000337315203543557022352 0ustar runnerrunnerPNG  IHDRPxbKGDIDATXWilTU}oЅil$bI0E%! l&cD" X$E@EB"(`TP kRJh ݦ돷 H]}D@DFDC!,$H ~7rnY >w) 1 ۘlB&~qLP8$8vLך!EkcL.=~Y,A D:$ 2B(!ttB\hh Bv0WfBr,Ȩ(9Ӑt&)@!# ڍ'XJ-}<'dntT$D4=ͩ[<7X-퍭__K8 %ӆZZ,DAL4mziZH^[.bZk:WTV{cWCLS^k',z iɁ(c4v fI`hmP]SMj!uf,B\DM @@nT]C78fM+6xSn–3_<9jEע}]<)ZaВ!}^YSd!&:'Ԃ;$ݞo3!>PJ9b[l(L;ۼw$*)*mֱ7$k9W +wwO4W8:ALU^=5ܒ?ŭn|p \24mW 6 є\1}9wm[ȷ rb_ef/btrR3M9G^>lBzϬ &kI\az;fDEL#]%+Qe'lRG֟Tbx`zt,mỻpcZQi™KtZo<~OツҦv>UŒV 4/ZD{y\ǑSlnl,31FJD",VB9YhnlT7'U߼ADJT"_k DQ֎jHO;z<tB)v_f1M02(.-(ܲjU<&- Ez8YDĝviY'H&!w OCk,{Ҿugh4Oͧoth%5a"" *m\oc~]3'n= vT[biW^d %s˻O ̞|5*P^؄QW}޺ˆ8q4x7179V.Wy .3εtti2kX`H }).Au購Zf9*LӰ3/fن 9:?7³qWw.-jDˡG WݿhJa5;SE^D oʚJ`A<8jmP@t7o ѵ,u\&s6.uZw/4ey-;.gr,حL($4\YQe"ՖXĂ+ ELY.aY'z|Rh Po`WAH:\0cKmØĸ5矖_ 4J6lDfGvoT:7^PM~RV /.oG2NoN!] 9Xc5 s20p)訂w< GN6 )Yu@ׁsR|yl'b'~a841}V 4hРAU LɈn'IENDB`wlmaker-0.8/tests/data/toolkit/primitive_fill_solid.png0000644000175100017510000000014115203543557023114 0ustar runnerrunnerPNG  IHDRbKGDIDATcth8@ `"I"VfjIENDB`wlmaker-0.8/tests/data/toolkit/primitive_close_icon_large.png0000644000175100017510000000050015203543557024262 0ustar runnerrunnerPNG  IHDR22?bKGDIDAThK EIcҝQ?Ӊ&/1< "B!P -PU5fuf s$XrfhWBDlHW8A 2 8v }ށ>K%`2f%OisH؝);elI$ؔ! Úx]9GDgqσ/gϘ Q¦LXܱ#;31^䳙9 -J9SŪn1e< !B+7sIENDB`wlmaker-0.8/tests/data/toolkit/title_blurred_short.png0000644000175100017510000000104015203543557022762 0ustar runnerrunnerPNG  IHDRF}$bKGDIDATXߪ@LbET/ *utd]D!R!Pb@>]ڛ~W}y3 ~~h%wcl6,꺞G,k( R(yʒ%qqa!4 $c0 nJQ@V;>۶]UU!rLmVqz-"ByJǦiZ-IqDzo x71ƏqO<+QhIENDB`wlmaker-0.8/tests/data/Themes/0000755000175100017510000000000015203543557015742 5ustar runnerrunnerwlmaker-0.8/tests/data/Themes/theme2.plist0000644000175100017510000000002015203543557020173 0ustar runnerrunner{Name="ThemeA";}wlmaker-0.8/tests/data/Themes/theme1.plist0000644000175100017510000000002015203543557020172 0ustar runnerrunner{Name="ThemeB";}wlmaker-0.8/tests/data/menu-generate.plist0000644000175100017510000000007315203543557020326 0ustar runnerrunner("Include", GeneratePlistMenu, "cat tests/data/menu.plist")wlmaker-0.8/tests/data/clip_raised.png0000644000175100017510000000273715203543557017512 0ustar runnerrunnerPNG  IHDR@@% bKGDIDATh՚[oEߘ?9v<323/!%{B$JC$VxƓ8r0Hᢺ=ƎGuxVU:1J_=|f^g3Ϥe%1ӂM'ċ/QKergg?rx{~kk˳7o @6H>eŋJx1kt-g =b:;Hq#KCq́ކӻ8Po,?ۻwD8\?׉ /U.6N#~)b0ɅJm<ʧf?˝;;2y0QϽ]r$Q}m0(s]W3=HҡAȃ;;@AQ1ҙ]ՁwX>MoO&=+Coҙ2ʟ=~)cjqlG'-&@m0."j>|TBeu(/PES-{pTB DonzP6'U$:j- S0}?R*#%+ w~\bV ,Ťh{_(aZܞ[WKohLףeR*F)F>"=y<= V1ܞ V=f Oopy+ Л=AFB%!"T.iR }t.Ԝ-TRz\iKIc%T|zQ*}R`-#}ne;JH缾*%=J -GVZlz׉$T|z$@I58]Bz< $.+wkkkZ8Y*D7ީS0x,W^P0y"=i 畷Z` Kn=@+o%`JoV[-z入Gy!<ۭ՗j- KCJH߻};+oT6 Fop@U W땷M jQ zweh#^eD+/fz;$I]m땷[M a8*rO2} Bgs>L40`+Ϗg%I76?uW^i" sG BɈ֍$w.X|e0O3O޻M 4ڞ~s{dv Coң$gڰ^k$A`De=V/^_on}vdFKp!H1˩֓#ܺuӏyK'rm4IENDB`wlmaker-0.8/tests/data/subdir/0000755000175100017510000000000015203543557016005 5ustar runnerrunnerwlmaker-0.8/tests/data/subdir/wlmaker/0000755000175100017510000000000015203543557017447 5ustar runnerrunnerwlmaker-0.8/tests/data/subdir/wlmaker/a.txt0000644000175100017510000000000015203543557020416 0ustar runnerrunnerwlmaker-0.8/tests/data/wlmaker.desktop0000644000175100017510000000047415203543557017557 0ustar runnerrunner# Made-up entry, used as input for desting the .desktop parser. [Desktop Entry] Name=WaylandMaker Comment=A Wayland compositor inspired by Window Maker Exec=/usr/local/bin/wlmaker TryExec=./wlmaker Path=/usr/local Type=Application Icon=wlmaker Hidden=false NoDisplay=false Terminal=true Categories=System;Compositor;wlmaker-0.8/tests/data/menu.plist0000644000175100017510000000006715203543557016541 0ustar runnerrunner("Menu Title", ("Item 1", None), ("Item 2", None) )wlmaker-0.8/tests/data/input/0000755000175100017510000000000015203543557015654 5ustar runnerrunnerwlmaker-0.8/tests/data/input/cursor-index.theme0000644000175100017510000000004015203543557021314 0ustar runnerrunner[Icon Theme] Inherits=ThemeName wlmaker-0.8/tests/data/clip_pressed.png0000644000175100017510000000311115203543557017673 0ustar runnerrunnerPNG  IHDR@@% bKGDIDATh՚[oE:HCNNlp-M+Ԫ~Tp$hA9\ !v|HlvC%w7nbv7TMf}+yOTM:|w ^;pd^rqp*}|jcWl ?X*~7NTM'F!jj'cg2ܬcozF8ٹiriI]޾^Ndm{nnD\#3;{s!|ێ^FEڶNT'TuWG镨כ,,fm˙ΐ*UϽučpB7FTCu JlW\Y['#ygӫ:D ^ɕe;w2A '+esYJQR ^z%*:)O,V%-WCT* Jw{ӫ!5kbb#ӡW\եѢW\xuuqp X("J>`L%hSEDLՁ{1QzSopӻQZ(t, z7 FޭKAIu"H ,Iz¯*!EKGnzoY(p@D1r;Az E!*=$/)zB<~,5MvڔLD Gѣ2Żfډ=_hhfv t=0|j 7I_29R]C=Z ~nxוr AEwG_n|S:n-47!HHoR(HY[GwUSu $gW>DsLJR4 M5%'csAf[mߣdBO3kk.xo;hj^ۻsdz@';{M|J_~R޽q fK9IENDB`wlmaker-0.8/tests/data/menu-include.plist0000644000175100017510000000006615203543557020161 0ustar runnerrunner("Include", IncludePlistMenu, "tests/data/menu.plist")wlmaker-0.8/tests/toolkit_test.c0000644000175100017510000000432415203543557016477 0ustar runnerrunner/* ========================================================================= */ /** * @file toolkit_test.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 #include #include "toolkit/toolkit.h" /** Toolkit unit tests. */ const bs_test_set_t *toolkit_test_sets[] = { &wlmtk_base_test_set, &wlmtk_bordered_test_set, &wlmtk_box_test_set, &wlmtk_buffer_test_set, &wlmtk_button_test_set, &wlmtk_container_test_set, &wlmtk_dock_test_set, &wlmtk_element_test_set, &wlmtk_fsm_test_set, &wlmtk_image_test_set, &wlmtk_layer_test_set, &wlmtk_menu_test_set, &wlmtk_menu_item_test_set, &wlmtk_output_tracker_test_set, &wlmtk_panel_test_set, &wlmaker_primitives_test_set, &wlmtk_rectangle_test_set, &wlmtk_resizebar_test_set, &wlmtk_resizebar_area_test_set, &wlmtk_root_test_set, &wlmtk_style_test_set, &wlmtk_surface_test_set, &wlmtk_tile_test_set, &wlmtk_titlebar_test_set, &wlmtk_titlebar_button_test_set, &wlmtk_titlebar_title_test_set, &wlmtk_util_test_set, &wlmtk_window_test_set, &wlmtk_workspace_test_set, NULL }; #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(int argc, const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; return bs_test_sets(toolkit_test_sets, argc, argv, ¶ms); } /* == End of toolkit_test.c ================================================ */ wlmaker-0.8/doc/0000755000175100017510000000000015203543557013207 5ustar runnerrunnerwlmaker-0.8/doc/FEATURES.md0000644000175100017510000003627615203543557014765 0ustar runnerrunner# wlmaker - Detailed Feature List This document lists features implemented, planned or proposed for wlmaker, and sets them in reference to documented [Window Maker](https://www.windowmaker.org/) features, where available. Features should be in any of the following state(s): * [ ] -- **Listed**: A desired feature for the future. * [ ] :clock3: -- **Planned**: Listed on the roadmap for an upcoming version. * [ ] :construction: -- **In progress**: Is currently being worked on. * [x] :white_check_mark: -- **Implemented**: Has been implemented. * [x] :books: -- **Documented**: Implemented and has user-facing documentation. If a feature is partially implemented, it is suggested to break it down into parts that map to above states. ## Windows ### Anatomy of a Window **Reference**: [Window Maker: User Guide, "Anatomy of a Window](https://www.windowmaker.org/docs/chap2.html). > [!NOTE] > Window Maker specifies the default layout of an application (toplevel window), > and does so by decorating each window consistently. The default Wayland shell > behaviour is for clients to provide their own decoration, with an optional > [XDG decoration protocol](https://wayland.app/protocols/xdg-decoration-unstable-v1) > to permit server-side decorations. > > wlmaker implements that protocol for window decorations. #### Elements of the window decoration * [x] :white_check_mark: XDG decoration protocol implemented, applies decoration as negotiated. * [ ] Override decoration setting per application. * [ ] Configure applying window decoration in case application does not support decoration protocol. * Titlebar * [x] :white_check_mark: Holds the name of the application or window. * [x] :white_check_mark: The color indicates active or inactive status (keyboard focus). * [x] :white_check_mark: Dragging the titlebar moves the window. * [x] :white_check_mark: Right-click displays the window commands menu. * [ ] The color indicates if a (modal) child dialog has keyboard focus. * [ ] Applies ellipsize on long names. * [ ] Left- or right-aligned, depending on language/charset flow of text. * Miniaturize button * [x] :white_check_mark: Is shown on the titlebar. * [ ] Miniaturize/iconify the window when clicked ([#244](https://github.com/phkaeser/wlmaker/issues/244)). * [ ] Hide the window when clicked. * Close button * [x] :white_check_mark: Requests the application to close. * [ ] Figure out how to forcibly kill the application (kill or close the connection). * Resizebar * [x] :white_check_mark: Drag with the left button resizes the window. * [ ] Drag with the middle mouse button resizes the window *without raising it*. * [ ] Drag while holding the control key resizes the window *without focusing it*. * [ ] Drag while holding the meta key moves the window. * Client Area: Holds the application's toplevel surface(s). * [x] :white_check_mark: Clicking into the client area focuses the window. * [x] :white_check_mark: Left button drag while holding Meta: Moves the window. * [ ] Do not pass the activating click to the application if *IgnoreFocusClick* is set. * [ ] Right button drag while holding Meta resizes the window. ### Focusing a Window A window can be *active* (has *keyboard focus*) or *inactive* (not having *keyboard focus*). Only one window can be active at a time. * [x] :white_check_mark: Click-to-Focus mode. * [x] :white_check_mark: Activates + raises the window when left-, middle- or right-clicking on titlebar, resizebar or in the client area. * [ ] Add configuration option to permit "Activate" without raising on click. * [ ] Middle-click on the titelbar activates the window, but does not raise it. * [ ] Add Focus-Follow-Mouse mode. Essentially, having *pointer focus* implies *keyboard focus*, and inactivate window when losing *pointer focus*. * [ ] Add Sloppy-Focus. Activate a window when obtaining *pointer focus*, but retain it unless another window becomes activated ([#245](https://github.com/phkaeser/wlmaker/issues/245)). * [ ] Add a config-file setting for these options. ### Reordering Windows TBD: Raise/Lower. ### Moving a Window * [x] :white_check_mark: Left-dragging the title bar moves the window. * [x] :white_check_mark: Holding Alt and left-dragging while anywhere on the window moves the window. * [x] :white_check_mark: The modifier key for left-dragging anywhere on the content is configurable. * [ ] Drag resizebar while holding Meta: Moves the window. * [ ] Drag titlebar with middle mouse button: Moves window without changing stacking order. * [ ] Drag titlebar while holding Control: Moves window without focussing. ### Resizing a Window * [x] :white_check_mark: Drag left, middle or right region on the resizebar resizes the window. * [ ] Drag window in the client area with right mouse button, holding meta: Resizes the window. * [ ] Drag resizebar with the middle mouse button: Resize window without bringing it to the front. * [ ] Drag resizebar while holding the control key: Resize window without focussing it. ### Shading a Window * [x] :white_check_mark: Shade or unshade the window by the mouse's scrollwheel. * [ ] Double-click on the titlebar shades or unshades the window ([#246](https://github.com/phkaeser/wlmaker/issues/246)). * [ ] Permit a configurable keybinding for shading/unshading the window. * [ ] Shading and unshading has a short animation to enlarge the window. ### Miniaturizing a Window * [ ] Left-click on the miniaturize button collapses the window into a mini-window. * [ ] The transition to a mini-window (and back) is animated. * [ ] The mini-window is shown in the *Icon Area* and is distinguishable from an application icon. ### Closing a Window * [x] :white_check_mark: Left-click on the window's close button requests the application to close it. * [ ] Define whether "forcibly" closing means to kill the client process, or to close the wayland connection. * [ ] Holding Control while left-clicking the window's close button forcibly closes the window. * [ ] Double-clicking the close button forcibly closes the window. ### Maximizing a Window * [x] :white_check_mark: Select 'Maximize' from the window commands menu. * [x] :white_check_mark: A configurable key shortcut requests the window to be maximized. * [ ] Define whether control-double-click should do resize height to full screen. * [ ] Define whether shift-double-click should do resize height to full screen. * [ ] Define whether control-shift-double-click should do maximize. * [ ] Add a configuration option whether 'Maximize' may obscure Dock, Clip and Icons ([#328](https://github.com/phkaeser/wlmaker/issues/328)). ### Window Placement * [ ] New windows are placed on a suitable free spot, if available. * [ ] Once screen is full, windows are stacked with a moderate displacement between each. * [ ] "Gravity" to snap and stick to borders. * [ ] Pulling a window towards an edge of an output sets window to occupy that edge. ### Window attributes * [ ] Permit to configure attributes by application ID and/or window title. * [ ] Permit to disable titlebar, resizebar, close button, miniaturize button. * [ ] Option to keep on top. * [ ] Option to be "omnipresent", ie. shown across all workspaces. * [ ] To determine: Start miniaturized. * [ ] To determine: Skip window list. * [ ] Specify an icon, where not provided. * [ ] Specify initial workspace. * [ ] Scaling factor (fractional scale). * [ ] Use *XDG Shell* `wm_capabilities` to advertise capabilities, as attributes permit. ## Workspaces * [x] :white_check_mark: Workspaces for startup are configurable in the *state* configuration file. * [x] :white_check_mark: Navigate between workspaces using a configurable key combination (ctrl-arrow). * [x] :white_check_mark: Navigate between workspaces through scrollwheel or buttons on the *Clip*. * [ ] When saving state, the current workspace configuation is saved to the *state* configuration file. ## Workspaces menu * [x] :white_check_mark: "New", to create a new workspace. * [x] :white_check_mark: "Destroy" or "Destroy last" for destroying a workspace. * [ ] Menu item to navigate to the provided workspace. * [ ] Ctrl-click on menu item to rename the workspace, through a dialog. ## Navigating workspaces * [ ] Optional display the workspace name after moving to a new workspace. * [ ] The transition between workspaces is animated. ## Window assignments * [x] :white_check_mark: Send window to another workspace through ctrl-shift-arrow (or configurable combination) * [ ] Navigate to workspace using key combination (eg. Alt-) * [ ] Navigate to same workspace in next group, using key combination. * [ ] Support workspace groups, addressable by an extra modifier of key combinations. ## Menu contents ### Root menu * [x] :white_check_mark: Menu configurable as a plist. * [x] :white_check_mark: Generated from XDG repository ([#90](https://github.com/phkaeser/wlmaker/issues/90)). ### Window commands * [x] :white_check_mark: Static contents, shown when right-clicking on title bar. * [ ] Items and contents adapting to state (eg. no "maximize" when maximized). ## Wayland protocol support ### `ext-idle-notify-v1` * [ ] Implement. ### `ext-session-lock-v1` * [x] :white_check_mark: Implement, verified on single output. * [x] :white_check_mark: Make it work on multiple outputs: Lock surface shown on each. ### `fractional-scale-v1` * [x] :white_check_mark: Support fractional surface scales. ### `idle-inhibit-unstable-v1` * [x] :white_check_mark: Implement. * [ ] Test it. Didn't have a tool when adding it. ### `wlr-foreign-toplevel-management-unstable-v1` * [ ] Implement. ### `wlr-layer-shell-unstable-v1` * [x] :white_check_mark: Support layer panels. * [ ] Support `keyboard_interactivity` for upper layers. ### `wlr-output-management-unstable-v1` * [x] :white_check_mark: Implemented, and verified with `wlr-randr` and `wldisplays`. * [x] :white_check_mark: Scale, transformation and mode of output is configurable in *config* file, by matching output attributes. ### ``wlr-screencopy-unstable-v1` * [x] :white_check_mark: Implemented, works for `wdisplays`. ### `wp_viewporter` * [x] :white_check_mark: Support surface cropping and scaling. ### `xdg-activation-v1` * [ ] Implement. ### `xdg-decoration` * [x] :white_check_mark: Implemented. ### `xdg-shell` * [x] :white_check_mark: Support toplevel shell and popups. * [x] :white_check_mark: Refactor: split `xdg_surface` off `xdg_toplevel`, encode as separate classes. * [ ] Accept state (maximize, fullscreen, ...) before mapping the surface, but apply them only after first commit. * [x] :white_check_mark: Accept decoration requests before first commit, and forward them after the first commit was received (see also https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4648#note_2386593). * [ ] Support `set_parent`, associating a child `wlmtk_window_t` with a paraent. * [x] :white_check_mark: Consider suggested position on `show_window_menu` ## X11 client support (XWayland) * [x] :white_check_mark: Support windows and popups, enough for `xterm` and `emacs`. * [x] :white_check_mark: Investigate if the connection can identify the real X client, not the XWayland connection (yes, it does). * [ ] Modal windows should be a child `wlmtk_window_t` ## Dock, Clip, Icon Area * [x] :white_check_mark: Launcher item display when the app is "launching", and "running". * [ ] Drag-and-drop icons between icon area, dock app and clip. * [ ] Investigate if & how to use icons specified in XDG desktop entry. * [ ] Aspirational: Aim to have *Dock* and *Clip* running as separate process(es). * [ ] Entries in Dock/Clip have a settings menu to define path & icon. ### Dock * [x] :white_check_mark: The entries in the dock are loaded from the *state* file. * [x] :white_check_mark: Edge and anchor are configured in *state* config file. * [x] :white_check_mark: Entries have configurable icon image, for when app isn't running. * [ ] Entries can be configured to autostart upon wlmaker startup. * [ ] Entries can be configured to be "locked", preventing accidental removal. * [ ] Entries can have a drawer, to nest a second layer of entries. * [ ] When saving state, dock entries are saved into *state* config file. ### Clip * [x] :white_check_mark: edge and anchor are configured in *state* config file. * [ ] Entries can be configured to be "omnipresent", but are per-workspace by default. * [ ] When saving state, clip entries are saved into *state* config file. ### Icon Area * [ ] Display running apps using the configured icon. * [ ] Consider showing a miniature of the toplevel surface in the icon. TBD. ### Dock Apps * [x] :white_check_mark: Have a demo app using the icon protocol (`wlmclock`) * [ ] Configurable to show in Dock or Clip. * [ ] When starting from anywhere, dock apps show in icon area. * [ ] When starting from a docked or clipped position, show there. * [ ] Add another demo DockApp (julia set). ## Toolkit * [ ] Use pango proper over cairo toy interface. Use relative sizes to scale. * [ ] Support configurable fractional scale for all decoration elements. * [ ] Text strings are looked up from a table, for internationalization. ### Menu * [x] :white_check_mark: Permit navigation by keys * [ ] Position all menus to remain within output. * [ ] Re-position to remain within output when submenu opens. * [ ] Handle case of too manu menu items that exceed output space. * [ ] Dynamically set item width to hold longer item strings. Apply ellipsis on overflow. * [ ] Show keyboard shortcut to corresponding action, if available. ## Devices ### Multiple outputs ([#122](https://github.com/phkaeser/wlmaker/issues/122)) * [x] :white_check_mark: Output layout configurable via third-party tool, eg. `wlr-randr`. * [x] :white_check_mark: Save state in a state file (in the *config* directory currently). * [x] :white_check_mark: When saving state, store the current layout in the *state* config file. ## General * [x] :white_check_mark: Add a logo. * [ ] Use SVG as principal format for icons. * [ ] Add an info panel, showing version, name, copyright and link to documentation. * [ ] Upon first launch, show an onboarding screen with basic instructions ([#131](https://github.com/phkaeser/wlmaker/issues/131)). ### Configuration files * [x] :white_check_mark: Support `Execute` action (vs. `ShellExecute`) ([#261](https://github.com/phkaeser/wlmaker/issues/261)) * [ ] Change KeyBindings format to be similar to menu actions: Lists, where item 1+ holds the action and optional parameters. ### System integration * [x] :books: Store config files not in `${HOME}/~`, but in `${HOME}/.config/`, according FreeDesktop specification ([#262](https://github.com/phkaeser/wlmaker/issues/262)). * [ ] Review and define what to support from https://specifications.freedesktop.org. * [ ] Set `XDG_CURRENT_DESKTOP` to a sensible value. * [ ] System Tray (potentially through a Dock App) * [ ] Notifications (potentially through a Dock App) * [ ] Review whether to support Icon themes. ### CI/CD * [x] :white_check_mark: Have github workflows to build with GCC and Clang, x86_64 and arm64, and Linux + *BSD. * [ ] Generate the screenshots (extend wlroots for a PNG backend?) * [ ] Provide a binary package of wlmaker, from HEAD. * [ ] Run static checks and enforce them on pull requests (eg. https://www.kitware.com/static-checks-with-cmake-cdash-iwyu-clang-tidy-lwyu-cpplint-and-cppcheck/). wlmaker-0.8/doc/manual.css0000644000175100017510000000511615203543557015201 0ustar runnerrunner/* Doxygen extra stylesheet. Make it look like a wlmaker window. */ body { background-color: #505080; margin: 0; } body, table, div, p, dl { font: 12pt Helvetica,sans-serif; } div.contents { background-color: #a6a6b6; border-color: black; border: 1px solid; color: black; filter: drop-shadow(4px 4px 4px black); margin-left: auto; margin-right: auto; margin-top: 0px; padding-bottom: 20px; padding-left: 20px; padding-right: 20px; width: 960px; } div.fragment { background-color: #202020; border: 0px none; color: silver; padding: 8px; } div.header { background-color: #505080; background-image: linear-gradient(10deg, #000010, #202070); border-bottom: 1px solid #101038; border-left: 1px solid #808088; border-right: 1px solid #101038; border-top: 1px solid #808088; filter: drop-shadow(4px 4px 4px black); margin-bottom: 0px; margin-left: auto; margin-right: auto; margin-top: 20px; padding: 0px; width: 998px; } div.headertitle { background-image: url('primitive_minimize_icon.png'), url('primitive_close_icon.png'); background-position: left, right; background-repeat: no-repeat, no-repeat; border: 0px none; margin: 0px; padding-bottom: 0px; padding-left: 22px; padding-right: 22px; padding-top: 0px; } div.title { border-bottom: 0px none; border-left: 2px groove #808088; border-right: 2px groove #808088; border-top: 0px none; color: white; font-size: 14px; height: 22px; margin: 0px; padding-left: 4px; } /* Title elements */ div.wlmtitle { font-size: 2.5em; } span.wlmprefix { color: #ffbc01; font-weight: bold; } img.wlmlogo { height: 1.25em; width: 1.25em; vertical-align: -0.2em; } h1 { font-size: 1.4em; } h2 { font-size: 1.2em; } h3 { font-size: 1.1em; } div.line { font: 12pt Monospace, fixed; line-height: normal; } /* Colorization */ .contents a:visited { color: #505080; } a { color: #3c3c60; text-decoration: underline; } a:hover { text-decoration: underline dashed; } a:visited { color: #505080; } code { background-color: #9898a0; } div.line { font-size: 12pt; min-height: 14pt; } span.charliteral { color: lightskyblue; } span.comment { color: gray; } span.keyword { color: lightgreen; } span.keywordtype { color: limegreen; } span.lineno { color: gray; background-color: #202020; border-right: 2px solid gray; } span.stringliteral { color: lightskyblue; } /* hides titlearea and the main navigation. */ #top { display: none; } hr.footer { display: none; } .footer { display: none; } wlmaker-0.8/doc/wlmaker-header.png0000644000175100017510000002660415203543557016615 0ustar runnerrunnerPNG  IHDR斥LsRGB,gAMA a cHRMz&u0`:pQ< pHYs.#.#x?vtIME ! IDATxwxUǿl B($t " R"B @t@PPD@ ]*%{2?fwfvf@y|̝vw=seAAT2 Y Y t t t  % % % Hg Hg Y Y Y t t I5􌌌Rɲ,0 Q./JJgϞ}YlTT… 7lP}~遁xuW^ wq͛vJATlΜ9UqfSS{'{ٳggeeQk ͶCJze\p]v xSr1c*~ggg// TQ,Ao{*rEݹsرc. ^i~#F;v3DEE/22ʊZAo@VTj.j[*DZYZtY':uj޼yez1dȐ-Z8;;ر3u&Ȭ-4B">ޞloo3-,,5jD͈ W[n ٳΚ֨Q#88811̔)SΜ93tЫWVvP8 x ͮ.c|$֬YC͈ Wa78y_ !!As>|WСC$&M0ۚapbEEEԆx:ѣ>111…kժ%u.lէwƍ{yK/yܹ x^_ zuäO9亸AUSNߞ,ȁVуM4id2~kKgYd`x:gTIIIh4aaaU`zݻiTF:\FBޛ#6 ?0l0]bRCrرc-,,8pmSLquu6sufr 2Nm3wJ|W3xMΝ_.<zg}fxX~3[>o4ԆF_WTT_m4Lf@g'O;JoѢ\./\SBZxwev un ŋLp} JJ8EvΝW^E VO{40]Yj=ATj4 T*j0}ڶm/|N: p¶p#7ĭx g`fS (.c@g tgN>]Y: W`oPPu 駟a "~ߙ+*j3)2AHFEE,$C:uvv%ŕawVRN<\ Jٛ7o)vE v-_ (pj[8F:~lݵkb3gi^puuիW/wչsg///qeY8m%\7j?ATΦ^vMLǏ<'2DtfܹN=$՝#FTrGy0ٻ%aii9x`}/PONM7#Hy I{)aSGt7=}} \w!Vxsslg4Wɸ)(YӥK@aFɓ--jӦM>+˲:ubS#u:V5s۷VxF4講ۂ BBBּ{(!q:;028tG,ZlUFXgY7pX0.Yu1RáyAvlR:5,]/8?þϝ;w qo޼OJ~?$&& 58GFt̘1ɉhq|Wrr~/(6бc͹q<~߅C5d'4}ix#nލoRu0>عI+g,!!!KqU~ƍToHgBz}ر3g}~Zf;vB~;,ʺw5o/9C&Fܹ\)xhgKP" р/9cqqqbJ8p`ɒ%TcKgJnŋWh<+w Ud?/ebb2b7 . ʭdܻrVǎtVzt>kWjbb>4= 7om "ΘpacsL cjj*m)&..K.{W(~sFٺuW_?ĉ9ڵk?֭ܟ:̜9K.&&WPPݹ, (**:|9sϒ]"$tQ---%UtQ!gj^@9s{\RCN .JQ5h{h$:~kQ\(9|~FR|& nG$_'+/ %^d>1Ž"[f+򢘙͚5K;ٳT \^f_lYE?99+ @XdK,((UVlXXɓrrrΝۺuk>-{}#GׯTd"Ag}||$Zsao7ۭPחXDP_`l 0n_r]ˤc/A]u<*_U9C7ͳ3HH)qw xVZR39*&{([ 8"oa/ѵQڟ-_~ѥּ]C >|񻤛 3gAT*?zO>Vs皛wڵkO6m˖-vڵk׶mfΜNzb~(aaa&L޸m۶ÇQ*!!!:RاO78qٳ;vlڴ~0 ˲s*Hϊ79lذT密D%ڛ[FX{g1& 2ḢpdZJ=chv8xf(O .޽[;kʕ6mtٲeڵ0`̘1FtR7[nݺuU^x򰰰VZyzzK˪Ngׯ_[&p@U%l} 'AQKr.}ZD9_ț,^M<} vp'jnwEOځ1 <:F[&PLI.m|`;&^) $Fr{Ty؎Gh.!!6CbP -C2qp OQ$jA}KKk@'L?vňM0QF" ܼ[n{ў3{$ Ν#ٔm;uo[h޼;[Ϟ=[R-JJJ j߾tYeꬳdkXL`hhVV`uP,,!S 8]`5KM[aʓ'~׋!&w9gphX>)KxwxV}S{rVN2qO" ch]a]2E]]4w/e&cWE}h| Wѡ)74NC4 po:}fjڽ{7O>dԨQډ_Ebmm-aN:چ O>yܩSΙ3G{sSpImUj׮-P-ڙXΞ={-wk׮ɓ';::?_lԨQ.Ȏ sfP.Vt'>͕gJWrby?zP"`a4p/e"7KBKNKg8㒝߆WuvdE}OQ!jkYJoU^f<h٦F/gW2x`lo2hpG,X\hQ=qDRBqʕ:뵽 ;uŋ˯ZJ;K,8x:1fGe.3?`ġQEY#}3FWIJsyppq DXnb-ėڪQ1I\f /\[VmfH7.xK8mKd?T0moC[kg?*!sjުDcf:YF#ȴHW^0#/^|/#_{.AAAk֔5֬Y˼)iii]1Mă.zO .'b~+ՙ\\ڀSVu2pn׃+í`QA$ה̉G{rWq?~F<݈oUB/y> $+Q.n֋/>x`ɤͬYĮåKf͚uH9#[llei&LNmj2;os  v\ӧ: È\2u^z2L{RHe5`XhBOjqYyXsX;<m4e>&yn*t7f"G-XTz.yӏyT,XSԆ<ՀM3Wy#)))*eY[[[IڅZf޼ZTJEwww[[<+ⓝ[Նinee,={4U4 {CZ@_FKz84gw+qR'DEY#^]$EHZα&652}cXd_/BCMRYWW׾} L=ѣGGipW}3ց ѣ1k׮ 5S/0,=< B%SPqA Iǝ:'7h$[n/ b}7X(!/#˚׷4ȸXiRnO AIW, 36Pf#2罾7KY]3rH9_HMMYgϞSNm֬Yi۷o]%44T0k׮t҉YڵkΝ^G5L>=zٳٸM(jc&@`mPߕtPN>DHt@nz6`) E )<25b1W7@IYE,_o54hvaѬ>F60\5]~];tj˖- iժUV8qģGJJHHL&5kV++WT,ZNjX5"+3fpDvECccy0<}- /gP%Xc6wqz98szLXO|V(6 En4ޞts WOrcǎoܸEB ]v5*#SMN֭[_ +x^b9rӔ#Ѽ ^tQ%RnT\B ,e}g$`b穆9r,.˜ToqϏ5)qWf viNebZߖz SkUmg\ Rp;#z Ƕ IDAT} Om3336mZHHԎ3Sj N؆ ֘hҤIŗskd?m`㙸N#Fs4l:nTt|k[{P.zk*xmfy~I.)ELy .'jDXœ; Hx. bt(?_"y|U[ Ԛ _B,@WR:9mDDD)TRR҄ c[KbΜ9ƍӹرc3f_,ZH8uzzݻW}u@6m"ߥX 7IށFh:r? Û4ibeeշo߯g ]p#)j:9U6oެѱ$L֩SݽQFNꫳ%]Z$8t萄p#5t2*6w"*lbwj h(];NLWq`"?xkp̹>2|3;4Z)z:f_DYyvRKYaLұcGJr7o~ρtu0.\7HmPP֮]{޽ 511UyƦ:*{0JvV<%ׁXEp7* l ^ŻwFpY8{ot oK 4 CahMGB3Ꟁϧڵ`mϞ=oݺջwΝ;:,,, \mܸq:Ry-[ 0sL#PU_~Y>.]۷O7w~7OGOGQPl3wp:e6##Cf$M돁o@/r߲4V?: n^$rը7KiRH\Snw|Ǘ!k vpKSƃ}9sFL0*u<kgɛ qgMLgڎfS8*-- Pe&LaaۏV(L͸C3=rP f!^tgA +Z_ `nk|f(rvpjM^^^fffffL&/CAAAVVVffJ3zIשnݺլY3/^JMMH7px 6>eZZ bRSۛ7o9'6qDKKˀ^͉5^ddKY2?P[)ߍa5X,Ā?|FZd oj Jr0M1q/HON:з0K-AlS}]dÈb+-F~86z)K#-&S`1Y;h?5AVuJL i|EL"4e>u 7DV&#$8&vO>^ݸSle1mτ b-^ZGJ%3VNImIsph`ǡ | `R kñ A:[Y,ƥ`H>x [Fާ .T !.FB}in}¶F% nr&s!vĦU%4FDl|,ܩY d og+ԯY2F3wISuJg:XGeZa|o8f2SV.)e9lA1Ҳ$\n9IJD5B`7nܐ &tL& ˆ>{˖-kԨA- JY ˜UA,A,AA:KA:KA:KAAAAAA,A,A,AA:KA:Kb xIENDB`wlmaker-0.8/doc/manual.md0000644000175100017510000000263615203543557015015 0ustar runnerrunner# wlmaker Manual {#mainpage}
WaylandMaker - wlmaker
wlmaker is a [Wayland Compositor](https://en.wikipedia.org/wiki/Wayland_(protocol)#Wayland_compositors), designed to reproduce the behaviour of [Window Maker](https://www.windowmaker.org/), which reproduces the elegant look and feel of the [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) user interface. **Key features:** * Compositor for windows in stacking mode. * Easy to use, lightweight, low on gimmicks and fast. * Highly configurable and human-readable [config files](#config_file). * Dock and clip, with support for dockable apps. ## Documentation * @ref commandline * @ref config * @ref root_menu * @ref protocols ## Download A few Linux and FreeBSD distributions provide a recent and compiled binary of wlmaker, so you can install it directly from there:
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/doc/Doxyfile.in0000644000175100017510000034243215203543557015332 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 # , /

!r߅LHۭ?[COFd)o--0ͯ^o'S՚ن J t^Y.`WS I[;˻ f3FufYAj_FPLJsbׄOє chpwxelĘ S?u2ɋK3S*Kvzbk+ECdŬ˅Rl:?a.t3콪&7GլMfF954.t\.Or" B?3uLHeExfKctM}wTu=?tפ-$UN?fX%,0 %oUH~,2Al~ eŲʙ·Me0F}P놪51}h3jCNWbi͠\ja"A( vJ}(ZVVD XCHFPK%Q аjmY;nk:~}X]~X;S쾵*zV6wG S.قٚQ$7r Gsk淇8vqr-QpCl< !ᵪ=1}^5]w/'G]r.cQ +e2~;fFXW gO95U|aj m N;9]9oûf+2%cmvVnp}yo;4;v} {g3@s%1z D=[ԜQ)xğX"@hoZ3Te9ՃoM7dڮlk8()\a!C.΀(4046t]{r-w -ZfDƅZ3LH0v b O^a 0 scJǚG,KKE 9 ^T*r610=/4@Ѽ"HeR?+pp yX?J(w>9ݧC<2A*]&-wRC2r~} 'O99]ocE-ž=;*97+kH&:5%Rx~%2xi4Hs s_P5[ļXF={W0gueU4p_CM5;$lDq j\xmEG>p9gV>G,{s h674vsp':2xJۇ9灋T]ƚdlLͦ1܈nQc*T ж?0(7 \ OsЈmKzdw{d. 7x"Q\R4Kc^B~N@ghKcd..wQvԵ `q[ %?.rbg*(/Й%Dޯήα>t<zjɲnTr`++Eɷ+d| v:6X\IWD)E0Ғ"1ZԲ B k4 ׈~\7{A;{@I9ͽHKG˹m6m :q hI+NTL쒨~I]aYJ-? v* sp5̼\"?|~R"\wXn*,B gό)Hn b7̠*+êkp.JuƯpp-w[pSwG-a{JNp|gC0[zulfk.v럝 ;8Z욤F-Jފ@Qigz^;W/NO+gm>墍Ѫ“z'7qْk{>Q*i knHA ZΫjqZ???mjw e|;/h,a@.|$d|㔠Tޝ,jZ=@4NV䤖 ޿Ty1tER7Ϊi'ng\si㺚HR= D%'NI6hvGdep*>>`PYaHQOu; ,ȲTb:v?SBfx=[8ҿWh4>ݻ-}.Y&^sb%xYہKI$I(нSh4&{~_6?~Y2hē9bͮcro ' M$.DU J_"F*䳲}ʓ0VY擘OgQOBj0,]';%rWo=A=|7ԛ=6h/n;Fnx2)Q/8 d|812:|~6r]\YuNNS4΍cP.Cdx2ǁH C7J3|~>~du<+R>7|,%9|||w`ؐn]]pZ.cjmkFk{%d M,t*"(ApQ 0:AQaAX8y\ {&d)[@Y4tZ9;Ka lj \^_56w2bz^9돸Q0LukFSZY%F `0jw *GBTD< jF@k{uP g::$j^} @܆^9>~ @4Oi(1d6XXE48ydp8RRxAԔqM=B,iNtgDCg@:z9<}}LQ㏬&%͟,E!jLc46DF_1s+^c4H<`gEo Tds(WD&Hbt(sǒz`REpytBn34pONECI40T^zň4@$>t@G!R\DF菟 <BdR`oA7A]aAe=3.9YZ+8"y CJ.\ԃ/z#0UԇEG+Ej]f>{;}9jNN77_} 7'6lgG"8ϸS+)JȲ Bf>1X&j-vcB9ybN])~.9 Tô"y̚~7aG0@x0R"X2sGP*FMLFMU08е!iBzj!Ğ}P.~)fHS]F[o,jHek) jVT_F ޘrUG?/SDJff۞ fr/Zk f.b҈(>r#+&uEiz[?v}LeXt%u^ZFSjo84GCt,F|,j-4,tocZ J&B|53tp4˶0+SQ_D ꒦d*0l=J+cKݐ?mӚ)VܳP4pw= kOuC C]зY6lfmlVDSTۗWJ(AVN/stG>,hm" Xe[U]敽qCADA[!Xy7G\;QﯱnĦn 7mPTݐgھr. =I.zֻ f0\VTA FT.6QZeFO01 6PplWŐkA4z8p1oòQ9zLaJ(.ڢ}t) %id+P]F0qsgH(NXȝ aϚqs@X&Re w^"h! @viGkѻjP!eֲj(_DÜNB8DD<̣.[ėKx#z1^yG܃!-Q Y_%VVLJ9G(EQR>4fbҡ\Щ<W7 1IK%;Ʌ*ڐKKC"34\rlW--(ZP=V2Q5My4 $Z QVWJ&J8ɍcY:LsoFҔΪ-[V_ Iޖj)s2m W5EuZm`WUsgTaRKtYZJEz]N&Zd@DL P2|@Cm\'V>c-Ս"ݨʔNy])vJԀtL*qr@FlF4$7)DԆ-8{C1yQhi|1],lZj"+' ˘%Iڒ VRfsq+e|cw`I$T6yd%n4 LBDqc{d_i->P t.&?ٔo(Nw~ ޑXdK{-ˁ3뎕Z:iX")Sϖ2k랾ju_IC7]>q>c8}z6XiDI*EF,(n9ɺ" ,WqY~~$Y@'1gjh(26C`{> ;ZBpܘf&ym|-_Nv{n'qk:X=4H<3Q$J_>H^`gPP4Р [&^Oy#O4aSz[>Gߵz]-\TgCKK:.m8/E؆@O'P,7aCjBfPx$nTG` ݞ|A<YR>E~>ѯ2Seb%wFMÿA39upYﰀb}R59FՓ%x%p7`4U|. &\Ԅ7E;(r{z4]IFgUvwe5}7s_շfͿS?$U$\E1t_zy#}J.6O\̊ m[]Pd%~VȬĊd..;P?^qiWdA~q#(Xb))=J0dX!XX]D( |v|. VãS}<&4}$ ('J\ɀ½X,]AjӋONtf%9MUXZpi ^euo}M r!In:U&S1XVם[@y낺xTK9܋U8gʧ֪Ae*MxLik XVTu^?ޅqj_ɚV%'Ռz"/f`uV3"y`X$B?#:uC#.PسT49-׷;wץi('5ERN]gahN经Ky*f<Ĥ4΅+XeVJ2bBJ^() P gݦE!Kͥ*;ޕc.XPD,::?w!XO6y58D[̪W>_R =>&yVu 6?d>6ō\bB6¦ڪX*(WL]VfԾTS2e/F\Q-ڔ."M#xVwPݐJ4%^ 6j ?uI[䔓LeMfU4}KF'ۿћCmvӆc(=Jgj!J}IxY%^wRx6-Ve,L>+I0!I)% &A?3iƒ-?[$Yp[T X&c=W)"(Z-|60~*qk]"uI_jt="@ڊ ,lJ(18廏?j&CBޤY 85} =)MJ;'wuf<7wKj:\gMhƆMK9," k5ZPBkB5D(2C[O*BD擨pѽ10)Xi!vMaM"R^8QȊ2Q"}JB$zbZ4d!ÄP ׇ>1.F8BZ2A)$H qE*?Zr>/?~|R;I )y58.gPqm* 4Xi:hwGE{zPļ4<+=P_B|zv̺J.XɌtVg{58| f\XNixŲG{!j#Uz\mhbE 8_Eɳ%VfK&I".a pijȞn S欭8faW!ΘJp@B!w:΢t4LO-k0usV'{+UۆQ? Lfz;j;I!O2@N !*k<,TK^hIG`pVA>:DSHx}6K(D@ ~|@1n<@1P'`J^xQLVt"2D tb++sݽ$<;D5y7z=BF*cHaMXo=Rw^j9/n,e\ ,B\/˔nMc,Ki. ẄbL"1o5}naM}[Z4BynToÃz9蝵~VYwn{˾de~'#Xa[ \;x>?g83=Q%~佳v_:o[=~$S[`24_g',>x:C"K/u_2=PC^ cdNu|eAzd=s_]1 ZroEcxm.\uzjqej^<s$`$b[q.!'BWȂzswhn?DyZȹa)ZS~&o!*ͧ.[$b|Xϟ.$u'K| _|K};HA1Wfjg*wqdכ;vΓҷ*dV{?UX9y Q߄: -o U'~^+^ڂHDn7 5,~YE .yGC `WK2P ÃNe7+"/ `KBw?5dwwNf񷖆z;.& +gQԄvlOm/O~Ʒ^EϤ6ʔD]cD/ O:Ǹ68_mG^v^~8$SB]BޚZonRUBs8}~^;?_d9V 5 3+vOx=w7`ugWr$Yvڤzkt-}iNI<iF@r1$;qz鵎4$A@8{c}e3 GtqӋP?A16Mիxē2/#K'qvձBM&]ytÒ`sFx_,N(/fq8 GBi#K鰈zp,YTH/b?ۻjGm;ͦ{3Q/{u{cM2y22`-Q4 f0+2dS,2`~E "#,x/$J2>Wφi:<#};nr4r)E'h8gzi6'A>xR"b#`Y)0*``P?|l"E'_,˩ X J`6#|=x,D@'R]`p8KmVǎbkpF`l=ׇe//*<eH$`@DK?s r^d KvI]Qȓ8K9`AE "QeŨ^Iˀ⊧ql Ø?)PeXfO?{GJJ,G#XX'n 2:JIBa s]3.W[T9^m dpr&`}C}{wb7o>c]Ne:޽ƺ/h9+2k-8C;,,MƝaB񌆣j Ao y*\H)u\ vŚ%<{J:d9| %s7w{q>}#;bAgY-?9>}_x{o^zʱyCW#z.Afq[hπ=\!n/Ʀ[𧴹f (Cc ڛZ{Y'ѼJ(ՅQ!fe\&JtP(QETxH3[&.MHh#+4|䐚Ӡr>A|!e G*A `nKT7֋AHJx)x ٠zЊ#Ku9G#O NͯS+CCx:%JKpFܰ+ ɋ߆+T$KeߴU_B ݔqiZf"ʢh˺I/l>*kGvWɐKLofc[ )fxZԟ'W 3rihҐ&E:e $.qj\t*ucu۝lv`O'A}pKC5vhh[uW K ӊGztWw: ($R/]ЍP:Xh\Q拺c9ұKjDƦ=l7\37X^f#x]#`Nƌ!?$SHK־8?lDt4v-Qƣ׵͞U@0 $TѥV`4(p H-FatiR$i~\M)GG%eSךR``v Z`:!/8OEn : ]k ?;UGN5fbәݩz5 V2۔k?Y@t?pY`L)ҳ /}'R+mSrǏz=\ٯ qx. hv9\|k11L /i@iS2/yRmW f 0һCMV6nS={})ux2è_erw Ҭ);UKm/7 Btj"ܮD[yت.Gqwl,bZ Qv_HQ_QI1ɥMU: euN'粪AC$/C\/JbE)I$ŞxyO3ҧd\I nY0ˤdFBwy( @%;`]KS֒ܙ3HS4ml+h@RNvoRuTs_հ8V)@ON ]y:{,iNd +d69w/s+bD3JyfmMB5r8}ݨgJP[fiwJX, ! ܈ݸi}{ҧ cUw>MA WӘ57xˆp=zqxV)Jqe bEߟB (Q0@TВcU"[7: *ypyeK͎Q.$mʙ&U[S*=)/@)A|(᣾;Ii=; _qJ ``9BoFȃkҪ!{pW-rM<@o }̒o4m*eG6*kbc!y|6'c3gF[lngi hUmW|DаuF hhOamxLr}e{#CR^9|ڜƧhv& =@nA2RxȥPWQ%̽c=Õ9qJ`}%>t+,/r.oHgg5TvVeOjm>Z%4Kj-gzaIӸW4#ne 2F1-_0R HI>Fܭ'&򧅗8) uhi8fGN4G&d=՛6vhy4iЪp[/Der̜$bF!NggWe\yhO2Si9U =0ZbuWc>SW9Ǝ8yq /ʹic+<(2abNyתsC/^OϺ'(j9gޅVu[db5nэv(ScI"ʀr3"39$-6 \3 {xWk:WԚkn}TP[V9Ga J!G뗅B_ģ,ws]_BvOa];Vu>rz7'dG8J|7lȮXU +"WM| Dmd8w%l!$I5[Qh쇰ހ0!g7e6z+Jf huH+57":2,PP-+5KI*MG՛5 1,% d`Xhw_qYDeQ^˽b=ߓ';zUk Tg[J*F_ˆnc,[ lh$5.My%BkIf.AdİȾ[).^cþT H1w1=|R{MLckN .4\R%jNElY{ /_ vlCJWd.sfTMn2u?; (k.> .5/ iaqGWMr zA#r]b[^X 炙3֌1N`mdٌ'?ӳHy%(< ?U!ǽM9Et[1 +?o~A;& J8Vx̸HsگEb کc%^^[a U\c_swEUw90hmr鹇óz dd5? _,^hDXO~|Qr$1&reHX8>㽥8\Awi\[ZޡN1i]hMκgWA hv2NɴNmڭo{0;"TlcZ;5fClMc:ܜ(|X.'Ƭ,J38uͫqCgXt׬*ྉգ5/= \Cڙ(lQ݁&r[j0`߉`IXn9d;8gT3խ|9"uz|=?=kPLUVa&1Ry%W;6Z7fr:ݥ{Yע2uly4#kM(GAvi q_<8zWU,ԦKɬ/k6j=I6~!|_:Bk.`[(jiTy6S^!@V> ZLt6*MgK`7TW-DFGx'=ۭJMfe9%?)63=尵Jv*tQJ~aaVR o…i~- N􋒧\j%(VQ\Uub(#^)+D\Qs8ӳ:ˀH?u[mPnqiD CƸjm ^IgӢM6/ь㼮4YopzMx<,xJ6CI7-bT cs(tTg|.X>SF3:v[1_3}y/_,O/k) iLKvպ#҇=oYٌ sڒ 4i&[Up [3@y21.>Ez4)xތU'$seM[4k'c['A/&2{=+侗xX[oH~WJ,4[٬$dZ* [ jfܙ1m{flǘIJ.H̹~b<ւSsZ9(腤$HD_…6wg u~Qd08˸̧\P(b_ӆTLp8taVylTlDK.4$XOc /q)^X;)!#ih5Y(Zk6 /ӺFb.:A&J̲C=3d`Bz9~A߃*qf޾VŠ:$R4~mѵL^ xXl۴ bDjF]wTo`EG(V˛JYC>n3*G06Lr(6}Tb2ñub1W]-ʾlec.?1qIfZ 0= UslZFNl4.:iH%cV2HslDs evՒ^,,ujx7t<"+!*$䩃: f|Ţ=ll^;d|LRI XDXm:x% ~DR/сVv.C6I\՞?PZNF l)5/9#7([/a׮6%]8,.ۜQ$P̵~830* r;,[D 6m Y34(?98>-13(I3jWM?+6nS2ҁ:>+8~;KgZ1 5,o$W4},[qi*Mq6-wׂOV/swzΩxQo0)N4i/ 4@DNIiO\R4CW!;,][he}|9+(eQ"L&nwb1P#85O駳 *-ds8#l`WPIFrA1>ڀ J0Ȑ$: NޜkZ~M <7oGr9-PbT>xw ^m?J~m<`Rbɗ B!ٌK%327KT2Icv*FV҉4ntíN^i0c8| Չ},?A [Cl"\>S򪊆tFޤ&TJv緤26t"IFfP_t3\hX)/'Q\ {]{Ömjz@!M>d(E]Ts"’2F >\StB,\Tr:XE1;~{|; C/Y4gm??'hckt˾']ȶsnenZɔMͩlE;0\Ögm^N>,5]<QhHimGvkOATidӫنXGc?م^ϐϩbeNN]]9xgsˁ-Xf2Džɺa`벤Wt7;$bk/bkscMglN'zx;N3k$2\ x340031QHLJN+(aXm&jqJytaCJy;^Ko{2}I**_blVXf,m0uLҴĜbtjSь77wTU\\R5tMniDI1BN{neM5xKLJNm x-M 09,Ф=^@7>0MKZ} 8%M ̾ZCB?3S,|#qO(/B:UZhlsȅiyZQZ3>TmO&0̽ xWHM+RPN4PUP*K)M5PVGR" &d&5U+gf.]k.TX_ ԦfU˥U I2x sb``p Ҍ $'RlI. Ap品"<.!33 TsgtsYixW,PHTHM+JI/K)M xZQs6~ׯ23*StJNSGΉrrÁHHBC,Zd߻ BI؝9AbPVmЂɄQxt3xt[ 5 {ϩ74r Gp O8R5צ(hHIL-nUm"uYu:^:DAY=efpAet5QsjC; +( XV;oܴLΚnS /Oiƴ1Wko J֘BcZjUuy~kL.*X$E#.<bjRss&!LL5OќY>iT``L"jp4K6 H5inf%uwvuYHw2SeG(ˤc%RKc@n!'rkw|o?;5mгh /ƴM{n˶hMuL&qJI<Vcs~υv~"8?\ q"4RWrx0ۛVt:[5xt A7┉4_X ET2iAbe:rNJS  C0xHīXEGشdNİUʧ > C|GjB<+^Fp%d,x+&,v˾67F}vÏTf~|J ^g) )̧ { ;z#T<|u^~r)gUP81řcRr&kU0IN6 64zolB c+ dJTOLJ8[\ x QL^cӡTWPN@CoCLIh+噌nL'zc; #7..g` up Wp(Z-[]P MO]G'#ߋ}RLWX̨ lFP~7@͈/j?rw E};0F"}Ot/ ݎJPFH_D=WT sٮb ?,@4=(V9+6 'l5Lmn?Ry{9t&٢ 2?>$M}(e95"LM]]W[g/q1K'e zBMr{Pv]d^u+¯R[HQBu+_Ik3I-mDÕDz!ͦrx-j.~X'\T/;W5 Y(Q. k٪Ң$UAF!%W [ ~MPb֒F.xtVםӛr/_ t 1xm✐ꖔX̥+)d&%%*hd)drJ2KJRKŚ[ۙ&G3sM>Tĥ`P]kͥ0BqjIqt,HXA-8>="4-nsqy%"kiB Sr·iuy̼7g竻Ԟ8Oԓl~XKQ@&«|]_xlZ׺)J5Fh0?~ z= -+pEg xu 0ES$6C t".RD?X7''wp "8y}]Wm :xWS%SR+FOi- :Zv?kbK a QyQ#I5q6@YVC c2v$0ʘϖ,D&(l)jEG*xmn@D":"eQXI "HQ tֺ;$D&U, qdyho3פ匜[xr~QJS$@65^d5*M/̵2#r&çUfM{)NW!q7Lae_Jɞs PƣDl¬KZD6=? Fgk'v'.coQS \xq}B.{qfzAfAB=̜JĢ̒Ģ̜JcҪ`Ƶ,/%[i--2#D+ZԖ;\)IhչP,'gɇi\xWB݋~~plZ<=u!H{X)$9=nPRk[ZJcmbFt~0~lG~|YW>"1E vݫ x340031QpMN,.)+(a7򭛗ޘgJI !* r S}MT?*ǔˑx/C@Y9(3=DtnbJJ|jEjriIbRNBANfqI|AbQq**O/yr7 0W +'3)),eŃU"s 2sRrӋ'[ #3j&dQ n$QEq:Car-gP-Psj^Jf&QĶxVQo6~zHad(0,M-Q6H*[HIdt<~;R~뵠,gL('RQ7FElm\pp X W)W4'T^Q$ƿjƁT*&8 Cv5 V&[BC(b0\5r%}Fo-6Fwܟ ::^<,Uq/X=xpᜦ  cAѵNZaN*KӧKI+D.p[@SURb_31wT^MVK&1B95˫s/gQ~̽[iF "1jT(,*slUCR]H޵?.F~0z]^YOqs!wg}: gXCcҫr-d\,)ѴvIXGM`Nݎ xT4LA< =AdP7u4 4;*;7[xS$._Nv L%wKn wka.emHa1Zovc2lÞ\Ҵ،H.ۺ* 4*ۗ& FscW!`eŗ{LR)kBA#dq)\IX(OK%Mؕ!|WᧀNisd i҆,0l)QfxЕiOXZ!C4b1r;_$| xQnpYx^ tw s{[ş6Fzi%݀otEj0aY\`18F%]zhHYlkȥi| Cl!wi_jD>V&g>Ӌ5)lΚ)d8Ո!%{s*n4xˍ{m/VKJ0IHZdn_z#$2G;ą%P6r#Mg8HDxw-:ke^'s5l"H"v/-€Eo'גw ZopQm|W(OVIjⵦ 8- s q$sbd@9J-T::l,n i\G4W[?C+ų[:UΎ`Д94<,0'i;zt2~BCM?ikVXsD\D=e)dY:Zƪ@HfPRޱA>:O:^h`py'IWOivفO/|֭pTU:ٓWӃsV!׋j"ܧڨZ^WT’rwrjI]W (`%T=gVa3_&˻.8BUaHhx%GP}w3i(Z5u'^Qi1TŋgKؿ~AiCK}k#9n v;܄*Bv50d)&T w:eN~?z ÙzJ>BL EZH5z_5*M~ga=$=>zoRYnיִVt}kO08!TS`A93x[},鷔BEzXh |"n1ʄK_{C%6U[Vm2ӕ2Z"L0Ķ_֚Mz+D9c:ud@eTK@;]ıo9.nFk*n X)]Tw!ZOFf Y`dpMp] pyu&Oߍx:h甓MJó0v{_zեd*BC5 4Y!L`' ڞnZ+k~]f*jr?aKmYtNJR`u7muSb\ 0_Ӭȕ*  ŕ 5Z($$=P""i6_a\AsW -PYq8{_µ^XyB( PYNj5G'𽿌W {h]2*@(,H\3{acs@Vphv\֑UlVON'+)`Dods=6,1U`#{En ¾Jqb޽^+`dzA9{5zɦn+: |Hn ղE߷D":^L'ÍC>ŵz*`鑥Z:L9m#mbP6\Qf>².^ ÖEI h\f8@\4Hlarjn"OAm,pR$4G<Vt紃CƇ`ِ0'F`\!1b >.3ok>R־ϙѩS74OR NdhWJΗR67L0YL7õ|؊ws24u_X󳵿Eawy^9͇<2ZqK Cm~k_ ajud"n<=wrݨ3t_ >p]<5lIrM-"8J+dhF8b?Y3V`$Zan!Ch|4* 0Bm{H$><xǔvP}0ڷ)/;} y,LH!~ėC-6  DK38.m !"a0d!' FD$FAjdhBމlo(Khl]}Q&D2ͼGx#?\2~ήNد  \;V)H ɹo&@'{Co#uĝRO h0wlmaker-0.8/.git/modules/submodules/libbase/objects/info/0000755000175100017510000000000015203543561023115 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/description0000755000175100017510000000011115203543561022773 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8/.git/modules/submodules/libbase/index0000644000175100017510000001714115203543566021574 0ustar runnerrunnerDIRCVjvޡjvޡ`>:UΎ`Д94#.github/workflows/build-for-bsd.ymljvjva]W (`%T=gVa3%.github/workflows/build-for-linux.ymljvjvb4W[?C+ų[ .gitignorejvjvcq+`dzA9{ BACKLOG.mdjvjvd- s q$sbd@9JCMakeLists.txtjvjvev50d)&T CODE_OF_CONDUCT.mdjvjvf,5zɦn+: CONTRIBUTING.mdjvRjvRgOivفO/|֭p Doxyfile.injvRjvRh,]zJ>BL EZH5LICENSEjvRjvRiJR`u7 README.mdjvRjvRj ?D9c:ud@eSTYLE.mdjvCjvCmNj5G'𽿌Winclude/libbase/arg.hjvjvnYv;܄*Binclude/libbase/assert.hjvjvo50#$2G;ą%P6rinclude/libbase/atomic.hjvjvpx( [`%Uinclude/libbase/avltree.hjvjvq iCK}k#9n include/libbase/def.hjvjvr +۠n̶' a>޽^include/libbase/dequeue.hjvjvs.b:include/libbase/subprocess.hjvsjvs5Z^WT’rwrjIinclude/libbase/test.hjvsjvs:Rt&CUinclude/libbase/thread.hjvsjvsI#Mg8HDxw-:ke^include/libbase/time.hjvsjvs<,0'i;zt2~include/libbase/vector.hjvEjvE-3x[},鷔BEziwyu-mappings.impjvEjvEr 4Y!L`' ڞ libbase.pc.injvEjvE،x26+1tŵzsrc/plist/analyzer.ljvjvw!ZOsrc/plist/conf.mdjvjv-PYq8{_src/plist/decode.cjvjvxեd*BC5src/plist/grammar.yjvjvˍ{m/Vsrc/plist/model.cjvjv,)*`鑥Z:L9m#msrc/plist/parse.cjvjv)lΚ)d8src/plist/parser_context.hjvjv1lIT_ src/ptr_set.cjvjvN }o VĊ7 Tsrc/ptr_stack.cjvjv.^ ÖEI hsrc/ptr_vector.cjvjvBCM?ikVXsD\ src/ref.cjvjv;'s5l"H"v/-€Eo src/sock.cjvjv/4bP6\Qf>² src/strutil.cjv-jv-Z {h]2*@(,src/subprocess.cjv-jv-Vi1TŋgKؿ~A src/test.cjv-jv-~Zz1_cAj\ src/thread.cjv-jv- @ 1 src/time.cjv-jv- ds=6,1U`#tests/CMakeLists.txtjv6"#jv6"#ȕ*  ŕtests/data/abcd.txtjv69jv69KJ0IHZdn_ztests/data/array.plistjv69jv696]<9Ku>Еitests/data/dict.plistjv69jv69W s{[ş6Fzi%tests/data/gfxbuf_equals.pngjv69jv69muSb\ 0_tests/data/string.plistjv69jv69cs@Vphvtests/libbase_benchmark.cjv69jv69&\֑UlVONtests/libbase_test.cjv69jv69΃{ϸ!5Uqvtests/plist_test.cjv69jv69>V&g>Ӌ5tests/subprocess_test_failure.cjv69jv69V!׋j"ܧڨtests/subprocess_test_hang.cjv69jv69 sß'(R.?8tests/subprocess_test_sigpipe.cjv=jv=_&˻.8BUaHtests/subprocess_test_success.cjv=jv=jz_5*M~ga=$=>tools/CMakeLists.txtjv@vjv@v VF%]zhHYtools/plist_parse.cTREEQ86 5 [Vm2ӕ2Z"src29 1 l!wi_jDplist8 0 6_a\AsW tests13 1 jr?aKmYtNdata5 0 zoRYnיִVttools2 0 'גw ZopQm|W.github2 1 workflows2 0 D=e)dY:Zƪ@include29 1 TK@;]ıo9.nlibbase29 1 w:eN~?z Ùplist3 0 ߷D":^L$jo$!Up}wlmaker-0.8/.git/modules/submodules/libbase/info/0000755000175100017510000000000015203543561021464 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/info/exclude0000755000175100017510000000036015203543561023042 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/.git/modules/submodules/libbase/config0000644000175100017510000000206215203543566021726 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 eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUl2ZEhkcGNuQXZaMmwwYUhWaUxtRjFkR2hsYm5ScFkyRjBhVzl1TG5Zd0xrTnlaV1JsYm5ScFlXeE5ZVzVoWjJWeUx5SXNJbUY2WXlJNld5SnphWFJsTHpFM01UTTVOamd6TXpRNUlsMHNJbVY0Y0NJNk1UYzNPVE0xTnpBMU1pd2lhV0YwSWpveE56YzVNelV6TkRVeUxDSnBjM01pT2lKbmFYUm9kV0lpTENKcWRHa2lPaUk1TXpZNE1qQTNNaTFtTldabUxUUXpaVE10T1RSaE9DMWhZV00zWkdOaFpEbG1PR1VpTENKemRXSWlPaUpwYm5SbFozSmhkR2x2Ymk4eE5UTTJPQ0o5Lmltdzc1X01raEoxX0FnQ3VZWWU0RnlKOWhrWFkyR094WWRjTm1SbVBEQXFUS01BRUhTUVIxSWE5NHdkUEtGRnE4dXZ5QVpzamt1SHB5SjlsTDdkaXh3 [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8/.git/modules/submodules/libbase/shallow0000644000175100017510000000005115203543562022122 0ustar runnerrunner54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8/.git/modules/submodules/libbase/packed-refs0000644000175100017510000000016015203543562022636 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 54e60555cae53ad99314c7579dce1ad38373ecaa refs/remotes/origin/main wlmaker-0.8/.git/modules/submodules/libbase/hooks/0000755000175100017510000000000015203543561021654 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/hooks/push-to-checkout.sample0000755000175100017510000000533715203543561026274 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/.git/modules/submodules/libbase/hooks/pre-applypatch.sample0000755000175100017510000000065015203543561026014 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed # 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. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : wlmaker-0.8/.git/modules/submodules/libbase/hooks/post-update.sample0000755000175100017510000000027515203543561025333 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/.git/modules/submodules/libbase/hooks/update.sample0000755000175100017510000000710215203543561024344 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/.git/modules/submodules/libbase/hooks/pre-push.sample0000755000175100017510000000253615203543561024633 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/.git/modules/submodules/libbase/hooks/pre-merge-commit.sample0000755000175100017510000000064015203543561026233 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/.git/modules/submodules/libbase/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315203543561026675 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/.git/modules/submodules/libbase/hooks/prepare-commit-msg.sample0000755000175100017510000000272415203543561026577 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/.git/modules/submodules/libbase/hooks/pre-commit.sample0000755000175100017510000000316115203543561025137 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/.git/modules/submodules/libbase/hooks/applypatch-msg.sample0000755000175100017510000000073615203543561026021 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/.git/modules/submodules/libbase/hooks/commit-msg.sample0000755000175100017510000000366415203543561025147 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/.git/modules/submodules/libbase/hooks/pre-rebase.sample0000755000175100017510000001144215203543561025111 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/.git/modules/submodules/libbase/hooks/pre-receive.sample0000755000175100017510000000104015203543561025263 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/.git/modules/submodules/libbase/logs/0000755000175100017510000000000015203543562021476 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/logs/refs/0000755000175100017510000000000015203543562022435 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/logs/refs/remotes/0000755000175100017510000000000015203543562024113 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/logs/refs/remotes/origin/0000755000175100017510000000000015203543562025402 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/logs/refs/remotes/origin/HEAD0000644000175100017510000000035115203543562026025 0ustar runnerrunner0000000000000000000000000000000000000000 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1779353458 +0000 clone: from https://github.com/phkaeser/libbase.git wlmaker-0.8/.git/modules/submodules/libbase/logs/refs/heads/0000755000175100017510000000000015203543562023521 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/libbase/logs/refs/heads/main0000644000175100017510000000035115203543562024367 0ustar runnerrunner0000000000000000000000000000000000000000 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1779353458 +0000 clone: from https://github.com/phkaeser/libbase.git wlmaker-0.8/.git/modules/submodules/libbase/logs/HEAD0000644000175100017510000000074515203543566022134 0ustar runnerrunner0000000000000000000000000000000000000000 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1779353458 +0000 clone: from https://github.com/phkaeser/libbase.git 54e60555cae53ad99314c7579dce1ad38373ecaa 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1779353462 +0000 checkout: moving from main to 54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8/.git/modules/submodules/libbase/HEAD0000644000175100017510000000005115203543566021156 0ustar runnerrunner54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8/.git/modules/submodules/inih/0000755000175100017510000000000015203543566020064 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/refs/0000755000175100017510000000000015203543561021016 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/refs/tags/0000755000175100017510000000000015203543561021754 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/refs/remotes/0000755000175100017510000000000015203543561022474 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/refs/remotes/origin/0000755000175100017510000000000015203543565023767 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/refs/remotes/origin/HEAD0000644000175100017510000000004015203543561024401 0ustar runnerrunnerref: refs/remotes/origin/master wlmaker-0.8/.git/modules/submodules/inih/refs/heads/0000755000175100017510000000000015203543561022102 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/refs/heads/master0000644000175100017510000000005115203543561023314 0ustar runnerrunner577ae2dee1f0d9c2d11c7f10375c1715f3d6940c wlmaker-0.8/.git/modules/submodules/inih/objects/0000755000175100017510000000000015203543566021515 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/objects/26/0000755000175100017510000000000015203543566021744 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/objects/26/254ee9de7681f8825433415443e7116ff24b980000444000175100017510000000026415203543566026550 0ustar runnerrunnerxMj!F W C5*3 x^ŷ9PZtN9] uP diK[2SήDb֛-xg_w=q{]~rСk JK)]uU_P햮 q5U2xNwlmaker-0.8/.git/modules/submodules/inih/objects/pack/0000755000175100017510000000000015203543561022426 5ustar runnerrunner././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootwlmaker-0.8/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.idxwlmaker-0.8/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.0000444000175100017510000000551415203543561031016 0ustar runnerrunnertOc  !!!!!!!!!"""""""""""""##$$$$$$%%%%%%&'''''(()))*********++++,,,,,,,,---.../1233333333333445688888888899999:::;;;;;;<<<<<<====>>>>>>??????????@AAA.:X>YRNjED'c P]S+ySY^Hѯg 5w )!VA)밙 Z:W3kXh ``Aoj9g엽$O@؇̬<`!sygn2}oR%gQm x}-&|f()Z9w@v1/,oXuHujMeJQ1]SA9gW9,О2c¤5`.&WQ]>քyQ]݀;e>Bxa0aNf@c~ TaBo~Am'.vזmCt 6C :GNQDuzr7C,QE`Si Qn|tРV"ykllSlZ23Vi G 1XgހWz7\֔ Z9;iD٘ͳBØ1a% DU ]ejvվ[DJ8+e8x\mu'Z["+娼e,|X7ڊCώ'hYk{E3f@;뇘hN\w:(-$VSTR`$@p*O f1-6?H,s+^韬x”4YЄ!}/do[QN5|C4QF&u `.fh/y|6a$Mf$p&MCvz|_i{<f pNڬO1ِ=M.3#}6#~8!Rԩ}&WKfhb{l<0-;#רY+8kO}2\{4^y[*nq/y)44 1c oUB1\8 }r5cF ɿ>J _mc\f7퐰G H VT)yܞ|~{'{ԺO]>O5V}jDvE# ;x~4)buqg!(G:!Kݨ&([ :_} V*gZ2ȼ3NuP7P@*G-9~XQ9 X'WӾefn!Jyl**H2wv/lگaZGJf_#/0܌2rٷ NZXhƑ{㲂mEg>6re\A9Ōauwv~YOqK|,CPb.@%OG{8Ȫ,CӱV躖$xx3U!_!Df88xfxg:Y1QnWq6MY<)7"agu`'- +y,5r4[b6YPp9J^bl. xi08ULjaqY`5_3\68gac,R[Y#p.\cteiY,_\ĜJQǩ&&۫c@}ldV.i4\"NB././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootwlmaker-0.8/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.revwlmaker-0.8/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.0000444000175100017510000000047015203543561031012 0ustar runnerrunnerRIDX3)0+7$9>:#& 1/2 '  6(8-@!?.,4"5;<%=*  JQǩ&&۫c@}5xNԓ ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootwlmaker-0.8/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.packwlmaker-0.8/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.0000444000175100017510000007144015203543561031017 0ustar runnerrunnerPACKAExuTIϣV+K"Ϭ&Ƙπ}1~8rHRuUխOSsli&4O$|%|Qf,B§>mF`q0 GFI  '#%4GLX8Eۃ},Ǿ(|Ij擿Ͱd}~j>}W^}O@rHdX$A`x"H#_PBRTHET[.7 ,!DQE,WoّM{)jQL9so@6\Px4.y{5Rէq,ygzLeRYiIwVn%GDkKqX:1붃bh vWgՑܝR"RULE:ɯ%|LêM$K-Ӥ{[i'X{Z5oiw3 ݃2maPc_L.^~γ#aKknHԽ9 fi9j3Ya9DkV T8oQS|j6]E[YY71*ɮ rѨHyje)gz`񥄍 $FTSBvgDWfm2SII/))to-c@nsrȽyGcoG`1aOwP$;bx340031QK,I,))L*-I-f\W'538u.I| 3J}/!Csfݵn0+3=/(֨ . 7v`vDGOT_^IE G?N_czn@:0['/yYǺ+ nJ.(`p>ϻݏa՝g_v}"Z[TWr$WE-m>~ %iUUy t_/V^)}ŦÑ,-Kf甼71fgmsBRً^J}onyPyzI9) vZ~V΍nîRQdCBGOlk5TޙQb *)fƴukR.z{Ne<x1 @:9; s`aa%ʄdv~;5vSOU "k [rFda?='}u|ٗ`%xK100644 FUNDING.yml}2\{4^40000 workflowsi{<f pNڬH!xK,(MRHJȯ,3x340031Q(I-.)֫apmU+>-D fgq 1xՑN0}y7N@ctZB!vc9c}?}C'XطRу^=9ԂI|DPJGV (#ww@ 7}Z *%pl bTn+*h-6sP zˮcg\࿤`5S7c\3$#"BU+V\R@YdY##G( RW'E! TLREW :%DQ0a&Su(eRPf lQZ,/ubAsB0]T6 G =Uӎa/!}5%pUWK*2|Q p`F'L$!:ah&`T/8ұ[S?Ng3Av$ G I"hE2",L 4~(x>jeytwK'zݤdTZ0 ;+嬷 X-ˌDM|e50l*Q҂"ba@RB. ~m6x<8n-~U(JdM>8S*ɐ%<=q ~b;^ WЦ״9Ȗ"a X-A!'f' m .ľ߿l![$ ݈OeL_Ga*B;*1V.bp; ~'wӋAxhw˝.Akzׯ"%Og[|(ebePmf7At݂tw)!Gֲ\B^DחgW3D":Ήf[YhݐءgUfϐxS .Og^9G.*j :# B>.ZnOnn?pp݉cCHʩ֋| C C!Xċ,֝䣀R;yu=Ч$HF,#ʡD..k-KǞ9#. 䝢xP*XZ@уZ$B.[^m"j~v{~~}5?ng777 J[7  Wd%ⅉ`F eW0G {*O8ACo%eLa􇓫wg7stSTO6S*)lN%Q_aڴ`|b^Y ; 2>gg+d]o@5b d,|M 5q! ۰2^Eu G/~ɕNsGsxbdS)B{X' !cY%gv6UovASzm($[c/[ R>LDe+F@c1*PaPBC6Obɪ~h*`'?>8"_ZApK|(4 CA@ID6\6S;-b(|d'i/7B,뀅E@t:it-dA1ءa+E;2NeqޠPWOGEzF'ܜUGUQ|YkG2"> @mu+_{冡GA/r-Šfzgp "JA cq z-{YrK1`}hLf8׼x`[@ /IGAeHG>IT(L 1g֕f',0)Z^zh+pd6/XE#fxz:flj]Qo#%o75[KH TxG-Flt 32w 93|Sg0~$ IBÄ x_] (*?n/(;?"_ؙ1{7kr@VӆCX:2ʈ^xJ4Eţ^2a˰xa@#34 #@|xQS%=y)a BBwQ"?IYϻ?\+l̟I*4 ]Ґu ? #oI ABTLV ߺbOщNO( Ziʨ[I/gZW)ɃF$g\[W$583=wR,przt yӳsUKIfٗ!7T;-˔6c$FRboƪt491b~Tɖ \$2iqQu2,M2_KTMOޒhwĦ+ U 7X + kybRY81S|,J!CLP`hE^kBdܡtF(f!!I1VGSϮb$-^V`L?xP. ~7Pߨ6j?ƾy{̘b"x:~늢l?'Xqccl̠lOp/%_c|;13=Y ܓۇsjt"tWU'Ms ̯!JHTʟSgbs[CK~WkW<s"ZC Iq%>2GGwoxM cC9jLFrNuS^&_𙗱h\p0qq]Q:sisQ(HTb@ˡ]QsMé_8.qkr&Ydy6 _ ht=SY)S瞜#~gDL5ƛКɰ+] _js٥+_ ~1[c^fsw smxK4_d)OS!-Mu\Ev5`e׏.*@ =n#4Sτ5Z X F7*#~Ua'G0Dae_"LE48X@Y+n^b'_vN0Wy!x340031Q JMLI-K.(`VOAo疽seSf0q ץ`ڕ==OظxYo6زuf7.$heYB%"H%#)Y6y=t:;f88K ]`fR%p6-,R ؚoXVrYZi̅Y…N)/t$cD"]4'F FG_p.7p"]) BfX(%%%Y+bvrvyuw:Ik VrM,9,dkY 9FnZ:WpU9cιX}C"qV&l) d[O8֛(J'Y:o0$akj+U*+c |z5x\?JylG z"^1PܯX8U~Ї)zƋ>b`{A}t^.DH}YC#4aW~%ns0)awF UNYi_6&CG&lQٟ*ȥJuza͙PtJ,DcbJ4o% q\OBBe֓7$Ik[@TR1Ӹ* \X }U ͋u* ”O*xf+qH5;;4ts} ,$ h=[唰xp`UKe'$֩29H\%0Wj4_ \DTi AgC5GJXT,0 ڰA+?RXhw֌vmAL:@IQp(WfjlC,~XbeŒj |3\ !=3B 'ޱ^sݩG.&ll0Y$b{7:M|;vͿmݤ'P!7Ƀǜ\ԟƏ*\2"vppx9PGcxVE GwCve7k2bԂ s"c׹aK.f.ijD2yimN%V ՘lgp53SJ}Z!Nka;TЈ9C[nP}p 1z`Gh~)|\L ctQr  W]]ʡtie ahZ*!w4-]hWØ`eq̾+\K\\qmӵo,w\}(ȊRAJc V]d88Hussc=ξ4tJV7*1 |o 3kOBB[SI_А]~Cv q1OXpP8u&M}K]g8m!E浝 vĨL5K,ލȏDՂL3}Қyk Esз-sU<N%Id,NiƢG 9/MiX est+YkEExtA=!-w u1w6]nQwLZ:X[dZUfa7N^^\D//O/~.~N~}}ݷ%-uh={JgfS #K[K_qېJ<龴a.#`sZ:O 3l1 2Bx3ƭ6fMKMD7MWUaH r*SiJOYʂ⤾,R 纉7xqF|O^۷%8AyD˳'[xrO|509^xSRwz@ɘ.q9T׹zAp:t=x xoơ*MKyWkʽ.25^w$^ݪ 2HJ?]!T\CT /A.A2iD|T@Љ*\hȟx:a*hUu唂tlgܒJQT(faDNLvT [4a`Dl>멼*W@V^wrZ11LD$>-_j΀h/ДEf(P@ )pr@BV>xO)7ױbR, ʅ]և 6YՈY 7G"d "MU(Tՠ6\3 5oVj M,sr°,h3q2M$9V vچMhw 1.|U~WBK iUC$i'}ۀ[[);g1Bf.aH:!IĥX8RŢz /u=$uxs\}1zۯ.h@ݻޏF^4H.Nt ƫ6fx+1wt/3nVW 7:NSe؍v1Ѥ5UӵR&^*zR޹lT}$ic;`*"~H*%dTm`1pjȩb܇RtvgqmDPۈXP;,ڞEliT_tWoC7+nѪca(!q/ҚNuiNY8tF r7ѫ-EW U;rFz.WI<S42$np[悼i@{G h og^+~ӯoCj6:/Hm4p ]jz)ط#65 ]qPEUL4,X"o"5ƼcnHKΝ%mȹțCk圬uO[j8?HVPf3>#rRȮCܷ> > 댷6yR}(r_mx340031Q JMLI-rH-IK.(`pHP'D/Q)V:qC]d-7[tY挳0JKLKIMcHVU`^Åy_Ix:b; Tenj4$D8"K䴾_}kyfTUIE CXg9+Liq#^T@X>\Yg2aY9CgeƧ%3=ݠ/hքUT‚aq'/(Jެ]vd*r!àC3q}UZT°[MOvCkK^~ Tؿ@CRN͇r>ߧ\uxŕ_o0). Mxh=&&Ĵ&ڑ1kJ'ϹnfN A3_)5df')*H8:^22E0Bj$IQ [U<'A%e"ا* +CzhP FqYaJRRL<m([*)  _)v0X!Br5*ͤ"-zT]BI#)Zi]D~S(fsbkeHJna.yTp}?}<# !B|.JGؓ;Lz1hb@7RrcT"JHATÊ:ƶakZ߆"l 9hL!EmmyW~2WMpr׸;]ٲT^[L׭ajkaTI3 ܧw m3E`ƒhsw]FE''쿽3.v40ѨKQ;w"No>vU:_l-~=n_9 'xPj0؋12dH) BXD`IF:CJWn {޽wBX5 HmP:y Byf풚gɞ-Ľaz;+Xv=U@Zb, xWpvs(NM.QKMQHIMK,)I'()(A%KS`ڔKJ*|Js 3* Rs3(T8@T%Q.r,˰xSV/-.OO+SHJ,JV OQ JMLI-rH-IK.(PRp9`f^^n>..=}t!;\LMxZT_TLc z8 "pX*΃Ci;N*s J=د%8tt"{X%TSB̗>]Cɹz+ Xf =iI -{w2ПM4շQ z,taA!l0a㜨9q.BU?+@a6i΅&w$b(mz xE10{^r$2 Kٙ5扖RD4k 4[}y 4C%vnlٜP jb-EOıW|#}/c4}=xmSMo@ﯘR!NKRr喓cE+HIZwv!|xRUoj[I Et{(dMX,N*aklAmV4,dғB+-z6bA22LVIV̓J6 8M1| x|awV{ _G%>I`th8.4gOwN5S$0椝T޽Nuؖl2鞱]vFϫh _9ݛ mCFkbJ}?vYԺG]5lo`}ɽ'#x?8'%L%ǹϒs`xY)<>=<0nCVIY( B}ΌRbmNoOK|胠VAM !,Շ9N9GKVnCXS##x;P_K"1 'J 8U!Q8WHKL/-J,SH ismVTbV^YjQ1HмԴҜb$y@~>>⩹9P"i H5 VQ]\ *jRKJ`ZUѹIxTK0+F$X=gHЪJ*rI,&RU\\+ת0'^rw3w0YF[YVHŇb]'u{y],4\XTKޚo>md)]CZڇ,q.(fiDד!u')bWk{VYּ'ZQ'DC`Gb$0*ױS+l`;\Pט>2BT7ehn2 G @hN8+yt%Yue6De}*8Bbq f)o[-o:m_pԪ|4 x340075UH*I+`H*pu[E4v4z}6I+)c0y:3P' /Y6(def%3% ZrT4AO-۹ &@PZ\XZy_0G;] Z~0s/9xSV/-.OO+SHJ,Ĵ]#=S$0+=9Y!3/3#J/Y(pa.|ҷxSV/-.OO+SHJ,Ĵ]#=S$0+JA7S$$98XA7_!-3/%3/U+sp^Kx}T[o0~W|cJ4#ɤ$eU&Ra(k lR%m iE6IPAbb_")= +&YY#k!7'QȤhR SQFסj()u!5 'Y5߭O(jZͶ⵿+E!1U,q 9 m+whx7GLR,Xˀ%/^)k4z) V 翵./u )k48 Kh FA(2A?cf/&5\1X l℡6Uβmld|ٜEk⌄h4gƋ` "ܛi}ߟ%̝{;'菩x!V,ZKba`:A^_iC~eP%A2ǼݐX9xZB#t{;u|Fzƽǽxb qp7^ƾenaw4]&Yb6Kؘ0u${̹Ҍ% cJ ? ˥7Axã>5ΚpKp?qpe>1,O/A42Dӂ Ǐ4%8>( 7dˬxc6[xXK8%Y*Kdb`6~#82 9H!MyFQIJ<„eypK+h#g6@1XD R XV cofElIp]"eAqH4ՖJQ*F#趪W'L?6Ҋ":!0E{ۆ 68WiȿTP[LüK1f6 L5L(BëB%{)dmpFnrik=_5gh8?MQmxRWH؅ήϬ#<Ghp S(ϑUýr= awMoSAŖϙkKg/2q~:wϋ8 6Aq">90 =iJt ? "zJ"lဇh[,N2̑ x@Ѣ=C:郸HTgҢ܋9yvbe4OYфBW$neg/2[K~1ĥOizw/lg S}뾺aV{: ?_};%VrQˏX"P9DH tËʒP͈c-';=^,p퀏j}(T< \M L&A@i*4h쾿$/;Ӛ'as ld:sxbTc5,eUqx{wQHvx'Ұv07Ú;B~bR+a#8xq@KXRR՗,[.qrDUk_ K[8 ^^G].HUo"(.`<۟y\ k:\cLT{_y ȥQdpFƼVbk&y7*mFoVV/\՚,'dފX%&&!x ;H%V^ՠ^,(DSqmSU7ISRc$5HRQxSZLʮ|naaÖr4ziH~R_p%tI>6xYoWt@ͶR힔J rU{ sk<3NygldJx^{d"WfZ7`x"OjzK{$h"12"d׳v?Wm*+&ܾ]ŮE>yu? y8~w~u}Egtpt3) 48&1"kbmN׆?}ɾ_`Ef5;7j8˳ԚodAo7GBeTĊ$0R%c ag(عa ke˕ͶGgbaU T V<=ٌ*MI``x[&F$ň\ɖ(CtK.# zeft؜R4h*2M 8*QZ(. pIv<ħ䦔 fQܪb{(K?I*kW8.PIjEvEKat[-*iW" 8Nw5PKu9&"{[l))̜J(sF}(\,xZI# jl8<52Y'tV_U~5sV~qQɶ \j9ݱW.ddZ1mfQ`>~pO6"te-"xzAKJu-2} ';x qhpxK 50KyaDyKHGB$r "MUԞ֬W9H)CYHԸ[t I-mԆ#oLmDμp}A;kTbZĭV.Jͯ`!]"r>b g <=fT 5 p4 X BYq+Q)fk48(WKˢnC*[xCeMDĠ+&.x0iAc|J59Cl/,v'Rv/r皯XcOHV8]{SaL8'K|r$MN2Dx^_a(]yZ`Q qRX|$R xL:o Sy^`PI y`T 2fFCFߢCzb4A 908\^:G)i,~fZ"|SjLXL+h7T䞐ⱑm[cKsr1S']bF A@*+cSÝD⽨p:[h ."Ky;Fga4 J{>9g9mh;kC5#lWj ݽx{Zp?T/.Nsa0sY[-5ɿPp{&g;t(X-#W N~rw7ve0hs?+(x(͈vN IBHӳo+(r7f|׹xS%\p*Uv<;UN8^B30KFU /_m9'gQD!vk Q*DH U$!Jg֎+^VvMZJ m˱% :( ߹Jsu ~)t^D!*[έԫsw?**"*{5fI;B CRDzcRg :.fdABrTF qt[OozuoIGCB x88I }[dy#~cw~@gz/_K !h,}VU!A4S;K-&>Owsots,6|HcDD9 G&lښB {Dko-E{V+kO7Kc'"lG{[6tѨŏ7ձ#!(u{UG(m#I%'ĉD8U_&sqv*@uU I2nKjρA1ʡ|ݞ3@Yҵ 0,^tQr(x!X 9TCf  D`ǂs;hpyD2(ۇv]wLG wJ3P'&(۞QPHQ45ԕ@MU}U`܉LRrUI^ <7G˕*3zc)g*FTafj>קD>, w *plJZ jTJ" &N$e#lS&$d;( $ ~=`0kY~qUƞX<_L*P%-kLmQߨp29dșA>b8c{y(%[\X亭-~pP  Ѷ:U:eX5[-at\i*#:vloeE5mAF:6\eS/'rL#g^7:b_pj}rΒsQ?xMk0=xbXz -چ BGYrHu%ԹbYzwFc;fS4䃳R PTj&:"}(kqt؀_BAAQ}qĞB'n~'BԶHc!5-x!NQ*53J:xTf G WSvN:DAWZ"NA7\¦hjvs&<)0\TL69N\b 3L+08TtJr%y1n]pci8ƙ7~ NH07YiS\E?PbUؒ1 &!?6 cEj|q,㑥vExI  )1؅TQ$tD6ľY:䨕 Q l?ljǚ]d}J )y{y$I3v[AlS Xhlcã; =5!yKU$~fGrNJzH SK2J3fŔܶk԰ Z5Epũ9y99z%% Ssg?{ZEZM6ye9`mRo8>ɧ2 {ޭ;-/>#1/%'(>?/>/P D1܋ s⢞,nFJf1<+wٻtyߌiy׼.kf\g$eeڵBj1&?k}ė@ K9[v?ئ> %`];{e$ݬik RQAUPMdZY\RW ”>[T͖s ]$ >jV tQDfÞ7']I@r-PJ,*.O-*/={>pNM|z~~.83R/38dKTIJiANfrbI*,QC_[- b}TGN~^:$\@ [/{g9>+-)PkY!r>iEN~Tmnj1PQRifN ãyvWܮۼa^ho浊V|5Te^bn*0ps*J9 MRoU$FgּIi uo&W~Qm~L@׆S.%0,ٲeࣙ<_MMJJRK3&{(v(5*954/,)MvZ>mtbϜ~c?tu }T0TPV5R).F i1-UTI=񐩱x ,V<Ԣ".5;xSPKI+IMx.NM.3KM5-K)M5䊆Y y% FF\yxˌMO04S.NM.ϋm2*R!JL JlJ K+* jҊRS543Rm G=xUIn0ȦqEJbpM4(H#RuU$8jp0 GOqXM@=-N])NU6piim@6q\B-3*Ag}S_NA\6XmW" fӡ vd))HI,HDFf)\p6BLao)B 6WT>2;ˆ㤆{&9m[юעU܈jIB[JƏ8gՊaq҂ޘ@{i##v5=b5-va?xba} ^;Vtmъ ~ h `vk܂o |m] V a7B4H)3m\Cno:a| k^Tݹf%Q7&gumGu%Ͼ+nTYfa vP> i Zo|7@qz-^XenE=#:sMr]DO)ّw];?qTK> fe"7[[ϓ ^_srPmy?{0[xeRMkSA%Mh~,- *ZK/#(ݹE71c͘O`"14&QƓϥvɘ>85%`Ü2y`a?=,1\{8xVZ ?WJ\U ra8p^ݺ^W‰L^Y(c,=/V' s ʴxҥ4ń$V1lHɁOPR35r.6Ka׎ K0.h, ^` G.f}]iOkS;m/=rx[ÿoĐgdJRKLlMM涛33NŤc  ,V<Ԣ"kĔԼ̼L+T["[CmLkҜ̼T #L3R槦@+Pi6/-%)%`Mũ%yj`\{x.NM.3*KI5TUHLJ򌀼*hlRRӐǶxm @gxU]k0}Л慥@cYǖ#yJ{$u2/Iq9{u#hJ RKƲ{>V; I7LbʬRtdq1 /쑅j6=+-QxT)zD8[Ww)zrN>{ < _$ "v am5a&U*ʪJ[1rQAx>d#SZDpR24R 9ȳL$`LXċIJxQQib݆X=\0Jpt<ލshkѬ_-}΃w==Oqۻ̈H+F״MyPjz[o;XC80ќEstTdG,}/ 7IɪIb W6¼t[Qw幉9egX&7m^֓e&%!`mTjF" ]2gRGnL@zA]A(;otR=Fꀅ5vIA9i-Aƒ539WiT!,RiKVQeMYG,~xy& x]A0E=ſ  ,Z`bۉP=$Nf1Gk"q(1R-pFEJsiDj%r98 &2[ԫl<,޻8hݨ?C$Mɋ:WSev9fbQ|o8Wx.NM.ϋ,.r ,C^.^h0PEvjmYb!D$ܝ.xOo0O*Q,NU=eO0,VN}ǐa{ o_bE OǠG׶dpI@>Dʪwe4P`s!q-z!^& hT`B]K+Bo_R,:LyG.cM͘eRrM7Nԑ]!`s O{qgSE{7P9CyHI:A\ -$j],WdFJ zrv ? hb(aW|͒^`xtTQȸ=31_GlxmCGZ#j6uRy&공/:4c9nr(o0q9ΙՉ&xSVO/*N-QM-JNQPRH,KMUPR6UTRR1RRQHLK *r-`xj0@e-첣b3иpp6[$}EZ sUYׁ B &ʠZn} >Jk!94mmEhn%&L|vMyn9% ]AZ$ |/U_ŤcBLaRA~]Қ1r9@ <,gf&V@IXZ+=F9"e}+KlK;!z#w(~jŸ;c8J~g^SC,24M4pփ]P[দ, :dvt|HC r3L?o$߃J[/5໬QE 7 ҡ^q~ۃ!{'&8RﳱxUn8}WLp,l/E[6[m "Hu/CJ@.Ї 9s"pz i=}5Bwt)#/[d@4)2b5tqǴm@)=R)s@N(M<t4kN6fw]@.VW@Td\-֍|O:87vg%u> &CN\\-=;i dW#OQ0}c@߮b38955QL) zQA ۝犏p3wX l-5ؼe W`Y-s6D_[LE]Oy),*…/gC$eAj a uz|q?iqt {GUqlpěɪbŒxvR#Ϊ) O uc>YXiS<#lԭшBY;Tr܌)|f?VxVn04FVBUQTv{qD&{6Izƞyyfq+BLݫj88۳?|n9ss\%~)G<+8~ܯSdGo]fyT]0Fi%1VzDS ѐ+QM$(%e!Z6 2a A di&s]b:22m 5OQf?Qѷ8/Jyo7VUuݐpw"VF ILȿ2r^0>T=/*xU UP.dT,zQ0~aWDC^\)"[=SBXدÄwX4SQKSF 2HI|^%kɮ18٭ލvl--ջ-yr7%ޥr0~@3 dQ2Π}LyIp=ʈW V%?0CdINjvjJ]1]DO#Kel.6$YY/KV gʕ Yx}J1ܹ@qZڛ7<#%3ʹ4'{)ܛ5|Ll$OQ( ;'J(Lx{ɹSW_K!3/3CAWW$X<$C!$?W!757R!1''?9$hzo}Y~f XO|.X\8*5DDi*Ts)AAQf^I*͘<% &X5XiQjIiQY@Z..5`[ҊRS5 icd oWj:przcSrZ*T#gzB*_i5՟(OEFjgZh<]? ޤuVɚfjLRNR,Vk0_m Y?x+ӈVz.B/X/lo0Ι^&iW ϑ-~.d2ȁ6Y&vI PaYcֳ=ivGߨfv{g-M&u~:qcu.ik[ei8 5U_^N}j*q >jvxX۸}|zS\: D`bztFU"`[0\2Ԥ.,f LjMRoÌLμݾ.śLO'ӱ7:?;O/g@[}3$ɱ[𙘵DOTv5 .v<_M~ό|H(٘^8EI/YCU{M>f Y#`܀澧['ۦ:öD * i@w$=mDgA\O=BVA}G%C&DE0%Ǝ`AE2uF3SJ [z!y3C 3|BzAQ Kh -B.37$2I^ik/'B[lQ_@lSf/k !VKQ[ȂK<+ugFS5CJy?bOPF_]ޖ U2 Qݘ|coðF1o3ز&pmtC~fSB1{lD_tRZd7.)ˤr2v*4Ħ2834&W6]c:iBBtRvtJ[ԴQ?ppTh,9g"' E@di@l(jpҢa)60\JqB%N>]V29So$˶Uq܎4'H8cs1dm,8<)#h%pFrdiAs_z{~V%JrqLICkx7~R~mV5{~iu}>J4U7{iN~҉UI<<v=ܚqIO}Qs9# nz܇3ݢ~`5+ tM4.~1'ݧ5Q3Mc|pVg#??Pd{8#?`B .xۂl =hnqDV暯!*=&L:Ȕ@ȶLmPW!cޚ N,VA{vajˈ3Qc4eMHIvJxD_[[nQ@=_trWpy42ydl\JVZţpOTO~Q!}E;kYE ,7/ii0GVت5 FE{LpiQ/Ѐj~(\\H8E=ㆯժejUң&&Q%?iF#)̆磟ON]ŹO-}%?+sp|bϢ懴&]e2ˊ. /Y=_\9*mU\` 5;ITؓhwukrRU߬zZXu:U\x-ʇ:HKy6ECw;.WjZɞ)DL6(n$;3{5.zAdf%]=+޴̅rK[q6Y^%56En%MRu27#ħF<)5^&in=(Cw_y1I +uì|i;ݟfE8f{g*[b* Wm' ߼4$)_8uU` zs4g׼+>J1&"ƜZ(kbSe{vvKod3-vyYwlmaker-0.8/.git/modules/submodules/inih/objects/33/0000755000175100017510000000000015203543566021742 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/objects/33/787047c04375515565b09f2bbf7f9116e962910000444000175100017510000000065615203543566026466 0ustar runnerrunnerx+)JMU017d040031QK,I,))L*-I-f\W'538u.I| 3J}/!CsfݵdVfz^~Q*Q~]-$VSTR`$@examples/ini_dump.cjv`[jv`[,.4YЄ!}/dexamples/ini_example.cjv`[jv`[-()Z9w@v1/examples/ini_xmacros.cjv`[jv`[.y[*nq/y)4examples/meson.buildjv`[jv`[/e,|X7ڊCώ'examples/test.inijv`[jv`[1Lejvվ[DJ8+fuzzing/build.shjvfjvf2W4 1c oUB1fuzzing/fuzz.shjvgKjvgK32c¤5`.&WQ]fuzzing/inihfuzz.cjvgKjvgK5Ho[QN5|C4Qfuzzing/testcases/case1.inijvgKjvgK6#unSq|Bg.ini.cjvgKjvgK7Hѯg 5wini.hjvgKjvgK8=>քyQ]݀;e meson.buildjvgKjvgK9uVT)yܞ|meson_options.txtjvgKjvgK;ԺO]>O5tests/bad_comment.inijvnhjvnh< >Bxa0aNftests/bad_multi.inijvnhjvnh=GhN\w:(d%gQm x}-&|ftests/baseline_alloc.txtjvnhjvnh?(x8įPɰhI~!tests/baseline_allow_no_value.txtjvnhjvnh@.:X>YRNjED'c.tests/baseline_call_handler_on_new_section.txtjvnhjvnhAWӾefn!J+tests/baseline_disallow_inline_comments.txtjvnhjvnhBj6#~8!Rԩ}&!tests/baseline_handler_lineno.txtjvnhjvnhCXQ9 X'tests/baseline_heap.txtjvnhjvnhD%WKfhb{l<0 tests/baseline_heap_max_line.txtjvnhjvnhEXQ9 X'tests/baseline_heap_realloc.txtjvnhjvnhF%WKfhb{l<0(tests/baseline_heap_realloc_max_line.txtjvnhjvnhGSQn|tРV"yklltests/baseline_heap_string.txtjvnhjvnhHXQ9 X'tests/baseline_multi.txtjvnhjvnhI%WKfhb{l<0!tests/baseline_multi_max_line.txtjvnhjvnhJnO1ِ=M.3#}tests/baseline_single.txtjvnhjvnhKtɿ>J _&tests/baseline_stop_on_first_error.txtjvnhjvnhLSQn|tРV"yklltests/baseline_string.txtjvnhjvnhM6Duzr7C, tests/bom.inijvnhjvnhNMhYk{E3f@;뇘tests/duplicate_sections.inijvvijvviO6Kj@%q}wx<Otests/long_line.inijvvijvviPnmc\f7퐰G H tests/long_section.inijvvijvviQz!(G:!Ktests/meson.buildjvvijvviRݨ&([ :_tests/multi_line.inijvvijvviS엽$O@؇̬<`tests/name_only_after_error.inijvvijvviTV!sygn2}oRtests/no_value.inijvvijvviUQE`Si tests/normal.inijvvijvviV@?H,s+^韬xtests/runtest.shjvvijvviW} V*gZ2ȼ3Ntests/unittest.batjvvijvviXMf$p&MCvz|_tests/unittest.cjvvijvviY O,oXuHujMeJQtests/unittest.shjvvijvviZ1]SA9gW9,Оtests/unittest_alloc.cjvvijvvi[j΢nsUD2C tests/unittest_string.cjvvijvvi\)e8x\mu'Z["+娼tests/user_error.iniTREE61 5 3xpGCuQUe+bcpp2 0 C :GNQtests34 0 F&u `.fh.github2 1 ``Aoj9gworkflows1 0 i{<f pNڬfuzzing4 1 -;#רY+8kOtestcases1 0 )!VA)밙examples11 0 SlZ23:,w4$&vx 1zwlmaker-0.8/.git/modules/submodules/inih/info/0000755000175100017510000000000015203543561021012 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/info/exclude0000755000175100017510000000036015203543561022370 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/.git/modules/submodules/inih/config0000644000175100017510000000206315203543566021255 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 eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUl2ZEhkcGNuQXZaMmwwYUhWaUxtRjFkR2hsYm5ScFkyRjBhVzl1TG5Zd0xrTnlaV1JsYm5ScFlXeE5ZVzVoWjJWeUx5SXNJbUY2WXlJNld5SnphWFJsTHpFM01UTTVOamd6TXpRNUlsMHNJbVY0Y0NJNk1UYzNPVE0xTnpBMU1pd2lhV0YwSWpveE56YzVNelV6TkRVeUxDSnBjM01pT2lKbmFYUm9kV0lpTENKcWRHa2lPaUk1TXpZNE1qQTNNaTFtTldabUxUUXpaVE10T1RSaE9DMWhZV00zWkdOaFpEbG1PR1VpTENKemRXSWlPaUpwYm5SbFozSmhkR2x2Ymk4eE5UTTJPQ0o5Lmltdzc1X01raEoxX0FnQ3VZWWU0RnlKOWhrWFkyR094WWRjTm1SbVBEQXFUS01BRUhTUVIxSWE5NHdkUEtGRnE4dXZ5QVpzamt1SHB5SjlsTDdkaXh3 [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8/.git/modules/submodules/inih/FETCH_HEAD0000644000175100017510000000017015203543566021417 0ustar runnerrunner26254ee9de7681f8825433415443e7116ff24b98 '26254ee9de7681f8825433415443e7116ff24b98' of https://github.com/benhoyt/inih wlmaker-0.8/.git/modules/submodules/inih/shallow0000644000175100017510000000012215203543566021453 0ustar runnerrunner26254ee9de7681f8825433415443e7116ff24b98 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c wlmaker-0.8/.git/modules/submodules/inih/packed-refs0000644000175100017510000000016215203543561022165 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c refs/remotes/origin/master wlmaker-0.8/.git/modules/submodules/inih/hooks/0000755000175100017510000000000015203543561021202 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/hooks/push-to-checkout.sample0000755000175100017510000000533715203543561025622 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/.git/modules/submodules/inih/hooks/pre-applypatch.sample0000755000175100017510000000065015203543561025342 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed # 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. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : wlmaker-0.8/.git/modules/submodules/inih/hooks/post-update.sample0000755000175100017510000000027515203543561024661 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/.git/modules/submodules/inih/hooks/update.sample0000755000175100017510000000710215203543561023672 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/.git/modules/submodules/inih/hooks/pre-push.sample0000755000175100017510000000253615203543561024161 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/.git/modules/submodules/inih/hooks/pre-merge-commit.sample0000755000175100017510000000064015203543561025561 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/.git/modules/submodules/inih/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315203543561026223 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/.git/modules/submodules/inih/hooks/prepare-commit-msg.sample0000755000175100017510000000272415203543561026125 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/.git/modules/submodules/inih/hooks/pre-commit.sample0000755000175100017510000000316115203543561024465 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/.git/modules/submodules/inih/hooks/applypatch-msg.sample0000755000175100017510000000073615203543561025347 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/.git/modules/submodules/inih/hooks/commit-msg.sample0000755000175100017510000000366415203543561024475 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/.git/modules/submodules/inih/hooks/pre-rebase.sample0000755000175100017510000001144215203543561024437 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/.git/modules/submodules/inih/hooks/pre-receive.sample0000755000175100017510000000104015203543561024611 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/.git/modules/submodules/inih/logs/0000755000175100017510000000000015203543561021023 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/logs/refs/0000755000175100017510000000000015203543561021762 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/logs/refs/remotes/0000755000175100017510000000000015203543561023440 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/logs/refs/remotes/origin/0000755000175100017510000000000015203543561024727 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/logs/refs/remotes/origin/HEAD0000644000175100017510000000034515203543561025355 0ustar runnerrunner0000000000000000000000000000000000000000 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c runner 1779353457 +0000 clone: from https://github.com/benhoyt/inih.git wlmaker-0.8/.git/modules/submodules/inih/logs/refs/heads/0000755000175100017510000000000015203543561023046 5ustar runnerrunnerwlmaker-0.8/.git/modules/submodules/inih/logs/refs/heads/master0000644000175100017510000000034515203543561024266 0ustar runnerrunner0000000000000000000000000000000000000000 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c runner 1779353457 +0000 clone: from https://github.com/benhoyt/inih.git wlmaker-0.8/.git/modules/submodules/inih/logs/HEAD0000644000175100017510000000074315203543566021460 0ustar runnerrunner0000000000000000000000000000000000000000 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c runner 1779353457 +0000 clone: from https://github.com/benhoyt/inih.git 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c 26254ee9de7681f8825433415443e7116ff24b98 runner 1779353462 +0000 checkout: moving from master to 26254ee9de7681f8825433415443e7116ff24b98 wlmaker-0.8/.git/modules/submodules/inih/HEAD0000644000175100017510000000005115203543566020504 0ustar runnerrunner26254ee9de7681f8825433415443e7116ff24b98 wlmaker-0.8/.git/modules/dependencies/0000755000175100017510000000000015203543566017401 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/refs/0000755000175100017510000000000015203543560020332 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/refs/tags/0000755000175100017510000000000015203543560021270 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/refs/remotes/0000755000175100017510000000000015203543560022010 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/refs/remotes/origin/0000755000175100017510000000000015203543563023302 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/refs/remotes/origin/HEAD0000644000175100017510000000003615203543560023722 0ustar runnerrunnerref: refs/remotes/origin/main wlmaker-0.8/.git/modules/dependencies/refs/heads/0000755000175100017510000000000015203543560021416 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/refs/heads/main0000644000175100017510000000005115203543560022261 0ustar runnerrunner971f717810690e3fa0208dba2891439734d6be77 wlmaker-0.8/.git/modules/dependencies/objects/0000755000175100017510000000000015203543563021027 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/objects/c5/0000755000175100017510000000000015203543563021336 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/objects/c5/9371abcad0d5e954b9627d0de1de43d35dce660000444000175100017510000000026615203543563026715 0ustar runnerrunnerxAj0E)f_jf(K)wM0bBߐtXnu@!\v` Yd h)?g ['YSc𳟝;1[8 Fz]k/ŎF~!`4ѲxG/lM|>Ibzk+ !DAgDMQRR=J*eVLx!q ,P6֭䙋f Q;i'4:3®R2I"aqFn ?qfQa&qj슧7 ]_wp'3aLes)z=A-s|+Svx340031QK,L/JexR}N=x.\CҜb[SMW/ҚFES71;'XAiS385橻#T_+y󻵽y']9' WG_Woխ:t_-iehf 9II%Eɩ 93dL1{l]+]\Orۧ SԡKۛIy e%|<7Op'v0kP#ۏ54J6UbOiҞa{8&tl"~c'vDbzWlŁ4 Ŏ1L[ 4C0?lXmceԐ9Uݜ( sCiVaktg+ D9bUK@諐=s{;dEw Ԭs:)f2!C"rbMH.Q@\4EkK4QA9J<#E8P]#B5TNiw3l$;nk;m\PXܶq;ybcH_w;6y6b 1I9Q|9BPP$ac@P1JC8l~b5薗Eh x_^49|#a}[ejX ZȃzMc8&sYE+@S*xZl+V.Wdžj|N(RQ2_r "zqN߲ ;-#Iojͣy#VJA"* o{w }߷pw4SZI_=p9}}zݮZ5nf'7e,^oR]E tJo.U\|a8M 5AQ"fi"/Xm{.)?OR`ׄ'vo Yk뿫ӞGfPCӟ/`ueX4SwC 'Dnt2XLK/?o!`RxQ@խwM>߸-.S$:}wy>0/lr~WMG2[Cj֜xq:a{^ ֶuZ,F@\oN`-ExOؼ+ ݵF ]3gq"E>\kw>Rm<1;)jwxZ[s~rSi4i牱䆭CiDn&% . =dwVM5Iٳ[)^s3OCYM'.d7J{_~͓vпyj%m37vܫXۛ~yZw7ww׷w7W=u\-ߐJպ(/63p;4UtPuBv(MW*Q+F aUoM5uE᳕v՛҉ TZ,+o͸݉჆L92DA}`BXo9bA[+aa2V6D(1vx@^ Yb <`Ҫ! < ~;v,+MۚKv,7wƒh{|4Rft'.%/5{e p/b0t|KV[}XbOJ[f1@ʅM=n{T+[o_3`6|4nMV DnTF(5r"=3g3eumeYLJA[PлQSq[NBm ^qVZXNdOEk* GU+LI(:3Fw?:S{ /GS*~=EZoGK[_ ߁;Ɔ򣶦˝@ 'e(k!zGDŽ5&!1 pzrpR >?Gc?$ 0R .#&Ƀԍ4!3\*M1KCIF\f#aMfÀ,".Q= !y!>{;?B25fp~+>(q=Kb7ҡ:J a­] .wV=hr%F1P`ac'ݜgUN9LCIVw˩O8T=IB[aU+uOKKvcʪy}"m Z0N:٪t @dkYR(zZG:y-Bg=~1e}…Z@aP WImC/ lvÃG]9S6"?T9QAT17 Y)&/_V,ieq T h q^Ȇhoq]GcfAnt v\J+-E=࿤ npqe%+R!wpj]PsG%`ccVn"IdF-GGUvl /=HJz F5# y[i?!ĎRNo;~El$"XV`o)\NS_c , m*vFA<eTtOJB~!~ܶ4`o.Hxc z. }X\\}mf4QYABL,xC8%0^ `~}M5:ӽ$;81~| nq2 UI{0%I5n-X9c ]~Z"jH ̥7X}[N#Y>L9'la1sЭD?,S -((!=5uX^PhS؁qe f죰MӁʈ]^f+ܺ7;^Ք ҡ. 6IdxA8wڂL-PbYc/0SeVrqH+rkk{IcfDğR4rF=Q" 'NpVV;)sX-1UTl2fgJh4RXWLQ^a|Ѻ ĊSdhN[u6E}Q=7SA%ՇN{*aM'n> wX !htqݣ˶G!2uߒ1˙\f;[5@7g-ǐ1z\eBc99 TOV]"hE]=[/^ 7)wO]ɔ˒2QCYQN5+9o&ƃ!P|]31-|֮l Qa;O@`7AMtLND{Vs0Yu,TOYM|opJQmyp> 3 % qujDC>!R e-Bd6ˆ*~'LJP[K2`;pD~&pQU6I`/af8L4y ԽYh+  2W\eid{ޙ+#6EJH>Dpl8TȺqLTh2Q'0qȷU ts*i6*5$1 1̆YUb &>7Yf0,kny (P}n0ma!JEA cK3:2 xAJJǽ @a a1ѕ!rg7Bhx%\cfKa~8m8DJjSm `I'B7-^O6`e%л"68=φl ~L `Ky. /mk' 9pK7X ȋԼ)Xs%U/PW88ZZ,3b\~\x^|\-V뵸˯oމg GtMRecҔA4'䒩! Ƽ_޿.꫗ջ?] A)-Wk~}`e.a/퇻ۛ5W[-lfaSMt3]4\sV#=]_Bl^Fq\kGLc̠Yi_6{`R\^ˍn|W҃eW ;AG贳QKɂAU"vQn|6/(L"t"l9n|P&Ѵke+>\n=}bW H`xr^h@h8|gUntɚcĘѝwf;1[cn'(ʦ%N x-u3ZF.*g[ r8HЏq^FA%i_߀ F/7xŢĚVȋ;/RΒ4]/ n ,SPtN.i V'uJŇy @q_-I16ktf)W;|΃+46?=vBJF=3|FKd!skHt-JB4)τg53&<;٦T f\Kry6ݖ1`2tج8olɦhhcԅzuukpY BF]¿F1&Zm k,Cjj*'@@3oRAd~u&B0/t d9RPNmz?ểlgw+x/Bg'`asҀ, D2VnfՍJ i(Gk֊ͧՄ>[.ܻLrH[ƚ!]&r_/7yt+=zi \^~"B?@ˏםoC cDE#Ro64-]d9p+/AeZ% )9C1H '4x;o2Z !ȉ@ѺBwf$N5~\f xU0 D|%V̬L n6Q'V{b(=.*L@8/jO~C՗P{ӷ7**ssJx\yUP5|ǹT]AfEnb[jp uK<2'1/8WhM!;Gfo~Q ߆D($?9?C@֊ 7fJS8(?Vjn3:2:}(,wlmaker-0.8/.git/modules/dependencies/objects/info/0000755000175100017510000000000015203543560021757 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/objects/25/0000755000175100017510000000000015203543563021255 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/objects/25/c2e4a15d24f6f3d46ac43de276060f267b16b30000444000175100017510000000277015203543563026401 0ustar runnerrunnerxWS6sKnCi{`jl>;u2$:l˕KR+;;0}Oq߮VAL%)糘Bg6>4>@4iEQrNH?mFEx @slV  ÔtLK!I3LcFt4q6IP|>/DϥNgXTb։8N1mwhkm)?Nc _g3! Ř, 3AqOrB06|*DP0b,(!|_")4!8&Cg6.љ7;r!x>k9#s ; q„e&$SH6 M`ѐMYq(*R 2*L^Vb0IdrE%1+Ä\S,o^o2``nμ}cc GFw\?O|ϱVӻ{樵A;4/ C_ joFihDណSl7gykBs*A@p>#uS)>W8Ȥa \1@gV $X@L4ygcK$VN9Ӳxp#Tk֕<8Az ީo[}{6#gkR% 5:~5 I':y3S6'(`"б;ټ+2HIHwhBED"4"#hX`@msNk^uY/3^4 y|+BKdz"aYB#ϿjJ-bћذ) Aa*xR$\ӯa?B(^m(D㚽e%2-%zhe AJwlrootsTREE12 0 nndkpF3 )fԢwlmaker-0.8/.git/modules/dependencies/info/0000755000175100017510000000000015203543560020326 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/info/exclude0000755000175100017510000000036015203543560021704 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/.git/modules/dependencies/config0000644000175100017510000000206615203543566020575 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 eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUl2ZEhkcGNuQXZaMmwwYUhWaUxtRjFkR2hsYm5ScFkyRjBhVzl1TG5Zd0xrTnlaV1JsYm5ScFlXeE5ZVzVoWjJWeUx5SXNJbUY2WXlJNld5SnphWFJsTHpFM01UTTVOamd6TXpRNUlsMHNJbVY0Y0NJNk1UYzNPVE0xTnpBMU1pd2lhV0YwSWpveE56YzVNelV6TkRVeUxDSnBjM01pT2lKbmFYUm9kV0lpTENKcWRHa2lPaUk1TXpZNE1qQTNNaTFtTldabUxUUXpaVE10T1RSaE9DMWhZV00zWkdOaFpEbG1PR1VpTENKemRXSWlPaUpwYm5SbFozSmhkR2x2Ymk4eE5UTTJPQ0o5Lmltdzc1X01raEoxX0FnQ3VZWWU0RnlKOWhrWFkyR094WWRjTm1SbVBEQXFUS01BRUhTUVIxSWE5NHdkUEtGRnE4dXZ5QVpzamt1SHB5SjlsTDdkaXh3 [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8/.git/modules/dependencies/FETCH_HEAD0000644000175100017510000000021115203543563020725 0ustar runnerrunnerc59371abcad0d5e954b9627d0de1de43d35dce66 'c59371abcad0d5e954b9627d0de1de43d35dce66' of https://github.com/phkaeser/wlmaker-dependencies wlmaker-0.8/.git/modules/dependencies/shallow0000644000175100017510000000012215203543563020765 0ustar runnerrunner971f717810690e3fa0208dba2891439734d6be77 c59371abcad0d5e954b9627d0de1de43d35dce66 wlmaker-0.8/.git/modules/dependencies/packed-refs0000644000175100017510000000016015203543560021477 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 971f717810690e3fa0208dba2891439734d6be77 refs/remotes/origin/main wlmaker-0.8/.git/modules/dependencies/hooks/0000755000175100017510000000000015203543560020516 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/hooks/push-to-checkout.sample0000755000175100017510000000533715203543560025136 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/.git/modules/dependencies/hooks/pre-applypatch.sample0000755000175100017510000000065015203543560024656 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed # 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. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : wlmaker-0.8/.git/modules/dependencies/hooks/post-update.sample0000755000175100017510000000027515203543560024175 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/.git/modules/dependencies/hooks/update.sample0000755000175100017510000000710215203543560023206 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/.git/modules/dependencies/hooks/pre-push.sample0000755000175100017510000000253615203543560023475 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/.git/modules/dependencies/hooks/pre-merge-commit.sample0000755000175100017510000000064015203543560025075 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/.git/modules/dependencies/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315203543560025537 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/.git/modules/dependencies/hooks/prepare-commit-msg.sample0000755000175100017510000000272415203543560025441 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/.git/modules/dependencies/hooks/pre-commit.sample0000755000175100017510000000316115203543560024001 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/.git/modules/dependencies/hooks/applypatch-msg.sample0000755000175100017510000000073615203543560024663 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/.git/modules/dependencies/hooks/commit-msg.sample0000755000175100017510000000366415203543560024011 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/.git/modules/dependencies/hooks/pre-rebase.sample0000755000175100017510000001144215203543560023753 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/.git/modules/dependencies/hooks/pre-receive.sample0000755000175100017510000000104015203543560024125 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/.git/modules/dependencies/logs/0000755000175100017510000000000015203543560020337 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/logs/refs/0000755000175100017510000000000015203543560021276 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/logs/refs/remotes/0000755000175100017510000000000015203543560022754 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/logs/refs/remotes/origin/0000755000175100017510000000000015203543560024243 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/logs/refs/remotes/origin/HEAD0000644000175100017510000000036615203543560024674 0ustar runnerrunner0000000000000000000000000000000000000000 971f717810690e3fa0208dba2891439734d6be77 runner 1779353456 +0000 clone: from https://github.com/phkaeser/wlmaker-dependencies.git wlmaker-0.8/.git/modules/dependencies/logs/refs/heads/0000755000175100017510000000000015203543560022362 5ustar runnerrunnerwlmaker-0.8/.git/modules/dependencies/logs/refs/heads/main0000644000175100017510000000036615203543560023236 0ustar runnerrunner0000000000000000000000000000000000000000 971f717810690e3fa0208dba2891439734d6be77 runner 1779353456 +0000 clone: from https://github.com/phkaeser/wlmaker-dependencies.git wlmaker-0.8/.git/modules/dependencies/logs/HEAD0000644000175100017510000000076215203543563020773 0ustar runnerrunner0000000000000000000000000000000000000000 971f717810690e3fa0208dba2891439734d6be77 runner 1779353456 +0000 clone: from https://github.com/phkaeser/wlmaker-dependencies.git 971f717810690e3fa0208dba2891439734d6be77 c59371abcad0d5e954b9627d0de1de43d35dce66 runner 1779353459 +0000 checkout: moving from main to c59371abcad0d5e954b9627d0de1de43d35dce66 wlmaker-0.8/.git/modules/dependencies/HEAD0000644000175100017510000000005115203543563020016 0ustar runnerrunnerc59371abcad0d5e954b9627d0de1de43d35dce66 wlmaker-0.8/.git/modules/examples/0000755000175100017510000000000015203543560016563 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/0000755000175100017510000000000015203543566021575 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/0000755000175100017510000000000015203543561022527 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/tags/0000755000175100017510000000000015203543560023464 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/remotes/0000755000175100017510000000000015203543561024205 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/remotes/origin/0000755000175100017510000000000015203543564025477 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/remotes/origin/HEAD0000644000175100017510000000004015203543561026112 0ustar runnerrunnerref: refs/remotes/origin/master wlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/heads/0000755000175100017510000000000015203543561023613 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/refs/heads/master0000644000175100017510000000005115203543561025025 0ustar runnerrunner1473d9a1bd005d06a7ab315b952a2d64dbfc76b8 wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/0000755000175100017510000000000015203543565023225 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/e2/0000755000175100017510000000000015203543565023533 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/e2/59a1fba6e1f73de9efe26efa9117f568d6100f0000444000175100017510000003540615203543565031132 0ustar runnerrunnerxr\וxSB)!!Qx<)2"!k\4H_•sbe}ddg^9O_o>p6M'_=|NN{=WWbz2wox]f6:XI}y珿u6/FI|ۼhzp6bz>ǣ^pֽYwߍp˟>' &~2]Mg? .yl<.3ǫx4x؝rsx20&LMgd,#`箻|hf%`>dR'ʩr6aiOgXgZj0Wv<7by<_3# mDz ;Y^?t߽~çϞv{o{a7?xˣ?uw/˧ggot^~|/c_:^~{xG:->cg|8|qx'G/5WGOq߽zm{y߱4?v_ݛo^i̓9km{?>oo^x-3_>yqpm^1TVy{rtˣ#>*8|aw {=ecF 13I7k{ g/{{*_}& G,`>ܫ xWlt%t./NfC abzhyjcd׉$bvP6W ﯦsx09 'H]5涑 fm.zn;CdC'g BƹG'Ę`Q)'yw-6cl: 0˓ ^-`\WKn$/27{b)MbϦc`D|# ' ], ٨El:!0hu9C Iot6m^ͼe;o|Rbm=ݷ_( hs9X\L'bBG6:E«jYF#9i}$;iX9-lQW-n$nVi:f3a}XpVя}p5v6K!3.f[\o,*/1FςY>ףw0+2 Uc E3WA-;.}C}AmC;\N|9,I=w_[/[%Qaɶ1;w0/~N.'?v#y b$r-t9t(p3TNvj;DMXtRj8ɞ~#׃J.Uv$z`d %ps CG]NF{&/WW{|``k ,{(Xlrx6C)?]ҮU$۟?8`h) O˙̉ A ;,L7+jye^VdXi1 {s[Q@feG\e:?FTʕQu3AKG\g.l@3R73[˖j.Ts׹Sk5!xp{mӥiTǖbY1^.䄈ߍ#C" ?Nq-,L\)⚜H@ 9V`5|!v0gѕ`fʶP| %bQ ZČ09m\'(Nz0`28 |_*tgJ͢Cexpt?sspߋFӥhȤ#@_s6ח?ОaSt 4*}_~BrvGLUYVR~',zX3NPP1Mxnv6;9U3%2gg\NW;r.m9Ӈ[#l]cy |ԋnbJIx22aqA,AvJpva $ lo. eFVFb8yT64I؀?vhrދ {#iÅ1]Xפī~dH: +q C%C)MܸC&i7VXL|.YfÈP ]߅bY]d'ShH8%:I; &鎻5!8zHղb}_z[^B9DuF0 !#hx~omztHB#1U~+7p{iDx_90<2G: cMNtR[q+8lwW_ y7I-B)%F.%%$[ (]֞| 谴%IlrKT%Pwl4͘@ʁ3z>yGۦZ&Bc*- Au} gVq?t^׾xxVe[^^W[bWo{Ö}kw]M__M|_BeNG/r,B$?>mB}Q,ձ!1ݝ&(X!W/8^=q.0'ALݝ-D|޹LՁU+Q̀g>~EiШvwJ@?pf=ĘIr_?.9D_-jhw|N-[Sڥl%>qN pgL{M@%Ov.e"ۻc9T|*;9;%%X`USҺN[Wu׭P͵U&ϨPMC㕅Ky;h88W*wW%GB2HBG?{hA}|bw,[[ҙl)s(Д;>Pl[iSC*.yfh8)#k9AQEJ=z [j.T8(cʲ>p,#6ܸH4?3 zvw $rqG$6hл;&(i <~T$}@3 OƔ?ȩ$}zD*`VGd,C@gs!E\qcq 8eȈ{s/>_~ڽۉ WLm3rN +2 *PuDU g T'_Ρ֌Nqx[hs4 d9X( >>P -Аxk&R$b0m4ЀTnL0lnf&N=Fp6 T\VxP|QvS>80{zeD跈B9&i6("h'c|3ڏm_<~zWwݽSƁJ^<,n=KEvhu8s0>~x#꒜u$Oė#BuVʑ[hO~n.%9kC6q*EZN U(b چ6e$ .(fEz$UkjU..NH.Ayqvn.ftqh0>zeV;6T(ߋisރKճЄWSx80WШ.a kىEgu$&,ی2_b2( 5mx_ob|xL)yRG]9NC3?BA'?"8&EAvfԶ|9rrEک|38,3h伉524Ae f桲UZHd#JechX9pw*bqI(k$p:2Elg"R`/L&"5}$%Tn$7CPa×bWf 0yj9#U .)M{ݸ8v:'mB'wP\-Dp/ ǎ,QY /!D >=R쨢k\ő]z 8# S7KG#{"S۽s:|,fVuq&KI\A$"kOըyK6b}ѧA2RWCڭDSLYvͰNZ2oIk qa -@b&t+̥aN)R>/+I|lu!XÐ*{nPūGOtyI뵊-"@ˠpN7*maM"[k [ZgjL! f7{T*\ܙ -)rGG8qGrJ1X*1Y^DBNj-΁I5e4F~@~X0p,G.ycz@26Y P em9}E"m܁i~(ذl\ DV[/Hs/ gJřr}d;C-Sd4LZɏ 0W |seWcBMEY "*Ϙ) , jT}B8ɘLI7tvvL[Rwrg ;J\ljk\ҞF7Is k>^Z幟o0Y1+/Q:[2?'^㎟M re)CH6yT- G]K2yP>rV3 +$G0Iujb^ m ̅ZZHUZ.MȞyYLB fBc? Iޖxxa;ɮ*j"LQDqS&p#z2˘W77moۢ,8nI + 4(>cA%adbfIiRs1e$RGM+t3"b\B:; UeM:/"FTDZV"Q\֐cJG!mP@ YVzOIJ;|IsGp*N} Yt@`(F]ʯ ς T'_SmXTa} Za` _y(h(REMNhi%jM;!0o0|]m{c1@q]^I( U(Uv)>3 mi9)K#XLIZa晒&O4}Q#ՠ-"裐Q‚ Cy@N.F+70ub:a†$0;E4}>I2ʱ3A*0˔? ӸUlRL>{uJzmH(4X ٙ/`[yb]IT#%@|shw՗r9qY' &.5B3e/F 5Hu6 w4UsV _ikFy"M(u{+0q#uc<@*1O>r=i-a 1mRn201^? ;oS keu=0+:'V%lbEv>QД;r($1Oa3Q 6 US&msϿby>JEoxe~?E1;#<`s +C)"Jj!Λ6{wѵ^?T-͒t6ΤӿAe{\y',B`ɏ:7仁Qs5Ǝ' `x˚[dmVk*>NsLa&6&9DlC}yVMAYLeZb+*m_|-ٴ۲v^-QW _Yh iZS_\]?m٤x'ҩdc;x])_Z Er!' `@*bb+d$F˴ЗJ-n]x8`3p%J>6'[>-Ioz`zt Jm H{Mwq.JQR4lb( -MKi-)RoS^.ViQ!B>l\igOYgqf!-V$\}rй$܅2u"ڷ#78a%p*6,Žjho.IGn9sԝy%{jIoV*)b0r}xs@ |N+,˽O:~C$l]ZkE/T^u-O D}7KJS`+;/9qKʊ~_lx}A~@ \JWB./g; {Σ7\ e0An he<`a{N'Z'գ $:h#d8E\nox8VXCwL!GIs&)e%f9jHa1{QڨWTI_rAU2:,x!hk?J =1k ^VJP3_u%k0}^a D|k*xȹO^^p$>O=a pqyT9q@=}J0yxjs;o|wKϱokˁwIěPވ%<-BO DT& sk#*;qa.dv(.k@fٹKe,a{; `Ҝ$8o_Yu`wd,j%#ѵh5@}Cx0ߐě|9hfS >2@e}a_%> iwʾ:*' TesB w'GHlDŽaw =wI} Cˉq8˚bN;"}!C`)/0&$E"U"{tsx`h%ZtCa> pg˓^2OWsWi[s|/ZMW ״ Q7-7HSia7a)IA!pL]٫8T%WӨ^W0䉴Xh>.|87ub_R\ 0WRJJ_?ѷڴކl>dNHH;2aRw>;R|>V/[:Y}3/uQa!+wg|5_[2o|>rXuuociO!%-hM'2u%uԑSy|^`VC<ݑCSzs #{{f_ӄ*%8M,2FC?+yd-}_CG2PeX3RRuDzJ3MO Cj-Mqæ+ZDHsd2┡*xhʒe{ކ ŎymjO]# Ld6b~ ZX ;x;uT]m J١w| ᚮ`gIEIZȎz+|enSrN.,iۤp $`>u *[09*L4\_"Q*X/Qf/fh0^P|hj\!ni:-*: :P!ı$~!GFɱ5eyu͒HjU3']-iuȨТ"6k2:Qnݻ,7R9 Gk9 qѕM*%摈N8Bנj*:) GfD$m"t#EM3bMW[_/ɍ/{OV)(?ZUEoOZ:)Kw4 ;2Kz7rZQWPԱ?O:[],8nTB> CfzF<-S(_!LUWvw?k X9aJsLRnjs[qYɵ7pQ2qm"v:s`UB˅ؤiW$]QNXޮv)ۍN;LM˪Zmڑ`Qȸ|#9l$9yB0=% y&[\:j4w=I b|\MhcyԈ3 !q=<f<4dF'F 1v6S]NF#1AD#nv6Lpg79VpoH_# a=ZK(~C#|Wfv1භ7u~)8MEly9v<^i>~`e`(f(Kӣ@vݠnAOT𞨢yiQ:.g{A{3b*yޭBobn({W^Rd ڸ2_b8Ggwx <})X\(.m3|֊&Y0k0p\Sj9)k \9һlA~L1rFJF#5gsbjW' Զ{*` 5ۺi-VR%ko*{&㫯MͮewoQU|SkՐ 98ɦb>)/=BD'8eL' -SWLPVG+o_l[YwaAߙ *=Q$Beul.'aI.$g,)[2gF;J:=}MKΊo*q gFIjchU3( 3olwm+yޕI6W[tv }ىH!F3 RtѰp lAVczћWJ*p+fhdץ@MsEex(|Wi68`߬jħ&83.Gd׷Y't<m!Ϝ/> KPWU\-k`Z1xcgS/7LoCzp0u*fFbBۗ=)מۨ-۠a `dI\A&֘lf?^(ũb #P9Υ( V:K#`kRjw̍7nnjeeGG9h-o[i[]A DA/uO$U7nvv֧F Gd&& ,4,ҡqXJQ"y"}ҕ7fړ1rwءWD7n/-$=Ѳ(֖`@vT^#цs([r*Vh3Y ,vP&SZYա=h:ˍDDSVm-<'{CZX#{Ђ(9X ݓevrb[Z U;ޝcoʪPb*;uhcûH LD:I#f13^Ju襍ka - ]V%>)t&Ե+ х*fD3Os\K:}!\ulSX,͇OܑjL0$%8hVo;WmKRrLJ Y ?(j`zn,%r00*| jPq׊xrNt.Tĉy`p74.;iXB^@C UzEQi[wĠ{azQ]Xʧbev=\jtȿNIآj+G ,HJx|Y#&87I&I`vP&@OW9l̆dU(Egzu0{^,3Cj$w>\3&.2Ic;c]XMcV'`(Dm42iS)0Auw"ݯyUdglFMکR[Y&T>Z WF"qDŽQ{IJu hm Yl>bV/y!rB1TZH@7WܺQ6B6po 8Ĺme :G:>>b}>AJ@:־!\ V{m$״l7_& n^S"MSɟȚ2b=S@q3O mx|{p:p t8$",#40r}>!|m,FMmFU@Mf w +UyG,~̯˳wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/d1/0000755000175100017510000000000015203543565023531 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/d1/669286f955a7a432d5582b768df36daeca4fc20000444000175100017510000000472015203543565030713 0ustar runnerrunnerxYkoJd " ٮV=D IC 'xkl69s0 UUƞ~y4Jzs,ŒDY c"D@8)=8ㇺjENo/^8Y-BA8&y2̲0YBby$h, /& yV"Ls/xNPŔ9ۗ%|B/˲=ȣ K嬏Ϩ/5YC* QCZ&:Tdy, "?ZlYe50?c<`;Lp j=lѤ duʌ_6ُ$LDKaNҰu"Yzi  x!-߰$ 'qٿt&XɳI{ee%*z)[xp`QrĘ_z~T*mxx;hYg&}t&ç bL>:3iR7pDc3n`8ɐ&PE9=CoԽ{L>7֙ X-v38ݧ~gDO7;p#h=6>~_<;| c/SռK5l \F,UZ*YWnp^ RE\7WȒJ2yHO+?k[7)`)7 3DwQb&8k˫gvA+Ɂ~*@蓼ϬJV#alnqky}WeE(Ѿ 8^!j~:~zqҾۉmx|!P=beۛYMX*\ԄE 5뚼l#ŰlG$vf0ȑgУ,kG輼U:Y1,=bW";yC: crDoTi|~k[za-`,QЏ{)"|_qߤEQ֦I"ZpsJadl ZBFH gk%8AJ,J$dGe3[$9^t'jvU⼉”**(ŲS#|aI 3<<zYohA̓κȇRu>:6$; !eÚl)xD DRO\A#DUkKo.%p,Hs!ϣVPO \-Ɛ-Eo]vn:4~;l[)[=0߄?ym8?kG~/{ZV ?n`$a[dknˆf_ձsIJ2׭ ^躡mm&(}CZ/eh$xKhǂ@'C}+V]:SEL|Q΂ozh?/M5B\nE< XY'i BHsmۛHׄ"{V,Q3Tue曈cD\#97"_W#F11111]5]5]5]5]5 g#]` Tv1`R#:1T:Ua\uUa\u]Ǯz #accV=9accV=9Uul` 'xg83= N IjGDԛ;?j~Cvႆ94RuoPt}Mh-'C*M뵗gYe? 9~":,:!gAI4&A6G*G`BS+ G!ex'Kd2G.HmQM1g ,at#bz' na ĄM"><~qu$ s ׾Kˆp#`!/9 \R/<c`)F[ 8'`@|AW?19 z8ΘNqdcc}f΂*nCZ7n8SK$ OE #|.>Kuȇpt-k݋Vzw%< /R#QN#Nt(Μğ#F\TcFz#1j䋋=9Ci\g棌bZ8͂n֪ HSSq,0ƥ?pPʪbVs nfl*<ܷO ?Dyxݺiwݛ~g[}e2>b#?DnhC7y>t2am0Ŕ^p+鋳pQ?Dᵝ؁qz z.OUO7jUÉլ5Pĉwdvkeuo?3;Q\gl !|q\H-Da1p@8 6f VnL& g)i`?azHxR3:vqArmxZmngDlA9+n=`6_c8`M1d:`ө0m'\;&(Ph97&ı i1? 8DvF TEN"r#3Q)񲔉W81 ?\ҕo%Hkvxm~saWx ٧ȼ2CʲzK \F R<ӭ;AbH~&,f4KC45GfYZL a65̦0fSljM a4̖0[fKli- a4̖`nhjA2 (@2-J(qТQ6 hQ@Z6ТѬCYU06fUuFìYW5 1l!T5m4ưPXU5֬74 M sCSBCД074%4rF< Yg J W2Ybh6R<MJ VDi'57J2X(Z'vK/nĎ0 +JpkODaZ]1ꐨB3^!U*k˼\ᏏD'{L&QV EQxn9v~Rjry.aLHUF/#h'rp[-8;˷Ɇn){[Y4Y٪ߛ j()3J.}~(-vݢ21e4',WBb4-ʤj,cdػg|Q]Wo()ϝFYG?%̷Hk!ID(S ^nA_+xr_&;t JDYNʤid @%?^n#FsghOAe([HTuݱ4\Pb3mVɷp$MҖVRDžT)$qD*vԬ-2.C0OQ2zQ­LߤYFlB6ϭTyta>\պ.KҮ${Ϻ#[v@̯xZpnԼB,5eҺmR^ -'WH#?a6?(uaK$jx?2 @-6wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/74/0000755000175100017510000000000015203543565023457 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/74/9e4a1f2066e29cbd87efc97ff73bb6f84e3feb0000444000175100017510000000204615203543565031226 0ustar runnerrunnerxUn6yr!85](6%O( 胲8Ȣ!RKbogH;vn+ qGy#sxߝV բP.QV);Dn;5\_C">ePJ ռ# SJsu֭ ZB>–w d3ъv He2ѧd2T%dJBd, ϴ380H%)yրh=؃е5t\NŤKs#6b`ʭe .ld)*˭m7B.@Leq&;Pi @Q1,߷>r ))XSJleDJ6|0 ٖUW1n fJzmhT7cMZsq%}u$bF |b4E)Kp=paE,Z2 =D>x=Bé EL|P1N唆pua EpE 0'd (w,4>zbF'aQB~! YȜlr Hf^X*oco-cz;c0) AeM@vThjxtԛ{F]  m^jFl<|&F1BE1;hB\bqXazPLD04lZ  H6#=;9 IX:(Lg'C bnOJB_u2f[6͔2`):],YStv2fN#1/H9s*m2x<"û΅igԧISܿU}[pw^ŷI(s1z'}hpo08VxIBb8oG鈅2TQ^G(R򫓹msV'58h5-lGgNS>ku\?[>r%,~xlo)}[N/ ^Ff wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/08/0000755000175100017510000000000015203543564023453 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/08/5d96ea059c541a127838325d87bc1eee8523560000444000175100017510000004602415203543564030407 0ustar runnerrunnerx}kFܿ IW 3v$HD\UkY{A7/IX32"%+s,։sEyՃٯ~~o{a䧟%/z<߽ y*z_<'ge\UKˬ.ʥZYENΊ['Xdy"_,\]WEM>N^-|VeՃ?ֳXf<%.**U$lTXUqUu-_U 8n>,yyRٖ揗?k52_UH~ٜ.Yb/yo>TMBWyV39ٶeuUR~_U},2U]]QSC7vڹr.ʕڲ cX$yYgBw?O%yw=U ͯ\- jWٲV7?x*W~k6W~zmog/޼{__?{_~~Qv܀8wn?y^gb/z\̓*W_,/TY2ST{_fД-yj5Gvwx=MdYU۝j9{00^ Ua0/EYV*u|,I/>L~}¯8SY?~zՏN?yPKuЎ/f'_r⋃z7ꇲIr}_}vPӃJ=_=Og\?::z8rïxҫ(}|]PD#=*{]de66:R  X@/R}{urG#Y.os (utT-Y_w^?9}a~]eڃ|8>Jֳ2z"?k1Gl8gJ ONZw_r^Tu7?v`2Uÿ;\\f?v #vc}8_f\`u4VeTxX1m֖lVWhIcPyjto-ϕOWj=ܛCʮ ޚS:/N۱l>knHmKkɺuY^i]6ݖ96=Az)|[ovwѹ6-%~ELUV-Jշu|+nļ+=,g/y[9*CLacgg*䔗ʕWEYYf˦+6lkv-]] zy٢TqDjR'It,oEX &h; N>UO8O6ہA['&&?Ew bL'?ŊO95ȶ@ͧi>tƾ #l ŻTjfgjLËⴉXisowʾ\v,47M|[7`,4B(z.2'm7SVy~Psmj'\ktG_Ot#ihy鼯:koN.Y۬{N?:窻rF8=4pwg+w1ygԦ9鲃NW@$^ꯤvޕ>ɔ~w"I(tg|wWy3~3~3۳nh|PAIo3ڝxnt'NvU^ݍg'O,:IO&:ޤ&Nݳ63Nn|"lqBL&L360N3̬eG1~4|RD=h  ϊ3ſ)eA}ɣovfc'll$Tc-I9QN 6ͳG>p ғl:k#N{F;,wZ ߗUcj-,'rq}|7%+JU[ʫub~~v7Z'ǒ&_6_,GnU5:l^RWoPߣziR.:Hj$?VoC ̪R/GҏE=H})z GG 㻖|rh}rd}rl}rb}oO>ɡUUUUUUUUUUUUUUUUUUUUQuOP[>1Q8G}b{C-c 7'P{C}b썓Q>j6:UyԩeͣN-clu'cL}bUhcjBSXǘZƪ<2^5V֑pl|l V֑pl|l ǻʳ;iMy~mu/P@?fruz*mV+|'tkZFQUS8lMck0}'yljO< o`#Bm*7줧4.{+}*eݻV;ŔO ,!]սȖKaY 뭽wMyْ޽6|7c /%9>AYEsYFpY1<jĦ<#HᑨG H~` |\fa8{󑜧yUս/^-kwT.]1*+z"ZCݻhIF4cX@QXZnm, ,p0X@L1X@:m, a,(6F A _ D$RecABiyrY€TfԶ|זlLSjGTj;4T;uI c=@e*3;PɀJTb~J2Y 1@Mo^\}J.> >)P>"^,CB^,Eb]*Gѥ],IŢ^.Kx„OdRcR`F@Fŗwd{0L#tn~s.|A' :7?cйR3HƆMm95SB?#;H#>#>×>|># ^`5) *]a'2Ƞ*; GL??G(RXK#b9cC*mPHe 1B!?F8R\#K$nrYĘcӉ)Ἄbfs}R?HT1U3D|#Q;H=E3D<$Q@6)e{+635gKKO+-*wE ZGT$XD#T4K"T$!X X M?1]E7@HA9%X7>ďe';?pɆN4rvᐳ \8aH*,z~B!۞74v7x}/-E P;oc nP m l kDGA gA҈FwA@O!wÃA#wØA%A/\zfCHTxDLHyDJ8yCJ8ĔzDLHT{EL=,{PbYEHg8Ty^]W@m8_գi.Gb+Mv+9:uF댂; 3 h5][g:P0:(tt`Q@1:&u5§0n?&`A57r~_ # sb܄y1n¼wuu5a>^0/M#'KwyptkrEE[PtoAD٣[Ht?nAѽ6SC,s5&#WVԖH_+Koe b|lOČAxAH bZ 5J1oڬtY奾a6˜ jOxQX{QX%{QT/E%{QT_{QXǥ{:.]q8+ j`kQPdӆEr|"\_IdxJd*o%s,sdK!XJf!hSMcӽrnt&[04?&: Mwalc3<5nGs14,API#} e=#"ۃJ.J\4ޣ ƻuRo`%8xV%z-,kT=REM$߲[|nA "ێ[|nAͷ)mLՆt}s\yutW0۽(qP 3Fs0|0zt;Z4ÿ9nxŐu;NM,Ue;k V|T酤N/$wz! I^D"};tdN?&w0C12|pEA-:'~ 2k;Dp|g4wFs|g4G3;93;I3^yQ,,r^~|wPaS֬z qt-2 x`Ia clJYDL*IS~`F4`O,*N~Yq%Q? $8Mg&N4ib*e6^<{Pu7׊β*sN1 .hiΊݪ8k !!G!k! ! m!#C9QkC  ! i !CEAk5 BM@(nI=[o 97,GM"8?p" LFŒ0d`σ0 gF&#aHORfXs7`$VCs瞚EytƦ06٧N= Mah[czt> NXki˷{`4ҹ{ RN3"=1/Jp'/Hi %8{V`8| ZېfgB= a% 4&?MAh"~lt 4݇~ .8oEw hwף,{@׻#8 IlNgsy6egS{y6%ɳ9 >Iq|Rқvܾʣ<;[uAcH\-ҏ"[UU\j9cI\fƦ06ݸ=MahwC{tfw8E=c0i׳FvOAB{FD|;X ]Xi /J'/AKUG(V17*H|"sQbn>Uъy,-x|] EVaT.z[77NDnM$6"eM$6ׂ\EmƔaҫP֖^ynH.HpD \b1]0޹ ,Hp\D A.T^Vb5qz1[Nrg% `uuB *mBoaǥۧq\ǥ[q\%ۮqX \Aeص0ݼy rwRXSP #g'A1 *>7NdVƲ!l_` x? 2e<.9toLwoEzjΉ'ce{i/8k/‹l/n/xp/8r>]LJ+\ >0Aq>f:x7ϳ,jgQr X̌&93?P &Ի&4Xln y%bI‰.$b9‰.#R) ."b‰.!r / LxSOo|Ы ,c#;h8f~r2s@%f'*1;/Pi Tf@5aRBN Xlap p|lt llpdC 6:]fsdLw,k J*# k3e&2/3yidZ^ rȼ @&:1Sm ,O;O@cMm]xI2`B&!RQ0 Fd(L~J2YADa0&\ac4#]u^^@E;=HMFQF>1FSLELvf b3SrT$&930ٙ)M%\zf ӛ7ue^WzGWOWu5 ]27,4( O_狳_K0 qP()@舧8N wP z3'a>( ߄)@P)`!q CC('>E2[h5}͎vTzP1] 4%PI$6HZM!qɵA2Tk K ^Ǔ8%*6!P9mp Undt$rXm oͲӴX6zCD^[p׽as}o]WYnW\-g׋l9Oӣ4an@BIbQFab `Bb`B}b c  3 9K/$K $F/&PG &B/&C(P>?(> (:(]pSC1zM&8g;90,DMI[$2 n`bX¤60yM`&OFH' $e aB,&hr]y.O8x qt?g8B$ ruT+Բ*SnpC8q}h\8Dc;! c{]S JʖK[ՠ=__tƦ06ᅡ. Mvvah c]t'7 Nɐ;-뺼3r:c*(,/U*i,D٦Bd: k,@d{< m,Dӳ!fB҅,uc 5|₢Es}]mw2  t7e {B,b K@wXW20#t yQ꾕Ӳ\}5`ޘ< j1@JGmM@{4zGkM]~q?&]AtCGrܲ7Iq\wwb]Ÿ _ IAƼ)\e۸(_))n'haPHV!|C$~ H7>D!2ltUÙB=L,+`~|PE\1@֑ @ɑ n8 F;Hd H 8N<]q2mc.bޭ>܂,-z+3-\!i yN WyZ2ӂqp+Km`!w Iށ$J)m%{U Ǧcٸ5n۸^6kؘ7wȘ7ظ8#^MG ^ܽK>  =a"VH@9]l=xd#`z>Dɵ:sqLpUA͊ e1"0OQOQ4 >ރFǻ(xό'8(|# G,]gQ~1Ӭn1y_gw`e\+%-XGiJ U]Z2ӂqߴqe\'ĥkbx$L( m{TbVn$ӗFmӣFq1jϻFQ|lFQ1mFqq}oc:;f5|͸ 4:;+1!dG!mrCd&029L7!dwB4]EqI=i\'bFig9:?u" ޚHpLFf2ƻm&af;o&!3 .H\J#S;ߗgWʖ,kXl"Dۭ1N ,@c> ,@FZ?H14҂2Er];i@ ~D] T'zA$< "~OwX' Х WY/tβYˢմE/r͢\'o9aSlA}md_Tж]f$x/u0|>g[|G_S;o>pCIwL8$=Iw H# 4߱_!摄'?}|9/1WyZ71Տx-98Z[+㻋cSڽn+$6\t@oAЁBAz vh)AcABb} mVl`WAЁvBMA  h&$>RnrvѼcCm**k^Cn6@lRbZ90ќbsL,'؜^ˉ46qڍM9:cS[O%ؔK>6 rbZithRDrQ,) Ho'},Z+ ?4آL>&~uǺ#kKHۧ|T>=*/~6\WvS?45 47۴(H!-q WyB Wax≃1;a@Xg^<{kfy 1A}5ȳϼyDQrmdزٱBd( ,Dn( i,Dvx1ņdITQ2A4(ڭLX2Н<α dǍ% h,c n@wX+ESН&.A=^m> ؕ*j -H ҂{H m"-D6BH o$-H'ic$ĤZ8jh&oMvu;yO'J#4Q9phDSН%- w`K4aICBTX.b1wsڠbk p-۽䰯#H1XWqZ$I3-`^$5 $ǥ0D IX^G`"}=El1ľL"Fb]}hLd,{PUY>ei6;-E-WNlW=Ak}7p^#ZcsD+9u#7e9^kHr`~4mw[_lLᴬfKfJdWl8~} CG1@c@ƀ>}3g CGx^{u^MIvkO̲uG;sD&8N!}!r*՝byў(;U (|S>䁲yUUVؙ pcw٫'˲Nv9)I}Q %ү`Qbx,JYoE (Ϳ 7tXfK7A_?]q8q.7eF;ƕZ:eRؾ7xT<\5ﯟ6sql'^>ʺ8C=}-N[@ οz $6ǾZ^eb\պPs+qWn9l.75%>_2W;=o.y$|.82Zs B)S48֓(hp~1mbY D 3Ad=B zR &[# c부gǰc0\6qCGp*q47Si23(JQAT* wweH#(#(ÂA2 "8U@q[DgBpCz 4Tpu}J|Խ4Э)pFtWhl hwk hoj hgi hch h_g h[f hSe Kd~d. ez<Ëz"~_/@},0oc |SP1 RPo! RQ1J0A_c93R;qW ~FtYå~KbDaLce4ubTNcFK=VK1,D`!d"K| "I|! f,BQ)sGIpF68eMJ*`+T#chc h'b ;ahOGG %7/#|w4o%dY xhq =64]!#FtxV '';s'3CsT ی񌜴Dcd$'!#EFIW4FNҢ1R)]$frrFeʝەIuuڨ?ZP|D_Cl'[\6}9PU u9PO[P~r ,@rJ+@adiQr0k*UjJȖ@2`gιIOqLHKqDGHGqDTICqLII?h*ʇSO1_T<-3'7qg/Vy+YhXl"b "/@:b3HM^lŖHP~p45$GfNwjq jXI[g#Ik pf央!v +'YiNHftRFJr謬$Ǡ:-xB=pi(NNmֿlg-oDW൷Qik hj hi hh hg hf he dhGŻtz[U"ɁnM+\ B Bֿ70*(\}3UW .0 .9 !+CZPC?2],qYŌ r5v\siE qUʵ"#5_|Rcg4]|:crX|,#$0*![x׼Xu^?Ot*bN 23iLش4Q43U32W21GW#g:O?biGor><N̈́;$q|:^ʈO{)#> BJK:(z?Ҷt!MM~gLrpCS %o3klɵ1766o54?4G#CfkwI`m#5 lbN,62]IAy?)[`ѯEqGDRt ;Y-@fX:5;ҩiOtnT 'Q9SŤϺ\-|=̔?/ɰڰj#$Ageh:ܪ+J#eP:DSh_ƞb^2:%^KGIԁ ?E@ qYEuE0Ft&Ѿ:h;cN~BiDa?XǒS*H4˄S*L< T2LD%XLH]͊P[)~&ٴ; dQSJOHEʣD.0ְ$jbKP[2آM&lEs'@lrlg;nC5&NY7{F .=%xZLO+&ierd`mOVKL`w\;N3p3oK+2 OfKEȄf@A)B&63 *3 -C(>ˠ7ǨݞH=?ܯXR탒KLe:dr_ǾR_4";E_6;d@8d3選%4ܯ|/72-D+O~ps^&[/tB/nR^P@k,Yt\[HI{Y/54432Gc##b\KQe?>cZ\z-*N7~nյgLxp0Uz qi! |PsDO?{xx Mk <*ɅӪA2R m;:dBn a;ITKzuc}K4Av[eǭO2H`BFA"+Є˥7 7۷g̬=mV~mW T|w+ 4yH~{U6  G€rQG0"H6"]p2"3DpKuvjh!vEP , ^/ )# 5K@WYL&)wtҮD'q꾷kk Q;Q;Z8w@<ErDܱEw܁S9]Я'-㤅:y_&[ӲrZH`p-;0>3w xc1+gjj/NJ b#dj(['BJHX$OH!E$ܝHT\  ܉ =Ȍ*#ҌsvqƢEe?W~w8duSi"c&2gQi"=&2iQi"&SiVG/,\e -Lo΄SW-E|RNԸ?nNYk{L٫T>|űW$7*`gֺW\w _}򻕼W'.>G9QY|p[s-F@j ܨ~KU&kD2W! P?4Ȗy y쵧 !zc#&qr~Z<[V Gc` wv p` ) ps` ) p`Q=pTE L[lv܀pUZlGI\6.(dtIMut-CYUISI\fC.iנGɺ6: }GZG _6@/6"v9 ́\lDbs Z;*jh Be4 Se"SMݾel$ o)~_M,ya $G~-n!(GIWj.duVe:WluL@L9Y/EWn,OJnR*ĨK*ŨIL*Ĩ¹L*Ĩ)M<5dQpE792 ZIwlMbߜI9b{a>PWmCL(l7"7UG^د4}@L/86x <`AY38^YNw pg<2x81ޤ ]KNmKo XDʓnN~NoF8sIlU/ig MQ}STەUV{JZZ&.( (Q3,gDn=#ʬxF3fTD&]#wHYv,I I8I I8 "Hh9m;grϖ`?LvJov@0v٤c]FjR.X'/O dusg,:M:KW?=}_^O_޼g=wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ce/0000755000175100017510000000000015203543564023613 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ce/386f49aede773484c42f08d9fec29905cb71560000444000175100017510000000030015203543564030713 0ustar runnerrunnerx+)JMU020d01|֛30yhx$+̄⹋rJ LLrSJ3sR$~Ws2Ϳ`[ePE?ρ*+-IMHz ^6{]ؒn/#aW[Pɠ%nb|=fD⼲ĜDļĜTOֆ/3; ϿVPwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/ce/5a5c0137b37823ccbca0455ea340f0be55ac270000444000175100017510000000260315203543564030771 0ustar runnerrunnerxWaoHHQܝ Vl4,؋"$&@U: ay2KՌ>dA"+e.h!SAxL\dHh%"vc.>]|@mVRЍJS&"_ˢ*c쑖y)A\R |) *#mD^@Xf2[RLs%+Ԣ|0Be M ,ֵݡ/r1၂8ڕ9{_{[߾4򜁅+ +Ǫ\!ccؼfv>y i5ħڞa=7h J?ܙ؁e 2=`\s ֬wlw\ q&sJ?v:օAey>E>katP(0W2,;RﺼlkbM $5VjY1ԙ{#Zϩ੷gwhd)J84m4(KZ3Ijk6JD,1RX3TCk+ڐM_x~AʼaC@kQƦéo y"ODX6~f*P\Ei<)nn!1Kt" \{ dž.{8٧x@ !$u02ڝekRXa\Lte#xyp˰$l<-)!衖 k!47m)`dؠ!#0pM*ZXhNI|8*}ˁ^OPg l=EH%ng޽;ӫ^C6olh6\XXhkk~}{6>N18GWNKꜥOubmO7>N鉮!7gb44I7.`>Q9R==U\KE|cMwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a0/0000755000175100017510000000000015203543565023525 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/a0/d18c70126d024a13cce7f0bc401f6b9f0123b20000444000175100017510000000527715203543565030630 0ustar runnerrunnerxZoXgRIJ;3ݎDx@Τd|&܇_dUyϽFɔ~埿CNf$O(ٰj$C7Uwo<qW\zAJ\?ei50 *Yp*I%ӦF+& 7ȞxݷtԛKws5uF %ؚLh4&f`[xg}{xI_794 CMʶwA {_|҅ 7vAoL7>\kk蜂+ޑ'hr $-KG7CWA/$}X:.{׽KnL#<`Z+K~{4d5GCg.;=6xlN`͆76@`Po kҳV:0hߟěDGo!-UZNu:f?W5, Q|$ k\[5^vk o[T2tЧu9( _.;%:ꠟlȝ$^}]d(ȸrlE*~<ϓe)8Jdw穷D84^6 +\u J+~@n@: j*epJa~y|]zg#⑏Hu~p]JNuiF,V;g?o-Z( ]hoO9jWQJbL[),c\4ɋ:pqEVDem`DkBofsGftd%j[kTFn_hd)<5RQ^f^zd4e e"AoMjU)T6X}ޙSY]Pֶ:WywX>+)% >c8v$*R5$T`'eJjdk[ AyVR3t翵ʻNP;/cT[zT( "[ Y7VVEP~Rb, jDT['(ѮkjP()4J;VNW>Yk em8϶Y:C@SrnmԖ l14j G,v֠ 8Ƒ~"b(fbnjxB8ȧlJC?zцE#E3Gz#*fIBQL| 8'z9#/ Pafddd\QhmLPXzp OG~[Yޖf Ŗک~Bl0eXhO&ضf0 7π; .ׇ`<)) wx=z֪v4= qMj4T5i&U\faOU{oLGRH<,i!wYW?#eҝn.>O>O)^#VE±2i:GcWw (=ܼxZO/oztFh>}Hy.1Q0k O9ŞTa>hh. ΙA?gϤ:2KD:' ޮx/Twlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/3a/0000755000175100017510000000000015203543565023530 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/3a/7d60832c5a8e75b8377c9538289e080d841b970000444000175100017510000000101715203543565030341 0ustar runnerrunnerx+)JMU072`040031QH,Kf!?$U@$g%&uL ufLfSSr > U5Z_ؼ] ކH/-(,MLNN-.bOP.8ggTބCqS 2}(iER\X45Dիmx+_aGV b~5Idd=<̜3ufZ1ZWy|܂TԢ >G<YIPPir|.8!cŢ$E 9!&!"*?[}~x?Eh!@`PYu 4cѮrՌ߾?G9%=~4,GKBOϧ)*%T=M]4jeY>+OIJjqȣYH_k U*tRim*vaO~bب,ٶ<&O>UrwI>}\ʒB:j woc dկ/os|9/ZJz{1rT˱ M<g:Q;m6^+wKwCrv^8;VyP@ѦLhޤ0sF|rYt _98hC}Gkn@yYNrh'np wgq9cY(L 鹲j5Aa:F/ \[lTaÚun Ϳỷqo7mX;oLuR;{aSEϮ`}*6[c̽ dy3CZ3KΦRF;y횾G)]pFI6ʫ)( 7]|@2w˷;'e^G+&O$p/yt ;6:x帜M#^i}~wB?72P s&8C߁[@Z2V[_a4-x340031QKM,/JKLgh4E]D(gd&1yۮX9^?7T_ʠO&{l?>O+֏rpswwMa8f~OH?M$3?T3ME> U)qh=NbkAQ~I~r~wi`C2 KB+JIM,NMN-.-{q;޳5aߥϏB ,.Jf8Gb퍏ĎnslIjq Cv*dYgW3u !ɳx=1 0 EIDBiS,Tۊ)YJP JXF[6U87ד(;導~&x'x340031Q((ɉ/J-,M-./I-I,IManlכ>/XrˋTN' xEKn0 D>Ŭ(+6It%:o_ НHqX\h]-8t+ZE|4%]uGᤏdx}⇣=C)W3(sy׳;hiur&)+? (0vmA)fyvV /ƀI4=`ZdK)w51)p*rb56^hry ;[=,2x#l mdd7ف8vĻ"r16հX^\/.o+u|Ʀi-A,{ .;Wֿ2#$ pJvyRQ4r}/إ= b;x=n0DĥǜrZUI6Y!Np[3˪ TEi0"uqD2`_ /?YkK_dX E e` Q}ӕL)Ůi8-m(K\U-:2"}$=E{$=O:3pixUQo8 ~ { ۓ׭C;p(dvȒN#%'M{3ȏlՄ @ozыr_8 > #FmG+L6(ۭ/(&E?Uxn\ $ˡ4օ70)1;2yBr=U옰z1x &`7dO7W?\UsR6٥WojDbH%a\G@Kj`tD6u_GF?Y3.Y] Ah_"dMEmkg􂹮7^r t:_[U ."0 !0OYHPPBMo,Kq&"eAb9\Ke'mz`VˠHz5O6٘RbTP5<Vح]*Dz/ؼ9qH~n~zL\cs!^~)7@%WW{O"k,I7=c&$vv{\sM6FaYy,UoQ3/23t;N@}JNr@w1$n8{x~u Ei1NAafGCzn0h΀y=Ws"0m V*r.yɆQ; 5Z,byK?X?z /3^HXI?xoT*D=P=9d)cʄ{Z^lH2h7:8+:R[bIiϟey+)Bз _~roCCZr8>UzU)xK*IJ/-(,O *3R㹴l.ʂx>%鰿xYr8}W`j#Y7T5Nֳ;r($HR= 7R^*YэӧO/"Ug^`r-RH/z>|a6dfY_Ǭ3^B̔zca2D"iG) ũ2B2dٗŒ.i7V:[vIFY4=*J7+3Tim̈X S3)LK-wA6(`1dתLVjeXL$L "qfYzVh#9qb:XU]R2anATrflR9VUVp;f2+Pr_jǃF{9[13$l"td0mjO<\i&] 0e KČ4;;u BTh@` UD^*pfN)8?OʜbItew.}߄;lg[[jBeOm skyH[SVMȉQRa]aloռrYޠBB%7)GaBIJ Bk,M92+]5'FM/`Pp7aT^0⾬w@ǣn/8,qfث_IGMK6($PjuPHҹs:'8d"H"\JkTUٳG[\q[Q #pm X:):ŭO)1V7\XO2_A!ww{VeJ' ?=Tx;1|M: @'/*OSDH\r#Wb߰ q5Ղb_"b%Ui\a2WHp 0:Ǘ9(JPhT)*gBh8<4bOD]ͷ RTWELPU=P1 QT~Oe)$&hlULo]ߢxjO$~"V'o\e1ؚPȷYSNǾr6Ѣ/?|n>|Tƶ zxxY TVW_92壟}&? 41>:dLhLm]0TiLPH:@;{"jXԬ3u&?__mk7`:o['pqO rM?⻲45u [g2WA9p=@hXEyQB.%rˁ\ܷ~FݝWP>I=]mz~^AxV< $bi\0VrZyfΆ9/3nĸ0fTuY'C D0!3?hגDZ(,1ՐJNW<-)jT`"Q{a X$, 4nQ"hgn4 p^>WGyV̥44S?@c+pvjhhM9z憤KG&8jQdМGGk9x J]3$Fwooՙ9RB{OXl6HSs*z,pَ?B^Z1߭?Yno)*&Y:R.(2i ?̓;ߠB2 0W__bw(xNst{Dj,UXo,h^tj9p/An!ul*ZZ]i"KuP9\T5k^_yeJUCT;XKUC O ,a%=@:hȑ9ݯȕ.p?ۚ8N >]g^zLx}[sG{ h˞w G@d ErI@э i?ef]Ў}9=nveo?Z\Zy+gce7㦳gKq9kPUu.Յv/}nuݯΟ\iբ;lsjmV6/ڮ)},=cZc6fY\:׫~njydcQ=ڢU٬,g_SnmS'hskK^鎴nft)C\uY׍i=6p۸~+u[-^ۺwwM/[5wmXֺ=,g+1Z!)־Udm75ݟ:WrO}dhv$ AO|S?6kZh ۞^j6+),q`kmJ< >M{$0/))oKەDms[\Ye-J{0M0~{*^ *-=KGk/WgBe0O8_֮i@ۄ9nv 叟vH5vnuHɞDJ8Q>{< [[}.4lAgX / K=Qc3g2mm%aV0߀3,úy5fʟ5^ܪ*gmTNpkI .,wwCȚ"o'ξiR G`@7] .Ŭ\o1R,ʢe(R)/֠@"X$9\p"|FZmw%mD'mV1SW0Fa=.ko`aĸUA VB>QuFj+WF$Noi`+P%3#r @"l%[mĽabLO:z`T(:F^0vD#iޜِX׈r >pN"B I<OJHnJ^4AZ;J(ֱkNU/"9ِsJwoqIx6%{&z ].d%ԄM[]/f bՠFZ#$- Xg2 R;0 譬Sd0.uұ;AA1-#Xr.@SGncxșɲ765QI,e Й0hCjUowĜ%EZ$+ _$DO"l'RҚ+C+TQ4,j700d3uޯ:1z"{R9aR0!!!ZG? ]Ǫ]yى(=AR, Hj9ך˃J;,d@-JP,-AS04{N +b[-Txf-Xjvq;B˺k+7#.FsxQu0н2 FBd3QJy gȽB^eoj&DE ȏE S:~5D33w}AĆM+>MݪkV0:5"$F9=@b1Ī_]#J7B'r]/RK K##.Tҷmbz XDz-cyR'L8RNJ-czqz}aqs} u.*ّߟ<$:f")߯eVN޺N베+(gbRFCʰg3UB$D eFWۑLjNXWSohyF#9]y*NHN詉 _$5ljHeKrVE|MD%" {l ʳ~eMbDaBG"S0aRu,ʐ D%=/Y?0ph, (_ kd b)FNV5EwE+`*쩗,ϫ9 { Y+d৺'6O2tG@Dz`jTTk܆c(iO;"f 1t[< I8x񔉅;sH=) G@ai9=M 9xJSV f.*^ [jE+E˂>}Xٕe ѓ;$Yӹk2CO ""W:N,cCmh &7%y INUNő ?or[–)A),˱`{/ =Lլ#- RQr#&.VaSdJf`t ^L=*WXaV;$fU_f!ڶJJ3{1ƲmME͒Uŋ_"=7DP&PvTѠ>T[)1uϲs۹GBECrA[(BS_KWuYT&mq9i [K`u & _GExVuR{P(%1< bc&#(&J6`6RR%1c /o䥺"I +!4^pV䒱Y2؟C#Epv*v /Jx@爋" {XlB/F|9A3'pM&*ERYQ`,q(7Zge1}΀at3>*$Ycp.v]^09֌ٕ:v2#\@ >iQo.1: D@&G +M|yȖ(zMP!4.Jz m5bh3drRc6lT7eQ!ˀoo3 #4inj:E6r +$:fm_y']!5b K2ɜPIaHhv^ *!CMy6@$@PýeX:{!x<]֒d 3 5YD8mѷ>`XtZo ]!:$vǷ!I?d+nj@Ѓ}Uk c^z}꜒B =\4Z xHږ<]ߩ-vU?sdƧӂxJmDr? 9AfC@zJ2("(ug0Qws 3SEs5G!w4 en'a: mY(ʾ X+$GWXQB>H:qeZxhN28zvj TC:,1mHoC%rtiF*)0A,t\OW !)32MBI{+,|I~O.') \))vMˀ7 vY ~zf^_2Ȉc+ezpyPD"Kiua13]jSeeBeï$(Dlo}Iڼd?*wp{ƫ/ۛDptӚ9s?|oKz?xʴ$xRq ֓'U s$q3APVJ.9"pXYҥGAĿ,H0dUxd6 FED2BMJdp.Te"[5P}f_(69UDH񝚸lL%׾Rd6.&.g]J@-@z.0ϯځI#0J@&9p c!])n@z3{1|snC;T0p!b!84`T{7OB&W=[ƛ*~I !ْ<וٱ1 #3oI=Uh8.R{ Z eњ^ H}Bv5^x ք0O퓷2=M.jmc.RK֋go(V\N4|m~ ]vƫDW}dlK%`l %#,\v(8:0^I Aa>\[rTgH}XG̍-2+'!:?g駺췢H !6HGzS I1Wf=>-<E|MQ+ƇP4c%+UYpfH$%y}uzőAbqϟ(4W&, 9Z1mON&cEt(90P% "Lz[bL(bw4"6ll=U.*V>J lCچm5Nm-Zŋt=eUJ"%?Zl7?˂Hr[`m,$HA^T_ϜPkWbN-K}H~AenGDs&'oLD[5Rb-IL ,IP# wP'kqUjiL&%chr(DjG >NӃa3lR Q#bD~ORiNC f1SJcY/Q?LWXHs"]ezuZё\!=jFpYR֌͋A% ׫Uֲe&(R` Q+%'ryb ğ.:KƘ8=Siٸf_\LK`R'ⅫG% @6t)3*뾑P(`'c0h+t74II/'0CQ$) I埰wrJL"C"X1?5Xxk+B$_q"uq~n.)یWRkt ObPx7p32CnH/>U>)FP$Qj#nAɒmnd\NrMqs~P1x$d‡A,:K'v CIվF"roNDx]c $)ҰG8Kc|')w0r 2$ANDCs`oG*L^Q>5] ;VZ0JTIf[ҸSmj;ݹO^QqHyr.smm )NX$]qz*H I`2 CeNM^nݗ\/Ƭ]M]> Selyd& Q=qVRbgj'D M߱XKW(#""]Y&-QX0!A2n7G zaUI(*WC!OgeNX[c>~#婨KnqgBuZqVMݶBZ/T8y\<<ҙ/ز~ah~ĎjO qIH^A?`Bi3BZPjzZ/,eNހƵ^ @Z#ySn8s(5 ɉt$ fDb1LA-FzPƚ.; ȷROkhCV=s 1d@{-#XJaH9(A( '8Y#c18Fsr|vi|NKEź] 'NgV88CNPjX"1 sRú;NQ=ZwӺȝ(㼃 Epc!Hnp(tbXɏZޤ/@~ 9/n)bRjE}wo~6OB܅vSv[b O~[({Ԩ+Ldݽ;[<ȶ"q9YA`ydl]~u5oD)`Ǣ mY?vܞQ#ġK0m{m= a$ Qsl<4$Qx0UHzg]7|tpȝd"bU5xRǨJkxBŹTK})!Ὥeu$ek< q]Vޜ ʬ->\m'Gڧϩ攒fc2j`p1~8 RnT66awk ͛P ή`uXs:=^Rj/^OXYRLEopfAV=+ 4 dA,_Q֖AMtj%bD97F9FJyԎtUUYacOf2F`ktqf5g)Ft̠Km+c4 ٪Ywz܏H5c}Єh$&g:8OR69L Eʖb̚|`7 X 7YR 󂳥Ci2$pQNleN~t$9Œi=$4N UX|/ㆴk>}D3Q4~SsjUhW/=/31Fi$⬷"^x϶T_yAlŶ',k4’%ؐ錱Ye'yXiF(diՔWH.1c1Snepkl](bv] Fڪ]D 4Y!+NŇT87<h@r-l߿≌9wQJEA\=Es+ eKqWq2Q! `Θ߹ &֖zYmH#.T3ofr||{?eHFCSh#bnSR3euRB'[1f+c8efBfP,̉)3'}R#T٣wMbQ`1 2DbEfDZg?ruFҡp'>iWɛ7֏#*:ތLFTrO7~ti1q'1o}gF_`7P{dd]To_R<.Oq$di,Z*eiR#nTL}۩i&cZazL.iM25ʨA3rԓ q %Zz-m+(sE$|>sFwfIȮGB TK{g:12kzؔ9jZf \D ¸02҂pR\.,ǾTL]~0$?Q-Bzuѧ"$zitJE'7/Cq@T\Z-'Nعʯ|` bL9c t乌=O rqXa@\Z=8'=V42_1FRlTR;Y ~HİS&J1}$Y_۞_Ӑl2jyJ $1`PEp4)} b'Jg@ӑuXd8BGEU$BluDa{ ~ ;LSdD:,#iSnSb+&kL,~pah&% 2Feu JRlb4b&z+M>aMK= sn4IQ sqXBe\,P" dLǬ`L*k9,#=--:컡=uuClU`B7 OK=o:B% hG!l!e `8W^n!9SF:#`HSJ=w'y:GK2|&cEl62Ψ<)X/iXz :]c H bIA暖 4;rC%'w~uNuL(:WuH^ UflGQ"I|;|umI)>O'= NH EZs&sCxIF< 8QJ<ցE#mڭ=HlctPb̈́!N=Ocn MEM~S5};갃HTilv !isŵ0d[0QVN{Vo~p#ؾ/soyuN=!y231{n y e~c)oGž!6t݋h~g-}e;1Rw0k#R|"||Q 3~2yr/}w| .y86B fxHloFڥX`@GW `)hX7`b5jtћ/PL|b~B`PGTT"aDA~k'>g5^j;h&刳^ 8hodNŧE \Zu xVd𺂋%E *];G7Lpo/#@өoJe kt52i?/2!y!x· m?vESn^Z Q/vn%%CG>g[M t'y&O^U˄r`(!]#\&EDz: ȥ TRs.:ܾCh91bt@7&aa\OvicM~?1=b/p3X&'̓~ZV)òaR5)-Hb^ JxuS5rͭp`Qc&id\'CU=x#1F>R–HNWQE( R">vqoops?ۻfa~`owW3Z\] q~mo?~[<,/on-~`>\E{;{X&;eC|E-O| }{7'h'xN\\_^}~OL;Z^-d`7}֯`hOˏjAg>,i L |53noVPHtE~Ƨ{%g6tM8z*}~ jn?/ϧxiE^/ W{?}qxwV2BCW-ĸ}|& *C*&̗ a0 !W"E {bIvyG,X½}6O({/JPu}Og/t;; NGx R`އDinAafbws<}7&D1./? A&\\m⋻3ݘ K2&7!OܟO ..>V쀕ڏt fG݇\(NtQ >(I%U^@腎oxFE+9k6ލ9-: i?:z6]Az77.=]L?5ho }kgVv5 / {' Vx) q(#Rʺ \dYĥD|p۵|c_w_ ju6o{(|,$ۡtj]juF[^d@HeR+ہ124؋5ڳO׿̊(w7WW_SW-_ާD?".7f;XHdr߼svW) |6⍜/3>!?W:'žxZ{fK$d[-_FYx>/csNb"XLBᤂŦ]1vGIg'\sE]ppIr8H2̓K4&1cJLJT2|9hՁmR\*dMH[:.DzA2;lL&dP"d:^d/wvI"H A([:㎚,4_}DKW#+W+5{rY~$_:sanB͟f|ܕn[?yxYmoܸ_AWPp}N—3K~J]6%%o̐u|89g:y񳺻}x~t}?Í¿ۏVYR[^|ǢP7nwv?T1Fs}[C[.՟] ?x)6:SΩn+Uy;v &ʭUږ FUv vcۍo]t] Ph 2(i׵W-9;h@[:Xҵ%K䲧B(tBՆtוHrvM,Ch)KR]K2kۊ:>`xc X$+M,r;~0VF8u6qApC]4 Jtl?0!G~1mЧӬG I2~cGXi2̽ s kU^a>S3fZ!h9=EWS92|@'4N!/ ЂZ4xb{e=\M=M {<3~fyu~r*,̠WzֻBÓYiK飹$2<)yrfb{($+MCs(lIRY;!\!՝4mL)3V<6eI©,RFc5\lhXz (8F,QҸyEÎ$FDfs<>Hv(jQ5bcDh/ i0sY23yX^͕R:txI-m6ED޻ƫF7beMǖ!Qxmdç 4:r.z$Oj5DT!#fND:y C*x %v>N$oxd&|[? #b&$'AqYn+aj|h\=kYoXr8 W0rON8Gk"uqg/9TRt'ºtU˜#b9HЯSSh`6e˭kN".1BdMU XMQy_A d}9igkXExeR_o08JQU{ڛIL1e<W!F]IHwwYXIk,㞱ԟFw"5dv\yGucOPS,lXiǣ 0m؏ւC&=wvPCJ 'c4-PW#9Hz{wΪbv?z-sӍ!<{wt7OCs3oݞt.t w@i G28=ep7͐-4Vrs<:;aZ+_mB{B?l;NY|DW tUo};{[z$"=8*0 J^d&2 ߳6,Nh^-b ?d% ~ZT(̥,|+~?t$5 HF%EEd+%>\l ΅ts Z(!m!Fy@UxTK$k*Z>/ ,U ,:\\0TsJ +,&Bh6KA%KTHUa4>LFV"eE YhJj"A\!,jt!-_=d;ɋM3}ӧGݯ\T%n*^^o<[h+'31y),R K]8U"Q"7cqRCZb Z gD'eys]7>j)JUJN3]]NйDUf\NL@[HK)I; kD3RR" đZ-̞ Y; O:9ՙv+%Dz$irXB""R)ƽw*7&R]¹ :bgE\sˡՊ,a$vu\sj>'sZ69nQOY]2.*$&if\jXL񒌤EE͇q?~ʳ~0`$'(d}3`)5Sh@:Y (Bex.ӳ'YVgSDv$t+SWC%#OBIU2:taXSvzm]u.dRky U2&ۺ,MEH][ "UOCk>N|2ÿ/#E3 Pt >A6rq ɔuv&ѬȐf&k'W?}EnzuW\PCD E՗u>?a]4P cHe~#}KGbzo6_FfwiCPIЭ@Wf7ؽџK菬~|t})!hO`ru!gFH`{C<Ȣc46m,(K=WՋ·ҮG/VXm˅&i4xM<=UyG$ U/|Tk%pB``~Z2f 7Lz )YV|/hNedV_9#Te[fΧiLfVM44 ~8fkGQ ^mtw,SȢSIBS3/BKڹxFZw17WNJi#bB$IsJbۢCۚۆ!JʾOڏgA "ϷݹleS#Ϝd4( aR&xU Qѕ e!b+D2W($h;+\Clʯ6}v#IBP1PA3xd8WkI&ޣߣ ś(f _:Gd,J-gw13LY!)"iR:Iǀ"b+=VNXhlå(!1!±݁}/7D>@axA`+\yTmĬͩ{{l*!H*kLܰFF+D4`^n,Eͥp) &hœdB6ΨyHCJʃ.ј/Եѷs~z}_Ch5&l^fp"^KCUE/tx9dLƏgeGf9a(L56hfRo*٥J X2EnM01C>4]J' 05$<Q+rbqarFsދ⇄KM=uaNDBBI$쳩7Dnj}wࣂXg.7܆Ve3 &+;D` >c:843t_C\|1BЦجiSh4| 2GkT>ɽxTkHb>\R,ٲ)ӖiIRy%ҮnweE?+۪pwͼy'e^0#qS bJp33qjYt5l`JF,j`(%EFO)fLSJTxMV]TCB NҀ JɤNRzTPI. n0{ XS*A|Q Y>CAbP%h4n o>O᷇o_~y|ޅ6LJ7R!]͵.B4X$IX1R.=`L%a%%EbZ>ɲBm$ 2 ru9=\C;MZqrؤ29r/ev ~ NBZ'3MNA~3Ctx340031QH/IL--HMO.+Na`2dH2#=K !rSJ3sR3'$8tq{ɔA*{sd^+.U=<^-xWn8갷-`[M/:^(h׶ Ii,ZmndQ :~OrO3-Y "r8o_9|J Y\8/I8N>,|4wЋu'&2$l _? >?wΠl)?nH˒n&SBwAf2wDI`")1mjR Cz/k҈w#Ō] LQToq#f#+5-R!"(V,͆n@H12[6ܲZBoz 2 njڴEӣ\|s筛Dcszkϒn)Դm72ӆ[LbJd"WNo-7M@Xjg ˶>"ayK~H5z^H"GR,)gy%7>UrE>1,k0Fx1Mx9r~x~ ȏcfi,`~ ?$OBk1h|K2bo5WA2'Wa,( ƷS/-Gw&?WZg؜h oL6-F0Qp}M8y飧ԯMbz0f޵oBDkcpw㓈{o$e.xQSpNiGЂܯJ7Gh} 0L|o1)~TzmY]=ilsӤ5ے )U}!_wq;2R1oZ O;tZ}.]';\;eDa1FݕoOϐ~Ւ)W~uwJ\\.-ܪW̹=rU(;䌦${1W+r91ѬP2,N:X{n y#`<=<& C)pRIYa"LݣҍT-2 Aɝe\jW@_]Y2R%u, :#>=8{زG$Jm$clN0Rn\WlB N`*1Kꑠϑ[MMݹgy]eUiOI#< ~;4nz0' INr?dw C NljLmi5 ~MvBJѷzuf8+GYpGCլ =xxJPu@ɜʺZ/P4߭{~gfbyxSGgcy87xR +A17ccwmV=Ʌxc8FҲ(5-DvDNFMlhhH|8kh-FsV\3ʊ :v`-^<~ȒAcKnל2C~ x>l8iGh5U/)vȏXրKy9Սힰ\i]~ч^$te0 PT0=LoD:5)^%)P/oFHx340031QH/NON+,L-22>XUOT6C<̜OL`0uݾ{s%!޼xu b9FBD*'g.J,dəL}fϕ^%諺tfalVXGis8C>{Qؕ./C J6;d _B%̱7] ăiYn/iG'xn0 Alk-bW:*#=4'9B(l1>Qw,Qa.aR9kp_ߔ(Y x31|62=1SyGܷ;((%f0H6dԝvoUV[ZB'm>ۻ%aO^FîAKݺ1>z4 ye9%y)9y 2 q?[_fv,rSx340031QHK/M+)Kfdx_֝$Ҷ^%ٺ9E)@}GKK:9>ʭ,]mͳY=e<}u\&XAfV&'&d>6{:#8M&PsW, ,o-.ԯbB&g!yЉUv\?nrM+MJ,6S"QI3JlU-M*Ia09`Wg(ť^pS.u0ey%߭CtG xh^`LyqrnI~zzNj1PeOQޏXnxYr^"/zz~xkS8;B$0lU |b r[.V/v~ݒlˎǰw) b/Z-]21aGLx |1*hD΍مr̖?'Ȉ->%W~H\hDZB4ܓYiD)aSΝhF50dIIΈC\ irN';x]-y'Az~L:ɜHhu9:C}i9[%$q. Vv—p8?F$@>5`?ſ\M?kd@c\ʱ" @ >e͹0He M8ݻ9[%MWQ$)1PulA) v,|((m ΄R.%gaO0Rk@t(2EC0KqeYߧLddzN/F_Vo-\3rL F/@Fg]ؼӳ193}tw4)x35Nu>,] xlC-x@Jk 2FFz1BX&Gu#qC]`AUg@rpp Si5KnF{]Vcx?y?ig'ƕf9 Y͑Otu郁>6/3, culsy6*<eP>ЈyDo0~1c`NRsфtb--qT30'(/%u#~!)~}d}nu\7Rώ?(V0db^|&x[ S }pq)JY2:d|=p\痛CS^Zs׼ ~rGa|ɕs @ a. z F#fI?`[Mރw\lhp }"NLVHhdSoFc4?e,"-žd?@)>"N6D .3ǽ!c&8[9+=N[{dsG#zvsRRW-W&7H105!K'ʝ u&pKaaGI оm+]n_ɠw] = FmQ`=K[e܅1@0|!AMZ52&XBoP‡{uF4YE,(c(~eq>C㚽Sևc^{Weqi5?Id n°6= _нBVs\`:-ʃ"O6Վ*a+Յ3*!sy>zz7< ?x&'O.`-"n?\:fw%\SLpLnHsNT*`hϺZP ֙4 Xagi#L;X}T'BQ.e=M$Y3do7b/v׼ף,iP^rАfsqTR.Q|~1A4N,_4,93Pu QzT#RO7TS| lo)館_u/M}'Q ;̃5bYHŔTohˈ`_B9O \gnN3a6GI[+L>)BDCw-9oS:~<&oe =Nh~`".&MȈĺ̑BSQO+۴ pz{)P۷!wi$Vη2x6i[p9Fr6bHnx25S:M:Jc!)w)Iul? ?'m,Ө|lz-Mŗ-6 ZRMl;bfor>N$|&l|d @f~%Pĝ&Kh ̖FV3]7hQp+̏hٍ8ҐOtLϭ\U4 yUrx[Y:Y{¾v.$S?"^Y(cW7[xDN>O!0ޥF rUej@*rybp3UZjE3~ Gc?CzZ 04^S^|IuJ%LIyq.FdAe%7qOL_b". *{`HY2g`ahrະ[q&íßϘԭte&#z9Y8O_E[PMsT7Xc|[!Rh|mS&M\*[|UKt 00b^Y.;S]  {xK)/*NGVNayz.#Ok}D Z8V92:فxcM:o|,=8( G%]F|jq%b}@YWN{SU#.aUP*f,^E imV"<|A("cV(,X2)(xj#i ώ^SLH&IY/_$xToEbGB$02ڵ֨inDB zn;Nȁ3j \y;3g{ߛ(مFOK(С[ C) H] _ )S];C;3(cSi w(6G`y^@\  U9 U\64Aۏ!F==BP ]\ +: UI cbc(}-}<:Cqn@MU%OºZ+ e?Y!}lqDSY/.6Ƭ!9aO"vi߉„; ⰇeO]f\a/-8g`ǨÁ}/o^QDlt\?f>wn@Y*hcc͇'krG8C|&JzmPp<,SfL%\L#eAh]f)!1|@|[U`Msp{~cewpYe)*;'Be!,{F4| 3$ҕ,&Ni݃=}C$R:>)DYtVV鶧'.<%É5E/NL<d)dPb_S#hH%]ݬ\7|~_]g@z 93+dlWrK%jflj楙Gl؋ͥYvWkVd9Eվ'\Er-c^I!C7(k 7rvYXQY_y}VWKOJmڛDb_F*||nbLX&+_#STY6f}o?$u6cxUQoH~WK!w X16M(=,x/Ƌz)$\zi}3]B Ԣ␉. )e+8Q.+5|x \W˔W%!=]R sZs ÖW R'X!yjҧdw *LJ$\h# C7l# IʓD d;`'t.k W+°iU)i8 - 3 ld*2&m,-HA/k6Ma-ʣ'+P(AnQ!-T%2\ng%DJnbR%3rdQe*(#[۶R>rPRj.lmM*O0%oX$4(؀ \" B퀁4@qݵ`Dcz--Ӏ!80s;8n1#p<rXH`Ǹo׉ #!0\;,!Cz7 M]!+cu =C#-Ce*LĂ=GDV|#(E.-2ΝY`NH>S917 J#BYȎZv+Sgjiӹ F] 6qwE Ҝ%Ç"뢸ʻG'4{ / @lM $õW]Kίa[_hY/,xRO\yL93׍`bd7ܲ௛%>N'\2s.R>3Q)i4o D' ¹^ҴPڌlLU.d?s' p ?4V+w"]rNH;F6)d t{8ɪXK{5֢z13Ȓбq|/b5t% :j0rδ+ʟb_!~ J%L6gԪ\0sKTk'J<,gLΗxT?m|G2_;wmD;(|X;<^| =xVmoHίT ~k%٦i>Y/EofCK_"zg晙ݫ.Dk^%/,y3`yR)+`D-bTZ3d2eņ%9!YO*ܒ,:)V) ɟ`ˊ \&< X+)_)r $O!)K A* O$#%嚁j ,ɀ@Wr-v Vʂ/Mlu7\{ sI# &lDʗTX<ڄ|'qEX,#o둝C^PSbs h+rtɔM*0e?l!i/E=y)/-[x`*J\H[ Gir`s1I#8(Vy5 ?n'iq +gÄ['pG`yC;xCp&Sױqlx#衝G:Xʡr&vs\'3aDaԂDNZLgmt?@Xz']W\ >@8\Wf>P.pFƾ;qg#3ڕ+ ZĄ5F%P4۱П~(G &XRBNyvB>U,\"VH$U. 0V"KXq\Va)(bWF\"gOzYY/V\iK3l(,xHf.zW<&:ΫT[So G`=" q[}X+x!! JԖ)[&쀩|I]^T>\wbbMI};7U\0"xPѪ; cF$*i&MoY1dRU<~* 6Իk’Q:1(IϏ{8Fݲ ]eCޢ#3brMS3j@٢Qg합ʉ"|klQG]sj[w$CyNWcZ$ŋ(v(F)ܞ ; P _QspS%_rey,!˓?U4&{lгBQzГrht_a{zkHxRnA#ÈR\hY/V NfwePl`2WC&=7&.Pzro/}ko}yYř/u=fKra$:<$^n M=mG).'BP-jTv˨R7̨Tиlo "֋k(3Gi"2xSk>1I3ػPѨjEԂ}NmZH~$ !O"w)=vxLuELdLX.–P++`C~~clr̠m@;EPi܃ZaĆT59{}:|xI rsSz:nCA*Vmji3缤A"tW,Rrьuhƀ28[\cg,Mμ4!]MUeehL3N{$&msEdͺM';bd 8sdzL/C(Z-?ӕחY0UɸeUNXIO@5 ~[~o\UNk7ppl@txRn@R tCD Z*IאCRL@5%."kc]+ P~87 9il؃=۝?<$! "c1Rh6}4]ҙ`|=ODpWy᥅`{9qC[m8w SEUX{$#A'Gun@(% M׵+42gN@"ZhmGދǼ-RD].kؗKr0e*w.;d>%642< ILN!KuK2b\MxP% SNPu|lA;]{C&@ד񭖜׆c4 QJ.N{ *& 6rcYK%FHhO)CFU*c}ߨ+ȓ1ܪʟ@Ja}-la7EQx;}un#zYԬsL)Y>b6bjVEJB-xVoTҵNӨ-VMamb$6]9mbƾIhHv_xI# ڱQ0p{9;'yyO_-jPeJ]"Nxtwb9;D<*Z"R**fPTh_/%GCHI瑐tRj' gޟ܄Ob[6uJ]/kΨ=SH-Efc'^=(^ٍ1:oյzni߻5F|.=64=a,9>)"w>`o%ZР*ı'*Y,1 ;EDnvfs/e5.~^Dpe~J웫[;g (7mSoO0~KgA.QH{v):㙊*+._޸l/eWrR_gxvyew^jOL8rt+  Îvo29qSgό!ǃp$ v-b HHRRTsiwsaݤg-I_dvǑX0Xǰ18M"H95.N킑 ))bR+n^K h/}T5ٟ-[f6Vh~Qd8\#pj!˳_9\I-ceauFZEC>_+Ե#ȷzQVYu1]`e!)VdBڅ"6R&8 ^4!=QD<wLSs")ɬLD2]ӰgZ-4鍏+ 5)-Np"1>MQu,~"{n`%6(_Xw$s-ZtaCRs]~[KO;>}%G?|,xUAO0U7Mm!۾frT[a])qd[NwO_vf;IDN|}ϟo3^썜k(iB#湨x-ic*=ngCk{0Xqʞ}р?2elQ#-zń`qY,j=Mv SNxJ԰ŧ쓙U~vɧ#&. OŇ"34DK* `bQD[}W#I(&`Sk '- tV;|"^5a0m~ߙQjW~LޠKiSJB:61=yo$3e_+=xY6q[ԅ\ H(FfdJraP8σ.H_zHrU 5' fW€aI_H2qG2=՗+jYoQ"M^2J9XE*.`drd-c__Ss(g4Z(NߒY^x˷lٕćOI1#a,bF8K;Pש=`/AZ\71VE1ߘUT^ޯ7Nr_KR_qmLzWض1]wYQȹxmK 0 D9wI rȊkbK ܾ,͇ TaL6 iuR-1x?g| Wys($|)*- Ublw:}ZA`N*^mFxW[sF~W/ȗu @M0b$9hmJ+c'oW" 'Oe0xww+.OxA,*X5G=rš>GV"F}ؐY9Ct-=G{` mzX s$\s?3$s@ Ե<ضgO㻁=&Oc Pk(حGX={l m05ijݿ.MܩY0?Ğ ]Xn##X2oF>f37*Sp?6[yci-(ݏ,{&}v&ʍ3], x[{۳ 2]SU8hM Eg ,s ,O) ^ZԅAz,?R\$Vk1"eaY WG| P2LRF~b^)%J,zڠ i%5iZ-«(xm\SGtU/*(faZ+K v 5 ڀϜI7 ͱg]^'? ؚ rO) rXAp7L; S#__#:: T6Gi/1o+b0n::%>ڕȃ`Y: \^mzYDiz_޶69 xSuNF|%"g1$_F< Pa[Qg(.,6neY0J>.ZGTDm^`l~Pnl ~! xeɿ%˽$;<3%=DA,83?/>3/-?>/\ACKғ3` KlAtJiA|AQf^I{OBjD\' ؉L~?y9|4drl@ls5ar`Iȏ:?>73(?B5.G@jILJyUkPiEh̴J g}\}4mQIxxE;@2$"1'3=1z\+ j4,J-)-S@V`US|xOIn0 zX}@,ѩR4L9ɥh2:t6$֙7D^kI(j0 üSAKݯ5o@ {_Q]ac\5\pgP{g䌜 M¦>vd _/v< ix,ؾ`7WW xmTOoGW$`+$J@EZ;&%n%D(K#ݱwffٙP3C !> KBĠ~yʿW?f2q܎l6AS5[÷=,b Z$P( 5s$f*oI'E[ Y#\ HovF0g<sĜE1QފY*9CYM:)*0aTcH)PEL1 菃pC,eϛhx ~&##Ad ĤF-o3 wpJChfx,HIʠr?ݼm5z&3%tBXaCN@JB$1ph8|\yA$R 'ÛitgnAk.x[#w[+}#] X SeMo6EU)A9B%wKR6 ʋڀC2 SdKxoG?rnAy86,*s,' 2DP$z1L;xvL]3 Vǭ=ws˃Quݗ b {z巛ll^>fI2G'ִ˭ybu{ɗN$B;lM緽Έ oG8 w#hn XcxaNgTunaw>~u'zл)eOȪSN.:7p edȜu#}w>?/GswXgS@.Gpc`Y)j88ruz5&:q 0s%?`'W͍(c*WҤ*:XUy B'֔E,%|U|[(̭q*w9Z8=d;+虵`:5NCܛZe +Y W27[D( _}W ~ZgYx~3\oXfR)fzjtf\4YvP HiG! _0 ýat 0ke]dr_ŷ%ÐP<[_`BJ!`Ժʧ-bw3rCSo^`,K*0.Ɉ.%B8S%IRO`w91Rb{{I H+sۥ߽epYlҖ*)=X@AwI[Oo'rؘ2q(p`wei TQ|OUgNũt0@F# BWYlyln$~1\_R$uAw''-d~u2C.Nq?x /i=z}ew7Cˏ6?u3TIYM48i_=`Fn!ܝˌWvKTΨ+6w+G̍`^3=~31 J~{&d(Hv$<rh"՟C~@["C&~B 2ڤ=p;8/qxAI:4Hʊi'=_kTlVADwb͗å89Q C;ҳKl7d_4;8/cJ`xp>oM1ݡw23vEãtHFۀPw7F&nÚ' JV< ;w/Zhtrw&H20.k?H[v@il\889X6QʴV#MM l]7 ~ ? 'T4"E{>׋OBя5 - Z&j&?&@ Y'4IZ@s-(Mʰӏze $J÷|U&JNeI$I!W8}@\gJ? e2u#nݭlmC0c9咸zm:B~E5x340031QrutuMa X{[ߥRl/3(M+KId60 tߐ!LQjq~^RifN ğ_-^׫q~e60/ax]PJ@+rilj{` VM2&[7qwb;"Jaag͛ަX>N0CkU[ {Ү;XSRv&Aqɻj$'.% u@B8S :0 HZl ?/mfm$Mt `L245vۍZjɝh7y޲R󩟦`ep){VZֈ2均wgy;oKwF0il83G'21xUJ0Ʃ"+JVۅw׻w^5"g}=1d)s!|7 `;1|oo`-!b\\a_ƴb:eBDsaA$Vƿ1gC.&=FdoTAep pggǂ>1aҟ\YSd4IB+kt~Q?hËzv\MIU,4i5᧯yS*DkbPFow-xe= Ew~kc!5b1 UQJR{6~2{NmBWe8gaGX+pSemAc2N6rSNʼZ<].V=++WY.9Pg|n 20}~ѩN/ ҪE>bXJe)_P_Ma^x340031QrutuMadǸL&?fer(d0ؙq&vؚ92/6ZTm\A/s+}n(j18(qYO8v=l8I5:xmSn0 +HCnuCP4h t`±iK,5}I͒4 nW!&EY!ML' \X2`S`=c/̸qTA2snae @IG&i/fd 4 sql me_^+prJ H.r1h:sr¶d'@ɣ!bbB OL@[r!*]GӊL0'j2.$>WIc70|u+Vv˼,k wvuJgӛfuvOBXKщQHr_wRnȹ"#{WI,|0Gj8!U4"$ a%rljwuI޹ 81T#<]qF; ? Az_#T0 AŐ%}u gP eJ\=/ǤG#}e(fծM%x340031QH`L;=moe;jV˔1IOɎ/OIK/+I-OI,I/(,`xs_ #k7њVb[/̚Ƴ N;c185 gl-ݩ0;-%I9%(}h'Ekcә_MJRZz@ܻKQK/-ȉOI,.\T[_VrYm{kN|' ^+9/)ˠSa> c=(.4GmF * iA6 z` Zn .^U纐*xj0,4J:3^%6qo h]a|YֶNJ^d@'FaeMa-P*XY\Oek 5qܜg%k%0$k|2%k"~g6c˲{Z*EMF՗m!DXKVU1&jkIslV&eWs7 l `{tB&е |C ʨ;vV\/@#h5[(J xG:4|`JC`NԆtrl+O֥(dApQQmiB+d7ѧysϙdf9%&5vl`b/iЀX`40›r׀K^PL;& ȉ˄biӹ9fhh-;W(BsReO!%Kb5+ZA?귺N )*pU0#sًvqɳ^><Ѯ6w^[?LF8YGL)zGyL}ˉ7CbkBe?oaw3;lмuw9g*y2}{|Ya⬾%,E#;2[4u"ebCv؏F?rP +NjdĮ5؏ď9qhL")K9{d Kcz晎Lܕ \EҧKBme8\㿦N ~xg2n C4b!^r}l?0&cexa  X/Vȿp%xnۺǿ)AqV{v$i4 IwObɎY2$999k̓ u(HJ1PH[C4~ٗ~E_^ktz~yvr}sѕU||//槟^m BB>smnI[7BKeXae*YN ۍнY2u`dַŷp%;p}p|'V@Myh"`uC6>m{v5>n'2q~k4_^ο\|+Ƿ.uL]o<+ -?~' .O>&__Lv+%oK巯Wg/Py?=]?vb]:^z6|{}T,,$Ztq_"w aÜv깿x1F&yP\Ÿ>x.{/1{Ÿ~2^̿Wgkѯr,/_zC(~8*"Fq@|;|#-͸N__̜NͣѿF(1Pqt(Ϧȵ_փgFIj&M=,Vso+ŏj8.n/B\9Q 3zp7y?  [66 5غ8y~6#6ԍ6(6;8?[JzNw:\[Q эt!كD)íUG0xomX(ì|6p .󍟜ǵIƏVt{w܉xKz -8ycqx-S+Hxl6$Nx|CWvPv ۢpkq|qHZ%FHJl(l|NqpNBQkT)1C9ŶG{9X;t)6ɋBv @;,g+'ƁMؕC$ kys$#<]ļN""XZ+):CZ; 6"U;88 m⸧݅ f6^6D;TԆb-xJA\$&C8Cq~&)O||}tC:ޥ+~:}}K*4y<6cEuȌ?M^3>3>3>sufKK8!#4 \֐uq5vY4}g}g}.6=i/ X SPSrV5aǴQ@ۚUVb7w l93~7wY~EO(' yB<|R>O)S)yJ<|R>j>ϨhQ2eFEAA,7joԖzTTTTTTTTTTTTTTTTTTT-3yF<|Q>(gAlP>AlP>A݄uK"zqYaЯ䲵h8֏M.Y%i2p-3S%yCxzli0ٔIQy'>}ZL%=Q^@ySXCΙKÃ0nyM~ v*TtC7Go]99?Ɵθ Od2“3rD@ِQ6Zzhe:an-/bl=Kt~q Otk2ZDUfpJU 1M9q>DgIGh"SQ"|hJ%nhCTh#p%ڢaL_.'gEg]e@C'7m7kcOhգAO7$vTʫ L={%6:x$ E IB"%1f="~ ‡wKL01U(ZUux@|\ZQoPD#*!!$4$JT*u5~_}m; eB& 囨.p> 'O-4(&.(q" $28#dd'&dݟ}#,ng_^Pl+BծEWQ/fşYC3NtRka݁sc_68P :," : r1 iA7Y+몚_&}Ÿs\1TO`S#lT?ܢ*U: K>'j U*JPgkI-& p$).a7c2$T7 %N0q(faPԡV׊|Q[:qҍżphHʓ -"?ZE| ** IfMY+H_+ocpzר< 9j2 #'H\g*%mzd8T)RR@%ԂV`U, MVဳ-lQh D[$e@,Lc TV^rhf*UJ@U%>h=V4=S0ES@<VG$B )@&VV0ߡf8QCR J2jU hΠ+Up{iJ)A $ H52n!g:@`:a?qKRp5kӬ SKﭵ >)`/nUCb 7R"R7"Rn|q`L~% e5~V&IզզQ}CimmRNU˅J7""7v"]Bj1iIQb]n[_ zۈeW.jTD9F.h8!x1UH_PUT/ n/a\-eD+ r(1PbBP)fQ"RTDH!TAhFy~LOjiѧ]Fv=}z't8CPxUQJDDIɳPXS* \=%(,/Ae *K ] m,¶Ж8l5!ՔVSRZ5՚ZR)-Z5:&"+3]ʰE%ܦ/.y^-~jŖȚ}ofD53}6uSu"ຑC\0=/[_iAot3Mm ]7馹lwih]?nZgΗM{{@V|S&_T7T nt\m?\VdsiUK=[Ke=%*SYBuB7c] V[IJ &_6i)IC<~23}br$Ӗ#Z!^r&rB`+ ~sT"!SQLE%*gc[TYyNKfnGp;r\ѻ6tVhVlv!vflAG>^_~?cV0m*(y~~quQ"߻Q_wމvE4s3_ /_P&gġ bl|F'* ʰ@g | -P5+`?|F֫dr DY ̅R[U be* :t. 6fmzqZd#rWc+Ia-%9(H+wN+G6rd90 te 0 *k2w>p^DhՓ! WP\A6 vDxn+ix2N F@Ȕ§Je}ɓ显tUt$MO&)MyrRoI 'F(^wo1C^Dխ'7:LQr&`{ k6:f乚l۾r[iӬ-슛l~ǫ.;es]wep]אe-7 ;͚ (N)&q )%W8RJ/q$}#/J21[07:( .펭Z9ԕf7U3Lfԛ6]eվn1)|L^(|h8/=Lgvz̬` }GvD$k`<<~~.V34&}aDꄊm\䊧݉TNjPyA XC*)ӚFaP&mnOY0=T&n>fGstڑɭkpgV:nwI}4ijWUak%J'˩yu66]a~/4\xX]lU@kaZnuvZ+P(5@-2.evft# CRn&x1$)@MEL6$ȣDF޹3;[^o~|X)p}[^уo o/ń4#rw Fxnp6i}L<}$ұ mtxR䔜DaboRB|F"6+'dww Y.+Da8eAe)4ddQQMʹX;H~.IKj7NO\,'+iY@ҹ QlӐNZwh4&C!X4k' s MHktг?`'Dk󪎮8M׆; \yB]%4B, SbVYz(Zʮt*b2ѹ}]DSpg#S.g.Rb+˾J_D9ep8_Oƪv4v}qUxYɲdNNŧ|2:E>VxpXA7ΦGZ 9nm99j1t2 TcϠRU%$87/CHyG6 {FW+gB&+;i\mGt~$> bC[uk: 6i}ɵ%5zX $>]' E/A#^JpG`ܯAj<Ƅ"ZMEYՌ7|pPPN9` FJR4d(=+p(Jpo`#AywZtQtJ)Z֤6LjP U-*>nNt69joׂW2a,Ɯo "5;.˪7*&Wi5c=sj0 qA Κf°.aͪ|K^cB[hqPW-Zh1nҷ-oǾ%ߨǓ1$8^zfUA#Ң*!6͍d%bamk]JjK!aBQ%h rW\pXQWeDSj&'3d'0eZǻ}E#qzcv'€<;h,^OT<G؀*?C+JܿBnZ}kff ~q]-6D>A{Yf ;&f)?bf5r%p to-\3>B^8*ffʜrt)[rYX #|!45nLvx;4h7fV G__5,Y]&i,&m5l\\ )ٮey% Z (_S_)T +16/QH,N.J-I(M [(dsrK Uy 9@E EEɩ `jrkZQbn捜A; Ɍڢ`)@WeŧăUNu\f1l/,&Ub+(a@1 ũ9i:H3P=4<`PA"VV₅. ؼU_PRcvɂnHrCuM.Ax++ɲf8Jx₥JXma1t]IݸU]jKcg'ལ+L< u܉Pe`&ǖHmTIV]fp/=5` yT!|,D7HZOw=Knl*AG`^!I#":bbX~^1CgPPZmw!fyPM#R:L*00 +C)lNB6,98]--GT ة ƞ$jfE3au0@i\]k;TnU3HMlp77oDǚ,-4h8o/~w\[­($:92t^Ǽd00TDZEIFHAFM`(]̝$84lб iDH#Ҫ6euAVp[jd&*rz"*qՄI6$y;1BZqMJkZ5qM 00eC]ϸZDcJdj:i";{7 j F{Ըu;’K]L.U4WshjuaǹXvr;17"s`;h ecYw#F1 r! TRzM&f[Υit2+p3NR<6M+?`i|/{q RDC0RXwn$C03t4GKVKf;'#X{HBlCM$AnGm; y5Uޒ!8 Q /o0/!Aa3tڏ=Fn?&EWT(z|>ry?{>,^ll'u} nfxGX$({eӓj9] )^>X仨f?RSmx;4цw̬!2+BY,!.% )ቕ9y)`,mؕ*ˌ㍌ J3JK2SҤT dN%263/%\AL[#.͋/N-LI+dej 'Ws곂y7rfOⅻ*3eH 2SK3S4нW` 1k+DlF` #a-PC n"3SlA4~/S+` r__PRP& I lg\(lo%)Vmo%^n${')ha$61L+mdm \ QHFɜr< *e80 b—"0Ty !.`,b5{!"6xQ)nF3eQT2& bccc|@gXܶ2x]n8 zEʓ$qLwaTRl.HпEmmd#I܋y}}=(u!m2g*~<|r~~A\Ӌktzqu=4B0F2 ^_~:{iEbqaCr"q=34 #-CK?Q2l%},i8h to_F_qS0;@ߖ8Ə"I6iDAGoTi̳A2k^eRATP,~ߡuBO,OÛU%I,I8]gxs@EF L.qo8Ʃ( e8%Cݬ7 >;C~ 27x5~N dI^kĪLh3C] 8"ϓ%lP\p*Ut@ 1qqkt7o q>OW| pB@~AyG9o.?}>|B''W_Bn2;SsUc'8( 8:- wtQujЙeˏxFK 2]ߡp$?@i(OzLzCHǷ[C8QItЛ7/NH _!B_O.>}_|}q"q"h5 Y,KLڬyabӦ0~{; tzUޗ7 KRnW}S>i>~WڽepZP,qL2s8mmmj,TX-!^0t ;dIyh,-S0& HHGCMR n!yPjyAG]KWa,2cByNZIJgoNTh!kB۰!, 0E{ԟ l2}yR?oVy^C1t*QsBV2DZ/[P8'1lJMØN*d0msQKÍ%o|M< [4Ag;3=wO.OB}ЬieQ|QТyrK Be^1ȼb8Za)iP #-ʈ" +2gߴ?r'A;? %gPb U(EIZ*Xⴋ.d+OLU?!D%:_ Hgr#Zr ; NGB[=Tq ޶($R SSt *0؃-hYlH5u*<č#s hHx=Km[fmsEc) H^dS#<-k]ddYȰ5׬;шrl/["GĪ9%,8KS2]9oԪPv"6Kڊ^ONݑ#R扊gm"+#O}Ԗ2CT/i5|? S4M)!%\G$z, PEY"*ӿk_:~[=F8Ϡw17>===u&^g1_cKPq98Z9ܖU,zKY}Q?|KUߚ[dR~Y}̣.nuO/ TmFM`z\:3W7JʷRuz yl͘s4Z8+nF>DMjnVtR 0]o[޳+кׁ۷$r7޾9CVņX|5 1.~p7\$zB۞[]hƹB+s$ޥ/%j7oMc nj>Q*5 x/}(R`Pl"]& fC7(Uz\Ym{f =!AS7&&Cj )}Rч3PK3zO4.q*l] X@9L7@-,=,̢sU/=8v"+It G8)eWs蜾t^sU|k,zkӰgbS`eY>!nl-UYgUs1\>er2Mop=K"se6a .}V3n (ƛ{aa.DLH]xE`4$p& քFc8lktFp v+.f e2hy[-q/r-LVE;X 8#ΛuݥZkk囊 ߹URlWm٤l%,҈i;`0]ؐ 9Hޞp5p!ͼpn L P՜xE Rb[)j JhvydTxފ3J 87(+]O4rG}qÙJ'յ0'}OyLD]N*K6E -_h;2*w}:fV4Ӌǵ!X35>qbh[JfЋӌΩaF#Et7LwnF-`bI:RzI. EO&h,7C*ۈn_-E Cv[M\|s_.&0۷&&r6޲4Dܞ47< D`vH&`tSodq(S|+]R`]bz>%^rK;,ǴŹ-+vi.9v²aKl}Su1^)aa^jn]bހ>677R t,eb8; M߳ 8%ŀPB KP mϓn<+{]&Z'*B]Lt?5@&;d<<"dU/;T[̉*5|+8fW.)!o0I<!:%afmV""~ȫ6c]]C h?ک8C wŹZQ8뚉t9Xa_v.nɛI(IoEX9)5;p3TS"Q%A-?㭤7#3,,?{^3)kmLBrbsZgQ W+/+]3k}򂛳 c2}D)/,wuel:Xdy?HqǴ+EoU?/o,$Fq6i)@3X"E)uuL nNvaT;;%DojێS5sfժi>tW39upRVlhF1;}ԏ?ռAYm]G<2#t%RB.sس-{ 4򱲣g \U& m@6`UAHg9JM@6 'x$zqek *id(vClo4}.JX({L,?+/9-Z3GqNf^CdV1}2*xBdb%wd-kyj8XsDj54hG41IhWscxfPd !!p~KK,H% Yq܅E'tiD#d3pa/tQI7* e^& TG* Ix2kz%PMtj]~ s]0 qC?RUx9X  k6OL#ŶD?q^,p>/v!Bưkri;J*WV۞C"6Gj]bILQ18H [jho1*o\EQ_\kj fKsw]Vkjȕ sl5t)5\c}.r ?fq1J2 6q sIN[DM2U7?hyLԻһ [ `=v鑚O(niVr3NClsN9":9-oxsazVzC5^:3S8M[w0i*|su&is3}R<XZImLU8Σh{.im('K W·Y{)߱7I2%GgFN]` l*ϓ}I$/Nf:݁in. nZ Mיxn>ZErQQmr h!l*NvTŬYT+Ml4ٙxAO*U߲ЋK 81@o|dS5{JP~Olks|7 0cBȢnY{cG䕥ce&-ݞd-I1ysfLC3f#\H=5ԥUBgfRCKb[aX,I1N|_k*8(wܫ`Ll9X TM^zb57@6@xBJQ1z`Mw`gDrIIDwڞ“ d-O @MȘqB3ŔkMye99[k ohV>ƙY8&zڥm2]|' y VܞS{tݤ ~8YZJ&lhE3-eMVE6=mY*x B5%;Q߶*un0ЧU@6cmVˉ Z`fV4m>8)'ou$hۋll?pCsY'gvy]OpێD]m< h|nVxB`Q12*Z|F.Vٻk#4f;M\,k)%F %?81-_9K`gJ2aG J_ YC/7z* MҟG|`ȣ@!Hvde~2(aem:Kz=^p9&+Un|+RE -Fpסk|zٵn[lfK0n7̷D~ >W5;Y ՂԹ/G1 NfUsۘEgwOZ,=ٺZyX1Ft!u{xP#Zm#SLQethct2?:LuTC4_P]iWs nYIA|p5ĤԢ*/*ZeJg9O)IKj2}9L$//cM>bs+e&Itj[6 cIvCNaЕij`؛E6K89!al~^s? XdyFn}6ӎiyG{MuQ;ZiH1£ hDgY|U.׻@mTeme 0s2'4I{_Oewl=Utwm~]u..ٞj_?I{g Xdv ~@wa z bgtS0f+ુ؀囒^Z.!O 0L'Zt}l[hLAHeNVA;v ,02Q s0C׿JF۲Mk~g$N!nNh$iI8E!;qKɷo׫xɍB$x]nܸ fIL.||dq& AVVK=vxy]8tvu|}v:Bѧ^^\WA݋u޿:;>jAVA ׇ?Wv>K7B ׃{ZaE['|-DO6to1=Co_88vv]؞lDKͫXo49RBwGfyޒv6WEhlQ(ݛM OckKݫ?0qDܻp|'=es3\;sA6/_f<Ǧ|LA'~'0K0זf׎(Xr{`y6jV%๡|97A^kx%`³>m"gF]O?㫫O?ރp sPj ~1gW'?.?^^p~yWt /Wח'>_/߮|z%9(Յ;zQhi;ރ6AN3׀l/o2_#w Ѕ40'Ng/Gl΃P|A0{A?(c&x}=Ojggw@Y/?~n}3\v}s 3o3wO`tYoFbd]ᄏAKg2D?'UI1J"^q=K6[/m7T4d*KA\vYvYmZ?+׋p̫חvd%XeU%\M Nl6Q@r "w 6uk7^Ν{)Uv0"׿Rq64X8iX6qo\ύUg\/<ؐUu:iUɪ\?˚['X9qnSaM :4pRDSqVk1Qsnc'IjDc׶γ3I!w %Y0b!MfeEF,:5Q;CN6a,7I,ӄʛ4`XލBO"RYMjKcGߌטLڰ(u@ұJ9$AYܼed8yNp@m_&R)qrCZ| 9jV4!nz.뿘N>u H`4: $÷k^Dg3~um/]soܷyZdfI%j\Wb L]x*ҕᵨC52ZQM5BGj5R35RkpY6q uC~PT. B/]\LޢflA衚lQ*Pʟv2Qg-N:Qލs)f<5E d_@E VL8Pba?CTȹHvXG%Yt=C^ x}͗`xm̐MWjAAnH#V&+J5%A+0LDsn)@ 8U4\,/ R= W3 o\y\&4ҹu2 U sul.c\*Y?z5sBp++' ߏnXC38.rpLlBUvY;Vݡâ,چۓ~"t"\qp6({;zk(uZuZ%f\>/<. m6ihk47QB33I"3P!MLGU17DUT㹠SªG괽 ^MƩQ\qVp{˄1l7Al0Avct&hNrYP~,wB R7Z`pkV$OuFkF-t߶na̕(n(ݹ#y0(j Lat; .NLwt6KtO@O-0^m#ǽ] Q_=MWwo$J{ҧUv Hxk +б8X7)4Bup1W0,JswƉR陮t5x]֨JUoY45Ft ƙi.F]#)IH [[ ujP5J.Lϰ[L TGtqjR*78R3:Qc cN5)P2O3Ot($`Qܬ`Ȏ-]o:%\ȵGrxL,^ O+2F^.] /mHNLbU:[Pg;67ɧs:QIlj A716LѤ^K沸!wg4B5.*| &:Qc|uZ~?v$mNk'饺Zpa!&jd&x@&'ZBTL0R=STwAcRк?7N1Q0s).XPǼ:1wbZEh*QEzmā1P ƄR)L>XPYT\ ?tN o)_ E[En{FC8ЃA&ƣB̞n)rC֍j}7߭/WXvD 0x9IzfO! s!܂̼ror$9b,`B*z,@@dJꞞ )+ĢԼ..w-rI Ee%%E)(͞0Hor:5{(0|Fv/x{AqDC+ $=DJ EʚK__A$HMW-&Ʀk056f/hجh^>ɗAa\ix}ےF{};)nvx"4-YV,;$y~BH`IDe'+DfdIb`M0s6wD~o^}ћ_G/z=?߿,&z0t=|ų翼xrߏYt?Q6Ol2G mhh2XAe:v~F]9.g.O[OW<gl Z߭}hޛe|]oOLpQ f['c2KEK,g|.rl%wYp?gebOgQ~Fy:ߥߣ|.L,E-nGY8uK4JDŇ+PmB~!J3}⣨eGEiq"e4JDtӰ}񻭐&S(M<,F9}͟޾}?ؚ>k~:bd/E//gqȳ|?ӫo^{Y۳_F_߽x<ΨTn0Mf'rysLb"_rŘXOҁe:l 712 b;'R UVQiIVEewwWR oA6\:hKF>Q:k]3 kNr1V۟jvd f$a<zDaqrD?M]ٲOsb1 p2/i?KglXɒMΡdUs*toJJm{1dfC&[a,JUmpm՟޴m1St^EwghF\`qo]uf6jc}M?hTdMIV $<>OdfKqo[y(8KU< ə U>d|/6Y~ݾojbkqO:a6\kh"q:/mY(H\X1‹$Q˽ˠ' z2ɠ' z2IN=YAj T?zAF-dd^'Wf.NnF춘s*ީvJ]UTYPeAUTYPeA}F]ʭcvMfC??wS~۬}t76}4(#+( " 0( "Q]KVGwRAueZc2*}W9}9rhٟʟZٮR[Ɵu1 > >{^yaUU>oRivvBmtfЙAg>Nٔ *4РB *TB/-Uj\DFIZQو(QDiAQʾkAň+ʼn_k/}+SKl [l nqmAqm5q{Й]wx7t練}}_ҡ*ܺډvF,Zj9堖Zj9堖ղN4JɃ&kKM!a,yB2Z]z9砞z9砞{M$ -[O-^l_y HCn7gvv?c` ?-H Ƀ$0Ү= W/W4r:]>8Tiw^tY*<m},t@ T0P@ T0P0P&OBgE'B7DhyVaF_Ch֟βx( ƐSZ[mяѷb^LAUyKmu̅U,&D ;gc1P7hZO>hCO$xDw}܆R&q&m}I[#}ҕ>>>>>ҒbnI1[R-)sK%ܒbnI1Rm)s[-ܖbnK1R̝ZitґFKG-] +t% +}r!}r)}r%}r-}rS>ҨJ+4ҨJ+4Jc+4ƺJc+4ƺJc+1sG#ܑbH1w;R)sW+ܕbJ1wR])sW+|!|!|9ngiaWD(ﰫfٸRe|.k=-ler]WJammBDqOuǵk_{|\[sMa+a׍=՝Êt9氋Ê"9x4ֺ4N*+)wy;Kl\Kh͕Tu>;&Qi>J%hѪri~_B_}el|P7$-*I҂Mf`PY,]*Kwt%cArE#] k*IʂnLIڍqmhsLF0Ng쫿 DDV衵wc\%, :91XmGViX]7Np*a uUΩ2ԵT)sX7nXN<"ZNþ`N㾒*`x&{.AnF؞5/0q5mQքڱDnjUNkMVzi *&++PEk_%RFXJvsK&5`uΓͲ0!7joNZ4V4B+I#4.,а4"òӈ P#2.JиF5\qkmo}rw #Ǔ)*:PaLd3dN@r99])<dN`褀B߀;9)S-َ~L~ )Q%:5Q%:AQsy%8MQ9%:YQO%6g,jx6R?qKjdL)\N?2] L6(3dJL<(1t ^;"ڿ/*x3F[ӝQ䍤[y͑ !ѹ,#LfN$ >M$ F2UZR/b'cJ1U|=%npne¹ &&lpju© 'Bb(9B& "?|WO.2ZSRyԤ ڤ `gg eWT T ע Ǹe5}Tv OjUV8oڀt gR3]yZ<׮`z+X0=u,j|i0]`ߞq\fG!;{ɘ՟ʔlLǔlfLĔ\6L lLSz|tʵv2^ThKރB e;+PfB> %&;(Pfk/tS=@_)ߍ8\Swj`sP \>M5Ppy 6g毚& j"aZM$lKMr`mWdW@!su4l >[W@7::n]ʺe9Owojj$u~͋KM~y]w8@凑\vzN7v᧐>l)tcOazRy=C:1,z7cu~ _)tKT ߦ_;$TwIӹ 1_H~MB/5 TP4s2ea٢LJISfmY+`N[fD–Hr} [o"M\ٿ4qU&z8٪B# [mhdau"gyd<ȆY^=y6 /ss#},Z,Ve+x*<| b_bBǯXX~}b4R ǻj)!`k&l6nz 7pZ 6j$lU6n _bGȁeL}cg>:D#A&G  6*lJ$$؀HHِpc!ODXapsPm)&OEߜ%nFL@@1݅]!݅FL@x< (͠P4 ԃ[ӸeS7'#elEa#,.aa؈ FTXD6₲s͸l׷uK5a*|<*" rW 0 va.@\r0 sȀ("! 鸟b0HgqIν9iE뛱qoUflXa`flXLX}kfAp<Ǡ HIݙ ``+,X=` u a`G\ -O8hBYz,GGP֡ G$H C$ 5$DŐB y}I2&51e)P(UY4oAIó xk \[Sܚ ŭ9~d= ATۓ$ |L̒{;գOZ0WCeZH\0k!qZDX0kaEq f=&.`Va)&O0Wv93 9fgwL.)3 9>Nfw@0xw"w_Nӟtn#<L՚G/Շ|Z/ͭ|=tr-_rwtWtr_s saveaV}aVCgHȃɬmxؓnzT"eyxwvvfuh7B-$P@sX'L+Q Cx hq82 G47[ G#pdZ hhR W aOcddVZ6|I+r<Ԁ5rj@ 95 a}_Ʃqj<º85 aUfTi +4ppiyGq!\)bYI,sG#\o.7x` <Y悏,rs\7'dq͒gyFn?;m/n1;_}Y*u@GӦ\[t4yi:6Z#t,dZ1t,dJܐi*C;7)k7[$P&Zj 2ϓjv&gV}(NewbBƛ(&dbB*&db-&`b[/&dc&x3wex{w9ɞGr*W G)i>ѽ3D$t ݻGDBDd:L@J pJ>c}g뤽&fawflsaa}vfh{qa1qpa; p܃4v!@V & Q3uÍ0p% 3a`uw' ݥ0Np+)p,k փ{Mzh`^P']!X#4\иq1B #2ZȰi1"[|7,flܯ4a+)WV(3+xDԜjB )dN9- ݠ) 0'dN`L褀-BU(uΐwUm>iŇUf{W]U lwU)UzW]U lwU&ۻTlR ~wU$s+9WNWT!WT!WT!WT!WT!WT!WT!WT!WTA)***Gڃ9%ɡ,CYzt(Keѡ,=:?PңCYzt(Keӡ,}:1Jcnr(Ss(Kme\zt(Nlʼn͡89'V.DPH)Cqbes(n)͡Ѳ97Z6bKY>JΆdf) GۄϢ.gN˳gM7#"ϖnFD%݌<;xVt3 lf@Y͈ȳ yֳyy{[QKg!=̋(d2Jŋ[ǛOZ).k%ǵ2F\]q׺6vm_Kxk#.U͓f`K_ o~{[""tG%+ެ(:X y1,Ά"gC^ɀddq2䥲8zYF^4KC^9KC^> yeи-Xy4f6'X-|&)\$)ѹ<") )z*#5<;Ró:]rZ'!Ʌt="Pf'Be&!PbBNe 8]LM@05ԞOAz( hO9hv>pq>pf=lۣq=pӣA4N9ZլhnK4:0/4,LK380-Ds' '&/25ﴗS h :1ML4Q05DtMϽ4M <41atM}7Fq9np6M2iX'9t`#M>1PoሁzCG ۄ8B <"7q@%AQoJb zKA&Ӡ=O>(~WGgaOʧ49ZK6*MWװPߧkd,hb-hb.`b/hH}qxG8Vi|k(Ɲfwz|7qz|gqzxgqzxwq M6$8ktHjX8 X Vu'ى.Nt}Dv#\4F$'4";ѕQS хQ鉮JOtY8W5L'i>[w8^n>Si>;?o5|BJ5VK&R\):4RtiBQ{ZKCpEa(W0-5T74R-;Z irW4+C':թǩbP|m-@8TFҦ( BC&QͯIL* 5 ",8.P"r(KC̡*52])PdZ$K: Y"GUu.rak \;xX $EY%xYl2NZfѽV 5R-?x]( 'Cҳqrl܀M`vlzQ,|3|#l4&ŻbfP~vKdWlP IJ,FH{"nwt!¦F l]t[!E;qOmw Hf:GRF{<Q CYp Wpd#ս#w$pWwdTt+])lO[ /Qx1˜f\&u̸LI)-iQ|Kha4ä`RӜk Lmii͍ػ+ળʮبۍب܍ȈZݍȨލ6U;Qu#U[yY0To?i_iM H HYQ(yx;J<ʊ% ezTSV)pԈ7uDkmҜ]>kQ. |h>Z‏,hqG8# S'-"q!@A?ti}\]G#'X-ajVK|XZx%>,`m',am `kKX3!;MfXdlҸ7GlMC&xQcI0}0v+6wu`$u|%Ɓ tW ]9]twJdGK -4TJ !_*%͎GAKl<'iXz?gg‷x8JjjLwVu b+W3 6ډC+^ .]*̂qb"U5\,nt*f3i): ZRa6|R-a|]q2/ǽ%Y5q U3WJR&siL8mf%qT6iUdKE)VN&QT;iS Ꞃ슟*`PpS{Ka(otrc[u~KoQԱz-:RQG4HGM~ o- CM5|U%>eʷ)/^jtbCwĦٯ!MSC|A$6N %P>5WD^c^yMETC|C&6Q]-s)1S꘩\Pe#_joc[ nn<}*ˣhƋOS`|T8>ƋOSŧxx8^|*?)۰O9oi2%DQB$[D j%@O$[9 lMg2$وɐdU<|ke'/V@ e-?l`OaK[Xzޖ Glm*{ _Ƨj!җIZDe|Z.9Ǥ `Χƪ0%m["xj`2ؚ 9Rؚ 9p9lM bk \[SD5AO@0'!Hc{8!(yS;+v=up[Q+πZ?Q'iIQ'I3N%(FHuK2\6WDUݨs\9NrC&QTi&RtH,  u+GCH?MŞiI/nއd2J>ltW_(sO^{L-햮v˅v˥v˕v˵vˍvKiHg< *W[FHEx2NjB=4y_+GgmCGpppppP-R}yYm1Ogq?(yk$ "N2<mwl'S4nJDwFh|_k5Fdx-^kD55ck~f8!G=^KK eE1XaLV9dNdJdJdJ`3XLO g:)Nzܜ[j˭j^Spu3d{ی[T~::{5IcTR=&GCT[O=&9PR%VJ8dhk? JL% ,X;$ \4qf S7k7Ys \s{CDX9ƣرrL`4 MLi`Y-iuw!11dc?1F+ #$E1FIK4)>b4/f{6?3u6ݞZ '6iny]a*Pܥ,tW;J(T28I*T28H,{9:eh[%gֲvNэݘ(Ѝ݈NЍ݈(ύܘHNϑ(ΚʧV0'Y/ uU>3w7\]M ux(!=-6b38?-6i.P MZhGfZlwgzp?_2yNǙ2vY~WV/R|%J8+o|(NJ8+ş$JV-BN[8eY>9]T$'ZX ?'e=1qZvV"$ƒZ?5 Y.e 5.[X0qjBƝٷmyT*#5 7XS)%ѹQ%Ob;iV%`2|:J:Ɠ~ڛ̒\ *V,a?:ONfC??_Qmq윖I5Mr.~Nךba$u8pO@cWva} X|i{4Byn,KvK6KK@[K@KHO @{O @ہ;Yl[%GԂп-+ T=ү 4c[ _؜NN6C) 5:WWAOPul=5<_@7u $ \KP3zP! Wdi^?&ylqjaǹ&f0r' 8D~ӝO!]&vofwF= aA c50Wzp ; /՝P1B1;~ZM$?v%g1(9%GyL9cQv&cXsǜt1%zF0柵1_Ơ+_xurA tjW1xulN^tulV]gup^-:n nЏzho1T/p~^?ڝJӲhwZcy,3@cbD U^pI5`?[\\+'m_׿x"3:K(2"3Z$l\,h)춞t7pY68 G Όu=TPax {=Iy@6Ȧc}z Ɵ!z!zCāg@ *>@Fr >~#QbD)8%o.M`)xjNU!hHx1e.-~8xjcPd6 8 : < F> F@BD8PaC@h`7NNuRq8&p>L|mz |FKm(x 怊3bm Tp*Ն@5! *<10=͇ >eL=G`l<{kYX/ݣ/l80.cl..nGp"w}öyU`!VW[]*pk η=uˈQ%2dJ]DSК,=%0,e8([hB ԟ\,MԷ|!Tirh>*5 \izM7lg|i9\7>@h G; "ndD>9P]Cv=JHBVfArl>`F'7>5QKD- &'k6Q7w9 n0m:0M6Mqܦ۴ap7>NQʕ +% k.! 4pHiοtG,xS|eT@b. $b@b. $@b $f1@b $fq13q&x7 o@H!R)~rNJ|<ѝʃg(Y 1O>)Z.א<Ô6yOnz0hNˀ6ѬObѬ\2ѬiѬv҇=!>rs(1 5jwF6Ç[nvՍE?Qm@;4@h?@ `$&(Fl `$&(Fl GH::h (]Smj Cu -TPBu;[LM呂-Cϒ~᭤yFe!.>ш0I~GqB<1Ix\lh&#,9嚒YQBeyfr Iͥ`1p(Nu)۰JnWȴ%%ReֶX,_$azQHqRE;. 9MS@oekA)[t\>m hY DR'2%KA%OSCHμ\rד_x/xS6#) nnGsrt7 ƟG7@4X 20> ⒫ #+`[ow#oJn醴v ſa?À̮H@rre|d4ayG~) yKލW&e*}5A*+|(?CMq/dWTԜ=gцN%@cA$m|TBUOR}_\GX.,W tJU>$聀TGe d*Hp3Ŋ\$PP8OQʒ^.|BY Գ8K,ԟ;g+GEk<[eʣz!<~^ ɫz4+cϵ6F poS&j R9NQnj0^E)?D( Ȕ8\܆A1KCXgHҥ_kVg.<}>FJp>ѡd0PN6 Zg+H8,e9N*0Y(AqO[M<3j֡rBB,l{}߯yVǶ%\o1pŃm:KUH;*I-mmEs@nBc*$*q 9ZИ> cP7*rlWm^֒f R?b[ѫƮw 9=k2j٥[ʏ}&(vEb>l6R6kFJ0)kvey):ל?) *UCVu8$w+)Dzucp˓rxOV>| ކU ^;^ИlrΤ`0fٖM#N;wlh9˲cQqWP ? iye[)Y4z_ϰIG ` eS,u`AB4{=%-4XSˣn >fŸpM. e7*5 d }ȶ"aBvBC:6Vh`]p૮GS_7JsnU!cp(eܳQ"Kc'Dr2@ٿwȳO:6 |<] >^J;5k; 8Ko^{Ch4h{C]Z^Q9دtɾ6xj26h>x1JEud֬4t~gкu7>;yr_f;&G Rt' $S/ZAX3{Zʛ;(0P%d7RZ˫$v90jUMq3|zu6za>LCh|7CA &u>>bq~HeF`/b44Χ >]lQ2]6(У8aĔP`AQ3cE?͇/pqY-zEh[o`E}['KrZf%fr ̗Bd4 ` Ld33kR݃;ӏ%y:sgdyԬwSyzgb t!ex3p2d,7|"]0yv6E އ!@ 2,w!VSlҏA[bZ3  Hf .}S'6A{b7bWz26kV~G]"L99SBPC+/ rX EuARw5@9ulր  _m#AT~洌 <Qha20Dj?9WRJUɁc1a_a)s, ϝ%/=pX2Q"CtB,\wCҵno/.cH}^0 W2Je(qћ?].;ΧCop24ya_[2;Mߥ[}-ôS+t`,lhxђY^1%FzļTn=(raVϷQ]7.Hiʖ}O гX#Tàq꬝#)-zoךH4q0Jɉ>!o w{2q>hf/\2DH'{SM>CpMmA|eIf[?e-#q<.Aү&ۻ59f ZxL&J6a/ؼ ¥`*@܋K=\h(ƉOKR5-RJc~szߟg>]zB [9GdAnzj: }0ڂl /&fa~ȻƔBڢht`yg\W] k<¾;5HTO-!5F{lBԠhצHEL Or~YI'gD~~5crC0ᇰ'j!!¨jk,&e*^I)%TH%BϬE#p?gl+m'lYE|mJ%HW-/8x$ @A22Rc&lGU;~!5B; %d &܃n pB<Z4'0c 1?ZB$p_f\}9'ׇT=yf,KEcsoS~Sh׷@9@gL&v mj|?Yb!Rs m9#~b_uGhsS#ݍλi.YWF;AJFm8r3SbؽnlڄԔ* s%L[gʱJ%ߞ)*?[a <0;J08/ݳ'0Gvang^F+u&kJ0Ŀ<!\3p9]myW҃t;F# тCgv . ?s9c*|]$r 6Z5;>qӕd*s.UstD#;`v0 LZoZy+PBRTgQVIO=xj׵z[y-\2lDY0\p^28iڬH1.1q5u`]Xjq*xyY^JQ)A#lij0/R5vY% m[BgK]~+JrD7ZAGn Tn@r[y $ 6@Ճ M1~V-kiIei]$%e1Zx"'ZwFMWy TfzRھYWm^$5.A?-zFGOTʄyTNg%,jᄌҺΫs ii; 5"n 7 &3Ŵ(lYzq:JT1Y|k-4+a(5V6|b\v-o|Bz[Z]yzaThV?hӶ_y()PJ &-Wn~3Z#Yr34K8}yMA+QAM7S܆ {9K`*:ͳ!"ȹ`3CN՟owgr9k\=~$e ˽ov hLX'orqwAYׇW2tj):X9%8@Y}969#+;rvﺅq\YDӲF=^RY{mJܮPX8v{刱JgWFe5݋>"&2m _4K]hsK;1W:혬T$Х*t,vDVG˓.FykZ o&ۂծv@}+u7=*-TJE^C}Y׵L 5ֆl㘨܇}mic.Hs垔^ #m'gČS42qVb;픺h o^UtGZe_Veu{PIE'1P!y@>}!fu^㣣#0)>: -Un/~w#[c3B%{8븗3cDd ^?D;}I^'S:ĻK@b?W,vp:^;,L2]*k#ϼaM ~Pۊ>Wk~;L=t5I/ǶK|ۍx}%xvwFsF|ax$DyzWX#dΥW?<-ُa>$)hDMzVݧk❇O*R:"z^/t*0:?Vd'$yrp['Q;}yLvb<$VoJkއ<_m{iI#9c$a-υJ4P>-tHz137 sX'HI('F+0ii{Аd(RMv5[iXM_  _CUTGT`]akOAc-r!AU ʚ׸T(Pƽ/QƇO&oڤ_ 3dBh;V6_5M5o7*d}T9s/W'k2tbc8 @\rDB0.š 7y ̘ڀL#B5%79[5DTKxZ?aymŋSyl~Aׯ\ЀoؤWOGNJ}B)RyqYԓG]C2@\vpЖؑ+_}##k-D7}3ŶxWmFί(Nx f: &:kl%jAvF#UOW5M=9>LŒz$}|݇;8c[0qR$.ۼ^xA!{fB@AʄDd<آ#]v C)-dYIгw^)PJBDcxvH [0i!p>H\. 4i1 r+7BNЛ_,ǃ;cxgy΄?5?|3ǙMpmσ bHsfؙ}(7c:XρPؓL ǟA-XX gw1lT?Fؙ3~g" Gk:UgUϮчtl#񣍖Yv M-Ʉd}Q\VZh+;1|M+dzM\ǣL9S8Qb@Pnf(jheYٳk[ƶ5E,̃ӢǏn(pvv Jj#>&,0سlhzG^^vq:]o#uԷIȌaO ;XvUqտ~ĆfQdž](d}T d-eMl׀9 E绂PҴеV4LZd'r(Xt†Y ]q8 ϐߵ,r-"< (/ @luco?X1~\nQfy̒d5'4 "s%NN8Q~ hh<"/l, pĬM\4!] kCJGb#`vaZ޽,ZD ], J_ɾqNHչQe.zm .*4eQmv`/<ڗ4QyLch=w5w$b?mjyZp̣äD[J`+MkCMNzTcpeA[W@ v[GְeA) 5NI-_+ 崔+084iwz7RUiD=T#&T+g^pS]BoW s>_/OIԦUU+k WMh c(Z#tn>Z:W.e}C_"E"\2܍*xxGh1R~gUš)8a'܊$1Etc C/8;tkKǾjyX|>\2TAx* $14eR)[;ϾgQ51r ,aͿxVMoH+J8sZq d(BӻFd﷪3ʌrZ)]Uj۶m-C,ڊ3xfҲLnR\4TL+ldd D y s@ ȚWhT : o6AQRWD_2иL) ye4+y t*{\ Me5h/\W ҒQ^wq8l|ݤSt`+ ^fjuU@ zi\ThjP7BbuMy\O Eiz_"~&X dƧX2okZ!RԵxr2R_-+l-3NFh@'U[ʐ qY:+s}+wF,Y8xx!3nBaR/ 3^!Dla%8ÀAᧃYF=ȏ (Dx*'zoB"hB߫;qxbr>7Y6N oInҿ"gJVj`Y m+ˢM&`50=7k4m-Kӡe5X+ZOJHS:MxJs+NΖ21hKjqi=3,zvG)Œ/~;WAZCNɮy7\9Fay=7Q^T2S"BZwR2WA%5Gѱ;5+hSL]Qh)PZ^2ͳ/ӷX߀1ϭ29dg+] jkk`'O@WMKV͏v;}cƫAhOo `YA͚a84ݎu=bSǝ:ۼId| USrN4i>7zؔү -LS7O0}Pvhl.f!90$7"HT@6.U,OEpPF/xWmFί^+7|I6r9%)°7 w,`J=ٝ]z=c013|b nx3is&\).2 6LXw$c Vo"fD1PA,g<[C1dA3JH2N RJ ,DCw/&#/df_L Nlw:=/_Unx6~ȍ7 ||~Wql]P@ƾ)i#7sJ+jdEz4\F=A[ Rn [FnHzV,΅HU q$2㊯Zpq= ( `j.gkk^30, 81&N =+yB&LR_LRF#.\7Zwin)#K X&jkNpo7o!7J:}mhAP[D"ϑrR!Β*LQTBNUMdd_|U>Z#nuFMkE(-n?º@{OȕタUjGn*f%9uD$©v0J*OTP]kRj8(5I'^p)I68 w4z6=jKYwAg+aƢ2^2Ti{Ԛfs_Px340031QH/IL--H`XNOi4NsE,egB%f0޽¢-Ljpng<x[msȵ>㩭'{nR&-`p^g*bDԒ&=90d?ʞRwbPaJ@  Dz*w?2ZmbHwߝQZJb޴k;#3tDw$WΕ8QKvW-~Fb0ݻ^np߈ƢׅAxZT]gD-|O-q 5v}g8^>:Cq0lnz];?ncW<Cn;oyCrpiؽA(|9f+0utZs׹qj,C^f{urA<ğ-p9砏ݑawD #\1XHe` 0r\9pTz[27㏓^3n^or-y cMyt/oޜc.4胀^I.M$zJWzۛxJ"#6x> #:TCM5Kuv2aopOr,behi` ߏIggs91>f$&!ogZ\D% *J Z#"\Cibs#V,b]f'Weo%|?TuoCpп38 ( <ʓpl@ꋫV-ƌ mc$̶8Fr.;F ZiY1C=A#KB(8OXKf(*l! H*$ioSzb-7mчtV8J"7 DV(?H\ckD͎`x@N|wYWn+6w(Xg}PAhy^[vSzGFfy{Vp.)\fX7L%Do$CH\l 3.zhBlh2TvhCNMD-)0 8BTz?ډY\>W8Oye?;) i4MEhТ)\x,B8K-Bsw J*8jH??'m#b{fG g/5p$؈H vgOMcB-z#R+DgnmF֜'$vuTw{VaǴ CwR!J6Pc8-> [q5z犙;KC>^ȥfaMUπ vmpmjo3OM+90,?|Fd҈+xHn瞹`+'6cVtbϋl(Xja}4~ql'w zyMt#ý3C~ 546X9**8kIFm*!>KssCMN3GCOtlcHբkVT@5FL(1#3 NC7c&_7|pNÐI8(Ԙq -5{ƒQH :flNv[i lIB` Pz=uR5}ߜbT΃r-}EiC'WZddVf=@m3LL(:t`ÒE}k|Sy"[DqP# ĶG>2מb H/eV+x9:hBg?P*f{f$| :9yU>cLEk> b/kRih*ى9v+#Fʡ.RKj2PYK!ҍ# g& aϏkU׻F %ai4:&fZ"ozlt ܅|$v%3Spܜ@d:`Q84[D8$ه?Ab0 \RX)^!|mpTXkQY-=35sTU^fIM8xi Ths9L; o+Ă2غ+l\!6[`+2WVmѭ>pŲzGV "A[-nKJIϡVVeó4;C%4~IP[)R~<2"mx .vj/ˀmS]M^VF=5+maow|('xі2Uqa)ɡj*buIX".VͫٸvEʕ1#SjVD^>ٿ%~IB Yzk}AZvٻm԰<4+6۸=͜ac橂- y脯<|- [ fY]!jO *] 6a;Ԧ[,6kV X6;$M2sfbί&estVl}NFٚR7\s*Z&@ Awg`{^tIz/_=fÕ,3 e`=t! DX^7-=V!_ouv=Z|lx{I@opY,Z+CHi:Tbni]{IT5FWUCxjŒě _@K?9H1sLEHS3BX q< [@ȧ2E5>`4z~ 5 <ߟ r^*snZ$_L)oԣP-;XsKC{ ]2(_=T|P{3* [NT 2+*䄕yoXy9\k0ao]ΘN:Q,)v7wvXY|ėPpLOզ7vu=8:kZ$7<߈(tV a%!7}- Fҿi ǰxK/ɎIL-/HɉKVU9))E%Ez\\y%@u)E@l]`2uK:%']xT=o0+ $N;pN]2t(>FĚG6%b>]c=QCy(0GԻMrSZ홷+HJY\J e0آ ~1ʯ:%oY&zUH*57MMGBy$vh$FVT ۬)N'ɄV$"x4V>5Q݃_Q9P[Q g-\{)ϴCʟݖNdZ pwM%US\R\6XS H .JdyD8zݐ_ ۮO鴦A=Wzg6 g{R:JVD:,&̖-Je2A2iAV;3Ȼi]Y.sYBslVx敥n"љ 0a:79ov}jhpE)Hp" "t{ Z/RO/F竚+'u"]KU- Fz6=HxKifCaNu!Q\]:=[щ BNIV]?_߳0ruK5/SS;x10Sl%0Ʊ bd lŭ7YI8RV9g6>&w6%)өν66FITYXJb&t$Џh^_3Cvi=g#ȣdIĩV+N(M[h #`4MLJ0mͤĭ.8a'ѢB qMuyI) PTZJŏN:dm._ÖY+Rӧ7C#|Qu + R`TA`5#ew~I.8֭kZ\~HhAvwlaEZa#_xwӛv3_βѯefDF?=~#ı݌^k.~Ka ]96z \I4eSd=vhҴs9>ԢcvL3팑WoL^/v4Q x\rH裡}LGDDٌ%D) H 8(@FdW] (I ++/wzX^T7GJ*7?wjYU2[G)oRM[&-ߟ)nVY,ԉoSzNY[4/^|RTj߹Mmn~\YXY5Kcu[͛MVk4Vլ]$9jemVƪV-ltND6YVmր=ߛ\F^+7UVjeJWіXofL;k"ӥl^WuI6 U͉~ ."E#euQ9rPc,!L% Tx D9rwk=3s3Dk4a$8Tj*MTr׵n.mkL|2# @ω7& *+(ȳp-"0uaEd5vF:4V'_|4U.OWtr}r>|Vsmtu5NdI]?N^\˳)<6Gzud|2>Αj|2UEI>>"3Ho4^_WSgu~yM}#|% <4&`Q"3P߀ŧWq.''c|ޘ^^_6W:E H\*i+Q>Nfb"pg3-( ~K9b{k;͚ƶUVoj5hDв= ޽k ,fY̆8 },*!)nCuΖQQ8rLcvXK]x ߏsosO V@1` .Դٲyy\Q YU"L\5` B-Y[Wz>lfLf`5t  0"zQw`jJT qn*UٮnA+mLމlְ"ق^E&;ѻZնS\Y"[̣早%5ʵC.y^ i;!0`ElD6Deuuf,*Y\C51hd$\;s* FvX-W\fm;TJ^a,ˉ5#OdE`^A!ϠYKU )6?757pVshAgfxrR ر̜2YG6^Jr#Qz&]: dUJGCOSpkƀ3A/Im_+:,l쮬6RBӳj#Fx{j*[Ϫ%Zѩl (җo s'\-؁l[uj1!ǃ*Qlz(0=" -3؁e],OԼ`eR/z!d#lW77cpG%dA$}Cb{Mh%} ilQS%22aԮq@rI5U/eGsr s1+:G+.t !D` V~Bt&+Z:Z6NĤL dL~#2ovC?( r7 }]JP=H #*fg,B8TKp xE,fsL \ 7}&I)9o G;e]:ju@ޫ Ԝ8<5B83#$W.45&ε'$Bu@3pIu@hm % V'o$ ]XЊE3kKTXBlY9G"{ `TPV?Q@1\8ۦa#Se- >\ I*<ܭLކ&᛬;\$DCty P& %}!Ԗhy3I%(ltՒmЉb#1-RakVl+dyd!FyʸÝc^rU.q;Hz|/{O.;^9qUL;Opx85ߩ YZC3W064ۗy3ߥMTmydqYHCV%ǁkժ ?B02Q,s\`NKt /H+~KW:+#bbݔְ(l-0Ú:yV5JbIHWe|ָ: (+neT?D.9)7hO@<Ɖ2>ۡ;#q_<.UOLHdeRnZ Hip2W[jyMBՆR: 6n1CKm ۡjF9+SJ8juU@]'HH̥GKFPWԍTP}$a$!#3H<ר>J6&oIW,5]c;Y 9u fM_ZqC>%5Fz!Zc JTW^bCyXBϛ/\(.)XKӌ/^ħ@YL;/H/շAפsz^%VX)mǭ^ۥMOE+n>`41; BW9}erl)c$u QM0coLAGbZدoPB@". 1kZW~115sUb8o $ؤ ԨM&\ݑN,!<̬-}30.k :aO zW /Žr1Ct a=D<;>6#|u/rz>8mϑIlYH}ǁ&9 PYTP=N)NJFg [r#rN9cY4!CY SE KRaFa*k=/8ގ&t7ꢄ\/K3D*̖!Fӄ>, G6 5+WdqiX&5QՆуYAYP \0T B7,*zb-Uu=R6,cN @@@)5\(<ٝ; m+-vpfV=ZtG at՟)pJ,䏋CJL iqh?!X7:dC:9feR' k9ؙB;-=(@`6OG1%\|NgOW65wtdTAYUyCw>.`Pz5z:7tLUQ6n*6|5ܵ<{!4R!f4h7VƪLMʝ7]Vnb+=[f0VŎ8]GЧ2 ga]ZS~ ,٥_#Z h d& `#j$0;elg8@'B `ᣪqs:FP1 Q}o07%O_V=T_*eLYXm,/eo> >skWY_*T:[ Moӣ=jt^`C2 <^cNe~s-d.Ss;AMMɔK2d``4g L'w]uxtro=TSAAO y s$OAN T8G#?wek9qaM f܅nYS`];3=n;~3jjJGwwMF_[ XCluUe:}KIT3Em{5Hz {"3N*avd /.e\8 ;j`3~- <5AɈL`l]8gGH'vYiO;tcuk,ޠиd^NgS+*X`Qgrݓ4ٮ!Tz7&khlI(ws;61RefWHx<ǬV:7@ǁ.㝽=FR n8&{vC]pwGP`//v"aС7 \BK}%_cOLd~ χsp4b~k0>ya~:gBw[1 6qF5IY?lX Ѵ5K>)+.4K#8SJI\ŗf7c9dI6p(`6]NMe>鏅ζ??[xɗzr_wI wӐGBbWXupGe7\'YҔ|ŢҙS ,F,bh1E ^` 8 G%Zgc՚u'0 jue)Jc vad9 Űn(!YC,L$.{,)(-!(}$b ?x,we/"Io4+`'E"sN$^8Ei]'ˤxIld9DE2\F9<<*(]@Iz'm| I! 5N XEhXe9 9On ˖8_u $^_ eOaGfYqDbzM~yd)4",/O*B'$y KRMYWiL8Z& _']=siQoXG4k \VhJ;Go[WR#‘UlnQ`ŏ[wpN$Pk&@ca-@c͍E3`s>@7 N||`U0iaF|ĥj;eUZ7C-7GY"4jF8;`%yM'+X9+hib8he5J+M[4PagpwLW.fZ iѸ"fFW Ẃ>HZFm. 8*W%Y)ރC1h:;ZM>¥7%ʾ Fw}ĄFV_zLx5< GÿS [5$IsF#0Ya.(!`XEnxK{LJ2y2j!>Ÿ5ygP*d fAOr (Q{"hz0*q9j/?ߨXPwb.oQVqtUB˸0#uh{cA3nhc* vqj{ay'C솵XJ.Cy5`Nt߮\ FB]k?=\w uG\k`j ;2|Iϥc2{*a$;F٠K8Y= k<@i aeve`'/#y4 ѐ{> }z7Kբ.Q#uWʛ8vi*3chOB;C?TQDRS: .@c0kY{F5Ex:% Iup,IF3k [a';-.MzQA/Y1BBi7U(OC]+w1? klؖ+={z1nv mQhB  Qڷ7pPVo[3lLo8 IhI;N~#0􃷝@UpA{0nɰ=./3נv'~l[tۥ E7_DPpvB A70[ReJu~0;H :?Dg6i Ҟ wDH7 ˋ×?Ra/~IxeTMo0 Wa inH?.A6ckD)hIf㓿,ET <*-&GCZ9 HF jЂ#rgc S6B# DM-Cդ8"LM~=Ou3~1@ x a7q^s9^@y2n`D>yq]aR8Z32a`O!+F"-4UӢK9NB' CoB^3rt4LfX NjBptl&>ΘRg|  QA/W̢Fj 1 )@hv<8gfܓcC6^֬y*k<)IÖ|pWkDA^cJjLC֢bJ;!U ;A1B#7QXcKd]{DtWP-x340031QH,Kf;-䬑V~Յ+JT/ XB$g%&u_R2K}UB}1td04(Y|\RlNBl݂2bkj/-2g()] ;8g0$l,D0ߧAR\X4Ʌuc=r]|ms= kf-Y"tB$V!%"l70jV 56T5Tp8'%UaTinjq~^RifN Cn}:3-ݘo+}<]l>TYqfnANnr~^YjQqf~(˫򇄯/qen iz\'ڴ=sCW y=3jX$x:8g0qoUݩOٵm[bp$%@"'-'^/[9\rNG-|:2v.vk[>~Ix`"FxZoOwJ"ݏKO& l, M'Z,= ؀NT*m;;3߷άg'͘,0aX@cACX!M``z8:aY ||1cX$7d΄`u:%f+?O;w$4] Wލo׆e$;|;5<$.c˅7]o|{ 77#$@*N2Jq]R\㣺+c RMPXPLXiXr9 o-EShgCȰ],b]EI8;ޖm~Ր aZȦ%lw(|U6N IfTϲI$Y2M-o +cIZ$lCVcU\6i n!`6G@5_ΊpKM c% WgQZL7oP66_E+3!W#(o64Aw\ sV!O#][򑳰v޻C}*8uդ=DAI(ۘlbRU/\< OЋ% VM(Abh.i_"/{b<#I\A0VV+^\/W;va7tlվ qoDwfI"L#S6\g}Z:t~ t h-?L_gj?]0g }FD]S{vsR 4/ :OqeIXx+cFn(/sMU8%]5Y.ݳG/]Ԝd(avo_G5%3-?jq96ѐ8i s|Z Q_}=\䔡m{38}Me ~$s?o;_1{x;0꙽Y?[K I)d|T\pШ2REbӳ5 *7#r_A_yIC*jZ]mnbrڬ*&)lP3i͏jXKnD$:z>Owզs)MHGklX1-xwskȒ3Ugg"F`M_R)Acji`5V.h2# 6C*_P! jU:i4wӍqaN}lT_z֭k~6C{h=>YRW 7D CMn&ÐRz'ii|/@&x ,\gƟB3Nĸ{9~_S]V۟N]E>tY㝇AK=[tjxvT;+BܫLhnlYA`/*D4 9M#Ck^dޛ\kjO5e$T,h9E\4oMoCKAbV{͔@QFj xYS*8kV [I\PP>M 3 :LOM${Nw8&UI}~J$Lf2 DEH8)}MtM*狌~d!=8Rj-U̔" S aff,t.) %"@P̗SV |ZͲ 8$_kH(Tj N8n^Ek-*T,h(V!ːoGr)F7k&ЀlRrQ+YM#M %2,j^4m*%-)Hmt-30%afDzۚDU08 ǟE T58ѹm}Q8VrҵnK/|(0j`RAgBui<&G1f4y6'0]KMqmAkU`v`3lFQKҙqb=&y{К7ݿbgbŰ~qyt9DVP9A'OtP: &o޵C~~n'$P{klDD%K)qfnH,FQoJJG1BN|jC'uLf '/n]>4[Qr&c?tb~o]&VY| K$cÕpg,^CBB!OM)BBYK?i7. .&.~Eb䰍:yX"$gMt\of-;0!S[%-=C_qkvS6 ҿu/muNh[Rҷwt*?M Ybm7kV93y3_F 2=: ÕyA>呧۰oa1H [#sNR)Ua:܍C%JR@E,U)GcBx|f1%fPZS)r9Üp;vTi6%>C ҲX*fhʉ_Wg84M}ɾ+LW)śvܽkzLZNZHbR_in3!??l[#&2|{?f^&[ Un2RaS HڔRy&0ÔnTpY"K9H?*/&hxwrY!o+KΊ.LʥoZ/2+IF>rwڸA}vv86dj`܋C\snKj(ꋝm`^nLUuH.0ͦ7,wr w oH:q'; T&gF=Z]<t:c!_?,r>&ZjZC[{z v6y]F:?l˹6TTtTY)}~ٶmnEeU"qptjM.gUn$c_` /%,3mL'8hJ?hתy/$ E+GcҪֳ>޾P l$Jo{/Rs)3=3I)3 3 4Z!6<>9WF907*}/,iKQzs`eOt}Y;/WP^Pi%< K]cTO0 HzkuoT|a@BpnqO9[p.yI8Q?n<yޓ1Vz>"]lT .N*8+AT@{- Sƀ(ҲɈ~i qk&FӁDN5nVЅ UcpQӢuCv\K%!mm=gHKM5m!7ǖFU[Lˬyjh,LKe H܅-}_5q% myPCh-]04`;r1٤6}J_qObA%n< co n<$\$'"w&4&UA^L`S/Mѽ?ytOfy.(G`a1]Z7K>V\ >@F1' L`⭇kUQݩ{YQ"{c0sg$Gg?p#?&E!“;Q" (7ZrqD=/beb$?L! >[HEe<`/;w-''*~nۼ_?.fvqu`o˥nTR~_ORf00n9j[Y~bs%b:!P:s҃?kaRl5D } Z+TfK R.+v%u iۍmDV - U .u'< v( ߯!U+KHP9OǮFA`k4Y- ~ɳ [&HPtFy EbUd[JEh(idF/`B9t?:=\pX X1f!umBo 'X1x”dEe![`ZHbw ʵ5`;ز0Bx,X(_bV`E|Y6_T$ԏ)\W% ֲ%xc!DD+?Il6^*إZ@zv`mϡ4k/}/r;`{>tl!D Cvlɡ"uMtd2)?l% ; E/i \I\mt)r-4`Rp{-вL ;?@_l>CjC/UtahRL?aXtx65|Oǡ>/701ԘJ)-lEƷƄ0ft!NhcmbP*>h$EQo0}?kk(sIjG(?CNȌtbk{'}u?䐻ɝ1 7ru1"HBZPkA*1Gp߻{Z{UZ|v>Zq^w|oQ:֊Ѳ2uWe@.ݖoH&f60j:YhwӡMǍ7(RqgC[- +D 6^)Y!3τ?uS|0&y䪜dOsDzz.CЛNhmi'2@1KC"xrOҿEʜk[v?>]/=FZ?WV8 R9EPLG}?3 nd@IJnb&4cd4PL {dh',  عۤsZϞ1Vryx_9gp%2tHiگZ"+(zŽu (UP,I^SQ)2}j\sꖬBoץJIImI#b;v:ST:>B&pZhĘ9l8sFKUH 9qU TfR§a Z-Z#=ָ|@m~g%B~kZ)p.t9jHmPvZI8܊$4+m"u9 {U\+re7թD{eO1$h摬Mْ.az8`7?xǜ*xQv$AtrjV"S IOڭ*u\/4*MDf7&ހ?w%sZ^L: -ǜ*t>t~ ^$-oZRJYD^lbGe ySwUşǥv R|ilcRGם;k^d|rvD*6S_rRqo袂T]* " \奺r4VP#G%J7Y ]+tX׼+:6(z*U @dY_p)>C$c@4Pe?P\~6H?"6DBHfCD3Cuvh ޯ該 53cwF|g9ga2aUz䒙Q=2_+0YwЏ-Ǒ*KTJbM^~soSR4sL0\ꊁ_4 GC8 bNȢ.k+\{3r@΀:SA$y݃Iu$2IP }_PqK.%| x sYAӖ?Ly5WmΊ7_5ZƎB3O*"ʾ7-Kνx}VQOH~ϯJIվzLb`Gi'kc=]7khofv̮(!XI*U- ecdA}SȎtBW\N*K>~Hn+%i+e-2F醑*vK1tIy%$#3[+T AŞ]ڃB$ѹBe>oVΒ1ґRԤtov*g)/XV{52p0ho^ojmL5B106N9Ⱥf.Wu·YZ.Kx JT]Jb 9dnk}r32_ƶ8[,]BCmw]h_[;J rXɩc Ƣ huH$bSݦ/)HhG߂3/ٔ6ARG#E䅏5S^~PSp^>lA8_=,nF)\h#?a{?/ }my P^!^G` az VJj娼y~eJha̻Y~J ޻]Tع6Kߙ3O(4Qx"8} ?%/.mˉȁ .. ??$נּp[gnl29W%VHIw=6қ͖sU#9Ap]4u[d43k6͆H0$ O R{ZUX,B% RCV)IڸK:*\H۩L4&"tqW~9^׺S]N(w?e'vG U򻛍!G1&A4;ˡw\ʌ9ʾ:Ny%'C RrP ~Yj%yVZʆ_9gǒF#/xio~ū l%췦^h, (E$aջ{!9<( S?f. Az,;GϱۧçgÔd⃟$>Ҟl AOm-{'1 RN DC?܁."ȔK6=:(z$ w};)#{9!зtϳb Hs|ɁЅ Ԁ=K3Vm?kDzxС0Fz\DGtDdTH= h!dh2M<TsyQW6 ;ĄBC䅨rT`äՐ9QtI$E耈ǂiS׋B[Vk/ 0WXοcc }}} ֦u;!z bKc̻4̜cs6kě-HԚ i؝~mNMAӚ$B_Z~/aq\W#ٙ9Y"ΘY/էSJGB|4'Χc L9+Tj4; >1,n}k#23Rc4YK|P˥U͕4Wdɓ9c. ȩAz_,cC"!^20KϏst_,Ic7Γ,:.# _]=#*v^SE ϟs{2D1"Njt$,Rb<\ PT}G D`gXj7+$Yii)i/tuQ"sMmsOwi*ꉧ+^D,^S3]WTF 'LӁ{y 6inBƨm'⳿"T.?_΁ > e҃pѼ/Ӑ/W3MAT>`≮y7;s6HZfi̙SNUIqVgW;S?r=*yUymՅ.@5! &7W7t9=+K]uZ9R C߫J5ORUҮe/ Κ4 $o(#.Gdb0Omv 6·9[ s0k1C߼݂Y0H[QEYS4Eը2RP HF9*ZM JZbR6{zzSheS1Kg.M(ͽnbT|_MFB׾Yaʙn_j6 J%FuC6Ej.n4d\FmX@O:Aǻ>7lsfZ=~"81#֧U1I UVn߅XpQ1¬.,HnW*3ף?|ηxbX-yqqADFߍ䇪^h6y^tk[RdWr~?>Gqa#SޫrBֽvm}Ÿ8#P2/ki'*"Xkf'iN5 z`^ C,1a7 3; \Kun'UFK$@c) [Rfz,\U0v[N`ɘʜy׭O"\2tcRfV0N`G͕llb?q 7V#g!ՙ{.aa URC; EP(A\ r5pyzikm?R&ip}4(bN3Ϫ򃨎Xhbf-2% A&A{|O$v-W{ɷG+} h4-z6'c`tsQsK>N>^/x:Z s?7[XTkɮACؚaD{, FiRA u&faM7DU_KE]3`4H›7MH >*Soj-P zmk+oO.L,{2!Rb+`{ڑ܂y6z>&w@yhA+-Js_K xks8{~&W0oTI\C dW[.8?d%ٖfؽ6ju2]xA셔<x=uI4$t`Л/b'dV /i(AhcbZА>yh%RfYvÏ #c `Nl) ځK(bg>2'Yv%9͉Kڋ,IH8 O\!')t&%s|YE~LbxK..5 ID}1x7_kA*+h,ElD4KHR>e 2NgpEѿڦ0n?g$t|VjPaTJ meM!Š}b!'Z\k/e ?69!wҸ$ <vɃ9O @G2"G^vؘLhLۻi;sx14 GS20t JT1Ad~+s:DWOyq?n41%ë1P1nT1>N܏9cfJnFK^Y|`RA߼mFe$w7bjp:.r<ͦ>Kcs=f874rikU`T냃H B( gC{Cul?x惑\L\Gw(fK1Sů_/>rUIԞ;EɝK|4~ ^zwK^O^sk"[ oшxA$ sD839@$AO)uyt`*zA K1Z FpR;p b,̩̙a A`!*@R*[Xŀ ($jͼyh:A!s-5 gY'bt V_|1HʋxK&aT+OٶZF?֠[c0kW6Sn/"*)|ߤA&1}Ȝ% ҥ$9Ìf9 6VfH)`820dOЋ Is<laBYk(xp1)f҂c',![Rh`fj?QN/L)%+mbڃ`{%Y?#bᜃ*P P˨7u49_foO2e,u I.L/bfRh1}\N=@+]B9hjC*|niX<3p`poVGVחs8n͡513W]W.ׯE Hԛ4N$PaH IuOpեyh;Ml`K {䆭y0 /VQ,|Ř Ć"j] hT3t) 06$|!)Cc%١ˀ`/Bp㤨x Ny`Xy0Odc51 vQO(k)& 0By2%τa" Nؼ %PSˆ-tďqSp~NSL!U7i餅@QlϠJl6ml뗟-% a~.ͨMAKNlIS8ɳLSD {pW2\Cҫҫ5˒tmEy+dBMkw1ar4KnVst_d&Z[k;mE^FJq9d)#a@7 Rh*0(aXFRn!_F3j֘U(ɿ7n#jsiEdA9ͬʨTwN={K$^fSM'@?9~ykbTbްKOlTv: 9:1`{hU[SyyXϣ|Q(z6ω7eϲme^;~I&XϕJbͷ 9 nB#rӐﭴZ-Tmӥ`P_ÞC'tfc*`=HdDI~ē:Q˹㋅=r-ԅCZGŭ9WL>skVɊt YBYʫе;r,P[8!q0;K[="YGCMD}h:)B%qpu_T)-Db^  d>{ K;[S]nd_NDvZ2|8!f "\.2qVBdaH\U?Q 5 VMP61d[NbVʖkvY3#OKGNgm'YMTᙆ,`_2 buQ؅[!"_!j[w[F;,*nに :۶q;ÃVjnr+@)sy])*Y"ǵ; ;Ã@V}TO/q *XPS9j/h))d"ۘ+ϲvIh_"I'73#<( uj+2ĊoG60';d,q +?r,C'ikV&͉蕩 P VuRnA^"1q*Ycδ] ~Q"` <%&cV^VZ'IW4*#_ᡭ.^eSndKN#`v/kMY)OkIP`k "6ZY YVUTt=PoiZϐaf(Iۂ98S.ySѵɓ,;}.ю.һpխ쾂Bl]|^mffԢ{y:! KbU("3D YK.Ӻ+{o|<ǗҰQG%BNRM]7lti.R)E^lޏh#RuZM+ чPQw%~\˕_N#O+Uʊ ֺR/TJV= ^j~isw%-5Hmc{geʚ5"kwTHQյ L {TZ( aJյphjJ?p՛7_gaO?̅G Ɓq~S>hK$`Zk4w'F!ӇKL.}zf +M(h2S$LS&(Ļh2qo2Qt6(LY8L#<Q^&Jz Y4xij'!pNfwJGA[S3Ok=f4/Cxǡ3q4 choM%b޻Qh_^,N8t2KGlw%J>I2 NܘZ%7 8#42 R|()> -8x ,qC0Qd#>8eWUtQtڨ ʻotVy!nhPנ浸y馨%zı|;?: v^O3 f6+UA?6Z ˼OYQ+ N/&fCAPO242ev=] F}#]CcfZup_&j5t-VuuI;G[;$\Kz<%K[P_KfmKwe݊7dkD*4k!0w.zgp"cx1 C'+aOw)˛bZck6y߇?JnE׶췻jav!KSN7}WA|wL|LX DT; ąnNYvPTٲ;sYض%a=)Ն@~4\8G_v+p0!}|(h'dU&֕+[_ ?ر8omshX1PLc?([O_P!pԻ;8rsB-U# `^?zյs)nۼJju2B8W"uLJJv#z/ |Pk66O>Ss>_+ 2͟\f⬉&tXо|B!Qځ[LᜩJ3H%猡XƵX+y®{cKX}q=3sʍsN>la`6qM1qS?ܙü8.ͣKC4[3k7T)".=^Qꔀ##{cIn~M{Ygjq>~}߰-w<-@#?CAvN&leߏ[vX䏱 iGɶC3 ,>V~<q38dArئmspe!Q2e <~a}}~׸.;ZqzU`<CqEp܀`,>Ev(n.CctNt:-,lb4?xWmoF_1r*9~}ؘ k°6{`N;@S7}yfٙYp#H XXNc$!lPd0fn1WI70I!Jd"s& )X<*Q$2%D6"Q!]Ld4$ _.=% 2VE+'MN̿cgc /a3 ոZ;l#Oˇĸb,#oFC/-r%KF2X;&<VRMlXEBІF^FjH6ʑ&F.mٲ A:폚I{?^ͲtOfݡw* ^_uBX=r)[i \x|Q{MdMB9}iXGa=\?UG^EPǧ+> oBuiEM]Dk"hb[;;ý+ W$&x_.wUs q,#SUJ4|y#cL+_63}=@yj'/f/mxN0o7hydj30McN:Ae:ox%'],e[X|{sO o4~1MgzhSFhs9eI3 Y~̙ݰUS?Ԍɚ\dcf2{nz7DD_t;vX%R>{BULS젎%#Rs!//@830 iJʝ?L}0OJ&{`fvb|oaԝ4 փS$'#%+.HƶG:-RkEl@5ӑ#BL.EPiYST3Z&tUf?Vrx]sHݿyO7pU뵫m*\ɓJHZH>LI#i$fz{g;ċ,=>͂9q8?s7fy+7/`n\$, O,Z{qarY;XE8,# \Zъ`;ذ(" V`2!pl- 8= Gp Z3Df@sl "'gl?ul֞@cBƸӀuxKf|[t{kE`L\F3' ZpaʆHq[7\w"ZQ$_(2Nwf'4B-mǣſHqZooH8WAZSkLJ -eO'} #NnfK: ߆[hg2e8<!3L? [ &S>>oFϷ=|uFC4hD:pp0#d>? Gg1C}xOÛQ Oӧlox8"`<"UohI)fy:dt;a4pS7рc~WM˔I^||86n& t/} n:A$N\1Hpx Az~ ^nbT;94 h}qVMI78ͺnK8 `WA UZ;<{:ho$d`{XBTmXR׽Vt9'')Y=gb_'~ngD[N]lSs_E?:EﶂFm۲MжP fµ\Ǵ. ?F|BrB1\yq %y-e`|f*uF pXepgth}Yn-PQ cL^KbǶYPwfٯIZ.#+%繶B+SIG 5)NƉF&&W USU$n9f{L{_=elgs-ͥg 8=Tc) }`QF2 <}'K$U|3`0.|ؒ'XT) / A8'9F;s+~_& 2S:;D6ID{l8+ (pXy@ me#\θ0.9Uz?h osu;_Q8%nE>;"*,?Ţ^+b>Ob-j(|0g${}ag.vf`J݀I$aa-D1;ÄjB o|7s?- ƐkkZ!R 5Y}*@b*+02Qieo+st(jH#A^yo,'{`RXvbaϖIVoRG v*6^$eHl"c:K+ gvb|ցn857HY եܣ|l(!_1(s-Խ}HsʾډH#I;-7mnF xhWЌ1pd뭼M{vK^|՞_Kmclx[`y ~ÜVȨE"NJ)hMhD: \tv6V,p價ÅIaWtָ:p|N+TL{vnQ^*3j()hyU,`AlE?Eh%\5j!}ɩv3)TAz#0;: ]~'i.:8|Xڪ xbD-AClq]ɝT4"w[aKұ!b{wvQUҷfMfVEryU*UZUYOPuD(]8Nqx9ݦ-e1Fz-|:>#r'fБ8UKnZsZ/ʵl@ˉk9n0*wmJCqb?Hg Dsw^}>KAۥ'2ņ 0?IͮvҒ,\fBL.}0}gf~목” ;MӴz9kqՀmwV5nQ<_pqm28fQQq1SzVƀ|\Pb (o֮B;hqMu|_^Bp' -͛hQcv(ْɦ}㨧aRǚ!qc 1E_k aL]3UA Aʙݤ1W%vSJ=1]#\ ӥ^X7|xS=O0]PK~‰.* (eKb5ؑ}!TGG'Mv!}{>||rv5C0 dxs9淬+1RaWu\"#rLHx1vvBLƁl@B}Fp"t#(ɀQ4jF pdom!` N&^Fܭx?)-M^\r΋:>bHB)6C_='Ϥ ~ma +u5Ljc (T5:"c-H fhdwTB~9Kj>x}TKOQ*PJir,̔(4QH$ vЩ3ãq rVc Ӆ[],_K3)gs;;G6CC-)d(hR#UH.HyA8ioŁf@yҪMCXÔIpSq^u/O/<" 9yL jX6tC4z`~K8S-Z~fN%6[&ryY2 0 XZ[XX+wqYi/@ǡ`Б bQRUTI>O-7 8U 0y0/%uњ") e{ l]u9? hēеZ)ڂ+VL;pf 6:Qr[j91K<1h⭲cnQ! @d_2X΂XD9H&UقAbj-( Ah}}D4[R;`gW<"(1;Af9ɔ ܎F9;%1~1nS%Lԩ;"+VZ]+a\͖NC"·'z|3e ^t/tקw 0讇LL'[_ CAPpMSN$:hndHz8wE[4bjEaNT9'DYw9Ѫ +|>F b]J-}(.W?W}j˃X0~L/m%jTwdU|% N]t{Ʃt}֫oMGq5L7ozG yxÿgC.zkO|phkrJjZf^B{ozzjiQV !:- g=CWʙ.E laƾ]+acnqtHmmu֦1.eLG&X+ j ӳTb^K1\01 uV #O%ׯUY`u -" tѨytISHҊ`2 fAoz]7|>Sѫٴ+cjuHjP1`ੋhԩnؚ:ϱ*ݶ.aPv&oۜd{ F%"sMKPQ xɭӒm\4uu- &1- >ڮ w0RS1Q*6݌x\im{4 r03 $[Sڹ-^O^`RuA)bokf7Dcw84dvW#%Ė<GDwYJ+<|U3Ih/f b@m &nEG)g >Z6%pYYWA[6cs`ݗRO : r^hVPĎ{*C%_,H 3#7=$¤"`h!s|Z}vxq5{-wj;.i QHnNrg\v[goo\]>]N>}e%Xbrr,MW[!dbd-05ˀ]iM>dne>N,6` ݡӞXbA(i]B:(MdDZZ 8Lbqǫ m¯:yS72fr t { DӑhUDZN?1bפiLzu´{} ( /Oœ $d~AC Ӡ\x }d~δbYבݞPr?fQhbOQL˂e|\ǼhQd%*)ֱ9P~l|cY0#qP|iK2ʹߥ;#\PNL;n [\YesL,&JbS.cUm)V {ucfeQɶai 2lwZ":kfoz& 3+,wk3zSW/B74B=qH.EOeIL;: 4|tW:m!HYsB+E[anV&hya^-?&Uh/An1Ps}I{){Skp&/lV{dAlO*:M 3'@#3̢?XE N3 &j`(xj\ / 5#4 zԃ \hJmUL+s3t4[g?^p윥a:bEŦ?֏ 'F7l?DߎTF0jVZo*Ah xgC%}y3 '2hJ Z:A/@fQLJB9ԪZj)7*" V-?~9 hi?s:RsBm"{,u$HѮm*@Jgk!.3VG-U8"/[{ո|;\)>kGŋ`,PZ7X''s&bVq)3x ΃1dA8]gAxKװ N?agK?qEޏ&HCy8 j n /02?@[,F843k̼A! cyKwVNgw7xi4 1'ӻ A #t"rN`N@2|%lRKzuvvvX!;J$LQr9nT%߾iS4ϗx%,'ť^fpի`uIKaa(K;csYtvuE)+7.Uc ae)FXsdE4[[ ʯ5TO A%1b/Gdӌi)~̯dwïL(ppq¥kA;eז%0Dk˘i?)XWkk)ܟ~t=ndFtN^ja@X|D;rMK˫r.6eGKE:`칷5Wݼ`\{ӏ ~&r^8/|3 ux}U&_J탆m*p d27pl>y/M6?;F WItݓA4<{eiEgӦ3 87SmN8C3~F,nNao LF$ͽ"?٦ҶKou>Vȇ DZs^~lPJi 'h`, s*uUYڙS c:ΚP4 0@,"Jj=rتhVi7,yB`Lk+)nZX?ư4XrRMJ'˲ -6O3!(3pdˎ7ߵw=.e`\ n(]gj i1bivJfcu(x= 2; soru^mNXCc#[ةX}]ޖih xr!WLJ^]`Uhm_Ǡ0I+WU4=l .eq,;0sP(Hq{Ouw/`Ɋc-t2ג44Z~1rw56Vm  O)nuZL@@˳`-KMa9Ti7R Z[u<;-deI( &IE.CʭgTh3I>cwqGEQݿ04ˣQų>'+JKȣcxxINH@ C3ĖxLI3Ke5JdzE%NG&'W^?{mUv_tLLP!S0w*+^<4[pȾR}B_(o)i3 Kɉ8(;ryE F Cy2;pCiPk`(Hks/4tˉ3_NXNSh[< }*wҼ}tvzOca.;5.mJݾ$7nrc=$2dI@ۊ3,S|N^u`RܼwtsTsA'5mmuz@pҘAN=(DX!^g`ȂQř?3g^O;y3v-](gv`"\ eiTs*4Ti6lkU u+w}w_:^ J8!& f粸*`]sKSq"BJ_1mѹg+yR߯v5y\oA4by*tNozՙ[fWxTAn0 RE`d!*ZJ~i'خ$̈X*ݯ?/prb4F/)ȱC%$䢙1ՎT2* PAK,h|dWDKTa e+]6&:kBD(P0gRNj134~޳V;IIzU>|Km-q'ሱ 9槈V+7Wm'wI /A ZU/xq`hmW+BJVLT30Ј>1աU lZ{E/ n$,A/$u?u;\ sҠ2ltʏ*^Xmh'=2K}+%xo]U+z[FקgVx[^wC2K}^rNiJRf^IjzQbIf~nIjqnr~nn~^Wq P%5-4$"iR6^LJB"i 1HE&쪣Q:YHU*Ո  5&(d]Z__{w|kL#Vcp rn\]](4)?C>n!-h t|ՌvqF;Xef``n`i5Mؐ9lGZ)iz^h|U$4$ pO9:0ЁdC䀺@6P$@ IqHAHF1AN:*QOp `N !Q:=91'')19;P o7Fpir8?mTxk+KF:;j%&')Z(XhZ++gV*(&$g(9ć{x zz{(d)$epq,.ϜEa Q5"(3܄]'/|r.}s-*:ٟKer=d9ng&M.!exVMoH+J"'!e=8Ѐc# ƸY6>Um&e {U]]p}A&*%92 /*B]tK)֙|(/[9/7,)%_`]F*9$57@IHXC R"}\] "d""ăT&GH~=qmDIR d*EB:%yv.6epEuN62+:m HAǵBcEF]X%T< AnQ!-T%ҼLn^g%Ze\ǤK" d"Q=X>sPsƅTA=mUe&jH5Nr*ICV<׫)Kc`0/ֈk=< <ƒ `=z`f9C{1 c`[O`3 4-  [Cc5anz5\ئ7w}#u,g! 1'BV mk*s=o=k2 `#{{5T6#sfNrni u(. KgSAƞTNp59AR>tgG-#fڈS3u[ХcRᅻLf#Tt'%NH!Ί);iHHؤ./8.Vb]:  Y &+Mc=I xk* [fQqBb^rF~BIBR~II~作9řeUy F?hOa5J,-ɏGOKLIl*3ٟKer=;9t8 t &e ZZY CMkQF M&qx&$g($&+䤦(c5dFVɺlp*B@2$6Ong>y&&W5MrWQfzF NN%\yxU1N@EdG)$ "։P .T4:e-;lLP{8'l}h۽d_gڻfՆ BY,o3UqFO̩d$[Pi"z j0jb6B3ڠ%XI-{RpLAR< ]9ƀb^gԊ:k#Pkq\]7wAxȆo; 'e'<.eQ0)/. ~bu x*:Aɍl.UXM&2Y)$?W!7(=3O8#4'E!#,U!/_!5--5D!?O!"98(\TZP_mr 5d$VCTN>ȩ,kR[d].3&M.>"xmOK0AA+lJ-CŃyct4=>DӾ'~4V]s|{o{﯃5+RQGTDU|\VeF7W[slDOB2%JT-"}dL;[P50Q'(r0k HJJ)HtOZ7Y0g1/ÓU=ZbN֞{"S5D]H <ͮgUI hp&՚-g'tx7Y:| xʳa!, X='?`S.K/Դ檝EPCz\UyNQ|NbejQ|qiQZbrj|^JjqIQ~d>Vi(\hdCLO/NՀp& sr u4]0 3x!wt&-3Yi*iZsq)Ayf^J~BrQjbIj|JjZbiNIƤo7 cxk÷ ,|9řeUyy): FrJOfeWUHɉ/3TKO-IL-/.-JKLNU0TPJOJ,RҴS4)\ @Q7B/9?77d^6ɊL\74hx*G`A*JU&e5*?y>cpkPbzIv|NbejQ|biI~|jErNiqfYj|U~^j|fq|j^bRNjFyf^J~lVKsf*YBƻ"iOO-A Ӣ`d`- ol$b5EӚKP;HwB_1.[AN*';uܘ49x#okZX'k(((M>ήbhi=ًݔh6~lM8  jP4B9t &jL\֙^x{wgA;׈Wx_`GwWԢҢ2CԒԊ̲T]CMk{XOǪ;* l操 3!xw{t&3Y4lB223KsR3sRKSJ28?;5X!(U!3,R^_Z4y"^zjI|NbejQ|qiQZbrBUyNL(#5''PӚkɇ<4,.Px;ο!>3$5($3?O$D79?77?O/C(^_PRe) ɉ99I\\ @Q`\XXSдJ/֋IL-,O/PPuA!9a:UyuTS*'Sʃ]\}\% Wb(Й@gg3ŰuJDR7+58$;Xl"vt4xk?ǿLFjNN|^zjI|NbejQ|qiQZbr,!p5&odƪ9y.&P%%A~.@EP布4,}x;?AIٸ4185($5P#$;>'2(>=(K/לMMGAI5EirlfYT6;?_z|6W@jaiE`]P/1irGVJxλoAjf9%%֓X]B]5SS2 r+SKs2K1RRKsJ445u uTS4=%KA ]V]?Th#u &B5A C6aqy): PլeXX2s *EULg3c$cRGx[oABƎA!%9E%y%Ey): ~>>: JJON6g3#Z/TݎJ~Mǜ$H.ĦƤ~UvSx{{䛬y% E Zyɩ %9E% )$VQx@ r%r]rԜɧ4%Nbx;{JNbejQ|qjI|^bnjqAbrFyf^J~RZ~~Rbdv^6{bVM lwx{Az׈Wx_`GwW OgG̼rbD"ҢT=T/U2T!91''5E!$;.>3/$>-(b5@ EϱH$($g$)h%m@ZLbU w @(QP*+-Hə|MlQ]3%xQAKAEq=azH3Y,%\%;0lb~B\gt?뮤( }{;VK?.Sc )ML-q&*\[ƕ 61<&E@KG(6r@bo|"'%L'2(8$>7(=3O^G=;15(>'5-DG4 $;aFb^rF~1fp$ό Ow!LVaT;185($5PcvM^7G`W%ɢ\fL\^%b@x{sw|DkĒd̼r-r04y>89U.*3U>>QӚK]B4Ks+S3 & A@H H҄RZZ_v+Vf/CN^"fUMswM`SINIJLΎ7uYx;kC,A!%9EťE%)\ @@@lx!Cht&-3Y4&l:UA4i69 Wiͥ%)% Z9ũ 9&BxylRױ+4(3KRs&LV̝,*)W_X4-$P\Zi=YUd N /3(,/(-P) |$NǸ 4CJdxk,aK\qjI|b^rF~Q{Ivxf^J~B9QHOIMS(/@%嗔" 䤦 q23J4'od5*:ٟKer=;P> ?7;+ؔs9'og1arG'T0'9b&ᔙnbѡ`iӐPWnuS̀q&O 54%vFӹL̠ "ڝJ*x!v^|BĒd̔҂ԼRk..}}<"b+}̒$\rlݜ"Ԝb}Cs X'lŸQ2WpO4ϕ>y!d}.YRWjZ+\ rdEVЊxl| rwKr 4@$ ',5GW"n&bx!XtA397FLa}r?xL\ڢ 6'xeJ@QCBւB+Q֛idx+>7;t;|8|klU;E`ouHcOC/ T\AjsߙaDbL0>\`eu쥩ڝ!aa2p #G´2\8R7c2U?*i hbjUK˫c;-@H(.E(SY,">Cjv2*ha4 xW#.} #gGdHGx3,p) "dEd"ݪ~׋%;:o&eK&6!YzmK\x)XrQjbIj|JjZbiNI|yf^J~d=V"̼L[k eT^XZ_Z_1\G!sr-PlA! |Xx&;e*XX(XjZs)Aqj^J|r~nnb^%%0J: Jx|LBNT -xkȷ ,/jZs)cpkPHkFzIv|NbejQ|\: : J)JYE'sLgc5"t~6Y͕Ctt[{Ufin5&M._^4r*xk_ɷ KkDsHkpFUyNQ|NbejQ|qiQZbrj|^qjI|vjeR~bQJ|f^IjQbrIfYfI5gPIdР{X&od Ȧ X\'b󜼎gr*Z0Qx,搠Rʓ9-`Q/p&M.+&x]Rj0abMϡ7 GHc[YG*\ӄ_CK? 5%f{O~mfy1)-z8ÜA(t0zoF4NBTᨕk6Ghx#i(bu4 tP 87؆ w)SnqJCI[*B018obb [)7˅0Q`Q0 >85e=:#ȣ=4h]`d=rٙ"'sNܾB3"?Zbϖ5<]O|Oy/nNΉռgԯ&N?u vw yUE;7tǿxAn E9lڑbnP늀3Mn_GfX|ϧO :$XjMz䔞PWh ܲ3-0FR›K!a[JP 3Nׂlsw{9X@syP4 9٭r&ME oIonvJ[QFtZ ?Qס6zNUsr~w7OxSM0+|3Q jjԏS3 VP.7 65wh$Q5("?ɐΝFhs$Y'-bh_1-r>f-Ԯ"̸R x340031QM-K*Ia9J 7*O-N-*K-Kf]u~OߕU*Xmr, 3ڌU n;}c.P@E)@rv`;?Z⶝̣[b?xeK0 9EvR8 TE1PǕ=!wo461\\Q PWiݼ^rM2DԊZcJ#L%{<%…L]AN2)OэAa~E1G0| r\tLĹoX3xYmsH_ѫTl`*IpLE\7(}$^rI0Lx<g"Q_gI'r`ph)n1ee(ϣ4!N3IŒ1HC7^f:)xXtUxQ%kGQDY~yj%xy H GX1r!b99QId,/':> tm#!ssbSmD!2nn|Cվcucf8&m4$eG-MmZ. Y"_2.O4Ba#IEo"l{q'izPvuhTЀ^CcO)#aviƅm=J\\\:wm{5500>r m̝Oc >Z7\ff8f/fSkq|„F΂ ,sI̮M _skf9t9Dc] nnDSd;6J1͹sRq WlEX|W\-fSM8(4bfX:LkW-ɄvwW&By]8bNf\,掍:Zi;;ki`֒ri/=W,8\77K.fDof4fkIUb H lS8gN6em'E|y Oh*M931vv4`~;Mև4 ݱjI'c# XY-+%/eyÄSu %e %6I4{a_&;E&@Fab%Aۡ]oȵa%hD.]%qtsS K_$DL?-䵩e߿Gch5wTpi8̽vں.x5y-^ᕉUcjL 3+i],Mȱ8*o5GOyz)r>.ԝV`l։&0'woSY%Gpx}b]x-G7irlwVTzKMDmX_ŮV!mw&Llp]` ߣ =B|y/  PWrRSvo49ZA*I1WY*> :w8KX}\ҧAՈ :jP!&;CNC]9 U8]y@QaYwe$ x׷cvuDK%q(j ?h(TRbٔR3C*S=+[u)6-ף;`ŵRǺwAz(#*RfZB)CIlS띾t$ /Wn!BX4D' y2c `$ e}DՆ>VⰮk&^*MewB Tܤũ6;gIP@ R`k1p|i8ԑ&51RN)T1Q[JbM%aS:?qx@'1i8rzL?I#E$+)X)Yė{qNdA˜Ik$zpc! U<rhβJiN>+h_Vv߷BX084mwi˄קgp0[ÐembSbkF'!LoUJP[)#YIr:e'<@7idJ_ӫֳQiBoQP1#[> ?xAh1=ɞC}d2d#T-MƵ{K7o4KUkO 5;>cB[UzisjNMr쯞f.vmӘu=Ճ$`CNzQIDUd,wQߍPz?){`\ћ)$)O{V[o9*Gy H"Xz+TZ6ƹVIigj8|=PW1a^e<' "P'I[lhMSIy :'aԳxVasH_eWreSDF6/rb0Xqdkg?ܹf7 gE!Q(JAXE&FVڋ‡Frs_˕)U!趨{Q)dőV{Z)Lr$z)lR6n *-ZRJR1F.6ô)m)Q.v b<ߐV HK**m Z4.2a(+ۜ9b]o8h iZłNkˢYٔz*l6 ksgF%G([N0ʆ iJ%ZuH}riL/dY-*/8m߶\ PJ*xp6폚U梯Q 94 /Ѐ5\;W.8unw^бb}lӭ]`8cr7ozaϛ Co&=o:.%}ԏhA!voEl{єcԡ'lt3 nN8{NPGG,(r& >Fͧʟ\avPHj4qk.k^>ڬgw{-9"ϟr#X2^^>s9 ]. gu&10M)+TdderM.&Q<QCQT'<6 JhMRuh*1)XĘA-h1rX~gX%QD;nZ*r@nTw1pW"9'To9踨dF}e&zP.R#>b5L~+#VKl 2_N9Fli4bX!a|׻/dqpy3μA:xwAUlwC=sĦd<R8}yWNLܯ;۷7^ t~P ^@?31ua'&QOkT]B$_ fdǍutt:L2U?w e5X?c۩_%qy5_bi(wo_=-qG^ۖiދGkR|opvږ L(ӽE!ʐEQ gn%G70uG uC17P |rrtGG,<`e[g¢.Hw`FFq*ݯvvmCfxotl}^|TpJF h\ ֦>UiAC .{*] ~W0tB\$JO|~?ÝK~+6O<[<293b}ȁyHg?r- ~87?N_wtb ǫEden6:#d25~Gw0/Æd x=s6@I"?r:7M'ʒOe8I|HIq. ELyZ"bXLJd RB,H(!%5 4JlMuw?r`ț7'd%(*H Ӓ&t@ 3JI<'ӥ,CY$$('S !3/ٽьiOY<ݬx?%%h:`̨ "N"A7Ih%q84 73AU znRt*sM[$ ҥCfl2xC&X80D xͩc0 ~D4$tIYY "c=/fq4fr(6$!(΀^NjaVJ>00Bj9xJ4uNI">d4 ]{IZ|o9cwap;&1ǟtC ш {}ۿ^v;hI  H֡@uG^|w^w!Wqq^ ݋^gHnn7 _~5^k?>^q/d뱮:@w140&Ksλ˻.zC.;ם.k5,C&ew}db\ǪuHg@@]Emj@it;=5:0ٳa*~y&G˖" Zo2|Ennި73He2g~ī[ }ނw"ig`\R4g^/j^anWP@za/d4 aS=+‚c7ɔKyj.W]94ڬ`&sJ$ot;\ps=pꟗޫx}3u>C8A71#(Il2gj=Qp承j6:r|}pS0gO'N8(~!K?L#Ba h 8SK[R,4^罤4h(m-,^RBuެA*%@TIPL`j ΢цMUCқ 3=qDt} |Fk޸gpHH˲x7 섿6'L04pB #N}MCG֐k"n)"L4UFC:hXӟ  x AY+kKٜ\62?d0C*3ruBxrd^@h-ҫ8Y#0a 0Pa,G\o<ʛ{> ~1;-D,hEˀn*bI-@ak.o[  %v. 5IR&E›Ql !PWZEUu /1#Vހuwz ;?DXFrx 0̸F@hbL>V8 ژC4ƲԝyQvpߒh[UKv->ԧtb]c-|t`0+C…J1`|MNQ SHx~^^Ph gs5rŦ%,K yb;bK2\Wҟ?]@(LMڝ5Y.!-RS-W2f:1d JZ{羠~5=# t\[Z?X0$H֏MMr]TRY%lTs^@ ·(ABxЛv u?>ۊ\`ز\Fpch8coޫ>Κ1 !$Cw4 #ϝ8DR&\U Dfr3;{՗- YL\y3\+"w|el +AAcDV; '.U-)ܝ Bl+n3!6TˬA颴zv8tc9 b&-ɉZ:`F6NqlaOW V-,R/Tq~M|<˅Q%:Շ'.Z͙nެaAt! [ G6P/ڕ/ڶ߷D% I{,Q<>k1rK~Sx,_hx)HX$uKU@h(EV N%ey*휖k 5*.{a++*T-l^^5GeM%'TjU,E\M3(I2#T6=u>zGNө@:U沰lLثBD@Eљ`~ ^T,SE[ tXRIO a+oPʅMźeiKU쵸m 5MTGcu .Ǻ̓^]"S?}ڳdžw>`\{X_VbXb z= š!m5?#<E^{\rk97N34M⒆j|qTAgad&I)4^STM9?V^yjrpE;X*AjPMX c|F~:ӶrZ~X{m?gm+,FV¶Nvr{MԹ?k"B.{ Mϣ#bRʏGQf9^L`lެwev_*:AŢ ,ptR#LQWm"ǭ٣8e !F|:gysM,,ؒrI`X<5djbJ7gIbɪrbykHsLY2ĵ]V[+zh3drV5!b:.Yn_:7N~CkP)[[Dm4yPS .":y)(ʗfYҦJ Yq#x 6cJ˲+:'% l?Ri!\/Uߴ1aߞ8>CkE;6pU^!br=bU|G۲jA`PJ⑥ID=ي϶Cꅋtuՠ5o5K%5m2usI;slMiP@l]OHY`[ojڞ߽!KL$wkUލݢԦ YiUH; Q'cfu^un{cY{9.;-B9F! Erc;mޒX3zYsL#Ll&vMѦ`qmf@>,qGqy eZu*P~ոZ; f -zN@si]kf}t_~oh,Z743ͮ_wPE aFh2 p!eaH30=_L! d--1W!9@ Xm| !#Ȃ1|3>5fidqCəʥ$'h!Ŷ/D2#\ð?IEA-$X6ڬ&HQK?9D / a+yN5heH:)3gdjUz>+sJ~/{u =oGl"iZPf*e-ms6/{#^Z0#+YpYBRv >R;$ީY4xht-!#/R ^ @s$,`ZY˓2xTݢb\UyENlU+I w -46kܰ2cr_^O0đ[/2/|z3vm=(3Y, !>ލfsb+m$^ӈ_~Y6_'җ&zc_:<|fwS7yμ|OE5G 4q97E8-9sJRYy%eX$goM.-{7+$q8{w;h>#FV7onĔDŽ[+_o겚Vsaw՝D;obg^pUV pFkt@&hm~='xZ[sHv~ׯ+p̋Lfp+D[,ӒҪX $1,.O<̾1o_n$$;MU\36>}oY[Q/8q&Ȅ bR1 2ʤ/ȗ8udM.NO4N6(эLAqD62XVXRx%/]˶cE;4Æx{ADk%N!ȜU0/e2O؂f/LFWSU V.Hep h(`h;"Dg[lc?XѿJEdB(r%˳M|Td2 Cך:ShE>m D*GJ\WqO28Ύxu?JEi6sH $VR@Bj\3I,>S>oMr(fMBL->f׷3jY\-LөǛhwx}W31ztv-@j4%oGs[ͮ; d6:&vrs= ]լSN ƃ^c>jp 'LL\^/xvoCu:Fbq~Ȼe`OC~|6"6ίfXCߡpM"O >DNOkEJ6&q:giJ/ gN VE8W#u1$3 UgZHbeE0=,PNPVOyYt*i@ j`)2J ز;gqφqROST5ds/ݽbBXXWr:COlpW_$E;2 ,1|*Gt2PHԍ~\:"j)%Iʲvc9¥)XG{Dt}Vt"_ᴆ=ڞsJͲ$"oc˾%r3%m8Oް"} spPIQ4S!\U^PwN˂oɯgO X6ZM*=3 &hPyYYV;䗥L)aw@_-inEu*_5n]Sa^)BDB)O `#~e7(0S{$82Yň4+gуυO;m!S_䵦 N3[`d!>ھP=8[Aj K+GН[UiвB-2jf;؉i: 0T7I1VQxV]I\lX W|@(LǛp6pSc^ПgZ`}=J{fr ҫd.8ВQ;&RDWᣕyGNΟT_Ri|.lcDBF_܋p>OxqJ```Zt WQ_9ԭ* 拀L !ǻUW9= afw4Sy?0OFW_S8'BB_мh|1Mjc45#L5fw7 _=X=ZhfY8Zzpæ90x(s~-j Cǹ.RҥV QC,)lCG?В YMdjdo TmzpLD7\*4ǣf0t [GFK%2I%qJ:^kIKN&wb >V@cYiP!aFfshe9FcPJ:Ws_FP DNͱK@ONuVA kNf 4\<:@"y=CQ0 E31jvuk_mo9ḵq8Pd\0l2NZs*`9ZTWAlʍ}aVxTX}tHeW-R˵`UJY\tc2?vmAt<월P YD6ǘnh|U|NU|X_dR_T\M""].ir毮D BkdDuHGooub ɅKw࠻ Hnho^WnU1?!ֹpx340031QM-K*Ia8Pah׻>rMr1(+I-.M`;w[ӯwv]!cx+I-.OSUHIMI,JOI-HKIKR̼xfQjrI~Qfj6A u=uMM.A@4xj@7YyhFacwuA$Rl70Fɀ<2ҘLжy,,y>IGV޹㧇dgoNXBI%{C.~TTA<V,'Ͻ~ٗJu^xGz FtDYvKuqB91OqSmj̄6X<j֬ 4rXce=qRFs !~9Kq:yΈ" [V53Iº}wH12j r ,F7ahsu`ܟ0QnL<'n7rL+ْ&s7V 헋F&"tLg^O[t$߁Bk *4۠pb*mxe1N@EXPFE=Gݱfw!nȕ8Ǡdb ioN/#H#B˜"XAm*Q M "*K°j9NRK$~o Es4cDzr iOMU ,޺{PApa߱6*^A1r6롮/|%j$cuB(3+$8S(KeO|3ٲ*=inb2xT6QY x340031QM-K*IaX*·]pVꕓ~?fQVZ\Z[ZT ԑk^h]]ժ)!w xe]! 9E߀&#l5z{A7dФ3_(U[T@..E:0KQ+^34fX>yqw/0aFCִֻ_Ⱥ\ὢM !œdLr*s\![aLxmgC6vzInNbejnqFjN^rf^rNiJRIjqnr~nn~PbyQzj. p v w H,JOQ0QPRMQҴK'g$)ge'U[.$(3$Ml ЁZFJ1ԒĬx8eV.(uhB0QaYa,̄ԒҢ<kZ.ڇyhivd4ٿ*r././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/pack/pack-b57911be68697664f79f34d9bf2abc14901dd372.revwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/pack/pack-b57911be68697664f79f34d9bf2abc140000444000175100017510000000125015203543561031322 0ustar runnerrunnerRIDX jVY$}-xRCHo8#BWpJ' Sq 1ZD!F4ez]g&u9P.l=bi|;+s{w%UOc:yMAk XhL6"/7ftT[  <0m(_`rI?av3~d,E>G)*2Q@NnK\^5yhivd4ٿ*rWic#bȝv0././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/pack/pack-b57911be68697664f79f34d9bf2abc14901dd372.idxwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/pack/pack-b57911be68697664f79f34d9bf2abc140000444000175100017510000001253415203543561031331 0ustar runnerrunnertOc  !!"#$$&&&&&&'((()**+++,,,--///00234444557789:;;;<===>>@ABCEFGGGGGJJJJKKKKMOOPPPQQRSSSSSTUUVVWWWXXXYZZZ\\\\\]^^_````abbcdddeeeffgggghijjjklloqqrssstuuwxyzzzzz{{{{||}~7)f($d^Gd߯ C1x0f)IӞ­K^u }|&t&|KN< h|R$ 4)(0=P^yz Sն[6{ ezk.u(冁:NӺ{ıs١]1[*-dv<ك :u['}aES i9/˝$`r8h{7 5$U!?7=>%Y@t<GfϤ.k^"ZOp[/]$uL ƌl 3%Ժ4;X-i _ߐ(SPƀ ,vq*»-oQf54+?F{SLTG+zYY&lxM-,\,lnnڻ Rw $,qm 3m3E9-2GgI.@lAe)IQ0.MI]@}'1{ZcB#5'φ2;!U\( 3w{{TWtF ?Y4:mn~1 /WFq F4(IpPk5G!m+#ET W7~ LqC[Q8MZ+QI%;7ql9@BgVE ^\c;߲׃üq;et2*o0աt%9eAYlI8 ,fB n'zGiHE8K"8>Z%F$*3R`={c[H-L6" ]KuQYLaou1s5NwWi(`Jwa{$PgdW ,+^k'oi֦HťXFk<=UrowUxM?+Uflnϕ;>iŅ#wPmuיni9FڮuCn YG*b3P3=_Joia\@@}5p>@&o,oS+uud"{`vpsǦ7$զk{qbh |]]|iv c"tx!+y:P(3vn c'JW@zvhsMDJMDž"ztQw ~Se]~KxE~϶~;=ͫ1Ҳ=L\Q/XD<ƒtl[ksw}lb#/!qŘbW9#ٟz2b$0Wn(-7&raI<<&Q|3A8$ ̈́zyT"2.ekU `h\A2`T_L%Δ)jKv O~ 480fÑӿ  AYWv8ژO⹂C] 6b.W)2SRAz5;hR sBXx?:d0n}Uj>%$SY{_Ƥ6I'?˒0iϏj ٰnHcGB#x'Uu_OO>." Hro|'r4*tq-ܯCk3GC~{ؠd)W#p˷GS[FzX]swo) Xgf];sڶ[bJr>Т&EyӛՈҦ"ޱ}'!X)IQ:I&ьm'C{흟1ȎqOʸ] ӂ9ㄿᅡY`lʻS,tLܼ==1`$̬tCLuP꙱πuɡ-]3HP nK7iOSKVե_~?G/тG s)pz9. A!#u]s^"$i})ə0(R`/Ȳ?8y^=NqEzp9vy!د퉧ܜ ьˣ)S,ܤ0'aoܲ+%ߑ͆.~npOZYL3±z tz{@}otbXw,ij hB`ᢈ6Vۅ%,H3&:yL2L2up-/m<5ZR͈a}UǿhX Tdd9B[+ I{|I楹-]BNM0ΈGG~U yԓ8UjC&"P)8ʡ ) [MFK8!qoJHI=U!JL٨1"89^i-P6m?NdZ)?݄Jc[an"/\BVqr41  u6:ZX-\boVۈb':=>Iz> Yhj>D:٣٣tv0cRGduE"#_g;!s}qeWZ^5L-_n;o*)~ W7|Vӓ&2i)6 3cy1j's#4 wh?9@ I1DۋP-UoN|ImƜI~% ScDp9,%ڏAS-#gYѷQ3)EQ2 G;.3w AѮE$ݛ"p\^ixy(Z{z{pȂۭa[9q :'zq,+Lԯ.~pn$s߹Ҋ)"QXILJ*Lj`Į2.VGW *$G{ 2|L܅:oag遄/iG%< _R!SOڊς9y{ŁF>aIjcaT IwVE Pm~ZaQ^'_!ȵ{2F;! K -[hn-K5{쵭ƙ6^q"=²^Xڑw3*}G7<1#dxfct7"GSdW 0 cUd yA}2[|HYXkgL[eQ3CodiwyzQs CDph_p:NXnFpOfiun3rj\tx^km9(:67]8_E]ڕ5`\"^ 3jk%onFsAS!P8z%6hll\ewo nrdZ?&vBzӵm.w\-m+Thl5pjgcDdJſnzI yhivd4ٿ*r,[fomM#wlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/84/0000755000175100017510000000000015203543565023460 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/84/51b3ddf596fe30e626bd4d86b346b1797765dc0000444000175100017510000000214015203543565030631 0ustar runnerrunnerxU]oF3⊾țd#KW}pc#,͓Oc<U{vEHhf=OJetӧn).eKֲ +uu!Z:6r_jHK)h+k-d-l[jF*E#W7)L vviiEiJGѴpPNe-=-5kNR(LҶULGʻħX~K] Gʐ"H@P=.Und+X90e]XEU ֱ*ٖNòKXU ABMaC T#Av]SIO25߰*urUm=ԋ05^sS/Ԗ)4&k GBV8Ɛ;֛CQ0v: 3gFc;yl֍&&X?Q0'GןY( $w\w?63_ &EC4(: K Og9@mZaN7z C8+Ǐo;r@4)t˘7sp@9=zhf )И K\waL?qe_\nXdn 9;= ڨT& h9,ʘKz;<4yG` p?h ܇\)ǣF UR5f?_^,(GgeiMF[,0˟1L INr3{8p`t<((F7! G'&W$J^>^]\\}/?}r:$Yڊއ/ z:vgX6i#~El1 vI4y{I`_@|tͩP>Tx}#?iQhBpsb"83@.LTSa?Ha0 =?'1Z~7b7 jrAŷTyZP+Ė5$}Gtlo#"Z:793B1X}#V'; =q.SlvhxuJ0JCN|ߣQDq2Ş{м5Hzwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/57/0000755000175100017510000000000015203543565023460 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/57/4f416c8a4cf2b28e78e80d1f783801b7f854020000444000175100017510000001073415203543565030472 0ustar runnerrunnerx[ms۸g ;MeWvvڋ3#۴,y$|)5%$e@%۩SM,žaw&x׿?ih^Y$q LQ4ϣP,apG8IY<7߿t1#qϋ(S WQ6x  v*ޥ* 'VäZ>Cqzxx쁳qǓN}9q=0g0ͅǏ@'vK8u}lba9=oH gГ81d֥y]Ob!Q3ץFBƢK^NVL*}Geb7  _f`NwIqwƧ5xH=٬;>ZE:;'` !U&|VoA(Y\Z‰"dF @5;xޕ ytC30 ??#D^"|qe`\* 83IUHF!N1G \Myiq()REhOkA7Dĉ 2lql HjqhA{#4wX뾲 fJ@ZG|w}H*= JjRGU쬕Ó!FC¨uz>wZ?:;=w6xg٧bޞ"a<6u\0])`WZ[)3g"epY%ڸ4Iڤ_9DZ.Iί96߿EAmo)AeUJBas.&Q:AL!]B98Ͱٓ+KP@/Qoe HZP C h,#Yzh4~X>=eGH͘v5^#FT&_.b̥l0ûs(!,J`i f db]f~pW ?)Du;'bO=?_C_<0Z?3mׯ_vXK$67Bg>T<*Q ;ۂ5uLlQ.X|gDT+Cq8KNiMQ! buc4hg to%merH,Qr(a9RCu2;8 (QC`M2HT'@+eI<3(r=(h{:xC,!C%0,yL]!EɌY i'6Y$jJ J7Q/9b8QWەSEHlRa=M4(u M/QfHje8%e~Ƅ3`:Hhj]l1)n 8w2{;g+Z{ֲ@'r_8sԟ(=]w'Ija1I+ʍha@e1Q}4 YG*7ţ l jYCҚ;d  ['R}04Q8 ˂r9۟xF*U!yR}9nt$-뫥mE -[lht63_LJ/Kdy}aO U #Ue cm 7Q,X"_U%bD{Prl? ND(wH7ZU3]$[*ğY:fCh7z ( H5pl˙\t@@hC~xn(h(fEO6y7c/LR_1͍6^m^-.zM,EYôxm!npóHq@ ry|MIGUAݦ:VޟQ;訢V2W=y]|8\CJ?<6KR!::1]xjMCKEk9߾}eC:hNB U〈fx$a [N9yVO|>o4&# ;ahMy8Vʉ}9y1[L¯{'>9|>aOQ-drdCiuJ1G\ y~Y\eOwOd;Mrr@?EhpCiD(ܵ P_1ӎ8X]FsGߺqJ)>LX&PM&4%gv@/8ҒtB8[lpZ(W+"ݰ}yPc2a\g_WOJb[3 5TAQ   MU&XʳjfJ}tqܾ1xUHQRnŠO48/9xdXUhΣUUG9"BR1jzs%k')ĬUh|ȼ_32<(l"Jj#QY.fX9RmY"zw 917g\]Xb+G{eV$Wr_XIs#]Pϥ|NYD:;1cobFz|fghݏ-6Tp7zMP|hby$|Uw6a@!u˃5]acPb )17SӲvL+URqVIL ղ w̬l YɦbE9)MCq O|p'_*3CUR5 4s}a쥴7F.Ci_Qv. Myu|6S5Aǔ"+ϫ<6T6I&/VgU [ǽVԿz>;E^^,K{J3n1 jM:y_XvE6-ި@5$0>\5tC41b;M&|1I$! W67s 4.ǵrK։xCo^;|7* 2},SCJF3ѽ1"ŨW9e"Kq[xT)˅RnUӉvR@C$AY囌>YvwztU"lEa둲rA- ! /l³gk"H`=ŰADfniZkΞA]ϸU zYhk 6>^wD'njIhݑFL\bKxiQV7>'}CwiۗH7WwzWh㸑~q-Jl\5IPhS{Dv7@ȴw㒚Pܞմfn.zA/;fB\,@嘼yWS27s*45b_` ׉wFȞwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/57/c56fcb159c22e045d8bb2a4018738ec865b5a20000444000175100017510000000120015203543564030574 0ustar runnerrunnerx+)JMU072a040031QKM,/JKLgh4E]D(gd&1=o~Əq1GVm3 $3=/(AiLTtu~5}V Uﮗ0ceEnza鲤9uTBx:ƻT0|(_kstcGvLwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/12/0000755000175100017510000000000015203543565023447 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/12/dab08cd5eb2d0442f97bd5a710aa3a2bf293290000444000175100017510000000340215203543565030773 0ustar runnerrunnerxuW]o7~y X >hVSq >;Jb}G^I^T,O,@X;;3lRz%o[Q6m%۔dqImC >lBg%+0myfg$ .d,WULd9o+9nlRw Izi1VvMv|3x_?{HVogr% Kpu$a%[ QdkӶKS?w3D.'M*()Y)YTzckjM~WIgD5k|LLVryF֛ek%nU"6.S@\}kG#Jo\P㠹4>LS "\5-ȰqmiNsn"TLYz,HD36ޤtӿs.ir2Y2L ꭑ&]⢩ɼL- =L`'k{>$Q]H 2xRqlkM̮Z1`5Oٚ4,RcpNRN7NP`}ig%/#t40ԭrZ6-NI*򻼡,EG٢uC8ִZLيҬ,]YB^=j&QDfq^HFr^mV>tKfMn{[Ză4CdHЬ2D.4ȳd-uU K Cm|&ڬBlVCG:BWCO[4pJ+dP#3 Ip)dbXb٧ K.pf\tR8U"A(cf|oiR]σu_*/ yŰDHII 9T$*Rui-*iZEIx%}y^A} 9ԡ}%L0f5~~]Q(q,i cA!2L8 tmP*+;n4u'a5_wC(R(4ut7Қ@ 2tbJ⢴cZYq`;rVRבJ($tЛރ:D.,ʙ68n֜q%p;cS{BK򯷗~|79-.i(iCG{4r?^tc~a쉠?rq;b~~P#<~Ȟ l 1_ C:nѸ;}ǿ}ȧMZ{|Q(љ=MV@yTocWr! ds"1u',WB4l1^9}wW}^M(a}ŽW}~s^ /li{GW9)A#V;zo\aMb49RI =ꇃj>ɉځWsOXq]Ƴg^&6td0X︤0۹璉5qT1x!*$ޛȵ{+K#Cv4U/ϟKޓt7xg C>x({ .tjZK;[᥀[V±;x<'W>bT6&.uX s{\y:d0$Uub;eGA1OZe1 73 cc;wB`I *q,7%}Ó[{˯lKHYG _Zpf>S2RTWMwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/00/0000755000175100017510000000000015203543564023443 5ustar runnerrunnerwlmaker-0.8/.git/modules/examples/gtk-layer-shell/objects/00/d4cd30780dd7776d0db16a95f3c20eb7ad1a640000444000175100017510000000307615203543564030732 0ustar runnerrunnerxXmOH#*.\Kչ$P0E|6&zjUA7ݬ;{iR?T%g}vfI&pdrsyǶﵠw KiהL$K!!\!BQ~i\OX`68iopJxL9?0sW.4;kǃDi;6Q%8$)R6.dlS?H& ?d\1?fҶ}A)ʣT'n)>;1$$ <