From 7c3b780ae9a5850385106b522f771a651ba83d0e Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Mon, 16 Mar 2026 20:04:30 +0100 Subject: [PATCH] documentation --- .gitignore | 1 + example1/example.rkt | 6 +- private/lib/linux/x86_64/librktwebview_qt.so | Bin 232600 -> 239032 bytes private/racket-webview-qt.rkt | 2 +- rktwebview_qt/main.cpp | 8 +- rktwebview_qt/rktwebview_qt.cpp | 61 +- rktwebview_qt/rktwebview_qt.h | 3 - rktwebview_qt/webviewwindow.cpp | 64 +- rktwebview_qt/webviewwindow.h | 9 +- scrbl/racket-webview-qt.scrbl | 447 ++++++++ scrbl/racket-webview.scrbl | 797 ++++++++++++++ scrbl/rktwebview-api.scrbl | 1004 +++++++++++++----- scrbl/rktwebviewqt-internals.scrbl | 484 +++++---- scrbl/wv-settings.scrbl | 121 +++ 14 files changed, 2424 insertions(+), 583 deletions(-) create mode 100644 scrbl/racket-webview-qt.scrbl create mode 100644 scrbl/racket-webview.scrbl create mode 100644 scrbl/wv-settings.scrbl diff --git a/.gitignore b/.gitignore index d4dee88..bd3ad3f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # DrRacket autosave files *.rkt~ +*.scrbl~ *.rkt.bak \#*.rkt# \#*.rkt#*# diff --git a/example1/example.rkt b/example1/example.rkt index a6d4d29..01d6c57 100644 --- a/example1/example.rkt +++ b/example1/example.rkt @@ -169,7 +169,7 @@ (letrec ((f (λ () (when go-on-counter (send this inc-counter) - (sleep 0.1) + (sleep 0.01) (f))))) (set! go-on-counter #t) (f))))) @@ -197,8 +197,8 @@ ) (define/public (prefs) - (stop-counter) - (set! go-on-counter #f) + ;(stop-counter) + ;(set! go-on-counter #f) (new example-1-dialog% [parent this] ) diff --git a/private/lib/linux/x86_64/librktwebview_qt.so b/private/lib/linux/x86_64/librktwebview_qt.so index 84f83bf6ac5318d2324d9489e91fa821e938f844..9ada80f266993e1b0bd4688a0e876744e6c01c00 100755 GIT binary patch literal 239032 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%0AuNFc-l5x#To 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 mdQ$!S;C|WHwBFV delta 78274 zcmafc30zdw`}aKq=!goqAh_Xxh`56*?x1LcV&aZ#sA*)Ss9>1pplCTFicWUSvS_hP z`xWJanhWk|T4-jpD5xbDQA1NBw|SrEoO6dqs{eaGUGB{HSl?52 zZ+T^fnhKY$Y2DS9a8eV8an*d}cLo;^{8%8rSH0eygB+S>T&bKp65>qw_aE1HMoU;- zJ8D$08qo5$OTnsF-A@Xd9d(#r-SuBfuF9*U2b&a_Fiyf%uM4Y7;f0RfY-nIr)yDUT znvyHR(aO}U&E4QmgS~_8pSHSo^@Eq@?@!CjVJ{&4D4YS%c?(=opsr?i%r&(zeGu;0 zU<$A4)7qnZs3q9quhn;)H??7je+Z6IG=`d7@Q;E90jLw#KY%_a6Y531VWnv zpQqXs@GM-Xsn65l`n>x50$g8IpI?IOEI4PwiFuX`Cmt#4IuEY%;d}+o1#m8e6ZJ2K za|xX3a4v%rkJsS398T!Jrr8y^LS0wFHB)_F4c9erI@EXM=kR$woEzYL8_sv&+z4kj zoFBsZ5u6{xxf#w+;lyJLTt9>Je{g=TFo12^7w}~poZA(+1FpGn?uK&@oL|Ga7fw9( zsVjco51;ekJP79@I1j^l6iz(y;d(-S#_!+2=hJYWRoM4%{Q=JNteK|$sKB4#`m6eU z0j?L}yyUp$Q7`-oeEJ>Et8kXUiH8o?QaH=h_v>)I0cSa!w^6G59r$z?&UpV|CRa=NX(PINjmI!^5$-+EB;2YDXOJR6plPH6L+A)@W-^?)lTCIz?_DI+AL{ zy0`f;ojZ=ynBWNZ?93WEE;aUJnU43H`FR`z`MQoXtR6e=c;C~H)o?IBKUU`WGT4tz zbzJiGV^td<|kM_oMbj>(@55X!GR zzO3uV>N!|*Kjz@?KQQ^R4;)cJejcy(GunHJAAAV#5#;o^Tf>0oqbfF%pK*8dV?Sbh zYn%InK?j@qvF46IXza0Lv9F)U)!ItJ^N+K{V3a8;Fw&;&*Sr22GQo23|7|` z{aENY*vOBKaxg$%6Q**<;y^#v*D)CkF5%&Sf@RN%ZXOjnt&VVT%T{6A*N*q=`LQ#O zHPHP&!g^F>uTh`4BuVF7+DSt#y~G4ePBj+0G;OcOtk!&c*ai6a~u zKH|6p5~m&E&|h~)czui^xYFYelw2tXUI4yK#~FzJWzppv^t#7;trdqyzi@+hj&O#% z;}+z?CSliO!F$;i)r+FNBaT4GoFO8@j*7#l*p6%EGkHDl#$B&M}rv7Y?XnUE+!$`+sNQZ@(pB^oYbZ~^#^ki?F3Q>eTb|sOf6-Q z-6<044H3&(yv}0~80eS`?w^L^%p<^+`*|W|n#w`dTEywD_-hgI%@ywbK#bWrBCm#t zF#u5nR~HCnUHy$N7Buu{`yFd6e(V)t>Ph_U(b;IsVQu8kR*QVQ+0e+1$>>Xu$5DoF z8;Vel5Rr0qn>sSb&8o|i*XyxJxNX zqJghP2!0m%7= z!p}H{K_#vsM$+Gy0Uk45sWVg5<9@<~jl!rYVxW902GR4b@q1kKaj5X{N1}Zv80_yp z+z8A{kwRfIGY*M(g7;zM3=*cb!4^F1M!Ozy#zY^RPz~9`}{x}|3 z4NA3_qYg|B-9)0!6b@)BhfkQ85Y~#KnIi`LB~ieC^ffes5Y(kqk)amhh0`wW?XWeL z&ci*RU}A|rCz#(6Zuwjktvts;2xpw=n5W|)6th+$pMJ+g@VM)8`HvzcZVCATI3n2& zQKy@T(V6OMvXAJ)Rgn#5QDhgnbX*q&cte94KFbS|e@yDLLOOcC8T< zUDRVKmW;Tdz~gf$*U+v_%b6q*g==X~NrSSgNkz$$UrUIVit=#b0moF;LU;$spM#54 zIG$39CIm`6&Fe{nJ^~)>u;q8;hJ~WK)zB`4$_8@&kQ&rIkM=iAd)R|)wWy6lIXE7} z)Psu3`$~R)*^agdQnQZcrKOFKfV#)hyt_(0#$t5?D#3;74Cx9jZh}-)js!TiMM;53 zT(-f1Yg0U`!JrA$>T7B0wg?KMIDTy`4GL>68-__&9FO4E4m*%kCch^s{1i+UHYDWX z5*iL+5nTJK5X5;%Mf(I*)4u8@6bL<760wt z5>MM{7$n=*rZ!NnQw{V~4bQVk0ZWb)aHynfCqm$*#i?6RqsLcNu812H(6LeJxdGQY z5VCT0zZ;?Od>hsmNR}*>EHxFOH7wRday>5_He&o27=RusE|rXR75^59y}%13|1f#c zruLV3-ZcpXD0~I<5czGzTq03!Qq$C7vf;Eo($$7RKdI1Is)S}#Tgjg^K z4tj(^{iGhTy#V+!n2Hh4dr$D&G{dFU)HqHUA{7mi7pW@b{4t*Um}67_NuANj&Q{iH{RfnwAs{xD6Wcxhx5FW&7GDHT=@5OJIr;uqs74 z>TcJf@b#)^IjUzV3V*M;>{xk-)c>;)8j*ALwat4>@SCa#|E-gaAEx* zjtEr`iaSUFW4z8%qa#7_TNM9smF;;7H-_aVm@RQu+*QRxhmDZgOtOI~O3{2}sgDZ5 zGKIgdMu%m%?AhmLNoWl901HdrjwcPB3!%Z}4YN!BuF6B#JIW4Qrc1zP-1wj9D+MfS zv%%Pts1>9HnoEJ|%AkY&C4Ua?4!}{N8mOk~L3x$AV7M|UOU*T5QjbkL+(#-Z$dwu( zDa0`&O5$n1NZc6Tk+oFFUX=KAihn3L07Ds8BITpyMGI2~iH$D6rbsa@Nep`%WP5w* zI@~P7<918ga9o-MjPZ(Dj1A;nm3;l=MO&dfm-e0He@5xah?IIF)%J|hfjTPLlW=DQ z@=r5{StA&qqGXmy%__v&$QadQwURMP>0~uWq^YDc@NN(zQMj&4J+CW+9IDVIsLBXz za{P5vJ@YA%0x;W(V}S}yp4!ARa>5@%fJs-OlFnFAxCN>ZC_|y6;t1&_4a#~~S`w@b zN_2zv(WP;?NeV{|ML4VG^{j9SJT1xEQ5c`tGoQs0Um>J4?R&5o^`wPM#>Ps|ezio) z>M4O9O3#~WPRVXzlZfHE$7);}yH-YMx+_mu4oF2tjFSSTL20;U2Zu44ZHIb*OSCw( zL{r(W;X(*KXElZ^#vhLE_hBPY75I+5imJ75^vNtM_H+^mGd z2-z%%1$xM*%BtGdL{%=ID5(gyeeqZnDtlI}b^;G8{H}^+p4#m+Jdrg(^5;F0jJ*{9 z87Q4t0rys^fTLw-Y+6Eu^B6!{{f27NtMRzsS+@@K!ad<#3kC>Ro3%&p|)c(7W^UfYggn& zdk7W?SI>}of)zer4c9n@)__lJ__Wtkd_)1uxXU+nTC7Ea2Ot%&_On!#Yw!OYIt0nmFIr6iJQgx+VWizfewjdiK>9*9i#vZcX5n@ zzu1FFl|+&X^#k^e&_l%;5=l^mlPalmdP@B#Rl^P#mdJ0*Rs!W$tkpOs_TCFZq zie|(}yz03BTjfe|CuvZ3Wl*RJfu=%W7&J3f>cK^<7=ISkKz$%!Qk2`IB4g!~qmrv! z-AAj@raAjb{+z3l-`Lxk0|ud<9JPWmcjIZr47FO{Wf zDm%>z9}+1QC7hCqj5^_fo(s>FNXE^IKSzZiOs%$!_5ZMr(0{ZjTP-NANqyS%ZW1q8 zCIQ0}V^nPy4=X`=UDd#gPykSmZaqp6_EXgaI(C)AcnVr#1!xY~Re{6$KTEmd4b{L1 zqs6~-x1I^n4ewm)@|Fc$d=6g*%#yH z@sn9@jkje*z3*0g)XbNkpZV-F!>7$nd1;zs&43`jV1`??<8;3OZjEqjvhBM8KQTvG z%qqvxk>1;?5Bka5F>0K**+lt5bt>*Ag_S@EXwkO-xA%S~2MvwDyIA;ypc626V zeETA|hK}cx4uH)*j+i+cdBR|~U`Ng6L0(;ljeP#4X>+>FO`bd_dBC*09*l>o=Puh!$S%ia99HMNBSL;4VR zerUd10RLmATitCFmw)c&*thcbwnLepdDM1?=hmK@8~k0ZcZV2t9FlrM#q9Cv+%^Nz_Z7ywTiV0;Up07C|5~o2pHdeR7?%r zM>P;u1urj?hNe})FUCv#*(cS#l1tHbDNtTTKvx2hDntf z;cEOb)#E}JK^mgnl-J@axR1j1D!4`AAuvQ_gR0gfDf@i5!qw&xM zr}MgUP8XIIACRtv`uYSOsd5EbZwpTO$7Ur2DD$o(kKjXHD11;$5oA@HfP!i!cqq~1 zLvZ!LN$~qi+$P%mmIzpgz$*j~C3q>p!wCMfP%ZyN5L~5+AmlxP8}%1R#cEB1x~V`U z5ojTGi@*8*f$KyM<+l?4rG_4Czu?~pfR__RE&;&f2*0|w7wX~(ZtOlFq4+;=ZMPc# z*kdY?Km^o?5e?}7ftx`Q9;k=%Clda@fre8(9>&H1@Y^&>z^6BOpdyMJPlc=ENu+_c zNK{8E!8;ltT$dC4kl^1Syq81eTKq))RaI!Ltdj_UMG7%>*wb2IbfYVE|FI zi{KU_P)KkOf>)3ZP`sS*t7}`~c#-fY5PphV3BOtx3#mlHkIQn`5k~}I$J%uy5dn(F z6aKL-ZcR%i{1i_h{C`M(8UHjQpa_DTNCeCzCzca_iYF2N4~aopgrDN6xoTS%buT9Z z>xlrx(?|n4;m;=g6kksG7ZUy~1IPZiB!u+_0k9;3XA^ur!8a2eZlPR9J~4>mIfVZ- za7+KghXNvi1moC61YFEO%pQ{~N=PIE1;o;G>Ep@0bNCwL*jqe%}eq;NS2zM0_00~{ph5WJ6iS5JuTA_Vm=oxt-5 zo*@wVr;y-35xkh-sRY*v-par+{^f+Q%^<+Fg5W)fCo~e8EP}@o{2QVtk>I-te-hv# z{}Ag)2suRHQ-bdzcs6MuOzj^aYi~G>b$;Z3;PI8X?TH_LB~%i$W2AvZg1SsS-oOCix}M+x1kWb8dbl8{n{9;9 zoCxF)Ts^rG)LjG*B%a74IR0eHIPwV&PeWWsfx>Ort6nZx(+Y_IyddH_iV2=T8qf*; zKEcZg4x7)eqk`b@z(@HX$zmGK8N5|f;)+!afoC8F$Av?fp{XYh~NnX??)6R5_~1$Pa^ml zf~OMPre5q6;6?-@2;RO@K#L^!(*(B?+@0u&BRKZjIN}8k{YTJj zpm0qf0yPPqNbp(&Pa=42f~OK(J$4n;G=h7%{12q%gy2mSWf6Qj>G67k8&fWdWfQy( z(X*N0brmk!&mn|*L|_-e>k~YW;2ViS`2_bR`~?K}BY0t+TK{7R{0X6$2s9+PPH^>h zgP@iZyb0VUAA%1iiu?)QjPP3sZoD&(MWSMTrEzMDQen&m(v$ z!M6}RP2x5&{NN=4*Rh-kv~i*EN&>;#5_~CJuaXAx2_8=P3kbf=;K%qE5`ubhREQN5e2yUi6rJFmNCV{rhgWD^M+L#Vx={FV zNUi^I_@$5r%s{{c@pyv!5FB28aUK2yhqs$thlSwXT_}u#O5CQwn<=g%tdgL?&mmk# z1i|~dP)&;@cqGBC1pk6`AdcYI1g^$kJRzJX0tp0i6MO&>$RhYag0Cm|8$?ew!L5XUv%+l{WA#3!AmE8Z2J#6$ znBWBjkFBhKFbW9aX(CWe@S&t(o!|ikFDH0!f>#iHgbT;`AFj-H;~h6_-%Rj0VvrBP zhY{SL;KK=SA^1R7!!QaE$M!J<<%AGM1V$4@5d?2d_#+AaF2St?k0*NK2yT0s2*eY@ zI3kch@PmXuk>IHWPa?SS5+JHeCHQ!$Uyi>tLYPPzUQTf1Wm_a?5&Rj#znP6Bex5q?cmZjkoZxoCU*W=`|Ko^&cC>O7tRMnr zf@c!khv2IT?oaS6f?Ei_7I9z3f3H~O*tppDt9k#_00w)6T z1aC(K5(vJY@Fx;{G~rJo_*;ZORpN5}zkv|ah``$fUrz9M2%bgok4eMp3H~nO&nEbL zF8>3=WG5%{P_gmWZ)S80z&x2Ai%Yd;GYt_nBZFot`mGS zF{qs2|0DbrfQ$UY3fPJWXvZo?fra2^f)^3ohv4}H_b2%0q+v@XZqxb_fzV2V_5~3L zBX|zMBM5$v=!qowZGu|~{-w~P#$OyEY$pxG6MQyNltA!Y!kh4XAwM{;OhzgHPMr;aIya1O9-2Zz&?WK5PUzucM<#m!Se{7NAP@t z|4R4&3kcyL5hx`1T+*{*f*&UQI>C<+yqw_OTzVk>6@+k%2x!MEN5OG|n+ZP6r3iLP z34Vg``xE>m!7Yem{^9WZnJ5Y+0)G-bjNq#Y9zpO^q=86+8$Vq|vX$VrM?_H^A$&^| z#S{E3!k<9!?+AY)!Os#riQwN$T#mm~LO4eR(g^+o!Iu;KM}lV&d_FOBJ;4hJfA(>* z{{Mv#HWPunL{SdGFA)A+1dk*9c?91?@O%Tu{$C`D3Jd~ZMFcM-_$7iD6Z|)V>jb|_ z@N$Be051IxA1Vl84Y5?quN(!XM3I@`>X)s8>O=5rgx{awC6)SZnuQRWRT8vNg5M;H z!U%qg;1LA>li-mAze8}Vz@h&*{O%G$91)mB42mbXmEZ{k|BL8JB={J@pG5FwHbO`x zgbISEQJgfeoZ$Bfo<;B>M9+GHKT!N4{@H}^m?+vz@HC<*hv1Chy9jO~cpkz3CU|~6 zS^v8cLIDx@ir|F=uTJn{g1gCl72VJYZYTWZE*$z_gAgiQ1Wh|ma83OZ0&`^_!OaA( zNpK&6*CMz-!4nZT#-D`{MiPNgf=?iL7{S*PJc8iG1dk+mOM=_1gzy_7#1TA$;PC{n zP4EPQt6wpS5F`@3IpI%|xLp6gP6(+)z?*bsY|G=Hd5o>iUYhZV*>%X78~&)2x{u z!|~F+BH7oP;SmBf>$1G$8qQ+xW@*^aVRZ%mR)W7vkYnqeCh#wj=HCfueYXVcN@+97 zn%?^mE`_Z2-;--uGkOR7&NO|P)9KWog+IqUPM0`*?porO-Ak#HexG?Sb^PgcE<5YJ zv?fpwPjBztY$>*-Su<>AYq~8$^IkT&%;^M!T7%ZZ(+zsm=n-+&W%rWXTkQ|P^5-2% zcZXOH+yPyAsom2bRZniX>_$phdIftq)PB@zFM^)<>qWPn&awsw){DvTvdZ#MR$YJU zvD0bS)AJsWkD4^`@bu}9yLT6`Q;ym9nwi=bI@aEM0se8@$M@RB%q|s{cozO2mh_&% zv>5w6tNkyl{g^)MZ?q`6K3EV7VEPd=G*Mc^k^EOX*2eL{Uqjho$L+tKwxvI=o*WE5 znU;Rv{pAFEfi=^2_j3?{sqRaxtiV}p&6sU2&3%BbO0H?mVAkWpONa4eMvOVVyOwM* zu+lGpuXKYBe=^~d5d8>0WY%s9FBn+u=EGUUy_ZUy&Vk_HMnC=^y<$)Fs_3tO>(%Zu z#}9vp+HALw=)HU^Scrk>x$K7bGQ53HuWz`|3MtH3si*Q z=V?-1BSU?P{O$=}zIF*cQYSs{fr!0I@ezdcz$j!N+EVY2 z{77vL8Y_ncrhJV3u{HCVa$vsbc<(`Y-&(*Llex&M&xJt}lYT4$yZamt7<-YCZTZj` z9X6FZW}*(;J3tpY+=veB4Cv6Gk(L^|BIO8}|2^c2p%YU*qnb0p->4569jOkL4||Wv^KRuH zY&{+w>;q{9(LAnuLh@UkdExhd37y- zY_Zx8m6^iNS~HS^eL%o!|4DE78^Xu&A8gX}>)?`%ruQ!}4eW`rW^{SrSn{Z6_tj8T zmgS`cmxV)N+yGR)_f<$eFtf`?@C}Mo%BR-!g7g%Dq{8$J@KC7-lNuRuOskr zYug&-?YF>gSBZll|9$5F_H&IY^wPpBm9Z8j=zM7laFchlXmkver1Pb|NKM8t%? zd4KYRHOz0FlX3{&`m#Uxm-SiG z+M}UJS~C{-gx}M9UdeU1u>e-P4H&C1pUZr|J8NG50T>EJ%g362%z_pq=_hXRNPpI( z$v2ghNNeWaV0?K`e-o=gMs;3LlQr;rtCAxDIYMCo=+EBxWSj?Mrpv$bq^9gy&+Elb zCzg!giuuWEtdr@$74EFYLQMQRZ&{rUZZZ>I@O0U)KZMa=I;I)?uN|CvLK$CGompzP z$6_UWQm=@AU7fY4|48-CcvPaR(zFh_WQyS^NvX>B)kco&w1JS+Isn5opPpe%u1i z{`eQCGs=F1AE?d(O{+?ITutV0dgBU60jMS{Hze&V}ovxi>mfu*!&4dMB=zsVeW1Pv*@mw)8fz1V$IwJXZM zShtja&ny25h#H)9nLq6V{{8rp6iS9MpoGT$Acgt~hzj{!;-A+6p@GFxsQzV_(BI!n zq05&LqCz=E+^Zf4b^T6eUF7#Nk`dqYdG%R4uZHKLcNTbc5b~<`McF$q$V_F;pJ(_l z_1S~!4KM)=&qmf}<_5oAbTOSMz{noAM+YYfPx@|GH09)VKsTJctdfLIo?gHc8?c+E ztKUfT%DEH2VeynS~Vn{e;%F%B@%hKTGJRB0HevTl}o-X=X3{StbN=oJM9QmfgTLFDO*ZX2%~=!X@Al!3l_Yap{o_KY2=-%GJACw*A3;@Sa&VG0b7Zg& z4*Og%K+@Xf^TjCNoXFR%X0`vUFZzZTBjw0>qPdrT9Fz&ovi1nQ;b-+6aF8`~yQoLl zLNSIK+6;p)`^6Oe?NQPBj)f;==L_KKs$hqYfd^i@r>sIdlVIHpBUa!2gK9ph9rXE+ zBv!p==_wiV7lVDG?av0+M;k=9EN7(Zd|-&~Ug5oL7Q7t_K21F*@tjk9Q2=Y|7D#n= zc-D+-&sh6o{jo5uL_d%5xCJ`~!bjH3H&nOo>3h#rI>W2!IB2gFpKAPrFM;xz7M57xkGtB;fxZ;fJ@~3bW zuE2NI!4);mN>{8o$8T6zBesLHW~?jk6UdshtP3rRSS`)-UiMLK__v55kV623?@Qam zB1?Qf%@+rk*7JnAu;BmJ_UxUJF_QV1feT^sSH(20O+#PA@&d%Y&GoDeg2+>dgY9YUitL z(YG1dj0q^FyE7siyOi{o`s4Jlf2f@z6c9-N1g zFTu!x2(FGY8h1yuj;*_Kj5f zzB(c73!HUCY0-U!zP-U_oXo*Q;h(hc_{68X%To3?r-$#&^OG| zkM_xHlf7`~-$G1i7g@c}VF$;&+J3>2j;fQb$>&b5gv!M1Bhm8KbD^k|iX+#U=pbVi8oF|)kcvv>` zsgUvA+Q+a-#G(IT+DvWPjOm5rGOnSncD}7lmhB>a_3N_bdM^aEnssP*9YR?8CoJRix?f|>I|{c+{+}={BeE6@ zp=BS6Wkc_3fatgOaJa*Y@IDSN6<9N4ac+35uh^9v-G<#|ru1XHXFC?(Xe=t+539u? zaLbJIq?mQ|jt48NvB$_4&E^A}F&)Z*JH~zlZVIjTo3Zw5dWYRk=YdEh{UAt3qcZ0C zJC8-#=lKIuto^dy6ucH|FNsC|+xo0M`0Wq9B-L-w9&%Ow;mY#SH?-O9lBNY{F-9%Ga-o zWa;t{-cCZ6IDNoBRHpBh1^n6pS#duBk4SYx@?^!`B`VXuRa~zFqT>27Fu|2%dEk9a zvR@C%Bs;bfJ@c2oewgVfoy8&jxh2rfVN!1Sf*3zbn=%JrdNR(4wNcZg32XB|x8 zyLo0f+z{2;#}9?G>Gj_LSCzS0;T{pZDa5Aa@LnC+ruyH2cULX+vap;Wjy8Y%(Fk`Z*2L7J4?qxI=QwcMn#Q@8XYw%Y9!unN5JkC zcP?PQbq9yv3qM;nz?!khXE!=K{45V$$odR4ho2Y_o~QqTc4b5d`@=w8l?e>6g%*k< zc%S3Z*y+0->*oiAi>R7oGoKB1FY}t772`ZUr28ML5o)F0z58Q;=0!n-aO?BS| zt9^HCMm;O+iso4}2KigV4_fWtS`Xf|Si`@ydVg`(YQJSYSPI{6!MD?uG=9*ZwY0f+ zi%P3-dM{0Ig3g1b)u&Id-2I5nSd|7{7~s7#FX~{id;jk4EP15WdEC0#iFmS4wD(T; zJ}94ZuCy5{SbPkt9qzKU`q6sO=^nGI`>HDu_+O0o7Zuj--&wt*Z-+Z8vsg1?gF|_p0M?4fHDT4N|GX7)Xx(G6{m=|20h!2`zXJaBBN5}H zCPYn)niTcS#KUFUx-jP7wUTEm@<3#_<=|eAhM^O?B=%%<-q29%vJ>7*@o|ZD>M@>B zgEg_m*b8C~>Xw-Bf>`e_@?!0WtOu`IV#5zvGopN8yV(+(8R?7(ERcli*39~6tp_i= zTiMB|^kN%$tiDfF+I{fr7W7BvGojXldi9vyhBsVU6q~Uj4LCsHY!t}%@7}py@_-m; zo-Jl^1rm~L4+LE%)Rl6sY_fInA#^Yr*D62eV1@e-?=KFz%# zgCUEchcR~OJQQni?`=H;lSgdD>)BsGAx}RZ7HfxEaZkVXKjBCVY%oT7JcD`9PjzJ> z0XTMtEQ7vyqJtn(An@3F9Hv;9fhKaFZYkL|`RmK>zQ zV8z)?*3@@lsJn*m8osg{i?GB)aKIdMO!|?El$+KJP_!S{A2Q5?a&Rj4J(byo2lcIjZ)(V~7hwb&nkWUU$~8dZ^WY zRd2C}pIOeDb*&Fx7CMZ@fPPk#UeE=Lo_Wv{E6iJI!ZonHg;Q^+Uc_hjV3x2+7!n*C zda!EWBHK?91mAA1_QfKmFMqc?+=Dy=#U5H_PxGSgtevUgbF?bJRIp3HEs6}K(EJEA zu4?lZeQypQ3Yxn16s^MTGEUbQ(X&vtcj9D$o5RQSHdry!4_na2Fp*&Q_1C}T#mTG- z&uR}hsjMgK)TsiT1sn4a*yAFwy@E+xe=$0ypAkMqXCMEZKi8ABv#hTo2<{=0uY4vK zP{TeGlc;xlp-I*JTc7aSy;v(YophDX?rc8a-ix(v9`Ffvp-3P7DPF~d zhT}>Pff9p#iPJyhtT$`ZSOZ_FIS&oHrHoC|$Ee2lZxM|*czX*UaMEY-isKk3cd zh2(>iaUe#-X1)kR(*8#@q{m!T1tZ1^(=;xk9Q=N77SOsg&WT`wS-%fTa1sE468lZP z%@h2W^}&L_`*lcC$^RAd+rdiMQ#WM7w(u+7;p@}>#~<`&O+()V$^n1u z`by9*HZx$O0;+}nBFcz50b3eF`NckPS3CtGUsjG?i325&KH-~SFGx)JX5B-y|Cjz~ z6A$dmI=z?=G>EzdqFyYAbHsiNv?Pb~9!MtfJR_CX?ZyxcH19=_EvtiOTpk7gw!C|p zsmxegnDtpe6bYvvRb~BEp4XQJc76e)2j<|K^RYezh@cCyGW;x-v}a1&d?>pJfiM3^ z5cN>*8_AltcoxEp-GX|#5F_FQhx=ee!D;@WB-Vak591RfSwQR=u~Y&sor4bPyM4fl zzO!FNliw5LqXOo zOdQn+Q#JiaqUgdz=$vrSIp_*LLWz}a>F($g840WYqat=t#1d#g`VS-;Dex?S)3aa| z!{KoY=s<5?9L3t23O?pu{a9d~ zjaXP*{P*=Cg1_TURRAsec5sID*w3I^Wt$a!wg-8PY}?4Q`mv_)Ke+t79}B8EL4`X} z@5g`X$6DF^LCcf=iX;B=ME1o;a5!UTiS>YT+JX1M573-QSgRf;+thW`Cqaw;HSz?z zzJ3N@v9DI$q`3FE0Xsi9fCbfCih&cvlUTYSt(New`?D5oFTd2EwJ{ZZ#J!?fi#n~a z9DrZqRVKyo%xD(S_L@-q$4vyw{m18 zI`SptF&r89uG5*eH#iM^A+v85C@#B!amJYH1t0Lo(JaujHY5ywzxy6@9~*KSV+AX z^f&Z2QeUkYpW^ohu-0rL4;%ZmqbgDkd7My!K03AhdhSkeBPtczQ}Y`A6a zVX6;HQ%JQKyxYbywh=QJe3Gb=Z{J3K#>$$uo+PHe5Ue?8X5LeAf?u!ZTu6+W{%q zrnDhKuUHR0g~gv4)|@HTV>5a;TL%9QHdwZL1FYxrVy714B4i4bMO>VTT93C4j6aNF z!K@Y!9K?d$OTo4LnMtf!jbrc$L-aYHHHdX-mI_LqOzn7*+B5j!L99p54j7n-vQCiE zC|VAEDQl(DJ5Hah77q=<6Rxs)9eF&;G(KSx3#xw>oQkoE)BP25!3I8aFbe|v)(nOm zw`{~552*v8rV0Dj2ASeL&>M<;CHloTs6Tz4V7?3sBEftH3k;a^mS9%?JOyc6srbHreI4)f zG;123shCFrvvlYJ9FX{UPC0bE;7|_jvz8Y;%^EcxjuT9!N4G&grAPmEa1ZEPXujkB za_Ju64?nT=mtD2_grO{8$`s(1t}Vo(ic=NXLtH!A#gMBQW+6kRYqJ#`53unP&>p-8 z+g%6#lpf>{hryz1F%BLW#y+@?hb1$vs8(r^~g@h0>TO^VaEuQgor zGmbU5aR=9!G1vCsfDn)8U||W1q~WYlqpz@33D1OLuGNE_uph-wWBHNckZPfz?aA@# zLvpPVuQ!4(vyzpW8;TCjA1lVE=57 zx62!K#CLdzro6+%cQ4oXTdR@Z$MyZvYJOn^YaH^CsC`ea%HtrE=ssgrp0t)vf@S$Q zETrH*Sp0%=nOTp36*Eh&%8S5J&@;IzKeXz}RrzHs<-&^)7QC_f5=@dA_BwxaBx`Sr z2enW7kuK5)LB5E)8Dp7V)iUeQ35D~9zq^|4BmlH8S1IXbNuQU7S`$*(7i+5gS}ft!!{1?FfQ}AZiE+ZhVFhb+NCW*QMme*~{aTcNtl|%Rg1;R3_rK{Jivnk! z5NM?YZe@VB43CZ}*aP{R9PTS=GELkdmyhE+GO#ODdmg;zX0=;kxW&W!GS|(p{OH7I zjAhMx_eEvtM_{XCZ*Vp+>#M*F(CYkmty$1xm)0x%&|CLc z^ZMi16#t!>5dU+y^A)xqZ_4GZa(1SO9eb7MjDr=#ApZS0$c2`?+ISY`eh*aeI&DnN zOy95Mh zhxNr7{P9HAqUJaVgcv6A<9XO57UI?_!^wyCGqtF}FhtNWENLgQRzBNd_g^*)kIFSY ziN7?FHS*yu6l;Y(kn@SGk9o{e(wN&}n3f(*WFf2u51zz2x}CSn7JNQeec$;y3@3OQ zkoTF$n$=lS{fUMwD`XF+xnR-KJv-MYu^wj6bkY>`>&kM`FVo)Tyu~CI?q*-Ex-)sX z?9TK2+%v3w@CJyhnt5??0;A80L2*sIm-WQg#Qf-4L)6_8{mfE6B!PACN)U>~>O4_T z=PMFeP~9}>fsnP@FJtx;EEBThX5%1(@zfn0B2)VrXob$LeFcZAfk8`D4NQ;=^L~)v zWRCstHKPKSri(eY*)Hh)lO>QYTek4@$t-m6Fmw^5j72{#id&Ix19Qp(U1crK#qhCW zn1l?KQ|oF4Kb3oLGV8)jY?Z6#c{EIhhmfHd{9<$h)KOgj-_s9e@`Y1b54VtI;vT30 z|86R)?mm5yx(R5|33DXVcL@BR2WuF-W~J%hE#=n7EC^O;6YcGM_=GzNYcX7wJ%d4X3hMfb?s`X5fUVX9C znf}NvIXV52$$Qy(&<=)_L%l&m;B7WdAH|nXV-vlXQS90herFn+>s_0IiA#9TS*!_P zJDv4x@C@h$O#ANh^?7suLMP7DkJsZ6ao0ho4b{bL+ zyn6tC)+P2WA-Tg(=ud$bc?S>+KHe=N+2v#45i`lPSbM1+3@p$${rY@(9G>yASk-_cpYcM5;|1!kFE2U`}i z0;c>_aDeEI9)rr$k67vECa8dXEz#Q{4g0J&$4}tdo*-8ymUy#UVM}6zDpYq@v?S%p1=rK=*V=B1ULL+RkV8^wq2r!OB?i3P^Y?W z)%Pl5eMLM4L|6H6;xM$k73sPBwVAA&|FREZ=7x8^CRj7)hJs2=oM3(I5?M3gL4;J#0Yuy|QNT9Mtv9h!363I(8q7 zHiDp2FfAPQFjU!&>ldG~{~l!pi4XreUf4!fe>W z41u9g8s$VO$h>8F8PI{`m~y8%B^tLrd|>M%xn(Ri7IUz~jhKRotm}{$;QW7op7ITv znd#f%o*eGtxzKaaoUy74gH;{BJd@QmZL*2AVelM~i1gkGkHf$N`fsy@{5kkBDY#RX zN-Y%&Pg_$z8@jbS|6mqNaC>>Klb`Nm3TZtF<0WLH!4J?C*gh3AR;WG|(h6oNa0Phc z{A^JkYxp8REG^;LZYJ~PUUudgFhqo?NZ&CBXp4{F>WB;-K4T8EzYnfdh~o$!0zadG{je^tj}fb4{jV1xfnYzZ0<-a?s1<5H#D<=qiw64f zy)QG*n)8yN23T;4Sjo@4%(k#Pd|@hUU-vfp2tr}*m(h6_`feECo67wA7lN1M5@Hb; zsFo1;GLJq<)+Biw6*U8H+~C z!~by6v-R$o!jlQ&K?iyhQ#~2v%c_^vajg&h^-HPzfAd-J&=-&&Or8sFfM@#R2SgxI zAM+nxW3nF`U%DKmjk|O2+=1rar7-iS_#3RHGT?%BBDcK4+SUz=vEPArIp7X3w-U+lJ@a=g2M0wBP(dasV?-h95`Yu243iGoy0N=&hU+{^| zXps&-f{lgOa^ZJrknZ4qcoov)-4RtM91)nI5F*!va~A&f9Gq}s?Fl~CU1%5fApNA-cFZYYr^IF01 z)|ZMwa9n=}`~wfW^Qy${*B{g%hrgBm3|3<0Kl&2? z`&BmG)cbipc@Yb#@B2KkK%VFeF?})@MCfy$<8LowW1AK}3&8Q{Hh90Pm0iGI9t8+m z(kD*ib!=>E(|Gtkh_aOE0|d)o`h{ot8enNvI90GTcWH6zM*+glPQ(T>Cg4_4k*@Kk zX)MC?NAM$j9bf9jpGjl=Z6|@(nlTA(>WlRDV5%6|zAycb@65rDJKloBO~m?~jAL=zcu854^*hJSJmStz|coYh`%62{8Qdt65nc{kzGqL^zt^kuITH z@Ux;9;iH}lIKIha>3%eY2QFs4Y|T&?yo(5V`&jSy3|J3SUMS|OS+9o%X8Invf)>eV z-x!F6U@LeEhF;w%*oF{kJEXhl&`8HRV>@Lw0h7Uu`kP!^k9 zF0{ZWT*7ZfC*nhL@DNN{O?coE7UVe=n6bz%eufWO!h$_BR9^i#nJ-wvde*&)QV_^V zXg#?0>J*+2e8X~tHf-cg^a4;m$HwIP&a5w1;9C@YQGrVp2x?2k$0B|96y7_X^|h5J zfD#qOk<*PResu|73=-QY5W^QC|75W;0XIGLLMkqn;Fc-} zGG4znlaE}=h7Y|5oxwd0cnPF(i{ldZ5TXHDp!TDRFb4sNLqsT;C=lP=i&%Y1rM}Skm+oNr%${IFCB2j!d!U7OA!mk1} zz1TUU#V7}*J#Gp6djOAA{SO?En z;^p_XV|kO6tfMUezpFvA1T2=NuDDQ^Iv-W))`OqqFlj%=C@em@31u^Vj}m5#@C%i> z^jN>CQr1U52a;$n5XyRGn0jQo>DcV z^B56mQ`9&ek>1N1%qnXSUzMG z>s9-(Y!W>XA=Asf1RTU~npgn$>drhpzgE6KJerrSf?p4YfQHqub>h!cRx?Y@>OjVS zwjdf_V2xbOI-Byx@_DOSvqqnQwTd8L$+SPfpQ`D$k=#3rwJ^nx;ZRdKSEJ(((rwO5k~mT-$&P8?AvK@|by-HLR8C+i_y4U9&Oz2D#uF z@ya!8AZ|45UR79)SvXr8x*)vp`h3*Mv;a~`w9njeMqF}&*<7%u@*U9A^{ zG*X5Q=b39@U__7Nx7)qEhvHq75shVfbsme?yk&gmR{Ji4Wt|6%cDR&Tk8<#Kl* zgB}8x9s@}E-p9e7?X_#@|Eb|EAXm{4@`Q$IY$%`SU;%BO{s-f0AS;`S(`O8Zb*3*2 z7z{g~wUssTSPcC<{N_5?(;Lpau7ex8;e5%WU`{_79tx5Prns{C*s zwu7ObpNVs{d(^C35;FYrhw{6v2&*5;uLS)WO>e`GuGA0Z|LpHbf7GLU2grEWFXh() zS-&s{>JI)={vG&M^_TKVh{0rR9`CsKsErwvmhOEFLl0g*^5?bOVCaR5juh$LhwxGB z*+|x$Z&}Ye)N>5PoT@IWoOtc`vXx(7&)W7q0tR5FwT1YI4YBgF&UB@9L}s9Pm-DjT zK)NZ$ejdky8Sd)Fyu|{V{RK9o5^v!N_5R-+@uf(=KbXJq7L1p$k$nGKEZ_5L3^K#w zJBDX%VA)=KAq~VmC$0*8@cr&aJpOGKY)Tu(7ro7rJQsl@ah%Mv@`|^a)wDN`_j?Cc zVLF(Gty*IE%y;0K*MedE!*|$XQ{huQWFwnzy3mimzmd%~>(F=jfyfXZwvpB6Vehg? z)5E9voOjt!R+Arkm%VM8H6-_$_n1F3-R;lkXS3$8$Gb5bCZwDFcwRPZX!^ci?l0MF z8Z-SmnD_gDO)fH06xJ`j(6Q2O+2jM>c>xi#JZYJJjFdWu^`i?r+BMPP}5$2N){2L=^x+~ z38#K5noryW_c(>UdDsE)bNMFLr$;op@i3OBuHa5A>!+|=8P(G9hXC1NIaV$2W%w_z zVjLDhwDEeiH&6T+>T1t{yvuwmVY|FIQ(_H_?l?=x0t3XkMBK4U!A+zn+aU%-T=sRhiFPv0JN7YgC6 zc=hvw!+K~Bh){F*QRSD?9NypR+@z&E0bEeaF6ItRp{mhIRJt z2yAeR277S0yb0zpU$Snd30?S-FWErTksf@-S#|&(?+*GNI@XuZ`ktL^877^3U0n52 zFWB=T^ZqpKE;L-5XK!OI{Qv5TUqx)^$=Kpn(Be*f<2g0~Zex3FXRnwdBKXejY^I+X zdSJEp_bWD~>qv7gy$zHSc` z5@%apw1=gb)`#bg{+g|1v7XQ$m;3-Je-HY)Pvoc==Ve|onX`QCe;h1v*M9*LJ`Rr< zoUy%g8e~Yc{XYKa1IBn{JAU&5dz+uw2V?U>E6(<_ex^^`^8x!I-FW9%tlo-UTeaSl2=@bO*R;JC7AU^0z zXl7w^nKw&=_{lHXe$#IjzTzubxAkq$zxaxsXmzKVbnZ$EUjLCgT|%%o@UM0R^7psF zPdK>vUNv{>1n~>o*mzUtW-@sSTZ-iI-`onNjX3rQkWHNp;JtRh{$VIs>379E`d|%SQJ6)t2)e@SFbRrZRn|hRF1Z4W-RqY$BVT*A&rzOP>iq!P@mkfJ~px zEoAyMDg+DvEq(HW__w=ZiJ2Y5Z|-IdOlAwOwuh}U75eix_pql-?=_J*6cMO$XbbeJ zY7V`L*uUq{0wDcI4iz`y?f0<_rjLC2$$hMg>D7iZjbZ|18np9BbgYu0D34NFP#1rcJj7_`Pb{gg2CFbIwbqO{$+v8*4-4Hqz$j+EVo#FPS#^ zV4y2)21BXC(4|BB|C%H)q2lQ5 zCDZ0yeU&z;b^kqW66(sdiL5KrX0x|Un^dz*o4&PV+7$Yzv>61ws+u-k5&QSFX$++Q zNSm};GHs$gWZE>XA=9R`woIF&wPo6Dg5LgH+SINo)8?XEZg~+)V5XHGeE22S-0er_ zJsqSsQ@EU6rGp|C}4+|CX z6NN#Uy>7ujO{IH31lZZ%r|b&c30ENU=m9eJH&+Pw6pS@oO?N|a=-$wKyyzN>Fx~x= zH!Nd2P5a9EnKE|D^v56kz3c2f)6APZ@&+tzGw$=LH`pfA_6qKKlWmAiyn|W~!!yKV z|NKqXctJcu*F4Js($nFOYt2*brnI2XZC-v8);!m5@U?%yzi<>{Z=6yw)w&aP;d6D=RX*kb;QI3;Ey$ zm^tBv0DKF$yHHl5AN&)Q7U?Jcz^ic6=L*^h&~#pQ4IX0pmP=2yyCyyLOsVwL#S-*X z5WB(u{F6;Hjl99f-GMji>faC!o1ml8|8m$jH<0**!zuvM|Aarri&oIRbLdwv@G~&D z@}6~i)|-E@EYpON+@O1`7c;$E!V~^tBTa?B@fRMz`mmYK|M-igdTxSW%A)i>ozJd- z`G4IV{%Hly8cks-^#7~u zOTenCy0FjP7Z4}7AaDUiFN25^UL0^voN-1o=MqiL%#xDK%DR|Fnpzm@HZ#Q>bHp?; zHN{UeODk+JG6yQ7Ynox1+E;S_ckR6n!}a^0|M}0uJ?}a1T6^uahqH%s_Bs4($r2}i zDk%QfVzJa(&$XlWkMgIx82|nEybLMF!o(xL$|2>Jb#aR+$6)AYNjZwK44Ic&G5W(> zPM#C)trum8$MU4G{~%xSSh8v@|3Qa{`eAvHaD>+`rBk&2p3IIggj?72cI(TRmiH44 zYT4mDjjUXkmd_BGV&N~#e-<;6MqQyGJ@Fh9Lj~l(vDA-y+C13Y*I~P&<9YHT1-|Wm z_mX_sBHkCROAERP5o8fVewIV5B27e<%5_%puvmFs`uT_#h5IL&?IT(=Jn&Pwr@L;z zE9N)&8cn~)?sjmm`AHV}h)_}E2lEuCtTYkjA4q{la? zw>fFy}*AULI z@)bY0$uT?+A?5j=ugfbs`{DS!>B2whQ;m44M0V5!`RP_VjyJz6cOmr0c+C2U8A+-8 z=9lI8h((t;D*P?*q92|!V1|p2IO#~$3t?BDK&2!9pkzhFls}yjapj5+L%-cW98ZeB zTKHFY<$~oN7hHCVLAmfV{`-JRXQ}(CDlWvc3%wTJTKFrT(Q;??;FZUblkCAu&?gTL zz>~^GLER6S~6CwhJpG46BUr6v=7TSoH`qPgkU=DMvWr ze?-`>dW48?Kt+5J2NAz2v~u^RqKeEL`@bVR{(VJ+A3&K>cmol8r|={EpNlFfM1*tty8Sr9xfEe#%@}h}Tp1N{-B%gcsV^$h zmc>zV#RyGOwO_?i3N1=h!!yzuBBDC{g8%T*Z(f%;U5ljh|@8;(slrB zyWcz~Uk(vTHfpNq+H-$~Q0`IZ%P}leds8iBh=MjI^#Z>n?@Xmg32lk_3(ARi2$*A~B z-Sw(C@G&X;4+nlf&lSewj?)gf+~d3)s4UB7l@aq*o}wbAdJ|wqT<(7Bvx>4zQ}I?@ zzsIW&cBulY>-J?$RbSrxViSK9vmRE6I;B(Kbq2`VF0p4@^G z<$faD{_p$Ve%mT+UnH6K`>{&f4lmnia#de(+nvp}51-b4zj}}DJY=BGlP4-||MO{u z-}xl3^gF1s)`YR`!E8J79^1|e+vR&J{9aUPJEgj9OQr2iP@+7!cuI9U8!32YH$c4r zS_JL(!b;O&m8MUT{N6TluPR5C?M*p~2}p)+leg|HgBZv_y)-G}CvsG2Q?h*KF(HuamTXNu}IYvIVDoWwyS@S;iL+~p7$yY zFFr~BYr`qpuD88 zCw7qJ_ZH9wiq1wut=sMhj(X@P@@{=$PoGL&E$WMj-#^F{_h{uLo^_@v-uLKk?Zc9G zdHt|Jw{Yy0kr40)w$t=#Mqjjnye>_BZHXS!VclGKV1n;+ePm_>kWQe*BwG4}VAIIsyg;uu$Ak|n;MPCpNKvBV7LB8x8C8l(CQAscIO~^<5 zOHz>owNrgh!g9oSPEEQs5Z@|txNm(f-BF@#*Fi5|rU&|$ z^w(f`2&oq99#D@e^Wi`0^*a3jv4IP*AQ$To{gF2YDq+`MGQFV)Yx)~56mv~qOQShO zJ{2p#ycZlcj*@E|iWqwg)ksWn=|P~=oq2-nxBG0DEN&=b#ne;sZbQ-8wvCFIl@i={ zKa#19MAXBFk!4iROFz=HVO$KUOAe|fGPx?3JnrVOM-N3D?r1tf1zpI2b|a8_syp}E zN>#e}Uk9N^DrFp&Sn#5qlk!X>5pA9JMY(*ktHsf{JpwP9nL#QU?lI)~lF+ZQ{6L3M zIduGEnUO3a>*`e+wC2l+w|=vt#U^XJW92fasm0MMhI~;r{Ev~rOnuUsQn@75)u)=l zjVF}BfT{+6qc4Ptjn=ULXp0F}q(&RLa+GoqYt|P%?{p8O>OsrSQ1A;+xIa5BpNtXl zwLiJA9C>?j6T<7XEsQv#>y=c&c{xoN_y~U5{!h?sfd$6fZudQ~drB&ck=e z50!@tj!+&>*Cz+b=Ov-Hs#|P)>|TqHf2u96ILvuC4>_leJbJiF9-gNDt1=Gg9CM)u=1w1Bl?_s$$UNlI~M$Gf>_+|Ry|7Sc+ z|Ih+U)eDh(dYZN1LvkRrMK?2(uuj%+3%m>5S47k$kJSpH3^X111cQhkT$AF&vL+1g zaPeJqKZ`6UwKQ5mMkHSIZHMRmq?1EBcv_m~o=FW5Ui`Vwj7BXadmJAoM0KuK``*Xb zPNPK!IW|?;YA=q&%j4?x*&n zR?&YQpw~pvxTJ$b8ZNmXMV8IGQ)6b7`p*_MQougk7T_*8 zr`@=&1hgh>LP2GmI-N&!o$JObHEoAgMg8FLy$QA8v#I|d32k^lC$w#CRbu=h-s>Nr z<6QJ&Lcc{XI^9E6CaVk5gA4LYzQR_0s53(p98$F_;&*Zq$sUQoRV%|W*=>;}rv7l0 z9$&B=OKT`z*@)CDg)sX)3V$JI<@gSw(cm8sRtcpKG%n(2Bm(vqP2ggd}c-1?*NN<&|jJ>*F9#0f4gT_LuqV09K z7ax{*dXX?Vh5{p(n;{7m?Z%S#G#Ls~E%4Bv8^mf5+|Y43-4~FR7)-(K`1X`}zZ@Qp zl)JO{%al$cOup7i1jc%TNP;g#V)b>;Fnz4L76-rf`S~GP*h+K=9!^(|`u-vQF9-%R z%O=SZyml+kwGs`3YLF1_1ybQb*jJ8lifG&8$uNj&;MD!+EZHqd#E3nI<*wc$+LZ;j zE4rqyR3lZ!Fk&aAAUHFXC z-P=srYWPouw#fInw{&s%Q_E*V+W1ZKt;lS1L{)cCJyNRr&S2v)2uIgeDs@w|=z{J{ zO20NDa9HVH_=bTKU3HyQ*7?5me~G1Y)mRd}V}bb;WnOw(i?AUtR|D$loTe@+6t^9YAbyGFiu5qIk%mtH{>?uEPFpd$Fld$ z{}GG*{}N04ss>BkTQ6-k+`6@>B%z*BD^IJaB)loDQF8U0kh!k!1(G?O| z8=leYSs%OCjHgS(QL77u#(Cy=S*w$Xiuh|6ouitB9!R2m;IAC4d5^Bg@SqooM8-M} z*eOqX@0E{s5^+Hjm`>5J3)}X}HJwB=v00oEKNfevZ#;((TLsMY>%9eKq13fQbH268u({C@5%p=WH3wU8wLl| z<+Ss?6AnC_8^5M7)TiI*6+zVBw5Ci^a^7)cgq7L$!AFx zNmN(7Z@0WdlDtGt4@vMd+_QJf=csCVyeYv}vh_#P`7#(s6+~H?EDDT|Sx@pYsCThvx6yXs4N6WS;D74SF;nTXJ zg~;0`Un6{Kn><9=Yn%L)Fl?I)?=D*Sxwld;u%fl4VLgF_>Lp?a6iIz-^fbJ9xVYl< z_eog9{LofegrLH?RhD-b@xp(rjPC&~c~=ewj@th&RlZB^!p(SkWPX?yEs$PWbpYw0 zr}p%R4;i`-?4&05`3`Dvl6$q1Vuq=1DmQ!wmeA%>7yHK^dAf(F?`lj9062GpZR)`3 zjzv6lLlZOda2%q=p?7}nLX2j%1KNT&UPp1F=kaddA`w|d^L7^%t$OnoIxR;xZ;mR> zo1D^9*t(CPBy#((b(20eOXb)g`XGqMJ#iy=WWy?w>t{1Vn0jTCEa)j32d1E5gxiO2 zqEOxIx5}$MMfbomYHv*P{;jfeFA*PT4(Tmg1hr{WE#6_9 zb-b_d)bWmkjf!}8Q3RFq73WTQy|+lNHC44mm+0GkD&8+SigW9$#M^ZPom_H%v|c{j zM>J`<7?;O>dXh=yhVF%4KEQ;+H;&_C%2I=`n^zb-$q&1M}*h8%!>Z* zG4INdzM{1_u|uZz6^&ZHkE|l2utzVx!5FGnsr$9HU7`7 zIoH9x441u*sM%X)L|Jd@h-y7Uc2Hohwem=R47Hlimp}Cvtz5spUG6#d0B*c3>Vr{g zKmXK4qi{!Hwli>FQ};u3#p0cj6L@q;k5?bOat&Eoc{?BfP7v>R1mnh5U3$L*j@`bp zitORv1Clju$!#~yjJ+}rhgHKrfRE9M@YtRRgieo2ZRMNfdZ@dhuE%Nm{qNv)^7H`F zDDVfB3baIL(GuN!SN?Z^h#eV-99Zbae8l1AH0vdXif+tinK@8o)armTqjTqKd3_-I1N&NOA0(pd-$YU9 z%>Hx*S+$->NwLrhl7pM&{ey(F?YmS~RxT!g_j9BL@l&cgqm`k$fHDL%o~c(>%Ys27 zuJKSZ4ZbP!Ln~Fon7VKEios}3{>hTDgE0%(x=IcoEZT^tSJC72heYTqd4=%(HwpX7 zl%W`mhz)YwP%%q9w?ckBRP=MrfJgM=V|YKyC0sglhD-mYivGCN67oMI=T4^f(-7it&h0_Ez zwWi|mG29o|%DiD%lJM9ES6*5tMkH}W#y|65>$}#td z^>XU{BH48x>6w|G<_>%Vr=96K!2)w0eE&mz6E1*Q@(_{+w5!Wvd~O++s&df%Bb5|f zwYd}c((-p7=D?*A^;fT$Uw9|=WBP9}^d^Xm8ZJH+59G=#!^I0xvnU4Cu<)y2R60#F zK%0B)%7S?h2wzL+G8m$bh&(cgyUm64)v&1+OA7Ww`)M|K<~8q^-Cba3l>@EZl`qeNZ}{5RSh-;QbUWD$OI z@yo_<4t~9Hfvv}H5q=Us7k>0fma3bON$F@CBEiuhgNAf8s-hV%ZT0aZo`#2nxOJ<3 z0Kezf(%kN9DaWj-@`;;cs}KQsQxOe5o|84pjMIkW%F-m@k@kva6~ z$xlz7X&5szXJmlzdm?lCj2RhnN4oeJU-E`a&1K#M;S8)&@T_7`<>sa@yMBXYw`V=!v4qXrf2qM;=eXF9^Tc_|f((ev~uI@U!8! z5kE|Vj1TZ@fZtjCD5uWh7l_}l_>rAE_|=vz9~R5y>4!0iX)sBo$&5+jAGv+9h?ZYW z76awuQ^e+it&fU}Lav`K#=DxJ-$}x+J!)q=)W*9gXFJ*e2gWlJmt0>o`6F><7Na`- zuiq*R)<&Y$8-@<41`3*f1?bP;f}Jwl75NdrEBKwn?>2rz2ca}3JTke*jM>fLuFQR0 zOo&KE2i6n6e)v6r-(>ur#&5c;`-B+cnuDu={K?kNQ3y$YXzg}`gT z5+GF>=b~~?TVM__9hklt_t$~>z%9Tc;31%634Ek;paW%?@*2Vc(}AhLeBhj#_}AZA zuOlFcJm7H>0E>Y|z?(p4PPxa1!p;M>0NR$8dxitEfOCL3z#NhTHvsd2`+=;B;UKFbh})ybR1hTN{G! zi>0H*2?<9y+}2OQc_DB(un4#keA=mU&uX0KoW@cHoYN!gwh=i0rrfi@g@Y0t><2o( zE%*Ej%=!*lggRRIeYs~b)zypTo+6<0hjLFGuHKBF$~{woIX@$-f%(7S9Cfr1XhR)N zyMmm-c{(r$m;t2!L6!yF56l6c2Ic`>;}F1hwcL{j%)f!6h(d&aB1P!J({92;VBTMF z3|M#@jx~ZotZ`ipEW@hX7WfWh36|V;aX{Y(UxLATIxq|8DOl0_HPBhxga1g1@W4#S z^Rbk8I?i(fJuXi^5m=slnhXKmz%t+#C^)fPdUy;Rz#{RRxEc$A#W>HwGVp}va0ILH zPXkl%2Ef0VTxS8NBCGQVk<}@UJ)Sv~^}r%v8IZ2#G`y?e1m?%PJf0F9q%?s6@)WNn z42J{hz<+^8GmQQaaUO6PFr_*4foVV+nvV=1-NGpX4hK40;N3A`4sb8f(Gn4&`Ede6 zfGNN&NI(%@<>(NLoOj_Bk^~5{l02TpAWHDwOc5{zZ^R6z0G;uT7GO>nkH>+5ZdO;k zfD0^2frH>nx_dkuaPI8o@r=U*gY@1W&o!JIc}SJd=QV&%!SBQ=W(2Cb<5M7d)Oi2@t%9 zfKVtzk(>uQ<|0DexGZ@I>$xG%nvaNqg)bu_@cFMm59dV-!Q;H_RTM>26wPAP5uB&I zhKO)ZpRA0dy0O&bIYjbYk0%}HY0EtxpJs@^Xayo@1_P_$05E+uYAvuR4`q(ZmA>BN z*^U5t8<2HCV91svIgN(L+g76Q{Yqey^7z%DIuz6DtiEClWaI^RVl2Ic|VCZb5T z!a)}fvT(2$SO#ap-ST-h(H=d|(z*VC+FH1v-Iy!50Dj+aTax zcn)*|Q-LYKIqeYPK4ft+^gck{0j7P3$_&i;4~ha<_7Unpd)WOLl@wTX5=DUUB`0zH zXX2m?!~&r43Ca>^1LgxAz~jJ_Q;4uF3%Gao5z^rdz81h2kd|(l9CD3*j zMbrUyzQs8(r5Gs(=9~wT{&`e3Hz{7g^-L)NCUk^>OSt@SZj`_P&hg%kl!zF z%o6p&#DIYC9s#!Q0se+T1$`O6gn2R&s);A(m3w;N+~^hHd;tn%9{(Q(_Ntpt0as@} znQtcZo)P{cVZPk@jA+#qdNU|IHVRAsw9@D0>S0fVv%RccGX(5I{t3Hww9>V31aAQ` zK214zwg`(%#qrlr#c)7vBp;{cFG7CbY?ePn((VDac|we$B$D1T$ilNR@UN7mlZ+b4 ze8{F}%P(h(7{6j1@6DF~#j(r42=HHoftuPVIU(TgfHQp{E&<$4FTF0{%DnW_z+Lmw zn*`1c&R0jFvTQcE%mrAOTs7jwATt(Jk2nw9bZ}Mm_JW)0#hnB<2^@wiY9pu5gG=|~ z%D|1Q2-{B=ppOrR(I8C^F+xzA<+-$h!wk!8TaCA(k*oFV*Qw!;e;A`G6y<6hQuPWI zeydip_8WMVl=HmsaSeyW2i9IBi5<X-o91#{sCLK86ez@Gz5ifzxSS3Oun^^)J-Bsm;dr+VYXE>Dhhs&*VL^Hqn zICh82b9B5H$KesO)(fJw>l%(z&f)10M@E@%_~ClRxUMP}0`8<2mjI5I3|H0b0*=On zRk<{9hpKR?h+-1i3-`!_FNofW`J{mHTv-(-j)U{RRqjDasErD?7~I#9GVw(b>30Xm zz3$4vFN)-0ZIOS)209$lRF~E;l?im72HAPY;z%}-E<4D2A5%F!9kMdW%s9xgEO6r@ zF7V}D$<8|uscSty|nIR3i6Jn(|(lQso4wjh}li?Z-M2Fd7`1*JXeI)&H{Hj5n~!uACDAq z^6FgCvTg@hp;^f3BxINDnk^d02VN3(Kbm-CbdrPTiT-{x$w<3TuA$>YICgZG*XN03 zzdJa-*-z`F)`9nfw9ZN#Z%ETRU*mZ8Xsr`h7g_h9*2%!_U08O0im@V4WLSJX3TT2T6d#sVmfyD7`2VW zF>N9EWr6Eb$KydCX&55|Y{OCM(r`E*>Fo7lxn~H5B|d?%a>%QA;QjY2qE%oqBu<2Q z8(Ka`n#=YJL{zlZGx7^#i9v(BeY~$XMq%`TRn}upgxJ2-+DUo5ac5uR{IDg(5PLW*#k$ z;=Tb%8YxM;z!c`+S8w~boYUj$cAQ)zrw?wLsB!C3kIBGE3; z7KAGBdAVm9W!q-hSuFkz3=f8luW@UGG(9o0$rABk;CzsmLG~wdTa2vnnrI=9ED;?8 z??6(B>90~Bq9l_8r`I!#)o05+5#;)x(enA%D7Qdn6qkE;`h)x>S{M9<*N~n%s4u5W z$~~E6y-kd4H6IsUx7Wqvf!iTj{bRZ3QIZ^smIq&l+cYUD#tiyvbOk=Ul%d9!bu#*0 zJk;otBmCR<3Pls@#w|z+wLY3Vp7eRby3fKrPkLLVays<7_<20Pl3wRn`FxIO8n^-E zP0Xvaq2MzzTJ9l|rZZixm3uCbW~*p<6=ahw&_I(^qenmj zIyiKWvhz|A@xXJ5#v_T2UI9}l1Z4CGnB5~F%hE3(J<;f&Xbeg;dM6qK5{+?*##p5@ zzPirn0B84r1Z9)Pe!7e8nP@zSuMeSa9)}NK|6cBSgz}@nK^3L3JiiomJRF0{oLU~; zmm{apmq!Ptf-FIQ@&&p4utRo!LktbvLYlQb9{kH%!>4_;eCrL=O`4EjtBa97k=AJW z`y0r8`WB@>X7>+J@UK*CVX|MYXdajY$y9XBWt4N$A?M{HwnC5znCy3_O7C(=*L*lv zoD1hdB5-|1c|1MHP)~;(vQjjX@yk&2$3ZgP;ql~Bg6xni!(+bfAP>di{b+LLY9qOJ z8C3<49pW*GBJx-x`Ne!R%bsN-BCu^Fim?^jc&m~0EEhcj!1Zd&CT28}Lsp1n;tJcb zg}!WI*9t78!+n`K-Pk}QD3cT8K%T}&Oq3IUHq=h!flS4$T{&^8VHGDXLlQR(69#f` zeM9O^Vfyk0NlQCsxdnfL2|Gw<_QL;24tTu*aAUK{T5te_xm8p@t;iI%DnJOsgF zO!1Yi8f>e-6b%FG;D*onvG@lk6y%F2*)3Z{$Q&uc0|!EK`XP@en6h|t6lHNcWo#J) z$8ld^3A_9F%~*O zhMR}W&8uJ|2W0VV&fELzQ{J|VLAznlt(COrJf2t_(`aY{x+PGCPh-^6YABmJ} zS5svLc@pGABHKhtR~~X8E(Ryhd(^zkCoobMp7U)K-XuucnvzQqRU{iAnPf_KRh3+Z zWSJ>(v80Tw#Ua7RA(=>#4~fu`kD^?dgX&CV^9b@iQchkY?13vG$$})4B%W~j#Tt=R zF9j#(Avk0TPANgVK)SC*UsYGeWw_k67AYGJvh52@&SY`{$PAMl!sH>4+f5QBsDhV) zbem)iCgV^tak$N-BKpa$HAjI=H_0PRE(W>UB;T^@^cI3F204RL^^CnDOG9Kyp704Y z;?cC>eu65szIH0LBz3v;f}j@!FH^XBc0DQ_yG}#}E`wygDfuc)OSX~ZG9A1?)QBgF(b)SV>Q)KO74%7J^5nXd}b2LVf^i>mTIr|-9bL|Ir8JrKaX`@O^cT7vb z`6@>5(7_v*!B1W8sW!vC18x#HQ<9lJ zlrtb>AoZl^Pr2_?lateMq71kF%v7PW7$x8utdo56hXw<111;9qcu(42KeX8AOiFK!ZH?I%{5 z5cdu!u~^;KDol4UXaZp`6}2aS5zI8fL@F;`WgAPs8hn_$O(;e8=Rh03-aLqQ+yWsXV;kMYrjL#6| zXO@63e2h=5gFf>yR`@((7zO8ch`PQ#x*Oj@`mLqm(VE6zmfAo08%srPx4*Gk6oJ2H z4Se6<*zOau!r%DLC)8cTDDmn`9Dtj5Qd(Z5=at)ecN0ug%MYWySyxUI%c>->W4dqh*K{Hp-t{15ku z@Pgm=iAYO9^ZjC%rT)iOV?f}4tj1DHqjOf{JIiR8y(k9Yrq{djuMfoZE|U#oa^SZ$ z4Qa7`S<~2T2`Q|JWQHxOY23DiAR)VaQeLiQ%=HcX+~3&d8?wpY*jl3jm=iT_3;Ff| zkyX&|pcr8(ICe@+Yo zumeuFDbkqrzS#3*(IBz!q{tK2hQmeflSL2K%Nt$Yoli#-KS-vZ68(v zozL|;mtN;Pz0OZ~oqy+b{%iGfm+^0P!l-?}cEohZ?sXpTb>7bFJk{%bl-Kzq)y^jw z#*@_u!*KCAZVaexA)jN+t+wC!ybhlmMf#vFpSx+AgbfoOwVm86KRu15&P99Wuct*~ z%zkyBMctiL@&w$fqK&}_94fO z;+bSJ6MYy*Fiv5d#hA^wf^ie$KE@M_XBkTwZ!!9P#SSpW6C!>inTbA(BN(SJ&SK1F zT*0`BaUbIe#~1mju8QpQ`1 ze&4VIjPc*-_>E*H`Y?`QoWeMZF`ID(<0i&^j3*e+GL|ylV)Q%94lu@_Rq00#Fwuu` z1mhIOS&Z3?D;PI1?qfW`c$Tr0@fM@sx9k98yo(Q#8T&AfV4T7@i!qyV1>+{heT*j< z&oY)W-eUACW(OEu@qCcX*oSch;}phOjM5zj<2 zV;{y5j8hnAF=jKaVBEyGkMRWKS;kVvTa13^*a3wo|9B>n8T&AfV4T7@i!qyV1>+{h zeT*j<&oY)W-eUCoo*f{h`WMecGGiac5sXt9XEA0ou3+56xR3D!<5|X1##@Yj=h*?E zs{ip!Bs2D59Kkq+aTa4X;|j)2jQbc*FrH;BWxU1ccYz(az}J5~6UmHy7)LNpVVuR7 z&A5Vb6XQO{6O3mWOBru5`dwrPF7ovs&qOj~AI1@kQy6D4W;3o}+{Cz#@dV>p#!|*x zjDDBc0T&;{GbS_kVI09Yg>e>RHscD$O^o{(PcWWkEM>gK=vSg0s1?Qs@g)WS{X*Qb z#Fp`Z+JX_qY$#a^wWhhZVN`hrUvVzGia}(uoPAD&TQ82%ob8(8+{C%TVU!u`yrx5v zy)8KOw)ZE8E%{A_J^8%|kFl5|*6TW~jnTpAybj$&k#b$0J163;i}iq3%o^f6z!?_dPb4goM9T<;kijY(~dDq z1qe1U1RQozB)ZH=$Vt`+fn|#kV3-q$z0A|&72?ft{(1J`qQW5D)GH?=r9W zpJ#sM8Xb&78B3U-BdhakO?@wZH}eTAtLq=vyvvyGCHU5iz>CK;S2?=Hi@(c!i5GA4 z(fSSEtR8VJ^P?2+GSh{D1PfMs1?b0ou@^s%`S4ZMBbZ@E;Kjeh{DP`H#lIW^s^eud zbzPxb^0W=~)qK$$&C|Vi+N^%G4>I;wH3TM%`S7=@JJghUrx#BPB@lm*k?JKd>twnY zKLi2D(IR@HfsN`qZDw7~^U^=c`i>{HB0UVFZK;|71sN%;MV0)AU;_&>R##6!0rPoY z{HM&jz4!~vZ}F6NXaTs_?aULzl!PZIb4dXS|Pw~=U$NU^G zelPPUy&U@3#e(f#0nRaB;uYXG=8d)09o6gqgNzU_z8;ogP!T71@$t;NQoIBmSuo0r zAIN;B7eAi)m0tW)%lMOo?`W1eWBmNi$BBqXioFDna{$}A>M5Sf zywi(c&U~5||1R_CUi^p5&jw$a|8V9E3s!pt_?h|rUi{z8zd+}zcxl-{}%7}YSV~baSZ`sg(Z=O5HlU}^p znHGETW@qZI%2WJiZ+fSy0KD0o`me8E1ZHO%?!}v(X@VDTcBUP?_+ODQ#E(A8OHk3v zdhvHTC#HJwHg(T0$e8WL$1=abi%$mcW8Cl{jq;#d)V1`IyWLgBORK%~%{qG0i#O}& z9WUOjiwPU5XPjBJrhD-t;17Btmyt)SV6f4X7TS($fl-#M`FKqmX5CutWyq{sg3BLpFA(84grk zv~8;HX;%m+K#Es@nTiiE%)9u_*w8xWaW`Kvt(pH8Jl8p9G?-)bE}KN}Y67$2n(gI~ z*$yr6;xBUqGUtLwh$vHT*zk%+_2d|G-38S2awQHo5_l@5MjnVhGKk8hu3_FRH$8HI zrj1obcO>&qFkg066Ka5g!}H)RMz~?l(@is16dzz$W0e<=5>(p`#fKZ_(dc`$;O!dlDCqhST-rrE& z4m6iCU&c9S*6K!)I_Gf1L%Cu;a(VzfCBVs*+N^U$^lBps%o`_W1P`G0Q4!!KjWT5N z3&5))qjw6hnOR+uJ2%ko`WMm|$|qX#hOJEP4WnBF!w3dnh<|}c8=5|~O<_LWU&k_$ z`RAB-&>BW;+{PPgz+?7dY&oFyRjxt)p3*mRWl&JWH#jCA3h(N$_z#e3YnmI>z~1kH zDD6qwI8B=#&SXBvPg~B^fsAbC(+6pNS~W}CZn9=6qrI;k!^>dY@~Y?CrsAky#nIA= zqazhZf0#!}jz-$s48DlXn$i_KM7Si$!+P+je zHgUr1w^FI8b3GU-oYPil?6oT4vlr7fqj(m3WbdTXB6Ow+bc z1+$z}5K%i$N0H9WuIb(vxkHh2x7FU3`$I!}M9ap85skZn`?;OuA(Qa}Nt3ohO0JeH z(y^s;GVg#V?+QzG>^(FX_S~R)_N4nUz@kt&_vxHQJE^wNCYmpEX)|WRlXH{ocFRQC zPk4oO(*Dvqv`mw>Q{Yu`_-JEhPj!*?o&RXXY>q2AL8oi4SGuxtj|SNL?Sxe2uGckP zZ1N4X8Cq^cTU1l+dC~9s{3i{@1Gy7o?Ut8lAF&TQWHYC(4!ewXK36(_&^{><;~N!k z4Xrbgb-FQci`A(xd$IJ~2?2J?O4>)9V;!TH)}bZzw0V?H@7yzW?Uv_gKjBz&N_&Wr z#l5}R(wqfPvF9b~*l8UTZGSWG zgQw!hni+N8kfZsP^&LGl@e=dFEp;^cqI#ypG4Gh76@Ovd;w+`4msA%5@-P24ZSW4;oyxrJM@9Y+cEf(dz*duNAoVD@RqhrE1zlWn53_p zLtcim!IPuj5$A!YUM+9GP9%4R#$MLXw`rah2+~%pczujAOsrGOaw9jojy=~_H$0+g zYaK~StPW&$(1UV=P`RH{`vtW3y#P&BrV*iSIH!*2gpB8$8YzSRK_$&*amoeF)3SBi z&VZ-%6qf08dJTxSa^`cAwBlgxiP5;N*3a;2e>yU6yQ0H?qQQ6yd=OXCm*qORV%ewR z8x1w&(SJd9Q{bA=PMdee>-cEhA#JZSZ{uqwP}9a9ljp{!AM>9upYpRNdNKbS^Chjc z{SM5BC#&3RW@PQt>7|zkXd4d!e#k{SLs`$eTdUw7+tj;CzlSQR-2Rg9In6j zGyf=f*coFuHfalHJ>AX$jM_RkW^sUH%sbeSo%x?l{WNVSf-}*UyADlE8?~Q8`-u7X zX%~ySYE83>uZJ{zpebyrh3X@Wh~f@-s{g6{C=pdbZ4upcK4fgx`eqr81h48u)Lhm` zxu!$#9K|?fYDE0T8im}ynKj)iCkU(E5~ZQDf~1Kl+WV9Tb%fl78w;2(;dX4Ij?H*S zhyF%^Z=>#}TSMpw71U8nXOyWw)zoOW6h~CVS?n>lT*e>FJAc(tJj5~8?7_Bu8<xswO5eU!#xK&#R-I zF>}C?yBkgJK(&8S?GLN{ZMCl}qy4P*sAP-*Rr}~Qxg7vr^}A7XStH-81>FeVp+;Lr zl&PeDK#+en&hZHCwm~n6(xpkZ6HrDN_=M>u|V%PVTXpY{>oL7S`;aznX_t^A{O zn&i%nwkM53FGxCBg*pr9y*=7?F`t&Dd2`-zMuqw>q?FVYuR;s#ulbzmIuyOuNLzev z_XhT)evn3EaF@n6J9vIl6p=q?W~Mzm4|3KSEQFe{1L7!0fao z?UQ;A(oU3lT~P0Vr|fW8bhU|P=l-L@UQqjp+#5mmq@=-GGoNc>U!7uO9P`dtO}wBD z8Lv>Nh(nN43Jp%YS&%;{X~+{;RvsJ}*-EEC>0E~nT@rV=_sHNl zg&bPPYIog_-jGaWabNc`iys9~S(^X5Hcf9r(zb*7wCno(Mdq)Y`lXsMo1{gL}5?(Ia#C+k}66Otd$N@dwv=~ZpV7z8zhVU+bH7Vu`8GO_-VB^(q?6rwStcMzGbXIy8^;jaYdG&O{7^m}bLiJR5eh zGvk%mlBpo-Eof1?oC~!PzIkW-0A4p|_2 z;=-MpHzy~FqqNIT?q6!MPlM##Kp4-~uuH>L1-_j}Ba9k84l<9TmeCQsPUj(kgY9fE zH6lL5kt(l`vV#{)^^)0l>(ULY=+>zUazm-^~2U&eus_dtKK7`F2A59pit=r~m-mFQ_ z%B2+fRu-qzOGMaKXfXa!;;5z%>d+}mH2(m{x){73b?v1i_~qbnsM+40F*SUDGmny- z7{QQkzD1jPT1AOh!kEW@D^!d*)tx5i1|y62X!uG43KL{d&GDSmTyVbfMzFO}Qg6@{ zg;Qu#vp9-T%%}5ouLbkdy_2A$m z3L@Tu78!ZV%g8RR^@WDNwQ~3bZPLkCxY?!ll}RC1d(y)!&ikf%jIk4S`ZBoQqZ?4$ zAbAwQEDtI0oeeGBfWEHzd=14KEHXOOYWHJ!*K<^e@y1|MTB9HN=fm)%H!o31oKUUSZCKFMM$UJ>TLHT z2XYM4aqZ-)9rFm+JgIs5AOdaU!RzKF+dP`EhjnwjO8Gm|fJ0ek?bRlX8?0Z#J&l8T z%T#!XCU`2}6$ocOR35b>-L2#eK*Yn)qRdlYN`;2mC_b;WB9^eYaIJRS?0J6DMttSm zaM)@jcN2_Nko3f(+GN%jI*i%=?f_2-aCnv9Q57uWB8zkQuADFXdsD~ZkUA3>CpAKM%0lvD0CkUk0@{z zKCX+h&})KQ3Z9bbee>1l2_0$5YHgfrjPW3N(l3kGJbf~Uwt39w@qH&V>)+OP#u)iL zh)-AQ802t(EG{d)q>Q6FfRTQ>da69E?|rj7T6S-MN_ammWy<384LX>al~Re54nwR; zlZQxJ$uKT4pUw?i4ff6=&%mlBSwX}|4Cqv7yKXkxZt%oYo4W zw{(;ar!`J4Zf313zi46|AU8I%c9CnFSU(nRoCVJ(SeIBr+8cd)_8dQX_QR7iGRJ35 zm^giMn~t(UGwXnmw)aTdwy!WaB_!z{al3Z1MRRLolyo=1ppN$_w0GW1wYitNH{$lm za(8oU2a8kwN{}pLTUa|ZX?;)7jvel$I?8b^tWEHZs+BFQ56i%o))3jgr8U9QUJh(& z9TIx4k)+n`*`L%sw!sC;BQ*JwKgOb`*`%<`U)Y;lyKGM#LFZgt}1{Q>LwAQ!C@sq6$ z3g&mRek0VOyw%m(reO1Z))jsQ8NICwY81o_vaaqUP#x{tMX z!I-D5eJr`o>4D*`o&W3W<-KPWte$0E9a8Y>BI~Yzf@!O)A6sRIHP&>?arx#NYpY=M z#aI(_obucnYhtkZ0DNSM!b6I&$<{YpYttZ|DmSFT}pF)|wn_zA$Sd zUYI?H;KAmLwIHi!'; return document.body.innerHTML;"); - printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value); - rkt_webview_free_data(r); + //rkt_data_t *r = rkt_webview_call_js(wv1, "document.body.innerHTML = '

Hi!

'; return document.body.innerHTML;"); + //printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value); + //rkt_webview_free_data(r); } if (i == 7) { @@ -75,7 +75,7 @@ int main(int argc, char *argv[]) rkt_webview_run_js(wv1, buf); } - if (i == 24) { + if (i == 15) { rkt_webview_close(wv2); } i += 1; diff --git a/rktwebview_qt/rktwebview_qt.cpp b/rktwebview_qt/rktwebview_qt.cpp index 1f95674..f383536 100644 --- a/rktwebview_qt/rktwebview_qt.cpp +++ b/rktwebview_qt/rktwebview_qt.cpp @@ -352,23 +352,10 @@ void Rktwebview_qt::processCommand(Command *cmd) } } -void Rktwebview_qt::processJsEventQueues() -{ - QList wvs = _views.keys(); - int i, N; - for(i = 0, N = wvs.length(); i < N; i++) { - int wv = wvs[i]; - if (_views.contains(wv)) { - WebviewWindow *win = _views[wv]; - win->processJsEvents(); - } - } -} - void Rktwebview_qt::removeView(int id) { if (_views.contains(id)) { - WebviewWindow *win = _views[id]; + //WebviewWindow *win = _views[id]; _views.remove(id); _view_js_callbacks.remove(id); } @@ -394,9 +381,14 @@ rkt_wv_context_t Rktwebview_qt::newContext(const char *boilerplate_js, const cha QString name = QString::asprintf("profile-%d", _context_counter); QString code = "if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n" + "window.rkt_evt_frame_el = null;\n" + "window.rkt_evt_frame_win = null;\n" "window.rkt_send_event = function(obj) {\n" - " console.log('Sending event: ' + obj);\n" + " //console.log('Sending event: ' + obj);\n" " window.rkt_event_queue.push(obj);\n" + " if (window.rkt_evt_frame_el) {\n" + " window.rkt_evt_frame_win.print();\n" + " }\n" "};\n" "window.rkt_get_events = function() {\n" " let q = window.rkt_event_queue;\n" @@ -404,6 +396,22 @@ rkt_wv_context_t Rktwebview_qt::newContext(const char *boilerplate_js, const cha " let json_q = JSON.stringify(q);\n" " return json_q;\n" "};\n" + "// add hidden hover element to body if necessary\n" + "setInterval(function () {\n" + " if (window.rkt_evt_frame_el === null || window.rkt_evt_frame_el === undefined) {\n" + " window.rkt_evt_frame_el = document.createElement('iframe');\n" + " window.rkt_evt_frame_el.style.display = 'none';\n" + " window.rkt_evt_frame_el.setAttribute('id', 'rkt-evt-frame');\n" + " window.rkt_evt_frame_el.setAttribute('name', 'rkt-evt-frame');\n" + " document.body.append(window.rkt_evt_frame_el);\n" + " window.rkt_evt_frame_win = window.rkt_evt_frame_el.contentWindow;\n" + " } else {" + " if (window.rkt_event_queue.length > 0) {\n" + " window.rkt_evt_frame_win.print();\n" + " }\n" + " }\n" + "},\n" + "10);\n" ""; QList scripts; @@ -767,24 +775,14 @@ void Rktwebview_qt::onPageLoad(rktwebview_t w) void Rktwebview_qt::pageLoaded(rktwebview_t w, bool ok) { - /*runJs(w, - "if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n" - "window.rkt_send_event = function(obj) {\n" - " console.log('Sending event: ' + obj);\n" - " window.rkt_event_queue.push(obj);\n" - "};\n" - "window.rkt_get_events = function() {\n" - " let q = window.rkt_event_queue;\n" - " window.rkt_event_queue = [];\n" - " let json_q = JSON.stringify(q);\n" - " return json_q;\n" - "};\n" - ); - */ - if (!ok) { // Inject code of the profile to this page WebviewWindow *win = _views[w]; + + if (win->navigationEventSent()) { + return; + } + QWebEngineProfile *p = win->profile(); QWebEngineScriptCollection *col = p->scripts(); QList l = col->toList(); @@ -906,9 +904,6 @@ Rktwebview_qt::Rktwebview_qt(Rktwebview_qt **handler) : _evt_loop_depth = 0; _app = new QApplication(_argc, _argv); - connect(&_process_events, &QTimer::timeout, this, &Rktwebview_qt::processJsEventQueues); - _process_events.start(5); - // See Qt 6.10 remark at doEvents. //connect(&_evt_loop_timer, &QTimer::timeout, this, &Rktwebview_qt::stopEventloop); diff --git a/rktwebview_qt/rktwebview_qt.h b/rktwebview_qt/rktwebview_qt.h index 30e4c6a..d9a9f6d 100644 --- a/rktwebview_qt/rktwebview_qt.h +++ b/rktwebview_qt/rktwebview_qt.h @@ -47,9 +47,6 @@ private: void runJs(rktwebview_t wv, const char *js); result_t doWindow(rktwebview_t w, int cmd, result_t on_error); -public slots: - void processJsEventQueues(); - private slots: void stopEventloop(); diff --git a/rktwebview_qt/webviewwindow.cpp b/rktwebview_qt/webviewwindow.cpp index 815ccc3..e146621 100644 --- a/rktwebview_qt/webviewwindow.cpp +++ b/rktwebview_qt/webviewwindow.cpp @@ -52,6 +52,7 @@ WebviewWindow::WebviewWindow(QWebEngineProfile *profile, WebviewWindow *parent) _resized = 0; _profile = profile; + _navigation_event_sent = false; if (parent != nullptr) { setWindowModality(Qt::WindowModality::WindowModal); @@ -67,11 +68,14 @@ void WebviewWindow::navigationRequested(QWebEngineNavigationRequest &req) QWebEngineNavigationRequest::NavigationType t = req.navigationType(); if (t == QWebEngineNavigationRequest::NavigationType::TypedNavigation || t == QWebEngineNavigationRequest::RedirectNavigation) { + _navigation_event_sent = false; req.accept(); } else { EventContainer e("navigation-request"); - e["url"] = req.url().toString(); + QString u = req.url().toString(); + + e["url"] = u; QString type; switch (req.navigationType()) { @@ -94,6 +98,8 @@ void WebviewWindow::navigationRequested(QWebEngineNavigationRequest &req) e["type"] = type; _container->triggerEvent(_view->id(), e); + _navigation_event_sent = true; + req.reject(); } } @@ -208,6 +214,11 @@ void WebviewWindow::setOUToken(const QString &token) _ou_token = token; } +bool WebviewWindow::navigationEventSent() +{ + return _navigation_event_sent; +} + void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c) { _container = c; @@ -216,36 +227,6 @@ void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c) QWebEnginePage *page = _view->page(); - /* - if (_profile == nullptr) { - page = _view->page(); - } else { - page = new QWebEnginePage(_profile, this); - _view->setPage(page); - } - - // Inject event handling code for the javascript side - QWebEngineScriptCollection &col = page->scripts(); - QWebEngineScript evt_script; - evt_script.setInjectionPoint(QWebEngineScript::DocumentReady); - evt_script.setName("rkt_webview_event_handling"); - evt_script.setSourceCode( - "window.rkt_event_queue = [];\n" - "window.rkt_send_event = function(obj) {\n" - " console.log('Sending event: ' + obj);\n" - " window.rkt_event_queue.push(obj);\n" - "};\n" - "window.rkt_get_events = function() {\n" - " let q = window.rkt_event_queue;\n" - " window.rkt_event_queue = [];\n" - " let json_q = JSON.stringify(q);\n" - " return json_q;\n" - "};\n" - ); - evt_script.setWorldId(QWebEngineScript::ApplicationWorld); - //col.insert(evt_script); - */ - connect(page, &QWebEnginePage::loadFinished, this, [this](bool ok) { _container->pageLoaded(_view->id(), ok); }); @@ -255,6 +236,12 @@ void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c) connect(page, &QWebEnginePage::navigationRequested, this, &WebviewWindow::navigationRequested); connect(page, &QWebEnginePage::certificateError, this, &WebviewWindow::handleCertificate); + + connect(page, &QWebEnginePage::windowCloseRequested, this, &WebviewWindow::closeView); + + connect(page, &QWebEnginePage::linkHovered, this, &WebviewWindow::linkHovered); + + connect(page, &QWebEnginePage::printRequestedByFrame, this, &WebviewWindow::evtRequested); } WebViewQt *WebviewWindow::view() @@ -306,6 +293,21 @@ void WebviewWindow::openDevTools() }); } +void WebviewWindow::linkHovered(const QUrl &u) +{ + QString s = u.toString(); + EventContainer hover_event("link-hovered"); + hover_event["url"] = s; + _container->triggerEvent(_view->id(), hover_event); +} + +void WebviewWindow::evtRequested(QWebEngineFrame frame) +{ + if (frame.name() == "rkt-evt-frame") { + processJsEvents(); + } +} + void WebviewWindow::moveEvent(QMoveEvent *event) { diff --git a/rktwebview_qt/webviewwindow.h b/rktwebview_qt/webviewwindow.h index cd87103..9416542 100644 --- a/rktwebview_qt/webviewwindow.h +++ b/rktwebview_qt/webviewwindow.h @@ -6,6 +6,7 @@ #include #include #include +#include class WebViewQt; class Rktwebview_qt; @@ -37,24 +38,28 @@ private: QWebEngineProfile *_profile; + bool _navigation_event_sent; + private slots: void handleCertificate(const QWebEngineCertificateError &certificateError); void navigationRequested(QWebEngineNavigationRequest &req); public slots: void processJsEvents(); + void closeView(); protected: void closeEvent(QCloseEvent *evt); public: - void closeView(); bool windowCreated(); int moveCount(); int resizeCount(); void setOUToken(const QString &token); + bool navigationEventSent(); + public: void addView(WebViewQt *v, Rktwebview_qt *c); WebViewQt *view(); @@ -70,6 +75,8 @@ public: private slots: void triggerResize(); void triggerMove(); + void linkHovered(const QUrl &u); + void evtRequested(QWebEngineFrame frame); public: QWebEngineProfile *profile(); diff --git a/scrbl/racket-webview-qt.scrbl b/scrbl/racket-webview-qt.scrbl new file mode 100644 index 0000000..9d9fa86 --- /dev/null +++ b/scrbl/racket-webview-qt.scrbl @@ -0,0 +1,447 @@ +#lang scribble/manual + +@title{Racket FFI Interface for @tt{rktwebview_qt}} +@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] + +@section{Overview} + +The module @tt{racket-webview-qt.rkt} provides a Racket FFI wrapper around the +native @tt{rktwebview_qt} library. It loads the shared library, initializes the +native runtime, and exposes Racket functions for creating and controlling +webview windows. + +The wrapper translates the low-level C interface into a Racket-oriented API +based on structures, callbacks, and ordinary Racket values. + +The module provides: + +@itemlist[#:style 'compact + @item{creation of HTTP(S) contexts} + @item{creation and management of webview windows} + @item{navigation and HTML loading} + @item{JavaScript execution} + @item{window geometry and visibility control} + @item{native dialogs} + @item{asynchronous event delivery} + @item{version and cleanup utilities} +] + +@section{Requirements} + +The native backend requires Qt version @tt{6.10.2} or newer. + +The shared library @tt{rktwebview_qt} must therefore be built against Qt +@tt{6.10.2} or a compatible later release. + +Earlier Qt versions are not supported. + +@section{Module Initialization} + +Loading the module performs several initialization steps automatically. + +@itemlist[#:style 'compact + @item{determines the operating system and architecture} + @item{sets Qt runtime environment variables} + @item{loads the @tt{rktwebview_qt} shared library} + @item{initializes the native runtime} + @item{starts a background thread that processes native events} +] + +Currently the wrapper supports the following platforms: + +@itemlist[#:style 'compact + @item{@tt{'linux}} + @item{@tt{'windows}} +] + +If the current system is unsupported, loading the module raises an error. + +@section{Data Model} + +@subsection{The @tt{rkt-wv} Structure} + +Each webview window is represented by a transparent Racket structure. + +@defstruct*[rkt-wv + ([win exact-integer?] + [evt-queue any/c] + [callback procedure?] + [valid boolean?] + [close-callback procedure?])]{ + +Represents a webview instance managed by the Racket wrapper. + +Fields: + +@itemlist[#:style 'compact + @item{@tt{win}: the native integer window handle} + @item{@tt{evt-queue}: internal queue of pending event strings} + @item{@tt{callback}: user event callback} + @item{@tt{valid}: mutable flag indicating whether the wrapper considers the + window active} + @item{@tt{close-callback}: procedure invoked when the window is closed} +] + +Although the structure is transparent, user code should normally treat it as +an opaque handle. +} + +@defproc[(rkt-wv-win [wv rkt-wv?]) exact-integer?]{ +Returns the native window handle associated with @racket[wv]. +} + +@section{HTTP(S) Contexts} + +A context represents the shared HTTP(S) environment used by webviews. + +Contexts correspond to native WebEngine profiles and determine properties such +as: + +@itemlist[#:style 'compact + @item{cookies and cache} + @item{injected JavaScript} + @item{trusted certificates} +] + +@defproc[(rkt-webview-new-context + [boilerplate-js string?] + [server-cert bytes?]) + exact-integer?]{ + +Creates a new HTTP(S) context and returns its native identifier. + +Arguments: + +@itemlist[#:style 'compact + @item{@racket[boilerplate-js]: JavaScript source injected into pages} + @item{@racket[server-cert]: optional certificate data} +] + +The returned context identifier can be passed to +@racket[rkt-webview-create] to create webviews within that context. +} + +@section{Creating Webviews} + +@defproc[(rkt-webview-create + [context exact-integer?] + [parent (or/c #f rkt-wv?)] + [evt-callback (-> rkt-wv? string? any)] + [close-callback (-> any)]) + rkt-wv?]{ + +Creates a new webview window in the given HTTP(S) context. + +Arguments: + +@itemlist[#:style 'compact + @item{@racket[context]: context identifier returned by + @racket[rkt-webview-new-context]} + @item{@racket[parent]: optional parent webview} + @item{@racket[evt-callback]: procedure invoked for each event} + @item{@racket[close-callback]: procedure invoked when the window is closed} +] + +The result is a new @racket[rkt-wv] structure. + +Events generated by the native layer are delivered asynchronously through +@racket[evt-callback]. +} + +@section{Window Lifecycle} + +@defproc[(rkt-webview-close [wv rkt-wv?]) boolean?]{ + +Requests that the webview window be closed. + +The wrapper forwards the request to the native backend and schedules cleanup of +the event-processing loop. + +Returns @racket[#t]. +} + +@defproc[(rkt-webview-valid? [wv rkt-wv?]) boolean?]{ + +Returns whether the webview still exists. + +The wrapper first checks its internal validity flag and then queries the native +runtime. +} + +@defproc[(rkt-webview-exit) void?]{ + +Closes all webviews and stops the background event-processing thread. + +This function is also registered with the Racket plumber so that cleanup occurs +automatically when the process exits. +} + +@section{Window Configuration} + +@defproc[(rkt-webview-set-title! [wv rkt-wv?] [title string?]) + symbol?]{ + +Sets the native window title. + +Returns a result symbol such as: + +@itemlist[#:style 'compact + @item{@racket['oke]} + @item{@racket['failed]} +] +} + +@defproc[(rkt-webview-set-ou-token [wv rkt-wv?] [token string?]) + boolean?]{ + +Associates an Organizational Unit token with the window. + +This token may be used by the native layer when accepting certain +self-signed certificates. +} + +@section{Navigation} + +@defproc[(rkt-webview-set-url! [wv rkt-wv?] [url string?]) symbol?]{ + +Navigates the webview to the given URL. + +Returns a result symbol such as: + +@itemlist[#:style 'compact + @item{@racket['oke]} + @item{@racket['set_navigate_failed]} +] +} + +@defproc[(rkt-webview-set-html! [wv rkt-wv?] [html string?]) symbol?]{ + +Loads HTML directly into the webview. + +Returns a result symbol such as: + +@itemlist[#:style 'compact + @item{@racket['oke]} + @item{@racket['set_html_failed]} +] +} + +@section{JavaScript Execution} + +@defproc[(rkt-webview-run-js [wv rkt-wv?] [js string?]) symbol?]{ + +Executes JavaScript without returning a value. + +Returns a result symbol such as: + +@itemlist[#:style 'compact + @item{@racket['oke]} + @item{@racket['eval_js_failed]} +] +} + +@defproc[(rkt-webview-call-js [wv rkt-wv?] [js string?]) + (list/c symbol? string?)]{ + +Executes JavaScript and returns: + +@racketblock[ +(list result value) +] + +where: + +@itemlist[#:style 'compact + @item{@racket[result] is the native result code} + @item{@racket[value] is a JSON string containing the returned data} +] + +The JSON structure is generated by the native backend. +} + +@section{Window Geometry} + +@defproc[(rkt-webview-move [wv rkt-wv?] [x exact-integer?] [y exact-integer?]) + symbol?]{ + +Moves the webview window to the given screen coordinates. +} + +@defproc[(rkt-webview-resize + [wv rkt-wv?] + [width exact-integer?] + [height exact-integer?]) + symbol?]{ + +Resizes the window. +} + +@defproc[(rkt-webview-show [wv rkt-wv?]) symbol?]{ +Shows the window. +} + +@defproc[(rkt-webview-hide [wv rkt-wv?]) symbol?]{ +Hides the window. +} + +@defproc[(rkt-webview-show-normal [wv rkt-wv?]) symbol?]{ +Restores the window to its normal state. +} + +@defproc[(rkt-webview-maximize [wv rkt-wv?]) symbol?]{ +Maximizes the window. +} + +@defproc[(rkt-webview-minimize [wv rkt-wv?]) symbol?]{ +Minimizes the window. +} + +@defproc[(rkt-webview-present [wv rkt-wv?]) symbol?]{ +Requests that the window be presented to the user. +} + +@defproc[(rkt-webview-window-state [wv rkt-wv?]) symbol?]{ + +Returns the current window state. + +Possible results include: + +@itemlist[#:style 'compact + @item{@racket['normal]} + @item{@racket['minimized]} + @item{@racket['maximized]} + @item{@racket['hidden]} +] +} + +@section{Developer Tools} + +@defproc[(rkt-webview-open-devtools [wv rkt-wv?]) symbol?]{ +Opens the browser developer tools window. +} + +@section{Native Dialogs} + +Dialog functions return immediately with a status code. +The user’s choice is delivered asynchronously through the event callback. + +@defproc[(rkt-webview-choose-dir + [wv rkt-wv?] + [title string?] + [base-dir string?]) + symbol?]{ + +Requests a directory-selection dialog. +} + +@defproc[(rkt-webview-file-open + [wv rkt-wv?] + [title string?] + [base-dir string?] + [extensions string?]) + symbol?]{ + +Requests a file-open dialog. +} + +@defproc[(rkt-webview-file-save + [wv rkt-wv?] + [title string?] + [base-dir string?] + [extensions string?]) + symbol?]{ + +Requests a file-save dialog. +} + +@defproc[(rkt-webview-messagebox + [wv rkt-wv?] + [title string?] + [message string?] + [submessage string?] + [type symbol?]) + symbol?]{ + +Shows a native message box. +} + +@section{Event Delivery} + +Each webview has an associated event callback. + +The callback has the form: + +@racketblock[ +(λ (wv event-json) ...) +] + +Arguments: + +@itemlist[#:style 'compact + @item{@racket[wv]: the webview handle} + @item{@racket[event-json]: JSON event string from the native backend} +] + +Typical event types include: + +@itemlist[#:style 'compact + @item{@tt{"show"}} + @item{@tt{"hide"}} + @item{@tt{"move"}} + @item{@tt{"resize"}} + @item{@tt{"closed"}} + @item{@tt{"page-loaded"}} + @item{@tt{"navigation-request"}} + @item{@tt{"js-evt"}} +] + +The wrapper does not parse the JSON payload. + +@section{Version Information} + +@defproc[(rkt-webview-version) + (list/c list? list?)]{ + +Returns version information for the native backend. + +Example result: + +@racketblock[ +(list + (list 'webview-c-api 1 0 0) + (list 'qt 6 10 2)) +] +} + +@section{Example} + +@racketblock[ +(define ctx + (rkt-webview-new-context "" #"")) + +(define wv + (rkt-webview-create + ctx + #f + (λ (wv evt) + (displayln evt)) + (λ () + (displayln "closed")))) + +(rkt-webview-set-title! wv "Example") +(rkt-webview-set-url! wv "https://example.org") +] + +@section{Summary} + +The FFI module provides a thin Racket interface to the native +@tt{rktwebview_qt} backend. + +Key characteristics: + +@itemlist[#:style 'compact + @item{thin wrapper around the native C API} + @item{asynchronous event delivery} + @item{JSON-based event payloads} + @item{simple Racket structures for webviews} +] \ No newline at end of file diff --git a/scrbl/racket-webview.scrbl b/scrbl/racket-webview.scrbl new file mode 100644 index 0000000..7fca68f --- /dev/null +++ b/scrbl/racket-webview.scrbl @@ -0,0 +1,797 @@ +#lang scribble/manual + +@title{High-Level Racket Interface for Webviews} +@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] + +@defmodule[racket-webview] + +@section{Overview} + +The module @tt{racket-webview.rkt} provides the high-level Racket interface to +the native @tt{rktwebview_qt} backend. + +It is built on top of the low-level FFI layer and adds: + +@itemlist[#:style 'compact + @item{Racket contracts on exported procedures} + @item{HTTP(S) context management} + @item{an embedded local HTTPS server per context} + @item{automatic self-signed certificate generation} + @item{decoded event delivery as Racket hash tables} + @item{decoded JavaScript results as Racket values} + @item{DOM convenience functions for values, attributes, classes, and styles} +] + +Applications are expected to use this module rather than the lower-level FFI +module directly. + +@section{Architecture} + +This module sits above the low-level FFI wrapper and provides a more idiomatic +Racket API. + +The stack is organized as follows: + +@itemlist[#:style 'compact + @item{the native Qt backend} + @item{the thin FFI wrapper in @tt{racket-webview-qt.rkt}} + @item{the higher-level Racket interface in @tt{racket-webview.rkt}} +] + +This layer introduces two main abstractions: + +@itemlist[#:style 'compact + @item{@tt{wv-context}, representing an HTTP(S) context} + @item{@tt{wv-win}, representing a webview window} +] + +@section{HTTP(S) Contexts} + +A context represents the HTTP(S) environment in which webviews operate. + +Unlike the lower-level FFI layer, this module does more than create a native +WebEngine profile. It also starts a local HTTPS server bound to +@tt{127.0.0.1} on an automatically chosen port. + +Each context therefore combines: + +@itemlist[#:style 'compact + @item{a native WebEngine context} + @item{a local HTTPS server} + @item{a self-signed certificate} + @item{a base URL} + @item{a file getter used to resolve incoming requests} +] + +@defstruct*[wv-context + ([context exact-integer?] + [port exact-integer?] + [file-getter procedure?] + [webserver-thread thread?] + [base-url string?] + [request-count any/c] + [sec-token-cache any/c] + [cert-ou-token string?])]{ +Represents a webview HTTP(S) context. + +The structure is transparent, but it is intended to be treated as a context +handle. +} + +@defproc[(webview-new-context + [file-getter procedure?] + [#:boilerplate-js boilerplate-js string? + (webview-default-boilerplate-js)]) + wv-context?]{ + +Creates a new HTTP(S) context. + +The @racket[file-getter] procedure is used by the embedded web server to map +request paths to files on disk. + +The function performs the following steps: + +@itemlist[#:style 'compact + @item{creates a fresh context structure} + @item{generates a self-signed certificate} + @item{starts a local HTTPS server on @tt{127.0.0.1}} + @item{creates the native context through the FFI layer} + @item{stores the resulting base URL} +] + +The @racket[boilerplate-js] argument supplies JavaScript that is injected into +pages loaded within the native context. + +The returned value is a @racket[wv-context] structure. +} + +@defproc[(wv-context-base-url [ctx wv-context?]) string?]{ +Returns the base URL associated with the context. + +This is the local HTTPS URL served by the embedded web server, for example a +URL of the form @tt{https://127.0.0.1:port}. +} + +@section{Webview Windows} + +A webview window is represented by a @racket[wv-win] structure. + +@defstruct*[wv-win + ([handle any/c] + [context wv-context?] + [window-nr exact-integer?])]{ +Represents a webview window. + +Fields: + +@itemlist[#:style 'compact + @item{@tt{handle}: the low-level FFI handle} + @item{@tt{context}: the owning @racket[wv-context]} + @item{@tt{window-nr}: the native numeric window identifier} +] +} + +@defproc[(wv-win-window-nr [wv wv-win?]) exact-integer?]{ +Returns the native numeric window identifier of @racket[wv]. +} + +@defproc[(webview-create + [context wv-context?] + [url-path string?] + [event-callback procedure?] + [#:parent parent (or/c wv-win? #f) #f]) + wv-win?]{ + +Creates a new webview window inside the given context. + +Arguments: + +@itemlist[#:style 'compact + @item{@racket[context]: the HTTP(S) context} + @item{@racket[url-path]: initial URL path to load} + @item{@racket[event-callback]: callback receiving decoded events} + @item{@racket[parent]: optional parent window} +] + +The function creates the native window, associates the context certificate +OU token with it, and immediately loads the given path through +@racket[webview-set-url!]. + +The callback receives two arguments: + +@itemlist[#:style 'compact + @item{the created @racket[wv-win]} + @item{a decoded event hash} +] + +Native JSON event strings are converted to Racket hashes before delivery. +} + +@defproc[(webview-close [wv wv-win?]) symbol?]{ +Closes the webview. + +Returns @racket['oke]. +} + +@section{Navigation and HTML Loading} + +@defproc[(webview-set-url! [wv wv-win?] [url (or/c string? url?)]) symbol?]{ + +Loads a URL into the webview. + +If the supplied URL is relative or missing scheme and host information, the +function resolves it against the base URL of the window's context. + +This makes it possible to load application pages relative to the embedded local +HTTPS server. +} + +@defproc[(webview-navigate! [wv wv-win?] [place string?]) symbol?]{ + +Navigates the current page by assigning to @tt{window.location} through +JavaScript. + +This is distinct from @racket[webview-set-url!], which loads a URL through the +native navigation interface. +} + +@defproc[(webview-set-html! [wv wv-win?] [html (or/c string? xexpr?)]) symbol?]{ + +Loads HTML directly into the webview. + +If @racket[html] is an x-expression, it is converted with +@racket[xexpr->string] first. +} + +@defproc[(webview-base-url [wv wv-win?]) url?]{ +Returns the base URL of the webview's context as a Racket URL value. +} + +@section{Window Management} + +@defproc[(webview-devtools [wv wv-win?]) symbol?]{ +Opens the developer tools for the webview. +} + +@defproc[(webview-move [wv wv-win?] [x number?] [y number?]) symbol?]{ +Moves the window. +} + +@defproc[(webview-resize [wv wv-win?] [w number?] [h number?]) symbol?]{ +Resizes the window. +} + +@defproc[(webview-show [wv wv-win?]) symbol?]{ +Shows the window. +} + +@defproc[(webview-hide [wv wv-win?]) symbol?]{ +Hides the window. +} + +@defproc[(webview-show-normal [wv wv-win?]) symbol?]{ +Restores the window to the normal state. +} + +@defproc[(webview-maximize [wv wv-win?]) symbol?]{ +Maximizes the window. +} + +@defproc[(webview-minimize [wv wv-win?]) symbol?]{ +Minimizes the window. +} + +@defproc[(webview-present [wv wv-win?]) symbol?]{ +Presents the window to the user. +} + +@defproc[(webview-window-state [wv wv-win?]) symbol?]{ +Returns the current window state. +} + +@defproc[(webview-set-title! [wv wv-win?] [title string?]) symbol?]{ +Sets the native window title. +} + +@section{Message Boxes and File Dialogs} + +@defproc[(webview-messagebox + [wv wv-win?] + [type symbol?] + [title string?] + [message string?] + [#:sub submessage string? ""]) + symbol?]{ + +Shows a native message box. + +The @racket[type] argument is passed through to the lower layer and is expected +to be one of the supported message box kinds, such as +@racket['info], @racket['error], @racket['warning], @racket['yes-no], or +@racket['oke-cancel]. +} + +@defproc[(webview-choose-dir + [wv wv-win?] + [title string?] + [base-dir (or/c path? string?)]) + symbol?]{ + +Opens a directory chooser dialog. +} + +@defproc[(webview-file-open + [wv wv-win?] + [title string?] + [base-dir (or/c path? string?)] + [permitted-exts (or/c wv-permitted-exts? wv-list-of-permitted-exts?)]) + symbol?]{ + +Opens a native file-open dialog. + +The @racket[permitted-exts] value is converted to a Qt file filter string. +} + +@defproc[(webview-file-save + [wv wv-win?] + [title string?] + [base-dir (or/c path? string?)] + [permitted-exts (or/c wv-permitted-exts? wv-list-of-permitted-exts?)]) + symbol?]{ + +Opens a native file-save dialog. + +The @racket[permitted-exts] value is converted to a Qt file filter string. +} + +@section{Permitted Extensions} + +The file dialog helpers use @racket[wv-permitted-exts] values to describe file +type filters. + +@defstruct*[wv-permitted-exts + ([name string?] + [exts wv-permitted-exts-exts?])]{ +Represents a named file filter. + +The @racket[name] field is the human-readable filter name. + +The @racket[exts] field is a list of filename extensions represented as +symbols. +} + +@defproc[(wv-permitted-exts-exts? [v any/c]) boolean?]{ +Returns whether @racket[v] is a valid list of extension symbols. +} + +@defproc[(wv-permitted-exts-name? [v any/c]) boolean?]{ +Returns whether @racket[v] is a valid filter name. +} + +@defproc[(wv-list-of-permitted-exts? [v any/c]) boolean?]{ +Returns whether @racket[v] is a list of valid @racket[wv-permitted-exts] +values. +} + +@defproc[(webview-filter->exts [str string?]) + (or/c wv-permitted-exts? #f)]{ +Parses a Qt-style filter string into a @racket[wv-permitted-exts] value. + +If the string does not match the expected filter format, the result is +@racket[#f]. +} + +@section{Event Binding} + +The module provides helpers for binding DOM events from page elements back to +the Racket side. + +@defproc[(webview-bind! + [wv wv-win?] + [selector (or/c symbol? string?)] + [event (or/c symbol? list-of-symbol?)]) + list?]{ + +Binds one or more DOM events to elements selected by @racket[selector]. + +If @racket[selector] is a symbol, it is interpreted as an element id and +rewritten as a CSS id selector. + +If @racket[event] is a symbol, it is treated as a single event name. + +The result is a list describing the created bindings. +} + +@defproc[(webview-unbind! + [wv wv-win?] + [selector (or/c symbol? string?)] + [event (or/c symbol? list-of-symbol?)]) + list?]{ + +Removes previously established DOM event bindings. + +Its arguments and return value follow the same conventions as +@racket[webview-bind!]. +} + +@section{JavaScript Execution} + +@defproc[(webview-run-js [wv wv-win?] [js string?]) symbol?]{ +Executes JavaScript for side effects. +} + +@defproc[(webview-call-js-result? [x any/c]) boolean?]{ +Returns whether @racket[x] has the low-level two-element result shape used by +the FFI function @racket[rkt-webview-call-js]. + +This predicate is mainly a utility for internal result checking. +} + +@defproc[(webview-call-js [wv wv-win?] [js string?]) + (or/c string? list? boolean? hash?)]{ + +Executes JavaScript and returns the decoded @tt{result} field of the underlying +native JSON response. + +Unlike the lower-level FFI wrapper, this function does not return a +@racket[(list result-symbol json-string)] pair. Instead: + +@itemlist[#:style 'compact + @item{the low-level result is checked} + @item{the JSON string is decoded} + @item{the @tt{result} field is extracted} + @item{the extracted value is returned directly} +] + +If the JavaScript evaluation fails, the function raises an error. +} + +@section{DOM Content Helpers} + +@defproc[(webview-set-innerHTML! + [wv wv-win?] + [id symbol?] + [html (or/c string? xexpr?)]) + symbol?]{ + +Sets the @tt{innerHTML} of the element with the given id. + +If @racket[html] is an x-expression, it is converted to HTML first. +} + +@section{Form Value Helpers} + +@defproc[(webview-set-value! + [wv wv-win?] + [id symbol?] + [val (or/c symbol? + string? + number? + boolean? + g:date? + g:time? + g:datetime? + rgba?)]) + symbol?]{ + +Sets the value of the element with the given id. + +Checkboxes and radio buttons are handled through their @tt{checked} property. +Other elements are handled through their @tt{value} property. + +Date, time, datetime, and color values are converted to strings before being +sent to the browser. +} + +@defproc[(webview-value [wv wv-win?] [id symbol?]) + (or/c string? boolean?)]{ + +Returns the current value of the element with the given id. + +For checkboxes and radio buttons, the returned value reflects the checked state. +For other elements, the result is the string value. +} + +@defproc[(webview-value/boolean [wv wv-win?] [id symbol?]) + (or/c boolean? #f)]{ +Returns the current value converted to a boolean. +} + +@defproc[(webview-value/symbol [wv wv-win?] [id symbol?]) + (or/c symbol? #f)]{ +Returns the current value converted to a symbol. +} + +@defproc[(webview-value/number [wv wv-win?] [id symbol?]) + (or/c number? #f)]{ +Returns the current value converted to a number. +} + +@defproc[(webview-value/date [wv wv-win?] [id symbol?]) + (or/c g:date? #f)]{ +Returns the current value converted to a date. +} + +@defproc[(webview-value/time [wv wv-win?] [id symbol?]) + (or/c g:time? #f)]{ +Returns the current value converted to a time. +} + +@defproc[(webview-value/datetime [wv wv-win?] [id symbol?]) + (or/c g:datetime? #f)]{ +Returns the current value converted to a datetime. +} + +@defproc[(webview-value/color [wv wv-win?] [id symbol?]) + (or/c rgba? #f)]{ +Returns the current value converted to an RGBA color. +} + +@section{Class Helpers} + +@defproc[(webview-add-class! + [wv wv-win?] + [id-or-selector (or/c symbol? string?)] + [class (or/c symbol? string? list?)]) + hash?]{ + +Adds one or more CSS classes to all elements selected by +@racket[id-or-selector]. + +A symbol selector is interpreted as an element id. +} + +@defproc[(webview-remove-class! + [wv wv-win?] + [id-or-selector (or/c symbol? string?)] + [class (or/c symbol? string? list?)]) + hash?]{ + +Removes one or more CSS classes from all selected elements. +} + +@section{Style Helpers} + +@defproc[(webview-set-style! + [wv wv-win?] + [selector (or/c symbol? string?)] + [style-entries (or/c kv? list-of-kv?)]) + hash?]{ + +Sets one or more inline CSS style properties on the selected elements. + +The @racket[style-entries] argument is either a single key/value pair or a list +of key/value pairs. +} + +@defproc[(webview-get-style + [wv wv-win?] + [selector (or/c symbol? string?)] + [styles (or/c symbol? list-of-symbol?)]) + (or/c list? hash?)]{ + +Reads computed style values from the selected elements. + +If @racket[selector] is a symbol, the result is the style hash for that single +element. + +If @racket[selector] is a string selector, the result is a list of +@racket[(cons id style-hash)] pairs. +} + +@defproc[(webview-unset-style! + [wv wv-win?] + [selector (or/c symbol? string?)] + [style-entries (or/c symbol? list-of-symbol?)]) + hash?]{ + +Clears one or more inline style properties from the selected elements. +} + +@section{Attribute Helpers} + +@defproc[(webview-set-attr! + [wv wv-win?] + [selector (or/c symbol? string?)] + [attr-entries (or/c kv? list-of-kv?)]) + hash?]{ + +Sets one or more HTML attributes on the selected elements. + +Date, time, datetime, and color values are converted to strings before being +sent to the browser. +} + +@defproc[(webview-attr + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c string? boolean?)]{ + +Returns the value of the named attribute of the element with the given id. + +If the attribute does not exist, the result is @racket[#f]. +} + +@defproc[(webview-attr/boolean + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c boolean? #f)]{ +Returns the attribute converted to a boolean. +} + +@defproc[(webview-attr/symbol + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c symbol? #f)]{ +Returns the attribute converted to a symbol. +} + +@defproc[(webview-attr/number + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c number? #f)]{ +Returns the attribute converted to a number. +} + +@defproc[(webview-attr/date + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c g:date? #f)]{ +Returns the attribute converted to a date. +} + +@defproc[(webview-attr/time + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c g:time? #f)]{ +Returns the attribute converted to a time. +} + +@defproc[(webview-attr/datetime + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c g:datetime? #f)]{ +Returns the attribute converted to a datetime. +} + +@defproc[(webview-attr/color + [wv wv-win?] + [id symbol?] + [attr (or/c symbol? string?)]) + (or/c rgba? #f)]{ +Returns the attribute converted to an RGBA color. +} + +@section{Utility Functions} + +@defproc[(webview-standard-file-getter + [base-path path-string?] + [#:not-exist on-not-exist procedure? + (λ (file base-path path) path)]) + procedure?]{ + +Creates a standard file getter for use with @racket[webview-new-context]. + +The resulting procedure maps request paths to files under @racket[base-path]. + +The special request path @tt{/} is mapped to @tt{index.html}. + +If the resolved file does not exist, the @racket[on-not-exist] procedure is +used to determine the result path. +} + +@defproc[(webview-default-boilerplate-js [custom-js procedure?] ...) + string?]{ + +Returns the default boilerplate JavaScript used by the system. + +Any supplied procedures are called and their returned JavaScript strings are +appended to the default boilerplate. +} + +@defproc[(webview-version) pair?]{ + +Returns version information for the high-level webview package and the native +backend. + +The result is a pair whose first element describes the Racket webview package +version and whose remaining data comes from @racket[rkt-webview-version]. +} + +@section{Event Reference} + +Events are delivered to @racket[webview-create]'s callback as decoded Racket +hash tables. + +The helper @racket[util-parse-event] converts the incoming JSON and also +normalizes the @tt{event} field from a string to a symbol. + +Each event therefore contains at least an @racket['event] entry. + +@subsection{Window Events} + +@subsubsection{@tt{show}} + +Example: + +@racketblock[ +'#hash((event . show)) +] + +@subsubsection{@tt{hide}} + +Example: + +@racketblock[ +'#hash((event . hide)) +] + +@subsubsection{@tt{move}} + +Typical fields: + +@itemlist[#:style 'compact + @item{@racket['x]} + @item{@racket['y]} +] + +@subsubsection{@tt{resize}} + +Typical fields: + +@itemlist[#:style 'compact + @item{@racket['w]} + @item{@racket['h]} +] + +@subsubsection{@tt{closed}} + +Indicates that the window was closed. + +@subsubsection{@tt{can-close?}} + +Generated when the user attempts to close the window. + +@subsection{Navigation and Loading Events} + +@subsubsection{@tt{page-loaded}} + +Contains an @racket['oke] field indicating whether loading succeeded. + +@subsubsection{@tt{navigation-request}} + +Typically contains: + +@itemlist[#:style 'compact + @item{@racket['url]} + @item{@racket['type]} +] + +@subsection{JavaScript Events} + +@subsubsection{@tt{js-evt}} + +Represents a JavaScript-originated event sent through the injected browser +bridge. + +The original JavaScript payload is available under the @racket['js-evt] key. + +@subsection{Dialog Events} + +Dialog completion is reported through ordinary events, including event names +such as: + +@itemlist[#:style 'compact + @item{@tt{choose-dir-ok}} + @item{@tt{choose-dir-cancel}} + @item{@tt{file-open-ok}} + @item{@tt{file-open-cancel}} + @item{@tt{file-save-ok}} + @item{@tt{file-save-cancel}} + @item{@tt{msgbox-ok}} + @item{@tt{msgbox-cancel}} + @item{@tt{msgbox-yes}} + @item{@tt{msgbox-no}} +] + +@section{Example} + +@racketblock[ +(define file-getter + (webview-standard-file-getter "htdocs")) + +(define ctx + (webview-new-context file-getter)) + +(define wv + (webview-create + ctx + "index.html" + (λ (wv evt) + (displayln evt)))) + +(webview-set-title! wv "Example") +(webview-resize wv 800 600) +] + +@section{Summary} + +The module @tt{racket-webview.rkt} provides the main Racket programming +interface for the webview system. + +Compared to the lower-level FFI layer, it adds: + +@itemlist[#:style 'compact + @item{HTTP(S) contexts with local web serving} + @item{automatic certificate generation} + @item{decoded event delivery} + @item{decoded JavaScript results} + @item{DOM convenience helpers} +] \ No newline at end of file diff --git a/scrbl/rktwebview-api.scrbl b/scrbl/rktwebview-api.scrbl index 1a7b44b..1b348fe 100644 --- a/scrbl/rktwebview-api.scrbl +++ b/scrbl/rktwebview-api.scrbl @@ -1,348 +1,784 @@ #lang scribble/manual -@title{Public API Specification for the Qt–Backed WebView Bridge} +@title{API Reference for @tt{rktwebview_qt}} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] - @italic{For use with Racket FFI} -This document defines the public C-level API of the Qt-based WebView bridge. -It targets Racket developers integrating the C library via the Racket FFI. -It specifies the interface, result values, memory ownership rules, event formats, -expected behavior, and recommended calling patterns. Internal architecture is documented separately. +@section{Overview} -@section{Calling Convention and Structure} +@tt{rktwebview_qt} exposes a small C-compatible API for creating and controlling +Qt-based webviews from Racket through the FFI. -All functions are C functions with normal C linkage. -Window handles are simple integers that uniquely identify native windows. +The public interface is declared in @tt{rktwebview.h}. Its implementation in +@tt{rktwebview.cpp} mainly forwards requests to the internal Qt runtime. + +The API consists of: @itemlist[#:style 'compact - @item{Header: @tt{rktwebview.h}} - @item{ABI: plain C, no name mangling} - @item{Handles remain valid from creation until the window is fully closed} + @item{initialization and event processing} + @item{version and memory-management helpers} + @item{browser-context management} + @item{window creation and lifecycle control} + @item{navigation and content loading} + @item{JavaScript execution} + @item{window-state manipulation} + @item{native dialogs} ] +@section{Basic Types} -@section{Opaque and Helper Types} +@subsection{Window and Context Handles} -@subsection{Window Handle} - -@itemlist[#:style 'compact - @item{@tt{typedef int rktwebview_t}: Valid for any window created by @tt{rkt_webview_create} until closed.} -] - -@subsection{Event Callback Types} - -@itemlist[#:style 'compact - @item{@tt{typedef struct \{ rktwebview_t wv; char* event; \} rkt_event_t}: JSON event payload for the host (UTF‐8).} - @item{@tt{typedef void (*event_cb_t)(rkt_event_t*)}: Host-supplied callback for event delivery.} -] - -@subsection{JS Result Structure} - -@itemlist[#:style 'compact - @item{@tt{typedef struct \{ result_t result; char* value; \} rkt_js_result_t}: @tt{result} is always @tt{oke} or @tt{eval_js_failed}; @tt{value} holds a UTF‐8 JSON string describing the JS outcome.} -] - - -@section{Enumerations} - -@subsection{result_t} - -@itemlist[#:style 'compact - @item{@tt{oke = 0}} - @item{@tt{set_html_failed = 1}} - @item{@tt{set_navigate_failed = 2}} - @item{@tt{eval_js_failed = 3}} - @item{@tt{move_failed = 12}} - @item{@tt{resize_failed = 13}} - @item{@tt{choose_dir_failed = 14}} - @item{@tt{open_file_failed = 15}} - @item{@tt{save_file_failed = 16}} - @item{@tt{failed = 17}} -] - -@subsection{window_state_t} - -@itemlist[#:style 'compact - @item{@tt{invalid = -1}} - @item{@tt{normal = 0}} - @item{@tt{minimized = 1}} - @item{@tt{maximized = 2}} - @item{@tt{hidden = 3}} - @item{@tt{normal_active = 16}} - @item{@tt{maximized_active = 18}} -] - - -@section{Threading Model} - -@itemlist[#:style 'compact - @item{All GUI work occurs on the Qt GUI thread.} - @item{Public calls block until the posted internal command has completed.} - @item{The host is expected to drive Qt by calling @tt{rkt_webview_process_events}.} -] - - -@section{Memory Ownership} - -@itemlist[#:style 'compact - @item{@tt{rkt_event_t*} must be freed using @tt{rkt_webview_destroy_event}.} - @item{@tt{rkt_js_result_t*} must be freed using @tt{rkt_webview_destroy_js_result}.} - @item{All returned strings are UTF‐8 and owned by the library until destroyed with the corresponding function.} -] - - -@section{Function Reference (C signatures)} - -The following are literal C signatures with behavioral descriptions. - -@subsection{Initialization and Event Pumping} - -@codeblock{ -void rkt_webview_init(void); -} -Initializes Qt and internal state. Safe to call multiple times (subsequent calls are no-ops). - -@codeblock{ -void rkt_webview_process_events(int for_ms); -} -Pumps the Qt event loop for approximately @tt{for_ms} milliseconds. - -Idea for implementation in racket: -Use a @emph{cooperative thread} with @emph{time slicing}. A practical starting point: -@itemlist[#:style 'compact - @item{~3 ms Qt pumping per iteration: @tt{(rkt_webview_process_events 3)}} - @item{~10 ms Racket time (scheduler): @tt{(sleep 0.01)}} -] -Tune these values for your application. - -Example: +The API uses integer handles for windows and browser contexts. @verbatim|{ -(define running? (box #t)) - -(define (pump-loop) - (let loop () - (when (unbox running?) - (rkt_webview_process_events 3) ; ~3 ms Qt - (sleep 0.01) ; ~10 ms for Racket scheduler - (loop)))) - -(define pump-thread (thread pump-loop)) - -;; ... later on shutdown: -(set-box! running? #f) -(thread-wait pump-thread) +typedef int rktwebview_t; +typedef int rkt_wv_context_t; }| +A value of type @tt{rktwebview_t} identifies a single webview window. +A value of type @tt{rkt_wv_context_t} identifies a browser context. -@subsection{Window Lifecycle} +@subsection{Returned Data Objects} -@codeblock{ -rktwebview_t rkt_webview_create(rktwebview_t parent, event_cb_t cb); -} -Creates a window, optionally modal if @tt{parent} exists. Registers @tt{cb}. -The window is shown before returning. +Some API calls return a pointer to a heap-allocated @tt{rkt_data_t} value. -@codeblock{ -void rkt_webview_close(rktwebview_t wv); -} -Requests closure of @tt{wv}. +@verbatim|{ +typedef enum { + version = 1, + event = 2, + js_result = 3 +} rkt_data_kind_t; + +typedef struct { + rkt_data_kind_t kind; + union { + rkt_version_t version; + rkt_event_t event; + rkt_js_result_t js_result; + } data; +} rkt_data_t; +}| + +The @tt{kind} field determines which member of the union is valid. + +@subsubsection{Version Data} + +@verbatim|{ +typedef struct { + int qt_major; + int qt_minor; + int qt_patch; + int api_major; + int api_minor; + int api_patch; +} rkt_version_t; +}| + +Version data is returned by @tt{rkt_webview_version}. + +@subsubsection{Event Data} + +@verbatim|{ +typedef struct { + rktwebview_t wv; + char *event; +} rkt_event_t; +}| + +Event data is delivered to the registered callback when the backend emits an +event for a window. + +The @tt{event} field is a UTF-8 JSON string allocated by the native side. + +@subsubsection{JavaScript Result Data} + +@verbatim|{ +typedef struct { + result_t result; + char *value; +} rkt_js_result_t; +}| + +JavaScript result data is returned by @tt{rkt_webview_call_js}. + +The @tt{value} field contains a UTF-8 JSON string describing the result of the +JavaScript evaluation. + +@subsection{Result Codes} + +Many functions return a value of type @tt{result_t}. + +@verbatim|{ +typedef enum { + no_result_yet = -1, + oke = 0, + set_html_failed = 1, + set_navigate_failed = 2, + eval_js_failed = 3, + no_devtools_on_platform = 4, + no_delegate_for_context = 5, + webview_missing_dependency = 6, + webview_canceled = 7, + webview_invalid_state = 8, + webview_invalid_argument = 9, + webview_unspecified = 10, + webview_dispatch_failed = 11, + move_failed = 12, + resize_failed = 13, + choose_dir_failed = 14, + open_file_failed = 15, + save_file_failed = 16, + failed = 17 +} result_t; +}| + +In the current implementation, the most commonly returned values are: -Behavior: @itemlist[#:style 'compact - @item{If the user tries to close via the window manager (close button, Alt–F4, etc.), the library intercepts it and emits @tt{"can-close?"}. The window remains open.} - @item{To actually close after receiving @tt{"can-close?"}, the host must call @tt{rkt_webview_close}.} - @item{When the final close occurs, @tt{"closed"} is emitted and the handle becomes invalid.} + @item{@tt{oke} for success} + @item{@tt{set_html_failed} for @tt{rkt_webview_set_html}} + @item{@tt{set_navigate_failed} for @tt{rkt_webview_set_url}} + @item{@tt{eval_js_failed} for JavaScript execution failure} + @item{@tt{move_failed} for failed move requests} + @item{@tt{resize_failed} for failed resize requests} + @item{@tt{failed} for general window-operation failure} ] -@codeblock{ -bool rkt_webview_valid(rktwebview_t wv); -} -Returns whether @tt{wv} refers to an active window. +@subsection{Window States} +Window state is represented by @tt{window_state_t}. -@subsection{Navigation and Content} +@verbatim|{ +typedef enum { + invalid = -1, + normal = 0, + minimized = 1, + maximized = 2, + hidden = 3, + normal_active = 16, + maximized_active = 18 +} window_state_t; +}| -@codeblock{ -result_t rkt_webview_set_url(rktwebview_t wv, const char* url); -} -Navigate to @tt{url}. Returns @tt{oke} or @tt{set_navigate_failed}. +This type is returned by @tt{rkt_webview_window_state}. -@codeblock{ -result_t rkt_webview_set_html(rktwebview_t wv, const char* html); -} -Set page content from @tt{html}. Returns @tt{oke} or @tt{set_html_failed}. +@subsection{Message Box Types} -@codeblock{ -result_t rkt_webview_set_title(rktwebview_t wv, const char* title); -} -Set the native window title. Returns @tt{oke} or @tt{failed}. +Message boxes use the type @tt{rkt_messagetype_t}. +@verbatim|{ +typedef enum { + info = 1, + error = 2, + warning = 3, + yes_no = 4, + oke_cancel = 5 +} rkt_messagetype_t; +}| -@subsection{JavaScript Execution} +@subsection{Event Callback Type} -@codeblock{ -result_t rkt_webview_run_js(rktwebview_t wv, const char* js); -} -Fire-and-forget JavaScript execution. Returns @tt{oke} or @tt{eval_js_failed}. +A window is created with an event callback of the following type: + +@verbatim|{ +typedef void (*event_cb_t)(rkt_data_t *); +}| + +The callback receives a pointer to a @tt{rkt_data_t} whose @tt{kind} is +@tt{event}. + +The callback argument must eventually be released by calling +@tt{rkt_webview_free_data}. + +@subsection{Memory Ownership} + +The following functions return heap-allocated @tt{rkt_data_t *} values: -@codeblock{ -rkt_js_result_t* rkt_webview_call_js(rktwebview_t wv, const char* js); -} -Synchronous JS call. Guarantees: @itemlist[#:style 'compact - @item{@tt{result} is @tt{oke} or @tt{eval_js_failed}.} - @item{@tt{value} is JSON: @tt{{"oke": , "result": , "exn": }}.} + @item{@tt{rkt_webview_version}} + @item{@tt{rkt_webview_call_js}} + @item{the event callback receives event values allocated by the native side} ] -Must be freed with @tt{rkt_webview_destroy_js_result()}. -@codeblock{ -result_t rkt_webview_destroy_js_result(rkt_js_result_t* r); -} -Frees JS result memory. Returns @tt{oke}. +Such values must be released by calling: +@verbatim|{ +void rkt_webview_free_data(rkt_data_t *d); +}| + +This function frees both the outer structure and any string data owned by it. + +@section{Initialization and Event Processing} + +@subsection{@tt{rkt_webview_init}} + +@verbatim|{ +void rkt_webview_init(); +}| + +Initializes the global runtime if it has not already been initialized. + +This function is idempotent. The implementation checks whether the global +runtime already exists before constructing it. + +Most public API functions call @tt{rkt_webview_init} automatically before doing +their work, so explicit initialization is optional but harmless. + +@subsection{@tt{rkt_webview_process_events}} + +@verbatim|{ +void rkt_webview_process_events(int for_ms); +}| + +Processes Qt events for approximately @tt{for_ms} milliseconds. + +The implementation repeatedly sleeps briefly and then calls the internal Qt +event-processing routine. This function is important for keeping windows +responsive and for delivering callbacks such as: + +@itemlist[#:style 'compact + @item{window lifecycle events} + @item{page-load events} + @item{JavaScript-originated events} + @item{dialog completion events} +] + +Applications embedding this backend are expected to call this function +regularly. + +@section{Version and Utility Functions} + +@subsection{@tt{rkt_webview_version}} + +@verbatim|{ +rkt_data_t *rkt_webview_version(); +}| + +Returns a newly allocated @tt{rkt_data_t} whose @tt{kind} is @tt{version}. + +The returned structure contains: + +@itemlist[#:style 'compact + @item{the Qt version used to build the backend} + @item{the API version declared by the backend} +] + +The returned value must be released with @tt{rkt_webview_free_data}. + +@subsection{@tt{rkt_webview_free_data}} + +@verbatim|{ +void rkt_webview_free_data(rkt_data_t *d); +}| + +Frees a @tt{rkt_data_t} that was allocated by the backend. + +The function inspects @tt{d->kind} and frees any owned string data before +freeing the outer object. + +@subsection{@tt{rkt_webview_set_ou_token}} + +@verbatim|{ +void rkt_webview_set_ou_token(rktwebview_t wv, const char *token); +}| + +Associates an Organizational Unit token with the window. + +During certificate-error handling, self-signed certificates whose +Organizational Unit matches this token may be accepted automatically. + +This function does not return a status code. @subsection{Developer Tools} -@codeblock{ +@subsubsection{@tt{rkt_webview_open_devtools}} + +@verbatim|{ result_t rkt_webview_open_devtools(rktwebview_t wv); -} -Open devtools. Returns @tt{oke} or @tt{no_devtools_on_platform} (platform-dependent). +}| + +Opens a DevTools window associated with the given webview. + +In the current implementation this function returns: + +@itemlist[#:style 'compact + @item{@tt{oke} on success} + @item{@tt{eval_js_failed} on failure} +] + +Even though the @tt{result_t} enumeration contains +@tt{no_devtools_on_platform}, the implementation currently maps failure to +@tt{eval_js_failed}. -@subsection{Window Geometry and Visibility} +@section{HTTP(s) Contexts} -@codeblock{ +@subsection{@tt{rkt_webview_new_context}} + +@verbatim|{ +rkt_wv_context_t rkt_webview_new_context(const char *boilerplate_js, + const char *optional_server_cert_pem); +}| + +Creates a new http(s) context and returns its context handle. + +A context corresponds to a Qt WebEngine profile and may be shared by multiple +windows. + +Arguments: + +@itemlist[#:style 'compact + @item{@tt{boilerplate_js}: JavaScript source code to inject into pages} + @item{@tt{optional_server_cert_pem}: optional PEM-encoded certificate to trust} +] + +Behavior: + +@itemlist[#:style 'compact + @item{A fresh profile is created.} + @item{The backend injects built-in event-bridge JavaScript into the profile.} + @item{The supplied @tt{boilerplate_js} is also injected at document-ready time.} + @item{If a certificate string is supplied, it is added as an additional + trusted certificate.} +] + +The returned context handle can be passed to @tt{rkt_webview_create}. + +@section{Window Creation and Lifecycle} + +@subsection{@tt{rkt_webview_create}} + +@verbatim|{ +int rkt_webview_create(rkt_wv_context_t context, + rktwebview_t parent, + event_cb_t js_event_cb); +}| + +Creates a new window in the given context. + +Arguments: + +@itemlist[#:style 'compact + @item{@tt{context}: the context handle returned by + @tt{rkt_webview_new_context}} + @item{@tt{parent}: a parent window handle, or a 0 value when no parent + is intended} + @item{@tt{js_event_cb}: callback used for all events emitted by this window} +] + +Returns a window handle on success. + +The window is shown on success. When the parent window exists, the window +will be a modal window to the parent. + +The callback receives event notifications for that window, including page +events, geometry changes, JavaScript-originated events, and dialog results. + +@subsection{@tt{rkt_webview_close}} + +@verbatim|{ +void rkt_webview_close(rktwebview_t wv); +}| + +Requests the window to close. + +The implementation closes the window through the internal Qt command path. + +When a close completes, a @tt{"closed"} event is emitted to the callback. +This should also be called in response to a 'can-close? event. + +@subsection{@tt{rkt_webview_valid}} + +@verbatim|{ +bool rkt_webview_valid(rktwebview_t wv); +}| + +Returns @tt{#t} when the given window handle currently exists and is known to +the runtime, and @tt{#f} otherwise. + +@section{Window Configuration} + +@subsection{@tt{rkt_webview_set_title}} + +@verbatim|{ +result_t rkt_webview_set_title(rktwebview_t wv, const char *title); +}| + +Sets the native title of the window. + +Returns @tt{oke} on success and @tt{failed} on failure. + + +@section{Navigation and Content Loading} + +@subsection{@tt{rkt_webview_set_url}} + +@verbatim|{ +result_t rkt_webview_set_url(rktwebview_t wv, const char *url); +}| + +Navigates the window to the given URL. + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} on success} + @item{@tt{set_navigate_failed} on failure} +] + +A successful or failed load eventually also produces a @tt{"page-loaded"} event. + +@subsection{@tt{rkt_webview_set_html}} + +@verbatim|{ +result_t rkt_webview_set_html(rktwebview_t wv, const char *html); +}| + +Loads the given HTML string directly into the window. + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} on success} + @item{@tt{set_html_failed} on failure} +] + +A successful or failed load eventually also produces a @tt{"page-loaded"} event. + +@section{JavaScript Execution} + +@subsection{@tt{rkt_webview_run_js}} + +@verbatim|{ +result_t rkt_webview_run_js(rktwebview_t wv, const char *js); +}| + +Executes JavaScript in the page without returning a value to the caller. + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} on success} + @item{@tt{eval_js_failed} on failure} +] + +This function is suitable for side-effecting JavaScript. + +@subsection{@tt{rkt_webview_call_js}} + +@verbatim|{ +rkt_data_t *rkt_webview_call_js(rktwebview_t wv, const char *js); +}| + +Executes JavaScript and returns a newly allocated @tt{rkt_data_t} whose +@tt{kind} is @tt{js_result}. + +The returned @tt{js_result.value} field contains a compact JSON string. + +The implementation wraps the supplied code in a small JavaScript function and +returns a JSON object of the form: + +@verbatim|{ +{"oke": true|false, "result": ..., "exn": ...} +}| + + +The outer native result code is stored in @tt{js_result.result}: + +@itemlist[#:style 'compact + @item{@tt{oke} when the wrapped JavaScript completed successfully, @tt{result} will contain the javascript result. } + @item{@tt{eval_js_failed} when the wrapped JavaScript reported failure, @tt{exn} will contain the javascript exception information.} +] + +The returned value must be released with @tt{rkt_webview_free_data}. + +The javascript code is executed as part of an anonymous function and must always end with a @tt{return} statement to return the result. + + +@section{Window Geometry and Visibility} + +@subsection{@tt{rkt_webview_move}} + +@verbatim|{ result_t rkt_webview_move(rktwebview_t wv, int x, int y); -} -Waits until move is acknowledged. Returns @tt{oke} or @tt{move_failed}. +}| -@codeblock{ -result_t rkt_webview_resize(rktwebview_t wv, int w, int h); -} -Waits until resize is acknowledged. Returns @tt{oke} or @tt{resize_failed}. +Moves the window to the given screen position. -@codeblock{ -result_t rkt_webview_hide(rktwebview_t wv); -} -@codeblock{ -result_t rkt_webview_show(rktwebview_t wv); -} -@codeblock{ -result_t rkt_webview_show_normal(rktwebview_t wv); -} -@codeblock{ -result_t rkt_webview_present(rktwebview_t wv); -} -@codeblock{ -result_t rkt_webview_maximize(rktwebview_t wv); -} -@codeblock{ -result_t rkt_webview_minimize(rktwebview_t wv); -} -All return @tt{oke} or @tt{failed} (when the operation cannot be applied). - -@codeblock{ -window_state_t rkt_webview_window_state(rktwebview_t wv); -} -Returns a simplified window state. - - -@subsection{Event & Dialog Systems} - -@codeblock{ -result_t rkt_webview_destroy_event(rkt_event_t* e); -} -Frees event memory. Returns @tt{oke}. - - -@subsection{Native Dialogs} - -@codeblock{ -rkt_js_result_t* rkt_webview_choose_dir(rktwebview_t wv, const char* title, const char* base_dir); -} -Returns JSON: -@itemlist[#:style 'compact - @item{@tt{"state":"choosen","dir":"..."}} - @item{@tt{"state":"canceled","dir":"..."}} -] -Must be freed with @tt{rkt_webview_destroy_js_result}. - -@codeblock{ -rkt_js_result_t* rkt_webview_file_open(rktwebview_t wv, const char* title, const char* base_dir, const char* permitted_exts); -} -@codeblock{ -rkt_js_result_t* rkt_webview_file_save(rktwebview_t wv, const char* title, const char* base_dir, const char* permitted_exts); -} -Return JSON: -@itemlist[#:style 'compact - @item{@tt{"state":"choosen","file":"...","used-filter":"..."}} - @item{@tt{"state":"canceled","file":"","used-filter":"..."}} -] -Must be freed with @tt{rkt_webview_destroy_js_result}. - - -@subsection{Security} - -@codeblock{ -void rkt_webview_set_ou_token(rktwebview_t wv, const char* token); -} - -This function configures a per-window "OU acceptance token" that affects how -certificate errors are handled inside the internal @tt{QWebEnginePage}. - -When a TLS certificate error occurs, the internal page examines the certificate -chain. If all the following conditions are met: +Returns: @itemlist[#:style 'compact - @item{the certificate is self-signed,} - @item{the certificate contains an Organizational Unit (OU) field,} - @item{the OU value exactly matches the token previously set with @tt{rkt_webview_set_ou_token},} + @item{@tt{oke} on success} + @item{@tt{move_failed} on failure} ] -then the certificate is programmatically accepted and the load is allowed to -continue. +@subsection{@tt{rkt_webview_resize}} -This mechanism is intended for controlled environments (e.g., local development -servers or private deployments) where a known self-signed certificate is used -and its OU value is part of the trust boundary. +@verbatim|{ +result_t rkt_webview_resize(rktwebview_t wv, int width, int height); +}| -Note: starting with Qt 6.10, @tt{QWebEnginePage} introduces an API for providing -a specific certificate to be accepted directly by the page. However, this API is -very new ("bleeding edge") and not yet used or relied upon in this WebView -bridge. +Resizes the window to the given dimensions. - -@section{Event JSON Format} +Returns: @itemlist[#:style 'compact - @item{@tt{"event":"page-loaded","oke": }} - @item{@tt{"event":"show"}} - @item{@tt{"event":"hide"}} - @item{@tt{"event":"move","x": ,"y": }} - @item{@tt{"event":"resize","w": ,"h": }} - @item{@tt{"event":"can-close?"}} - @item{@tt{"event":"closed"}} - @item{@tt{"event":"js-evt","js-evt": }} + @item{@tt{oke} on success} + @item{@tt{resize_failed} on failure} ] +@subsection{@tt{rkt_webview_hide}} + +@verbatim|{ +result_t rkt_webview_hide(rktwebview_t w); +}| + +Hides the window. + +Returns @tt{oke} on success and @tt{failed} on failure. + +@subsection{@tt{rkt_webview_show}} + +@verbatim|{ +result_t rkt_webview_show(rktwebview_t w); +}| + +Shows the window. + +Returns @tt{oke} on success and @tt{failed} on failure. + +@subsection{@tt{rkt_webview_show_normal}} + +@verbatim|{ +result_t rkt_webview_show_normal(rktwebview_t w); +}| + +Restores the window to the normal state. + +Returns @tt{oke} on success and @tt{failed} on failure. + +@subsection{@tt{rkt_webview_present}} + +@verbatim|{ +result_t rkt_webview_present(rktwebview_t w); +}| + +Requests that the window be presented to the user. + +Returns @tt{oke} on success and @tt{failed} on failure. + +@subsection{@tt{rkt_webview_maximize}} + +@verbatim|{ +result_t rkt_webview_maximize(rktwebview_t w); +}| + +Maximizes the window. + +Returns @tt{oke} on success and @tt{failed} on failure. + +@subsection{@tt{rkt_webview_minimize}} + +@verbatim|{ +result_t rkt_webview_minimize(rktwebview_t w); +}| + +Minimizes the window. + +Returns @tt{oke} on success and @tt{failed} on failure. + +@subsection{@tt{rkt_webview_window_state}} + +@verbatim|{ +window_state_t rkt_webview_window_state(rktwebview_t w); +}| + +Returns the current state of the window. + +Possible results include: + +@itemlist[#:style 'compact + @item{@tt{normal}} + @item{@tt{minimized}} + @item{@tt{maximized}} + @item{@tt{hidden}} + @item{@tt{normal_active}} + @item{@tt{maximized_active}} + @item{@tt{invalid} when no valid state can be reported} +] + +@section{Native Dialogs} + +The dialog functions display native Qt dialogs and report the user's choice by +emitting events through the window callback. + +@subsection{@tt{rkt_webview_choose_dir}} + +@verbatim|{ +result_t rkt_webview_choose_dir(rktwebview_t w, + const char *title, + const char *base_dir); +}| + +Opens a directory-selection dialog. + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} if the dialog was shown} + @item{@tt{failed} if the window handle is invalid} +] + +On completion the callback receives one of these events: + +@itemlist[#:style 'compact + @item{@tt{"choose-dir-ok"}} + @item{@tt{"choose-dir-cancel"}} +] + +For the @tt{"choose-dir-ok"} event, the JSON payload includes fields such as +the chosen path and current directory. + +@subsection{@tt{rkt_webview_file_open}} + +@verbatim|{ +result_t rkt_webview_file_open(rktwebview_t w, + const char *title, + const char *base_dir, + const char *permitted_exts); +}| + +Opens a file-open dialog. + +The @tt{permitted_exts} argument is passed to Qt as a filter string using the +usual @tt{";;"} separator format. + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} if the dialog was shown} + @item{@tt{failed} if the window handle is invalid} +] + +On completion the callback receives one of these events: + +@itemlist[#:style 'compact + @item{@tt{"file-open-ok"}} + @item{@tt{"file-open-cancel"}} +] + +@subsection{@tt{rkt_webview_file_save}} + +@verbatim|{ +result_t rkt_webview_file_save(rktwebview_t w, + const char *title, + const char *base_dir, + const char *permitted_exts); +}| + +Opens a file-save dialog. + +The @tt{permitted_exts} argument is passed to Qt as a filter string using the +usual @tt{";;"} separator format. + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} if the dialog was shown} + @item{@tt{failed} if the window handle is invalid} +] + +On completion the callback receives one of these events: + +@itemlist[#:style 'compact + @item{@tt{"file-save-ok"}} + @item{@tt{"file-save-cancel"}} +] + +@subsection{@tt{rkt_webview_message_box}} + +@verbatim|{ +result_t rkt_webview_message_box(rktwebview_t w, + const char *title, + const char *message, + const char *submessage, + rkt_messagetype_t type); +}| + +Shows a native message box. + +Arguments: + +@itemlist[#:style 'compact + @item{@tt{title}: window title of the message box} + @item{@tt{message}: main message} + @item{@tt{submessage}: supplementary message text} + @item{@tt{type}: one of @tt{info}, @tt{error}, @tt{warning}, + @tt{yes_no}, or @tt{oke_cancel}} +] + +Returns: + +@itemlist[#:style 'compact + @item{@tt{oke} if the dialog was shown} + @item{@tt{failed} if the window handle is invalid} +] + +The callback receives one of the following events, depending on the chosen +button: + +@itemlist[#:style 'compact + @item{@tt{"msgbox-ok"}} + @item{@tt{"msgbox-cancel"}} + @item{@tt{"msgbox-yes"}} + @item{@tt{"msgbox-no"}} +] + +@section{Event Model} + +The callback passed to @tt{rkt_webview_create} receives JSON-encoded events. + +The implementation emits events such as: + +@itemlist[#:style 'compact + @item{@tt{"show"}} + @item{@tt{"hide"}} + @item{@tt{"move"}} + @item{@tt{"resize"}} + @item{@tt{"closed"}} + @item{@tt{"can-close?"}} + @item{@tt{"page-loaded"}} + @item{@tt{"navigation-request"}} + @item{@tt{"link-hovered"}} + @item{@tt{"js-evt"}} + @item{dialog result events such as @tt{"choose-dir-ok"} and @tt{"msgbox-yes"}} +] + +The precise JSON structure depends on the event kind. Examples include: + +@itemlist[#:style 'compact + @item{@tt{"page-loaded"} with an @tt{"oke"} field} + @item{@tt{"move"} with @tt{"x"} and @tt{"y"}} + @item{@tt{"resize"} with @tt{"w"} and @tt{"h"}} + @item{@tt{"navigation-request"} with @tt{"url"} and @tt{"type"}} + @item{@tt{"link-hovered"} with @tt{"url"}} + @item{@tt{"js-evt"} with a nested @tt{"js-evt"} object containing the + original JavaScript payload} +] @section{Typical Usage Pattern} +A typical embedding sequence is: + @itemlist[#:style 'compact - @item{Initialize once.} - @item{Create windows.} - @item{Run a cooperative time-sliced pump loop (e.g., ~3 ms Qt, ~10 ms Racket).} - @item{Invoke JS run/call functions.} - @item{Handle and free events.} - @item{Close windows explicitly.} + @item{Create a context with @tt{rkt_webview_new_context}.} + @item{Create a window with @tt{rkt_webview_create}.} + @item{Load content with @tt{rkt_webview_set_url} or @tt{rkt_webview_set_html}.} + @item{Interact with the page using @tt{rkt_webview_run_js} or + @tt{rkt_webview_call_js}.} + @item{Call @tt{rkt_webview_process_events} regularly to keep the UI alive.} + @item{Release all returned @tt{rkt_data_t *} values with + @tt{rkt_webview_free_data}.} ] - -@section{Summary} - -This API provides a stable, simple interface for embedding a Qt WebEngine -webview inside a Racket program via FFI. The API is synchronous, uses compact -JSON for results, and defines clear memory ownership rules. -`` \ No newline at end of file diff --git a/scrbl/rktwebviewqt-internals.scrbl b/scrbl/rktwebviewqt-internals.scrbl index e047895..1fff21a 100644 --- a/scrbl/rktwebviewqt-internals.scrbl +++ b/scrbl/rktwebviewqt-internals.scrbl @@ -1,308 +1,346 @@ #lang scribble/manual -@title{Internal Architecture and Behavior of the Qt–Backed WebView Bridge} +@title{Structure and behavior of @tt{rktwebview_qt}} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] -@italic{Technical architecture notes for maintainers} +@section{Overview} +@tt{rktwebview_qt} is a Qt-based backend that provides embedded webviews for +applications written in Racket. The library exposes a small C-compatible API +intended to be used through the Racket FFI. Internally, the implementation is +written in C++ and uses Qt WebEngine. - -@;@abstract{ -This document explains the internal structure, threading model, command -dispatch mechanism, JavaScript bridge, event flow, timing behavior, and -lifecycle semantics of a Qt WebEngine–backed webview bridge. It focuses on how -a synchronous-looking host API is implemented atop Qt’s single-threaded GUI -requirements. Public API signatures and usage examples are intentionally -omitted here and should be documented in a separate companion file. -@;} - -@section{Design Objectives} - -The implementation provides a compact, synchronous façade over Qt WebEngine -while preserving GUI-thread safety, predictable behavior, and a simple, typed -boundary to the host. +The backend provides: @itemlist[#:style 'compact - @item{All UI work must execute on the Qt GUI thread; the host may call from any thread.} - @item{The host experiences a synchronous API; internally, work is dispatched asynchronously and awaited by pumping the event loop in bounded slices.} - @item{A minimal JSON bridge enables page→host messages without exposing Qt types over the boundary.} - @item{Window lifecycle, geometry, dialogs, and developer tools behave deterministically and are observable via structured events.} + @item{creation of browser windows} + @item{navigation to URLs or HTML content} + @item{execution of JavaScript} + @item{return values from JavaScript} + @item{delivery of browser and window events} + @item{native dialogs such as file choosers and message boxes} ] -@section{Core Building Blocks} +The implementation is intentionally organized so that the external interface +remains simple and language-neutral while the Qt-specific logic is hidden +inside the runtime controller. -@subsection{Command and CommandEvent} +@section{Requirements} + +The @tt{rktwebview_qt} backend requires Qt version @tt{6.10.2} or newer. + +The backend is built against Qt WebEngine and depends on the runtime +components provided by Qt. Earlier Qt versions are not supported. + +Applications using the backend must therefore ensure that a compatible +Qt runtime (≥ @tt{6.10.2}) is available. + +In packaged builds the required Qt runtime is normally bundled together +with the backend library. + +@subsection{Compatibility} + +The backend requires Qt version @tt{6.10.2} or newer. + +This requirement is due to the use of the method +@tt{setAdditionalTrustedCertificates} provided by +@tt{QWebEngineProfileBuilder}. This functionality allows the backend to +install additional trusted certificates for a context. + +The mechanism is used to support trusted self-signed certificates for +local services. Earlier Qt versions do not provide this functionality +and are therefore not supported. + +Packaged builds typically include the required Qt runtime components. + +@section{Layers} + +The system consists of several layers. + +@subsection{C ABI Layer} + +The file @tt{rktwebview.h} defines the public API. This layer provides a stable +C-compatible interface that can easily be consumed from Racket through FFI. + +Important characteristics of the ABI include: @itemlist[#:style 'compact - @item{A @tt{Command} encapsulates a request: @emph{code} (enum), @emph{args} (@tt{QVector}), a @emph{result} (@tt{QVariant}), and flags @tt{done} and @tt{js_result_ok}.} - @item{@tt{CommandEvent}: a custom @tt{QEvent} (type @tt{QEvent::User + 1}) that carries a pointer to a @tt{Command} from the caller’s thread to the GUI thread.} - @item{The GUI thread handles @tt{CommandEvent} in @tt{customEvent}, dispatching to @tt{processCommand} which performs the UI-side operation, sets the result, and flips @tt{done = true}.} + @item{numeric handles represent windows and browser contexts} + @item{result codes indicate success or failure} + @item{data returned from the library is represented as @tt{rkt_data_t}} + @item{event callbacks deliver JSON strings describing events} ] -@subsection{@tt{Rktwebview_qt} Controller} +The C layer is implemented in @tt{rktwebview.cpp}. These functions mainly +forward calls to a singleton instance of the internal runtime class +@tt{Rktwebview_qt}. + +@section{Runtime Controller} + +The class @tt{Rktwebview_qt} acts as the central runtime controller. + +Its responsibilities include: @itemlist[#:style 'compact - @item{Owns the singleton @tt{QApplication} and initializes the event-pump timers.} - @item{Allocates integer window handles and maintains: - @itemlist[#:style 'compact - @item{@tt{_views}: handle → @tt{WebviewWindow*}} - @item{@tt{_view_js_callbacks}: handle → host event callback} - ] - } - @item{Implements the backend of all operations (create/close, navigation, JS run/call, geometry, visibility, devtools, dialogs, title/state, OU token).} - @item{Provides @emph{bounded} event-loop pumping so a non-Qt host can drive the GUI cooperatively.} - @item{Periodically harvests page→host events by invoking JavaScript in each page.} + @item{owning the Qt application instance} + @item{managing https contexts} + @item{managing open windows} + @item{dispatching commands} + @item{forwarding events to the host application} ] -@subsection{@tt{WebviewWindow} (Window) and @tt{WebViewQt} (View)} +Internally, it maintains several registries: @itemlist[#:style 'compact - @item{@tt{WebviewWindow}: a @tt{QMainWindow} that embeds a @tt{WebViewQt}, handles lifecycle (show/hide/close), debounces geometry changes, opens devtools, processes certificate errors using an OU token, and periodically scrapes JS events from the page.} - @item{@tt{WebViewQt}: a @tt{QWebEngineView} with a stable integer ID and a back-pointer to its parent window; participates in load signal wiring so the controller can inject helper JS and fire a @tt{"page-loaded"} event.} + @item{a map from window handles to @tt{WebviewWindow} instances} + @item{a map from context identifiers to @tt{QWebEngineProfile} objects} + @item{a map from window handles to callback functions} ] -@subsection{Utility Layer} +Each API call that manipulates a window or browser state is forwarded through +this controller. + +@section{HTTP(s) Contexts} + +A http(s) context represents a shared WebEngine profile. + +Contexts are created using @tt{rkt_webview_new_context}. Each context contains: @itemlist[#:style 'compact - @item{@tt{EventContainer}: a lightweight key–value structure for assembling outbound events.} - @item{@tt{mkEventJson}: serializes an @tt{EventContainer} to compact JSON text for the host callback.} + @item{a @tt{QWebEngineProfile}} + @item{optional injected JavaScript code} + @item{optional trusted certificates} ] -@section{Threading, Event Loop, and Timing} +Multiple windows normally share the same context. This allows cookies, +configuration, and injected scripts to be reused. -@subsection{Initialization and Timers} +A context normally represents a https server that can be reached on a certain (local) Url. -On the first entry (or via explicit initialization), the controller constructs -the @tt{QApplication} and sets up two key timers: +@section{Window and View Classes} + +Two Qt classes implement the actual browser window. + +@subsection{@tt{WebviewWindow}} + +@tt{WebviewWindow} derives from @tt{QMainWindow}. It represents the native +top-level window. + +Responsibilities include: @itemlist[#:style 'compact - @item{@bold{JS event harvester} @tt{_process_events}: fires every few milliseconds to call into each page and fetch queued page→host events.} - @item{@bold{Event-loop slice} @tt{_evt_loop_timer}: a single-shot timer used to exit a short run of the Qt event loop; this underpins the bounded pump.} + @item{hosting the browser widget} + @item{reporting window events such as show, hide, move, and resize} + @item{handling close requests} + @item{reporting navigation events} + @item{forwarding JavaScript events} ] -@subsection{Bounded Event-Loop Pumping} +Window geometry changes are slightly delayed through timers so that the host +application receives fewer intermediate events. -Because the host may not yield a classic Qt main loop, the controller exposes a -“pump” that executes Qt’s event loop briefly, then exits via the single-shot -timer. A host helper can call this repeatedly for a requested duration to: +@subsection{@tt{WebViewQt}} + +@tt{WebViewQt} derives from @tt{QWebEngineView}. This class mainly associates +the Qt widget with a numeric handle used by the external API. + +@section{Command Dispatch} + +Many API functions are implemented using a small command-dispatch mechanism. + +A command object represents a pending request. The request is delivered to the +Qt event system using a custom Qt event. The runtime controller receives the +event and executes the corresponding operation. + +This design ensures that GUI operations occur inside the Qt event loop while +the external API remains synchronous. + +@section{Event Processing} + +Qt events are processed through repeated calls to +@tt{rkt_webview_process_events}, which internally calls +@tt{QApplication::processEvents}. This allows the host runtime to control how +frequently GUI events are processed. + +@section{Navigation} + +Two main navigation operations are provided. @itemlist[#:style 'compact - @item{Deliver posted @tt{CommandEvent}s to the GUI thread.} - @item{Complete asynchronous WebEngine operations (e.g., JS callbacks, load signals).} - @item{Run the JS harvester that drains page→host events.} + @item{@tt{rkt_webview_set_url} loads a URL} + @item{@tt{rkt_webview_set_html} loads raw HTML} ] -This design preserves GUI responsiveness and enables synchronous host calls -without blocking the GUI thread. +When page loading finishes, a @tt{"page-loaded"} event is emitted. The event +contains a boolean field indicating whether loading succeeded. -@section{Lifecycle and Window Semantics} +@section{JavaScript Execution} -@subsection{Creation and Parenting} +JavaScript execution is provided through two functions. + +@subsection{Fire-and-Forget Execution} + +@tt{rkt_webview_run_js} executes JavaScript without returning a result. + +This is useful for DOM manipulation or triggering page behavior. + +@subsection{Synchronous Execution with Result} + +@tt{rkt_webview_call_js} evaluates JavaScript and returns a result object. + +The JavaScript is wrapped in a small function that captures either the result +value or an exception. The result is returned as a JSON object containing: @itemlist[#:style 'compact - @item{A “create” command constructs a @tt{WebviewWindow}, optionally modal if a valid parent handle is supplied.} - @item{A @tt{WebViewQt} is created with a fresh handle and set as the central widget.} - @item{The window is shown; the command returns only after the window reports @emph{created}, ensuring safe subsequent operations.} + @item{@tt{oke}} + @item{@tt{result}} + @item{@tt{exn}} ] -@subsection{Closing and the Blocked-Close Pattern} +The native side waits for the asynchronous Qt callback before returning the +value to the host application. + +@section{JavaScript Event Bridge} + +Communication from JavaScript to the host application uses a small event queue +implemented in injected JavaScript. + +When a browser context is created, the runtime injects support code defining +the following objects: @itemlist[#:style 'compact - @item{If the window is not yet permitted to close, a close request is ignored and a @tt{"can-close?"} event is emitted to the host.} - @item{When permitted, the window closes, a @tt{"closed"} event is emitted, and the handle is removed from controller maps. Any devtools window is also disposed.} + @item{@tt{window.rkt_event_queue}} + @item{@tt{window.rkt_send_event(obj)}} + @item{@tt{window.rkt_get_events()}} ] -@subsection{Visibility and Activation} +The queue stores JavaScript-generated events until the native side retrieves +them. -@itemlist[#:style 'compact - @item{@tt{show}, @tt{hide}, @tt{show-normal}, @tt{minimize}, @tt{maximize}, @tt{present}.} - @item{@tt{present} ensures the window is shown, raised, and activated.} - @item{Window state queries map Qt’s visibility/activation to a simplified enum, including @emph{active} variants.} -] +@subsection{Event Queue} -@subsection{Geometry, Debounce, and Completion Criteria} - -Move/resize semantics are designed to be deterministic: - -@itemlist[#:style 'compact - @item{Each @tt{move} or @tt{resize} triggers a 500ms single-shot timer that coalesces rapid changes (e.g., during drags).} - @item{The command completes only after the window’s internal move/resize counter increments, guaranteeing a visible state change before success is reported.} - @item{Corresponding @tt{"move"} and @tt{"resize"} events include stable geometry values @tt{x}, @tt{y}, @tt{w}, and @tt{h}.} -] - -@section{JavaScript Bridge} - -@subsection{Injected Helpers and Event Queue} - -On page load, the controller ensures the following helpers exist in the page -context (application world), establishing a minimal event queue: +When page code wants to emit an event, it calls: @verbatim|{ -window.rkt_event_queue = []; -window.rkt_send_event = function(obj) { - window.rkt_event_queue.push(obj); -}; -window.rkt_get_events = function() { - let q = window.rkt_event_queue; - window.rkt_event_queue = []; - return JSON.stringify(q); -}; +window.rkt_send_event(obj) }| -Notes: +The object is appended to the queue. + +The queue can be retrieved using: + +@verbatim|{ +window.rkt_get_events() +}| + +This function returns a JSON array and clears the queue. + +@subsection{Native Notification} + +To notify the native side that events are waiting, the injected code creates a +hidden iframe named @tt{rkt-evt-frame}. The iframe is used only as a signalling +mechanism. + +After adding an event to the queue, the script attempts to call: + +@verbatim|{ +frameWindow.print() +}| + +Qt receives this through the signal +@tt{QWebEnginePage::printRequestedByFrame}. + +The window implementation checks whether the frame name is +@tt{"rkt-evt-frame"}. If so, it calls @tt{processJsEvents} to retrieve the +queued events. + +This results in the following sequence: @itemlist[#:style 'compact - @item{The helpers are injected after page load so they are present for app code regardless of the navigated content.} - @item{A previously prepared @tt{QWebEngineScript} injection path exists on the window, but the explicit insertion call is disabled; the active injection occurs via the controller on page load, which is sufficient in practice.} + @item{JavaScript calls @tt{rkt_send_event}.} + @item{The event is added to the queue.} + @item{The hidden iframe triggers a print request.} + @item{Qt receives the request.} + @item{The native side calls @tt{rkt_get_events}.} + @item{Each event object is delivered to the host.} ] -@subsection{Periodic Harvesting (Page → Host)} +The mechanism is therefore not a simple native polling loop. It is better +described as a queued JavaScript event bridge with a lightweight native +wake-up signal. -A fast timer iterates active windows and evaluates @tt{rkt_get_events()} in each -page. The returned JSON array is parsed; each object is wrapped as an -@tt{EventContainer} of kind @tt{"js-evt"} (embedding the original object under -a field) and emitted to the host callback as a compact JSON string. +@subsection{Delivered Event Format} -@itemlist[#:style 'compact - @item{This keeps the host callback signature simple (C string payload).} - @item{Batching reduces cross-thread chatter; clearing the page-side queue prevents duplicates.} -] +JavaScript-originated events are forwarded to the host as structured JSON. -@subsection{Run vs Call (Host → Page)} +Each event uses a generic envelope: -Two execution modes are differentiated to avoid conflating effects and results: - -@itemlist[#:style 'compact - @item{@bold{Run}: fire-and-forget evaluation for side-effects (e.g., DOM mutations). No return value is captured.} - @item{@bold{Call}: user code is wrapped in a @tt{try/catch} scaffold that returns a JSON object: - @verbatim|{ +@verbatim|{ { - "oke": , - "result": , - "exn": + "evt": "js-evt", + "js-evt": { ... } } }| - The native side returns this JSON as a newly allocated result object; @tt{js_result_ok} mirrors the @tt{"oke"} field.} -] -@section{Dialogs and Security Controls} +The inner object is the original JavaScript event payload. -@subsection{Native Dialogs} +This design keeps the native API simple while allowing arbitrary JavaScript +objects to be delivered to the host application. + +@section{Window Lifecycle} + +Windows are created using @tt{rkt_webview_create}. Each window receives a +callback function used to deliver events. + +Important lifecycle events include: @itemlist[#:style 'compact - @item{@tt{choose-dir}: returns a compact JSON with either @tt{"choosen"} and a directory path, or @tt{"canceled"} with the base directory.} - @item{@tt{file-open}, @tt{file-save}: similarly return @tt{"choosen"} with a file path and the filter used, or @tt{"canceled"} with the last-selected filter.} - @item{The host must free returned result objects; see @secref{memory}.} + @item{@tt{"show"}} + @item{@tt{"hide"}} + @item{@tt{"move"}} + @item{@tt{"resize"}} + @item{@tt{"closed"}} ] -@subsection{Certificate Handling with OU Token} +If the user attempts to close a window directly, the close request is converted +into a @tt{"can-close?"} event so that the host application can decide whether +the window should actually close. -When a certificate error arises, the window inspects the chain; if it encounters -a self-signed certificate whose Organizational Unit (OU) equals the configured -token, it accepts the certificate. +@section{Native Dialogs} + +The backend also exposes native dialogs including: @itemlist[#:style 'compact - @item{This provides a controlled trust mechanism for private/dev deployments.} - @item{Ordinary sites remain subject to standard verification.} + @item{directory selection} + @item{file open} + @item{file save} + @item{message boxes} ] -@section{Event Model (Delivered to Host)} +The results of these dialogs are delivered as events so that the host +application remains in control of the user interface flow. -All events are delivered as compact JSON strings to the host’s callback. +@section{Certificates} -@itemlist[#:style 'compact - @item{@tt{"page-loaded"}: emitted after helper JS installation; includes an @tt{"oke"} boolean indicating load success.} - @item{@tt{"show"} and @tt{"hide"}: visibility transitions (hide is suppressed on final close).} - @item{@tt{"move"}: includes @tt{"x"}, @tt{"y"} after debounce.} - @item{@tt{"resize"}: includes @tt{"w"}, @tt{"h"} after debounce.} - @item{@tt{"can-close?"}: emitted when a close was requested but not yet permitted.} - @item{@tt{"closed"}: emitted when the window is actually destroyed and unregistered.} - @item{@tt{"js-evt"}: wrapper events for each page-originated object collected via the event queue.} -] +Contexts may optionally install trusted certificates. Additionally, a window +may specify a special Organizational Unit token used to automatically trust +certain self-signed certificates. -@section{Command Set and Internal Behavior} +This mechanism is useful when communicating with local development services. -This section summarizes each command’s core behavior and completion rule. +@section{Memory Ownership} -@itemlist[#:style 'compact - @item{@bold{CREATE}: build @tt{WebviewWindow} (+ optional modal parent), create @tt{WebViewQt}, assign handle, @tt{show}, wait until @emph{created} flag is true, then return handle.} - @item{@bold{CLOSE}: mark as allowed to close, call @tt{close()}, emit @tt{"closed"} on destruction, unregister handle.} - @item{@bold{SET\_OU\_TOKEN}: stash OU token in the window for later certificate checks.} - @item{@bold{SET\_URL}: @tt{QWebEngineView::setUrl}; return success/failure after dispatch.} - @item{@bold{SET\_HTML}: @tt{QWebEngineView::setHtml}; return success/failure after dispatch.} - @item{@bold{SET\_TITLE}: set the native window title; return success/failure.} - @item{@bold{RUN\_JS}: invoke JavaScript without awaiting a value; return success/failure (evaluation dispatched).} - @item{@bold{CALL\_JS}: wrap in @tt{try/catch}, return JSON object; success when callback delivers result, propagate @tt{oke} to @tt{js_result_ok}.} - @item{@bold{DEV\_TOOLS}: open a separate window, attach the page’s devtools page, and keep it in sync; dispose with parent.} - @item{@bold{SHOW/HIDE/PRESENT/MAXIMIZE/MINIMIZE/SHOW\_NORMAL}: call corresponding Qt methods and return @tt{oke} upon dispatch; @tt{present} additionally raises and activates.} - @item{@bold{WINDOW\_STATUS}: map @tt{isHidden}/@tt{isMinimized}/@tt{isMaximized}/@tt{isVisible} (+ @tt{isActiveWindow}) to a simplified enum (including @emph{active} variants).} - @item{@bold{MOVE}: call @tt{move(x,y)}, then wait until the move counter increments (debounced) before acknowledging success.} - @item{@bold{RESIZE}: call @tt{resize(w,h)}, then wait until the resize counter increments (debounced) before acknowledging success.} - @item{@bold{CHOOSE\_DIR / FILE\_OPEN / FILE\_SAVE}: open native dialogs, convert result to compact JSON, return as newly allocated result object.} -] +Data returned from the library and (javascript) events originating from the library +are allocated on the native side and must be released by the host using +@tt{rkt_webview_free_data}. -@section[#:tag "memory"]{Memory, Ownership, and Boundaries} -@;@seclabel{memory} - -@itemlist[#:style 'compact - @item{@italic{Events} and @italic{JS-call results} are returned as small C structs containing freshly allocated UTF‐8 strings; the host must free them using the provided destroy helpers.} - @item{Qt types remain internal; boundary values are converted to/from UTF‐8 @tt{char*}.} - @item{Commands are transient and owned by the GUI thread; the host never owns a @tt{Command}.} -] - -@section{Determinism, Failure Modes, and Guarantees} - -@itemlist[#:style 'compact - @item{All UI-affecting operations execute on the GUI thread via posted commands (with normal priority), ensuring thread safety and sequential handling as the event queue is processed by Qt in order of posting.} - @item{Synchronous host calls return only after a definitive outcome: - @itemlist[#:style 'compact - @item{Navigation/HTML: boolean success/failure.} - @item{JS run: success/failure of dispatch.} - @item{JS call: structured JSON with @tt{oke}/@tt{result}/@tt{exn}.} - @item{Geometry: acknowledgment only after debounced counters change.} - @item{Dialogs: compact JSON describing @tt{"choosen"} or @tt{"canceled"}.} - ] - } - @item{State queries (e.g., window state) are also executed as commands, reflecting the GUI thread’s authoritative view.} -] - -@section{Edge Cases and Notes} - -@itemlist[#:style 'compact - @item{Helper JS can be injected via two mechanisms; the active path injects after page load from the controller. A prebuilt @tt{QWebEngineScript} insertion in the window is present but disabled.} - @item{Debounce windows (500ms timers) intentionally delay geometry events and command completion under heavy interaction, trading a tiny latency for stability and reduced churn.} - @item{Host-driven pumps should be called frequently enough to maintain UI responsiveness and timely delivery of JS results and events.} -] - -@section{Worked Walkthrough (Typical Session)} - -A typical host session proceeds like this: - -@itemlist[#:style 'compact - @item{Initialize controller (implicit on first call).} - @item{Create window A; move/resize; set URL or HTML.} - @item{Pump events in a loop; open devtools if needed.} - @item{Call JS to compute a value (returns structured JSON) and run JS for side-effects (fire-and-forget).} - @item{From page scripts, call @tt{rkt_send_event({...})}; the host periodically receives @tt{"js-evt"} payloads during pumps.} - @item{Optionally create window B as a modal child of A; navigate separately; continue pumping.} - @item{Close B; later close A (possibly after a @tt{"can-close?"} handshake).} -] - -@section{Extensibility Guidelines} - -@itemlist[#:style 'compact - @item{For new features, add a command code, implement a @tt{processCommand} case, and provide a controller wrapper that constructs the command and awaits completion.} - @item{Prefer compact JSON for multi-field results to keep the host boundary stable and language-agnostic.} - @item{If adding richer page bridges, inject helpers on load and keep harvesting batched and periodic to minimize overhead.} - @item{Maintain the convention that all externally observable events include an explicit @tt{"event"} tag plus relevant fields.} -] +This avoids exposing C++ ownership semantics across the FFI boundary. @section{Summary} -Internally, this bridge is a command-queued, GUI-thread–safe façade over Qt -WebEngine. The host sees synchronous operations; under the hood, each action is -posted to the GUI thread, the event loop is pumped in short slices, and -completion is acknowledged only after the intended UI effect is verifiably in -place. A small JSON bridge carries page→host messages and supports synchronous -host→page calls with structured results, keeping the boundary stable and -language-neutral. \ No newline at end of file +The @tt{rktwebview_qt} backend provides a compact architecture for embedding +Qt WebEngine inside Racket applications. + +The design allows Racket programs to implement desktop applications using +HTML and JavaScript for the user interface while keeping application logic in +the host runtime. \ No newline at end of file diff --git a/scrbl/wv-settings.scrbl b/scrbl/wv-settings.scrbl new file mode 100644 index 0000000..fa270e6 --- /dev/null +++ b/scrbl/wv-settings.scrbl @@ -0,0 +1,121 @@ +#lang scribble/manual + +@(require scribble/racket + scribble/example + scribble/core + (for-label racket/base + racket/string + racket/class + racket/file) + ) + +@defmodule[wv-settings] + +@title{Object-Oriented Interface: @racket[wv-settings%]} +@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] + +@defclass[wv-settings% object% ()]{ + +An OO wrapper around a settings backend implementing the +@racket[simple-ini/class] interface. + +A @racket[wv-settings%] object represents a view on one settings section. +The supplied @racket[wv-context] value is used as the section identifier for +context-local settings. + +The class delegates all actual storage operations to the supplied +@racket[ini] object. + +@defconstructor[([ini object?] + [wv-context any/c])]{ +Creates a new settings object. + +The @racket[ini] argument is the backing settings object. + +The @racket[wv-context] argument is used as the section identifier for +context-local settings. + +The class assumes that @racket[ini] supports methods compatible with: + +@itemlist[#:style 'compact + @item{@racket[(send ini get section key)]} + @item{@racket[(send ini get section key default)]} + @item{@racket[(send ini set! section key value)]} +] + +In typical use, @racket[ini] is an instance of a class from +@racket[simple-ini/class]. +} + +@defmethod*[([(get [key any/c]) any/c] + [(get [key any/c] [default-value any/c]) any/c])]{ +Returns the value associated with @racket[key] in the current section. + +Without a default value, this method delegates to: + +@racketblock[ +(send ini get wv-context key) +] + +With a default value, it delegates to: + +@racketblock[ +(send ini get wv-context key default-value) +] +} + +@defmethod*[([(set! [key any/c] [value any/c]) this])]{ +Stores @racket[value] under @racket[key] in the current section. + +This method delegates to: + +@racketblock[ +(send ini set! wv-context key value) +] +} + +@defmethod*[([(get/global [key any/c]) any/c] + [(get/global [key any/c] [default-value any/c]) any/c])]{ +Returns the value associated with @racket[key] in the global section. + +Without a default value, this method delegates to: + +@racketblock[ +(send ini get 'global key) +] + +With a default value, it delegates to: + +@racketblock[ +(send ini get 'global key default-value) +] +} + +@defmethod*[([(set/global! [key any/c] [value any/c]) this])]{ +Stores @racket[value] under @racket[key] in the global section. + +This method delegates to: + +@racketblock[ +(send ini set! 'global key value) +] +} + +@defmethod*[([(clone [context any/c]) (is-a?/c wv-settings%)])]{ +Creates a new @racket[wv-settings%] object using the same +@racket[ini] backend but another section identifier. + +This method is equivalent to: + +@racketblock[ +(new wv-settings% [ini ini] [wv-context context]) +] + +It is intended to support cheap creation of per-section settings views. +} + +@defmethod*[([(context) any/c])]{ +Returns the section identifier associated with this settings object. +} + +} ; end class \ No newline at end of file