From b551c023e76bf5602643ba1d879b4f362b4abe98 Mon Sep 17 00:00:00 2001 From: "Emma [it/its]@Rory&" Date: Sun, 27 Oct 2024 23:30:14 +0100 Subject: [PATCH] WIP logo/terminal stuff --- assets/icon.png | Bin 0 -> 11636 bytes assets/logo.png | Bin 0 -> 20588 bytes flake.nix | 57 +- package-lock.json | 96 ++ package.json | 5 + src/api/start.ts | 4 +- src/bundle/Server.ts | 2 +- src/bundle/start.ts | 35 +- src/bundle/stats.ts | 36 +- src/util/util/KittyLogo.ts | 141 +++ src/util/util/Logo.ts | 55 + src/util/util/String.ts | 6 + src/util/util/index.ts | 1 + src/util/util/os-utils/OsUtils.ts | 21 + src/util/util/os-utils/TermInfo/Constants.ts | 964 ++++++++++++++++++ src/util/util/os-utils/TermInfo/TermInfo.ts | 50 + .../os-utils/TermInfo/openTerminfoBuffer.ts | 104 ++ .../util/os-utils/TermInfo/parseTerminfo.ts | 159 +++ 18 files changed, 1689 insertions(+), 47 deletions(-) create mode 100644 assets/icon.png create mode 100644 assets/logo.png create mode 100644 src/util/util/KittyLogo.ts create mode 100644 src/util/util/Logo.ts create mode 100644 src/util/util/os-utils/OsUtils.ts create mode 100644 src/util/util/os-utils/TermInfo/Constants.ts create mode 100644 src/util/util/os-utils/TermInfo/TermInfo.ts create mode 100644 src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts create mode 100644 src/util/util/os-utils/TermInfo/parseTerminfo.ts diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..36b59c61e5252a4a434c25c72ec47cb940edc934 GIT binary patch literal 11636 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

Jl3wNb z>5KNaE6R@Ikj@CBg4dv);a3m`vvKpmOH%il!!Jbm80+RMNLV8+cI~f4Jn|?=AYZ3; zjfVcD%jYAIL;b8iF<$+R`iPRjmZa6@l|U8m;v0JScQu*|@r)}B(70KUi5VY$&T%R| z$e5#IE4$>ZNNh_dw`gLgY9rxX0Z6}%fnI4n_)x5_q<0u(iIMbWsRARl&HpzAvc9hR z`FYQwPvznlEGW*8ph%f0-XJom3r|uKpXSqsX1W?DdS{s|_uW5Tp*9UKG1Nc&i zPS##>M1`AClb_ESqcydZ_;XtlJ<=28n)b*xTpVTOKq@_sGN7fo}#kOw`Y6+Xr>3@{^XG$Q%8t z{&a1`F0vnYtfx*m5LHL^Hq}szaIdxBd##2v^2XLP@EVVP**kis^@={9z%hiE$?gEx zC-BXcRqw|gYqnr;+`KzZLS8)6qo4!Y83Qr-qm~ zcH6s|Pb(pE&ykH5(80Aw#mF~>+!Iv8#Di=mS6+Zvu zK`*387&OO^c#-OK#RDmptrK~bh-qo)m0zXQ$6D_BNiIIS~w|v*n z%IUFjy_XJ7IL1c0^?!~r!N-A*%8}FsVk?+B8>#NmykY+%WKZ;LRVFa>USF@%5%=XF z1Fgv@uIcI%z3+Tr)0dI+K9Mr-9_53X;IrpNESIC|t~pB%CyqfQ#8UaCnn|LPA5le27(@0$u_ zA7Xc5uBP(^=O5l~ID+_byTt&t`o{KtjBGg&%~oZ5f5MPKyyj5+&u}T$YpYDmLXFn! zSdy4;pd}6VdgFXlB0}X_wvi(=0uxm}+8bH`+wf4uijL-moGlzPVQ)D>u-GCeZT;A0 z8}Wv~we#205Am7q5wve)ZrO z>^T=5l<}ji%GF%;VHIfu(yP`Iu!d;wlbNvdY-^<>10aKis&1;hUSO!s2x67&iY(g= zi7A2q$=XTg!_@R8cOCm)#!UN z5_IA*CQGxhMVX*WLfzLR^gzl!v7+@)P$dy=(?As`Sf0gXKjp*Wukzkh>_|-wZgX#0 z$8Py%F!}?i|A|YR3dZ|$qctIle9V)&S9aAw5ii~XahQOegb$R+~5)Rhl$?Vv69?Ivcze24o|7(bR8H@Pcs?%gjI>##seP8FnrjS*6 z6roLc^AIWD0Vfnur`M+fO-+K!6QIcEVk4KG(+cxcgp zDp45Dw)8c3dsT;^Q_nc@2n}|(Qg0Zp2`>KDS!lHs^w^+tu4-2wcmlzpA-1P+poTME z;kq`6f0W1EibzdG6B0rV@MmzhQ)Lb$YiO2yAB33Z^y7qfQV?L7m~dJn1b^(kk7z8E zuQxElx+JGH)wiN2XpFpz*cERTAEZ?&F_v`7Zod-|b${w5J`I^A&z1w2Exq3~(Q_9N)9|GU4})=JnWV zW2+0e-4qtsK=0{L;G_6OlSs{z=_5Qt*<6MxK0ZUmv|@?O!;oU%W@WCd4DFJAi~63g z!(uZo(B1Mrjf)97+<7(e1pV!jD3@6tcr~x^Nd6<@L>Qpk(EYN)oZA7Sv0dQBi4O(8 z&Xci#UA{%_jdx17DShqD?D$KR-QdYX)C-&Mnt^nigtqAo;l7A=+)E~365?$*+!Ekk z9r!n=iW7q2WkSwE>(DoM!)Z(Xl9V$*X0{e~+6dC0dx3yJEyGh}Hr{$w7EZSUP+m-^ z$#w0(`29Gctq_jumdpH4+kr!|#ry_(GAL%xz7HkX2^+ugiZ1kB9ag?HRnWetUe0dB zK8zn1n{tySq1Vfi!f@R>xQ!nW`GO5k2`ON5?-i!hAiMKOQsky=(sC7h> z21Xvyeg2(RR(PwTkSp1vJOA(Tp`F**l<9GKh}?R9xrt%qQ&lf19Z|?t4sjDs z-oe+Ivg5Sge(cSVfwnMDmB7ttAyt(di`F$={?tTHZnSz=!g39(tHn@{L72>=0s&T? zbr94AN2-~umDX6~HX<|ZG%h)M_FZyo_Eg*eHGF}aX-{7pJ>R0xqQWEYq8oZJVlGzS z;Gy046!3CN>~g{n2acF1_SzXpO?+Vk+%O%OkPO2=&hx0XJ17u%xtA2*nLr%hnQL-RCZS1~ zc9fd4@g0vd<#Zsh1t52><*CKrq~BPU>enA5h@-|2jo3JK_`bI?E#&%|yh6jW4u*85 z-2S0_ zA7^*>rsLqky%H47NIzNo4q}-I6DmK9+|bORccAqUc?{J3#3&*O)%byA54^coT>EH1 z6MQ1@iY74EF1POpT3u}lXCTIjUKIPIb#u{KQpT!a4S?iJ0D0R(*VmEn=q^mry&L%rOoM4eW@KiKkCj~wIYIsvKjwt#3yuzQS~AENTakbGDvr-dMI=RJ zE8H{-COB2Nj2g((*!WCy4zBht>T&W9ATfa5&8O}nDzW8So8I$&L1?({sitPQ7CtI- zXFVG-o_tVNAs}-+(YRfu?)r{u>c$m2Bgxd&OH>+rq6(dXc&Ur$UNwE6ljYx1FVAdv z8qGVb>_{y*$}{v&wxHJ(w@^FhR@kgx#-pFL))X&!s3g838xVjl^0Jf()X(iXj^D8H zsEb2q13E%fC&aLK%lT{?!$yp<4b<0!4mvoj5p6CdUJp?wQlVc+2A*%d9l}!+>ZI=6 zCXy_kA3PHVMNgd}?;FZz6tl@7R5DA`;@CP?h|lQ(?`Y!StfV{Gf5CQoo|JVvtQ&Kr zlsDsg=OsgM14=5o{%}iG6s$!+&ufgoe-*>COJ$dPa`D8!QHA-gUf%ssPFJ}o*ha!vxF!&e!A$nD-=Gr7q-s7Wm+@2`sRW27jS15ZR{mV07_h@rPP4* zM{B3?P2FRS*nd{48T4(tCK%uJ+LM-=;dl3h6*wTu_CxobdBD<%?r zPHg<=>AmCuFHe(e?PuFMt2GyeuOBpy@pIq!(JCF?^Hm+1NKtqzK4h%EQ_C{SVQ=&@ z9B*n8(mPm_c04H;o7?R)nD$R+edA0qJkud{*WE6^3d9nxsVgH&@;oY)@CF@*o3wTo zS?{;j)A8=CZ@B*{33;|+=C%Fe#SEqLU4yRl2du+RT=!F4^$~T7Js7bQcJ4}iHf5nP zc;xMWAD9C4SB!4jVa~V}tmOT2{Rx=PooVZ25d2I{R&dLN*AYd4a z8goS@lU|}@{Q0V9`6ooH(rm~s3b8;cSe+yybf*@Mg_EFTD;bB@j2=rhD&>$hZKE3a zTd`ehd3(;{2jh?o|DZsj>|?Rj7-p3oy|}LXw0CZ6|LnIJs zHq74U+n6PngB8Bc)-IYDLm`aV>1Z~W*e`5z3V_tZPCsp@@!XPZ7KI(HIig+@pC2{j z@9cTGUhaa3AB2QNqMygK8$l8IDcHRTlN3Fjeemnt}hYg&Ra=q3d5;>)ZoC->r2 z59)4hPB;9`hA(Df(&KnOl3e|efm`9LbQ65XdY<{~)RiuQ`tSnyH;J>$n5N2%-}M!vwWnV@~`#qo_!u7KF*fcmIudu&6A#K-9Np=i#Hbx8UpLz31eO|Gq)fqJDV;>nyAnzCKy+Z(wr7)YTCEuDa zjoal*S0IQ@68xI8OJ>C`K%Y~gEmJGZF=f;?Y+f0};5#GV2rERJ1Q*mOz z{P@npjbafD51`(oIp#sE;>Y*NU4>*XeQMecTW3|hP+=w+y0ozuW?LHC`RuJNL zK5{><9P3+qkcu%HCsp*HrYY@*>etVJUD)pARO*x6dlt$~@&yO>zR?`LuuRLAo7b;` zqv{%%4E3*uU+aPre};ZX_&$>yWGN>l8MOTx4jSDycwAZBCF+K^GNE#pbld^%AY9HN0c2V_Z~De}%{3yUeKU z>NK;)3283E(q0$YSa^6%OCfFOB_dSu~YIVOSpDWz$8zKOCUAcOp4PQ9vz;-Br*N-ncunTO5j zwIW6UxP}CXQx_EIs@b%X(t$re|3$qw_~5h3p0!>6Bo?xcbqq5|FF1pi>)VkgTAb4PNQ5iIz=WGyp1kk6>wR<#_savt@MJ& zxrnW)Q%=D}kKfwMNp2{`&=zye2}%ZMiiLl7+h;1d)y2)vJ`xTiUD9ncIvbr`Td97* zLFLo)4!>1&;g5TG&ZSKJo+{Mq4U){QL)NMj3pc2#lh;C_xrEF~uCdfeWl%E1!Ut5e zi87m{Ynl@l$0pRo22J*oMqKu@!l!Y1{{U*hJtsaa?p8CuS-# z*OV$)g3b)_d4$D<={W)_EBGa_CnNnu;Dp(hK=(rQqR{kEtMYD081{tkZZ0_>Mdft1LW@VXdtVOmm`Cp- zjAn>tucP?g0YZEVqmQDtQ4#buzb60EaB(E$=mA745D+GiGoqTYs+HXAh0?(M!1;Ox zwNAi{-9f-e2R9k<9Mpc?`Dc*oWIl4PiT0gAJ@H_Cr$S17EkuVifR+rsvi#Q{bx5+| zRHD`{F1Zg|Ch_Coc&UyUhT=10ine^(VHt~z&vA2uP-__VB0)h(I^)}@FYl9Yyw(u& z_3tq@VW_IRwF#|!wpYUv_(}+fRX%h!)zmrmG<69H#WN_W#V*X@v=iWaEk zJrTz|8LhkSt*?xrF>l+2QuMY`ENPUwnMuT~)+gz0_F=QR9WW+<5cOVsZ|^&wUTM;5 ziELZfuk!o^1w(L}x3HrZTBun7rz>r-&(&cVzXrX(%Gx?8uL}5Nu>P|+zMoG2p{R@N z<0+tS3RQ)F>0ec9v7-$?mmraoUFK|$-GqJyJ!2by)Vys;tkZE$n%3|m_` z24l|Q2Q;T*R=x~bA|vkBqNGPl9FX@3;Y=s_K9IZiIrsLC7@BBZMDbJ*nCS?z6nC80 zOazB6?qy?%PiVCO;C_JQj4kprK>}Y@zNY$;bpfMumi;IP z01(nK9m#7h5=wd2HbzR>1jpYSY^hi zFB1?mSe$ssPi&O|-Ba}e88`a|HI0(S>j!?4~GyA$O+(3d9fKzvh&mT0j1vyo|8i^qJyEya-B~bfO)%|iDP`a2A z9OG2Y!AF5JhuX+J<;x$*e5LuT@L8EqL^T(F#iUZ2V(O#zB&`RlWdRR<9%b02Il)SC zg%sCsX|F!qs2zq|A^3a<2Jj-nXugsxkv>GhRAwPb_^VPU&iIHv%V^X}je=6U^kUNi zu09C2osj_ib_x&j+x;iQL1V6A&PO>vz4&n&`P}}cY3_`Y(fH0T&S9NJ$ zrc&c})U0&SqoBtEQ7JRC#|nGKYXJ|pa)QA!I;f{9Wl!rZex(mh#XZrmbXeSWPQahs zLVu@8E`Ml2Er2{m|I7QMk0A+5#=c4jN03(I%|=#ZhEMcM*xD=K61GOjO$ji=6lMiv zR_gsb4#+df2i%4oPNW&U4&7WFI;%2&A5h6?YTKFH9WORyZn)*hsB}D09rs;m3r4%` z9As0~_4f+S0n5B+L43G1mp=6qCKczjCsx8!_TeKxFYDyeLGN|}3}-e)dVv$_}4 zjGExn2DICD8)ja7h_VPL23b%ybRBXm3GmV%uw^95DJVWUNt_vMgF-0!bdOF^LMhhFp($K3#rO zvR{%*vpx^nQ{&7qYv+(!Z`y9uQZe`pmX@T0Z@`i#)y0B|tsRl=PQp}Jz%+fVO}mBb z@oqV@JnNOTF+@ih5l53cO@(rIr29K4uB>P33Vo9yUGlwG|LXL6YqFQOI0<{PIuBuY z(IxZ__yiq^+i_97fLfNG5`7{fiTzqi+Z8-mWB^hfnx7Q=$RD^W?IJnA$cNTV_>)36 z0)aLo;?iT0%W*D`;Bs+)NrMJ=D2Y2jA5hxC&U7uCZYM{~NQLtC2#= zF>%z9@ttr5a`IWe z`Vv~Gunj$~j@E5wwT?gaf#fiDMBqIdObW4j6|cdMpsJy}LZ616FIm1xMU^eJDm$Kb zfRFHkQ$pU>UkZ{ptmx28Ck&1{i^r2}`$W~*i7ejt7#9*P&dE#%G&;S6Mtu>O0b3>_ z=DW0dd4K!D%qwrCgHm;jP$;mZxE;cOIifye_2Ei>J~mI#FHeP3U-vajrI+I{0)4EZ zW3nYNb>%4cLccmxpOLhoE*o_O;laX4ne?UcQKZ~vnD+*iTQ(SFT^+2QWwEMYRK-_6 zp;x83)0~tpD0U*yy$V)+v%0@vJ6(h>86a!EaX+1~!gvQ(PuB7r?vyUpQ6nG4eGzPz zwIs&crOe~K=QUGTISlG4+ooE{dGRhEP-H$6Cu=)`6Xx{3EolSvWA|FcDeCX}kZK+Dv{fB5@wt8u3$m*!UK0qP#b6J_p*?ldxla7;%!H z`9w1{K}Qpv-ytcVx}^8DMiM%S`sK5hv#Q2uMmllTGTap6=_LAYzXfc~r;Oy+U8)ZA znalB%5si*?**q*qBXwk5BTx82@i#nEo*S4ryu|tsyd&hTwO(1tGsQ0;LC-tEj*V+K z!ZAQOb_HO?;C5BM516pEyLj#DC49U3R`Ke#lB{C!CW%E!!j-suds;vU1a~+7MAp-IU<>S#i$C;dES?j(q_n6QDdOtE|yCoBIM%GCsEM`*woca0E*|_TWGVrj-L*nXNcHgA-G3{jOoICAv zGz?Aj4^=P^=xod)^hR{pM-1A>PagTyNyvT~*gyuyex5V-BRQ@q&l}W>*wyy(Q{%mF zV+yf3oxfT#{5yZ<+U4c$5ieuq4x!}28Rbd9#)bB#(EH+iM+q=Fa_#2Nu%>DV!_cds0 z%7oGiWUu5#Ee51#Z+rtQpZO;8L>OHwqAR#x8<-xpwI;R9uyx z(fpWaXXn^Dw`*|^D?6u`$eVuH+D=gp(={JCsLFql@wEmZir=}zupMM5tdmfo+Xk54 zdGM_$2O_?UZE6GS-#KU3D>XEJ8F37Oz<;0k1~W=kXYn>zo?IRg*moP}QCR62Ab;3H zV)pM&GoSbN2i?<3aT46R9x+41u z!AQAryf)!UtM$H)Z`M3lh0HxnQP8R8-s@v*zE%(ZH1~;Dohe#61z{Ndh68W-sUaZG z`Un!2`6u*PF1j*so%^USr3_KB+yi^u!d-$zV?-E)N_;(Esz*;ovI%3|an}NR4d~DM z)M3`>VHYHok>PSa)U7`Bb6G!jWl47?%B_QWo3b| z{LtR{VbrF9@cg>cqZ7bg>7?r*UKoV8-p9?$)^qfo(FF8tMiti;QalR8SUe95!t5hx zl&@9XZ3{c*Am-&hC{dQ)XM`>tB3@+GYoI%?&Rr*+q$r)hZyA7!X8hnxjy6rGsS)!9 zf-H@0yE3Y>&37hlJG0j)QvaYC09leJ#K#X~@TV!J5 z&Q81D_LYxLV`U?LWoK4Jy!fLZxj-QVqD_iWPUa^w4{K;rLOnuydN^2{;)9@$@9zGp zzh4pWt>B%tFE}18Mi%DUy@HoWoq?qDmy|b=sA@-?gZP|N7>=uX+vr4fuDf9Jthly> zEH2I)IEj8@E6n*v7SQt%eM_l2V>zEXyDV5>V94tlJsRJ4PFNt|v3*Q0?&)A$z<7Bf zvLr#PE;)UPn%h%l!rS8Iij%;tqx52*g4!8+pjd*9oBr}7HlG(GoaQw)ThImLdMEtp zL|MV96I=E}hw=t8##Gq)>6)$N;r+9?mlKa!s3`XY6>4rz1_+32VS88ijPI|W0#x09 z3zRs^QrKz{=F@tZz_P%-CfBW~Mkc*BscHl$aGYG8agTepLeWu}rR_s(S;LJ;@N>7c zV3ZMy-3ok2LZ%c2!!b758KyaFyle2=jep#UE&mCBF^?ar-eeJ;Ze&|?Jm}?myyT|o z8U?A)UVQNVHB=N2ToJY#P^i7}c}}GdDY>u9K7ySa+pXd#=a|>Js6S$7)`%C##!Be* z<*wqm>F_1rRV58E9r66bv3ZJ}w`|>lC$2M^ceQY-&Gn>O19j%UqZiEG+fv?4<404Q z7%G~q<3S^r@ZEi`I6Hswpk|7ngFG4@*i{lKcrvOAi z2|HwG4_VMa=^-l$ohw3N5eg#0yw~CpOE#kzrsC1{hG$t_s$^m#1z!2SUgPraBzNE)nf9EF36zdYXWw29OS7jiv(V@nUjK% zx^;9Mb4Gdg8SaXEVvV+c797kbN$r--e(h=D`6Ux_qpl^?dwV+Zmz!N~h-Qijkr%*a z2x8E7jH1p)2}LsEsbER(`Sn^3|BjZO=5Pt#<7w2^jpxZxruwur_$Q~@ubSAWy{_G_3EKuR@x9~jci-&X(7C&?Z42K;s`fT^UIY6-TL@)S zlCxNsXh$vV==#IjJCcXkKXz*M@w&k>Oj^UiHqjkJ+Bof=5=)0>^5JsbvV)!X#A^@g_>CZ70MPhMZ9sUu^F`x%l?&CwJ2&DvAa3)+Btch+ z8b6iX>woDQ0>20t4RO5|(EPe;%}r`WxSx^bd{qiMi*pDK(Q#Uf;n8-o~u;+oEDrg{j9jl(TvTR+PMdiw337J}WxcWcwxRntJCG+Mxzj-Ho0E}9Nj3Jwwu)JDJirO^_MFf6Mcc1{aAzfR9e$ zv?Jb;^qW52)bLNsdP(#FRk4*SMOcX+pVZmDN?V=ld{jl%c~TXZxs_bh5SSdrdFe?2 zEy$IDde_WU=GPuZlsW>#&lLGy-E%#3IE)_`f`kvx9#x%&6`3%n=Q&0rwV;r)(sK(g z)7&WI_*Ej1I`OmK-{nS?g?UPnX_a zau&NsXwJO+V@WVQYm|;?Gb3ze!dYD-7+2a%V?CYz&em-f#cc{x8KMDJ3^iU$A#=1G z5JYW4p3^#eO3O%FaWdW^9nB_R(bgGD!5c1|C3938D5#iQC)0xl@mPK&KjgE~-iR(*T?aL^3o;M%T#)a0j0lmDw(~qT*wmBs`XS2y= z-xlr4)28{X*t*ZqQ5@xBiHIq1GS%*L!8m0B3mWfT^vHHbv6>Ij680w9*OW&IQwJ9| zwRQsL6I(xmQ5Ai26; z$ITO8(9uYfYr^(IP^7Cz&wfrHK!xfSsML4;GOz4HcKVJOMWXNDTJCe8wh~8FLetf_ zmNzWb1aBY=*)$FJxO}>`;^wz;4a^um(sF!8Vm#W(dCVAWWgIK z)Tug$QKq(+U674pMaR?kgTJB+fOE;wYI5yl8lP!J?uwOmBxs{8``t5u10SuX*A+=9 zNtpC>JWE4iM~($)d2G0SCt&HXKl61_onUt{H7gfSx0ZIAUrOSGzjDu4Qr;d`do{!? zj=-ee8Kv}f)9iuEeaVozp%lW6BdHgwG;`WXraAa~9B(QbzhNX$cPO3Hc2n6$48_+$ zD|z7|^DP{@wtKeuh|8dTsYL>Q7w6j2-e#$?<#g%LxlU!;atKP&i6LTho#AhQI7Wt| znuhyy#6W7ScPXA0cFTR)%-b|a@wk;++ZB^lg10Y;?_Vc9{h%ze<-+n?RYh)~v(gEl zmSfP%mznxJ6R)bs^iQyK((KVLYNmr?c!LC+Dq($%+^MP;_dF&{_Tg?)c7z)FKe<$h4wO20+~OUL@Cji1LOycEi6iKPpkCd+cY zdis&-j}TE^Z)quO$VqXYWapQ?2ih+xE4{_yaLy+}E)L>u;r+}-qx!Z3l6)sbNSpRy zY-t7L0!4)0Rbe`igkGNqq6ikfj2}(f>V7vz|8*C&dz%oQHtAjCu0>%DLKI=Sv$% zoPUHaU~D9m-s;JIkG!x!$cwQk^M@LZR~(++#3t4MTAB6Ej2y8ik_(RjrL1)h~xZh zUVd{ve3(;or^DzhrfI6xIs=^(z^=Fh2p8iAV*zJ0w|-W0eSyHEj2CD{b)CI9%&X=t z)F+oQdN44>_L)yCsJkU0iE#{(Cl&vL1IH8LMMWqpNj^-s1I8;Sm z&BEb?5B2g;0`8iMfAq}TMSSnqieDD+jIV-;V}7(HgQ4yBS9)D>%?%mBq&s10;m=#8 z=_6k10l!Ks4SpUn*S}oyx+_3|Rqol9NEy}Jx&})4b!}ODaW{h*#-ddgGb#U7IzrtK zv*EB}V@RVd+m7hFf=_DT>@5GmdHFs6tJs_Q&$P9`t$zhzKoI>6>?2UgQe8q2`4?{1 zq0MI|$s70S4kM;CGj=i;T0Qq2Xg(zmZT$+k{0w(zeI^)bOf6fqVqf%a>G z0H6Rms99T*-$Ez-*&IUwhXcErPeE$Gjnj$}FnEVz63Vptf@WoZ!X(760roC}fW2dB z{@JRQQpaQ>hfVacImvDNT!;3g#!bRiK??5{DIdy0{N#1yw2*A+4>KFS4;!LR>K5>@ zQuiMk=B+1yQ7Kx)(mh$RGvCR~ncdn?KUC@!AQu#9xFAU9Apqw=B`kD+BLQSWs;lj( z7+Nl}uMEmWN6pPEEHz8ZG8}SscK9E#9Yq{T<@7FIYdPww#^(sG<3(aT&uao3uK!W< zvU_gG-lG6}e32mD;DIl_T>Z$&h7b42P2Z3cSfAmV;|8{^vNep{P8dh$kE7iy(&DeJ zuAL$kJA-&unj$TDJ0?(u<;*E*eWm%&WR_Jy8s?D%d(EF+8!aJ(t19kLyOgm9ZnNaf z(xtJePJpos<@*I6NI^J#*58l71)V6w>}O<&M-Cn71oBozlUx>lb|!eihyUslI=!#c z1EG9ZkGvkXrBadNtL30jjm<(nj)eoTnCohrMoB1oVjx3_3ja#|Vzs+o7PX?0vm{(~ z;JBa;Q?cK0Rb)5d7Ht)9WSlMWi~yIXM5t;jAH+?F#lsb@>6+HRhrwLICKBl21d+rR zvsfeN7J&*IR1tWN6P(&1K&==V&J^I{_S93vP3*s)1>5avE8c&PpP=`VEi2Kf@K3?x z<&0=#v*Y~+qDeYZY+Fm+v*}34sd$L5#M+!+{QRy$#1OUIyQV(g*Zhs`cHOH{r;orD zbRRhvBo;DRHun1sLw~|+XBbk3J267Y>7iynBJ;8EN=EFavbW!#xQPo`1RyltV64|7 zZU-}+X{9!=lLw*fmhdK4F6LY~NA+>v`;8ZQ8I_}UT-9xB|;NStiVQIpbihu7{&ZBubo^BQ<%;^{1J{?T6Te zKOsA_t&NhG!kBsFzLUZfsmZ~-7vVy(bi~dZRCZRbwX$806l3sNTG>1Go8lv? z>-a_)4AoZhZFcGlYLtTjS8H6TpGXrN>O~B_$J1nwN8`kLq82?*zJxta*`F=&Kc1Yh z`Se^c82347)Sn#^hz>kvn$SwDzipL(hbdzmI$MjGx&MYprS1!kbJ-v;n_crLUATcq zwdulBXYak&y^I{uB=l*SvHn}-wY#ySA3};M@;Q#1e%}W27aw;!@El(iy6@Up9<0nO zYs!EVu{Oj|g2b+79fO^I_nHp@sZ2@j=DlmlPa@X?0m*Kpy{^<=qs#ZAsqin<^1`dK z0Q3*3ezgukH5D1Uq$#nhr z%@mn{*q>uHpO?3O`vUtUi^cG5_qM8l+mi=-RM{t_v3qJ;O76Bn4@{6b$LlQT%ap9> zmW(M`md^-8C452VR5pt>YgAwu&pB7=k40&} z_wPwB?eNo|4-Qvx^D1G5asbF~0x-hUC%bOWgjv^7b+ ze-ff-c&u6eN!OqbO8*(3X7b-7v;E&RmDly}sJj2@fvjIJ%ib$2Hrc!)@wa=Jo4|}K Ija*~?0WgjMy8r+H literal 0 HcmV?d00001 diff --git a/flake.nix b/flake.nix index cc624004a..5915e754f 100644 --- a/flake.nix +++ b/flake.nix @@ -6,15 +6,22 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachSystem flake-utils.lib.allSystems (system: + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachSystem flake-utils.lib.allSystems ( + system: let pkgs = import nixpkgs { inherit system; }; hashesFile = builtins.fromJSON (builtins.readFile ./hashes.json); lib = pkgs.lib; - in rec { + in + rec { packages.default = pkgs.buildNpmPackage { pname = "spacebar-server-ts"; name = "spacebar-server-ts"; @@ -41,27 +48,35 @@ npm prune --omit dev --no-save $npmInstallFlags "''${npmInstallFlagsArray[@]}" $npmFlags "''${npmFlagsArray[@]}" find node_modules -maxdepth 1 -type d -empty -delete - mkdir -p $out/node_modules/ - cp -r node_modules/* $out/node_modules/ - cp -r dist/ $out/node_modules/@spacebar + + #mkdir -p $out/node_modules/ + #cp -r node_modules/* $out/node_modules/ + #cp -r dist/ $out/node_modules/@spacebar + #for i in dist/**/start.js + #do + # makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/node_modules/@spacebar`dirname ''${i/dist/}`/start.js + #done + #set +x + #substituteInPlace package.json --replace 'dist/' 'node_modules/@spacebar/' + #find $out/node_modules/@spacebar/ -type f -name "*.js" | while read srcFile; do + # echo Patching imports in ''${srcFile/$out\/node_modules\/@spacebar//}... + # substituteInPlace $srcFile --replace 'require("./' 'require(__dirname + "/' + # substituteInPlace $srcFile --replace 'require("../' 'require(__dirname + "/../' + # substituteInPlace $srcFile --replace ', "assets"' ', "..", "assets"' + # #substituteInPlace $srcFile --replace 'require("@spacebar/' 'require(" + #done + #set -x + #cp -r assets/ $out/ + #cp package.json $out/ + #rm -v $out/assets/openapi.json + ##rm -v $out/assets/schemas.json + + mkdir -p $out + cp -r assets dist node_modules package.json $out/ for i in dist/**/start.js do - makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/node_modules/@spacebar`dirname ''${i/dist/}`/start.js + makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/$i done - set +x - substituteInPlace package.json --replace 'dist/' 'node_modules/@spacebar/' - find $out/node_modules/@spacebar/ -type f -name "*.js" | while read srcFile; do - echo Patching imports in ''${srcFile/$out\/node_modules\/@spacebar//}... - substituteInPlace $srcFile --replace 'require("./' 'require(__dirname + "/' - substituteInPlace $srcFile --replace 'require("../' 'require(__dirname + "/../' - substituteInPlace $srcFile --replace ', "assets"' ', "..", "assets"' - #substituteInPlace $srcFile --replace 'require("@spacebar/' 'require(" - done - set -x - cp -r assets/ $out/ - cp package.json $out/ - rm -v $out/assets/openapi.json - #rm -v $out/assets/schemas.json #debug utils: #cp $out/node_modules/@spacebar/ $out/build_output -r diff --git a/package-lock.json b/package-lock.json index cdfb564dc..fa335f156 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/client-s3": "^3.629.0", "@sentry/integrations": "^7.66.0", "@sentry/node": "^7.66.0", + "@types/ref": "*", "ajv": "^8.6.2", "ajv-formats": "2.1.1", "amqplib": "^0.10.3", @@ -83,14 +84,19 @@ "typescript": "^4.9.5" }, "optionalDependencies": { + "@types/ref": "^0.0.32", + "@types/ref-struct": "^0.0.33", "@yukikaze-bot/erlpack": "^1.0.1", "erlpack": "^0.1.4", + "ioctl": "^2.0.2", "jimp": "^0.22.12", "mysql": "^2.18.1", + "node-ioctl": "*", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport", "nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport", "pg": "^8.11.3", + "ref-struct": "^1.1.0", "sqlite3": "^5.1.6" } }, @@ -3167,6 +3173,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ref": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/ref/-/ref-0.0.32.tgz", + "integrity": "sha512-5q2nxslQF7GnoVzCYPsHD1B8pU020l6zy2rNw5Dzd01plmnRDj0b6thsXUOtbMjVEMAQ5uCgoajJP71f3FWiyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ref-struct": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/ref-struct/-/ref-struct-0.0.33.tgz", + "integrity": "sha512-xLed3yIeCQaxegdXG/ZzE8PychgrfjgyWpn5TnkOqeuiIWAhMUzQPssnV8+0q1xGvbMAjd5Atfb7TWyq2oC/4Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/ref": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -6509,6 +6535,18 @@ "license": "ISC", "optional": true }, + "node_modules/ioctl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ioctl/-/ioctl-2.0.2.tgz", + "integrity": "sha512-GPEiU99bJb3Z50JDRujQ9t+q4JaRvc1UrD7F5/kHDVWlRA3L4TMwqbMw/XIu/BzqccFP8OfsK+JIXmlAvJDs1g==", + "hasInstallScript": true, + "license": "ISC", + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.14.0" + } + }, "node_modules/ip": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", @@ -9042,6 +9080,64 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/ref": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ref/-/ref-1.3.5.tgz", + "integrity": "sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "1", + "debug": "2", + "nan": "2" + } + }, + "node_modules/ref-struct": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ref-struct/-/ref-struct-1.1.0.tgz", + "integrity": "sha512-h2OSdAUycdqiwFBp2wB3XEFheWA/U+n/JYnhEi3584JqnCCDXIA3qDIhHH3klIHMNZwrJW+VagpxPGeSf6777Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "2", + "ref": "1" + } + }, + "node_modules/ref-struct/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ref-struct/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, + "node_modules/ref/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ref/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, "node_modules/reflect-metadata": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", diff --git a/package.json b/package.json index 42e729f51..67fbe6c67 100644 --- a/package.json +++ b/package.json @@ -117,14 +117,19 @@ "@spacebar/util": "dist/util" }, "optionalDependencies": { + "@types/ref": "^0.0.32", + "@types/ref-struct": "^0.0.33", "@yukikaze-bot/erlpack": "^1.0.1", "erlpack": "^0.1.4", + "ioctl": "^2.0.2", "jimp": "^0.22.12", "mysql": "^2.18.1", + "node-ioctl": "*", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport", "nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport", "pg": "^8.11.3", + "ref-struct": "^1.1.0", "sqlite3": "^5.1.6" } } diff --git a/src/api/start.ts b/src/api/start.ts index 4a3f858eb..ff447f483 100644 --- a/src/api/start.ts +++ b/src/api/start.ts @@ -35,7 +35,7 @@ try { } if (cluster.isPrimary && process.env.NODE_ENV == "production") { - console.log(`Primary ${process.pid} is running`); + console.log(`Primary PID: ${process.pid}`); // Fork workers. for (let i = 0; i < cores; i++) { @@ -43,7 +43,7 @@ if (cluster.isPrimary && process.env.NODE_ENV == "production") { } cluster.on("exit", (worker) => { - console.log(`worker ${worker.process.pid} died, restart worker`); + console.log(`Worker ${worker.process.pid} died, restarting worker`); cluster.fork(); }); } else { diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts index d281120d1..df8e28ce4 100644 --- a/src/bundle/Server.ts +++ b/src/bundle/Server.ts @@ -58,7 +58,7 @@ async function main() { Sentry.errorHandler(app); - console.log(`[Server] ${green(`listening on port ${bold(port)}`)}`); + console.log(`[Server] ${green(`Listening on port ${bold(port)}`)}`); } main().catch(console.error); diff --git a/src/bundle/start.ts b/src/bundle/start.ts index fe177cbc7..33465abc3 100644 --- a/src/bundle/start.ts +++ b/src/bundle/start.ts @@ -18,6 +18,7 @@ // process.env.MONGOMS_DEBUG = "true"; import moduleAlias from "module-alias"; + moduleAlias(__dirname + "../../../package.json"); import "reflect-metadata"; @@ -26,8 +27,10 @@ import os from "os"; import { red, bold, yellow, cyan } from "picocolors"; import { initStats } from "./stats"; import { config } from "dotenv"; + config(); import { execSync } from "child_process"; +import { centerString, Logo } from "@spacebar/util"; const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1; @@ -41,23 +44,19 @@ function getCommitOrFail() { if (cluster.isPrimary) { const commit = getCommitOrFail(); - + Logo.printLogo(); console.log( bold(` -███████╗██████╗ █████╗ ██████╗███████╗██████╗ █████╗ ██████╗ -██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔══██╗ -███████╗██████╔╝███████║██║ █████╗ ██████╔╝███████║██████╔╝ -╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ ██╔══██╗██╔══██║██╔══██╗ -███████║██║ ██║ ██║╚██████╗███████╗██████╔╝██║ ██║██║ ██║ -╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ - - spacebar-server | ${yellow( - `Pre-release (${ - commit !== null - ? commit.slice(0, 7) - : "Unknown (Git cannot be found)" - })`, - )} +${centerString( + `spacebar-server | ${yellow( + `Pre-release (${ + commit !== null + ? commit.slice(0, 7) + : "Unknown (Git cannot be found)" + })`, + )}`, + 64, +)} Commit Hash: ${ commit !== null @@ -74,7 +73,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) initStats(); - console.log(`[Process] starting with ${cores} threads`); + console.log(`[Process] Starting with ${cores} threads`); if (cores === 1) { require("./Server"); @@ -87,7 +86,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) const delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000; setTimeout(() => { cluster.fork(); - console.log(`[Process] worker ${cyan(i)} started.`); + console.log(`[Process] Worker ${cyan(i)} started.`); }, delay); } @@ -102,7 +101,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) cluster.on("exit", (worker) => { console.log( `[Worker] ${red( - `died with PID: ${worker.process.pid} , restarting ...`, + `PID ${worker.process.pid} died, restarting ...`, )}`, ); cluster.fork(); diff --git a/src/bundle/stats.ts b/src/bundle/stats.ts index b690eb752..92e46027b 100644 --- a/src/bundle/stats.ts +++ b/src/bundle/stats.ts @@ -18,18 +18,44 @@ import os from "os"; import osu from "node-os-utils"; +import { readFileSync } from "node:fs"; import { red } from "picocolors"; export function initStats() { - console.log(`[Path] running in ${__dirname}`); + console.log(`[Path] Running in ${process.cwd()}`); + console.log(`[Path] Running from ${__dirname}`); try { - console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`); + console.log(`[CPU] ${osu.cpu.model()} (x${osu.cpu.count()})`); } catch { - console.log("[CPU] Failed to get cpu model!"); + console.log("[CPU] Failed to get CPU model!"); } - console.log(`[System] ${os.platform()} ${os.arch()}`); - console.log(`[Process] running with PID: ${process.pid}`); + console.log(`[System] ${os.platform()} ${os.release()} ${os.arch()}`); + if (os.platform() == "linux") { + try { + const osReleaseLines = readFileSync( + "/etc/os-release", + "utf8", + ).split("\n"); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const osRelease: any = {}; + for (const line of osReleaseLines) { + if (!line) continue; + const [key, value] = line.match(/(.*?)="?([^"]*)"?/)!.slice(1); + osRelease[key] = value; + } + console.log( + `[System]\x1b[${osRelease.ANSI_COLOR}m ${osRelease.NAME ?? "Unknown"} ${osRelease.VERSION ?? "Unknown"} (${osRelease.BUILD_ID ?? "No build ID"})\x1b[0m`, + ); + } catch (e) { + console.log( + "[System] Unknown Linux distribution (missing /etc/os-release)", + ); + console.log(e); + } + } + console.log(`[Process] Running with PID: ${process.pid}`); if (process.getuid && process.getuid() === 0) { console.warn( red( diff --git a/src/util/util/KittyLogo.ts b/src/util/util/KittyLogo.ts new file mode 100644 index 000000000..6e74007f4 --- /dev/null +++ b/src/util/util/KittyLogo.ts @@ -0,0 +1,141 @@ +import { readFileSync } from "node:fs"; +import fs from "fs"; + +var util = require("util"); + +// noinspection ES6ConvertRequireIntoImport +// const ioctl = require("ioctl"); +// import ref from "ref"; +// import ArrayType from "ref-array"; +// import StructType from "ref-struct"; +// import os from "os"; + +// const winsize = StructType({ +// ws_row : ref.types.ushort, +// ws_col : ref.types.ushort, +// ws_xpixel : ref.types.ushort, +// ws_ypixel : ref.types.ushort +// }); + +// const originalConsoleLog = console.log; +// console.error = +// console.log = +// console.debug = +// function (message: object, ...optionalParams: object[]) { +// KittyLogo.printWithIcon(message + " " + optionalParams.join(" ")); +// }; + +export class KittyLogo { + public static printLogo(): void { + const data = readFileSync(__dirname + "/../../../assets/logo.png", { + encoding: "base64", + }); + KittyLogo.writeImage({ + base64Data: data, + width: 70, + addNewline: true, + }); + } + + public static printWithIcon(text?: string): void { + if (text) { + const lines = text.split("\n"); + for (const line of lines) { + this.writeIcon(); + process.stdout.write(" " + line + "\n"); + } + } + } + + private static writeIcon(): void { + const data = readFileSync(__dirname + "/../../../assets/icon.png", { + encoding: "base64", + }); + KittyLogo.writeImage({ + base64Data: data, + width: 2, + addNewline: false, + }); + } + + private static checkSupport(cb: void): boolean { + process.stdin.setEncoding("utf8"); + process.stdin.setRawMode(true); + let resp = ""; + process.stdin.once("data", function (key) { + console.log(util.inspect(key)); + process.stdin.setRawMode(false); + process.stdin.pause(); + resp = key.toString(); + }); + process.stdout.write( + "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c", + ); + + while(resp == "") { + console.log("waiting"); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); + } + + return false; + } + + // private static sleep(ms: number): void { + // Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); + // } + + private static writeImage(request: KittyImageMetadata): void { + if (this.checkSupport()) return; + let pngData = request.base64Data; + + // Ga=T,q=2,o=z,s=1022,v=181,X=5; + const chunkSize = 1024; + + //#region Header + let header = `\x1b_G`; // enable graphics + header += "a=T"; // action = transmit & display + header += ",q=2"; // suppress response + header += ",f=100"; // image format = png + header += ",t=d"; // transmission = direct + header += ",x=0"; // current x position + header += ",y=0"; // current y position + if (request.width) header += `,c=${request.width}`; // width (columns) + if (request.height) header += `,r=${request.height}`; // height (rows) + if (request.widthPixels) header += `,w=${request.widthPixels}`; // width (pixels) + if (request.heightPixels) header += `,h=${request.heightPixels}`; // height (pixels) + //#endregion + + while (pngData.length > 0) { + const dataSize = Math.min(pngData.length, chunkSize); + + process.stdout.write( + header + `,m=${dataSize == chunkSize ? 1 : 0};`, + ); + process.stdout.write(pngData.slice(0, chunkSize)); + pngData = pngData.slice(chunkSize); + process.stdout.write("\x1b\\"); + } + + if (request.addNewline) process.stdout.write("\n"); + } +} + +export class KittyImageMetadata { + public base64Data: string; + public width?: number; + public height?: number; + public widthPixels?: number; + public heightPixels?: number; + public addNewline?: boolean; +} + +KittyLogo.printLogo(); + +// +// for (let i = 0; i < 10; i++) { +// KittyLogo.printLogo(); +// } +// for (let i = 0; i < 10; i++) { +// +// console.log(" ".repeat(i)+"meow"); +// } diff --git a/src/util/util/Logo.ts b/src/util/util/Logo.ts new file mode 100644 index 000000000..20a3253ad --- /dev/null +++ b/src/util/util/Logo.ts @@ -0,0 +1,55 @@ +import { execSync } from "child_process"; +import findOnPath from "./os-utils/OsUtils"; +// noinspection ES6ConvertRequireIntoImport +import terminfo from "./os-utils/TermInfo/TermInfo"; +import { KittyLogo } from "./KittyLogo"; + + +export class Logo { + public static printLogo() { + KittyLogo.printLogo(); + // const chafaPath = findOnPath("chafa"); + // console.log("Chafa path: " + chafaPath); + // const info = terminfo.parse({debug: true}); + // process.exit(0); + return; + // console.log(info); + // if (chafaPath) + // return execSync( + // "chafa Spacebar__Logo-Blue.png -s 70", + // { + // env: process.env, + // encoding: "utf-8", + // stdio: "inherit" + // } + // ); + // else console.log(Logo.logoVersions["1"] as string); + } + + private static getConsoleColors(): number { + return 1; + if (!process.env.TERM) return 1; + else { + switch (process.env.TERM) { + case "": + break; + + default: + break; + } + } + return 1; + } + private static logoVersions: any = { + "1": ` + ███████╗██████╗ █████╗ ██████╗███████╗██████╗ █████╗ ██████╗ + ██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔══██╗ + ███████╗██████╔╝███████║██║ █████╗ ██████╔╝███████║██████╔╝ + ╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ ██╔══██╗██╔══██║██╔══██╗ + ███████║██║ ██║ ██║╚██████╗███████╗██████╔╝██║ ██║██║ ██║ + ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝`, + "2": `` + }; +} + +Logo.printLogo(); \ No newline at end of file diff --git a/src/util/util/String.ts b/src/util/util/String.ts index 1000ebe9b..7b2dc1677 100644 --- a/src/util/util/String.ts +++ b/src/util/util/String.ts @@ -22,3 +22,9 @@ export function trimSpecial(str?: string): string { if (!str) return ""; return str.replace(SPECIAL_CHAR, "").trim(); } + +export function centerString(str: string, len: number): string { + const pad = len - str.length; + const padLeft = Math.floor(pad / 2) + str.length; + return str.padStart(padLeft).padEnd(len); +} \ No newline at end of file diff --git a/src/util/util/index.ts b/src/util/util/index.ts index 10e09b5c3..7bd0f631f 100644 --- a/src/util/util/index.ts +++ b/src/util/util/index.ts @@ -43,3 +43,4 @@ export * from "./TraverseDirectory"; export * from "./WebAuthn"; export * from "./Gifs"; export * from "./Application"; +export * from "./Logo"; diff --git a/src/util/util/os-utils/OsUtils.ts b/src/util/util/os-utils/OsUtils.ts new file mode 100644 index 000000000..babc99a1a --- /dev/null +++ b/src/util/util/os-utils/OsUtils.ts @@ -0,0 +1,21 @@ +import { existsSync, statSync } from "fs"; +import os from "os"; + +export default function findOnPath(binary: string): string | null { + const paths = + (os.platform() == "win32" + ? process.env.PATH?.split(";") + : process.env.PATH?.split(":")) || []; + + if (os.platform() == "win32") { + binary += ".exe"; + } + + for (const path of paths) { + if (existsSync(`${path}/${binary}`)) { + const stat = statSync(`${path}/${binary}`); + if (stat.isFile() && stat.mode & 0o111) return `${path}/${binary}`; + } + } + return null; +} diff --git a/src/util/util/os-utils/TermInfo/Constants.ts b/src/util/util/os-utils/TermInfo/Constants.ts new file mode 100644 index 000000000..34f6e4a66 --- /dev/null +++ b/src/util/util/os-utils/TermInfo/Constants.ts @@ -0,0 +1,964 @@ +"use strict"; + +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +/* + * the variable names and capnames are from the terminfo(5) man page + */ + +export const ALL_VARS: Record = { + // ============== + // == BOOLEANS == + // ============== + AUTO_LEFT_MARGIN: "bw", + AUTO_RIGHT_MARGIN: "am", + BACK_COLOR_ERASE: "bce", + CAN_CHANGE: "ccc", + CEOL_STANDOUT_GLITCH: "xhp", + COL_ADDR_GLITCH: "xhpa", + CPI_CHANGES_RES: "cpix", + CR_CANCELS_MICRO_MODE: "crxm", + DEST_TABS_MAGIC_SMSO: "xt", + EAT_NEWLINE_GLITCH: "xenl", + ERASE_OVERSTRIKE: "eo", + GENERIC_TYPE: "gn", + HARD_COPY: "hc", + HARD_CURSOR: "chts", + HAS_META_KEY: "km", + HAS_PRINT_WHEEL: "daisy", + HAS_STATUS_LINE: "hs", + HUE_LIGHTNESS_SATURATION: "hls", + INSERT_NULL_GLITCH: "in", + LPI_CHANGES_RES: "lpix", + MEMORY_ABOVE: "da", + MEMORY_BELOW: "db", + MOVE_INSERT_MODE: "mir", + MOVE_STANDOUT_MODE: "msgr", + NEEDS_XON_XOFF: "nxon", + NO_ESC_CTLC: "xsb", + NO_PAD_CHAR: "npc", + NON_DEST_SCROLL_REGION: "ndscr", + NON_REV_RMCUP: "nrrmc", + OVER_STRIKE: "os", + PRTR_SILENT: "mc5i", + ROW_ADDR_GLITCH: "xvpa", + SEMI_AUTO_RIGHT_MARGIN: "sam", + STATUS_LINE_ESC_OK: "eslok", + TILDE_GLITCH: "hz", + TRANSPARENT_UNDERLINE: "ul", + XON_XOFF: "xon", + // ============= + // == NUMBERS == + // ============= + COLUMNS: "cols", + INIT_TABS: "it", + LABEL_HEIGHT: "lh", + LABEL_WIDTH: "lw", + LINES: "lines", + LINES_OF_MEMORY: "lm", + MAGIC_COOKIE_GLITCH: "xmc", + MAX_ATTRIBUTES: "ma", + MAX_COLORS: "colors", + MAX_PAIRS: "pairs", + MAXIMUM_WINDOWS: "wnum", + NO_COLOR_VIDEO: "ncv", + NUM_LABELS: "nlab", + PADDING_BAUD_RATE: "pb", + VIRTUAL_TERMINAL: "vt", + WIDTH_STATUS_LINE: "wsl", + BIT_IMAGE_ENTWINING: "bitwin", + BIT_IMAGE_TYPE: "bitype", + BUFFER_CAPACITY: "bufsz", + BUTTONS: "btns", + DOT_HORZ_SPACING: "spinh", + DOT_VERT_SPACING: "spinv", + MAX_MICRO_ADDRESS: "maddr", + MAX_MICRO_JUMP: "mjump", + MICRO_COL_SIZE: "mcs", + MICRO_LINE_SIZE: "mls", + NUMBER_OF_PINS: "npins", + OUTPUT_RES_CHAR: "orc", + OUTPUT_RES_HORZ_INCH: "orhi", + OUTPUT_RES_LINE: "orl", + OUTPUT_RES_VERT_INCH: "orvi", + PRINT_RATE: "cps", + WIDE_CHAR_SIZE: "widcs", + // ============= + // == STRINGS == + // ============= + ACS_CHARS: "acsc", + BACK_TAB: "cbt", + BELL: "bel", + CARRIAGE_RETURN: "cr", + CHANGE_CHAR_PITCH: "cpi", + CHANGE_LINE_PITCH: "lpi", + CHANGE_RES_HORZ: "chr", + CHANGE_RES_VERT: "cvr", + CHANGE_SCROLL_REGION: "csr", + CHAR_PADDING: "rmp", + CLEAR_ALL_TABS: "tbc", + CLEAR_MARGINS: "mgc", + CLEAR_SCREEN: "clear", + CLR_BOL: "el1", + CLR_EOL: "el", + CLR_EOS: "ed", + COLUMN_ADDRESS: "hpa", + COMMAND_CHARACTER: "cmdch", + CREATE_WINDOW: "cwin", + CURSOR_ADDRESS: "cup", + CURSOR_DOWN: "cud1", + CURSOR_HOME: "home", + CURSOR_INVISIBLE: "civis", + CURSOR_LEFT: "cub1", + CURSOR_MEM_ADDRESS: "mrcup", + CURSOR_NORMAL: "cnorm", + CURSOR_RIGHT: "cuf1", + CURSOR_TO_LL: "ll", + CURSOR_UP: "cuu1", + CURSOR_VISIBLE: "cvvis", + DEFINE_CHAR: "defc", + DELETE_CHARACTER: "dch1", + DELETE_LINE: "dl1", + DIAL_PHONE: "dial", + DIS_STATUS_LINE: "dsl", + DISPLAY_CLOCK: "dclk", + DOWN_HALF_LINE: "hd", + ENA_ACS: "enacs", + ENTER_ALT_CHARSET_MODE: "smacs", + ENTER_AM_MODE: "smam", + ENTER_BLINK_MODE: "blink", + ENTER_BOLD_MODE: "bold", + ENTER_CA_MODE: "smcup", + ENTER_DELETE_MODE: "smdc", + ENTER_DIM_MODE: "dim", + ENTER_DOUBLEWIDE_MODE: "swidm", + ENTER_DRAFT_QUALITY: "sdrfq", + ENTER_INSERT_MODE: "smir", + ENTER_ITALICS_MODE: "sitm", + ENTER_LEFTWARD_MODE: "slm", + ENTER_MICRO_MODE: "smicm", + ENTER_NEAR_LETTER_QUALITY: "snlq", + ENTER_NORMAL_QUALITY: "snrmq", + ENTER_PROTECTED_MODE: "prot", + ENTER_REVERSE_MODE: "rev", + ENTER_SECURE_MODE: "invis", + ENTER_SHADOW_MODE: "sshm", + ENTER_STANDOUT_MODE: "smso", + ENTER_SUBSCRIPT_MODE: "ssubm", + ENTER_SUPERSCRIPT_MODE: "ssupm", + ENTER_UNDERLINE_MODE: "smul", + ENTER_UPWARD_MODE: "sum", + ENTER_XON_MODE: "smxon", + ERASE_CHARS: "ech", + EXIT_ALT_CHARSET_MODE: "rmacs", + EXIT_AM_MODE: "rmam", + EXIT_ATTRIBUTE_MODE: "sgr0", + EXIT_CA_MODE: "rmcup", + EXIT_DELETE_MODE: "rmdc", + EXIT_DOUBLEWIDE_MODE: "rwidm", + EXIT_INSERT_MODE: "rmir", + EXIT_ITALICS_MODE: "ritm", + EXIT_LEFTWARD_MODE: "rlm", + EXIT_MICRO_MODE: "rmicm", + EXIT_SHADOW_MODE: "rshm", + EXIT_STANDOUT_MODE: "rmso", + EXIT_SUBSCRIPT_MODE: "rsubm", + EXIT_SUPERSCRIPT_MODE: "rsupm", + EXIT_UNDERLINE_MODE: "rmul", + EXIT_UPWARD_MODE: "rum", + EXIT_XON_MODE: "rmxon", + FIXED_PAUSE: "pause", + FLASH_HOOK: "hook", + FLASH_SCREEN: "flash", + FORM_FEED: "ff", + FROM_STATUS_LINE: "fsl", + GOTO_WINDOW: "wingo", + HANGUP: "hup", + INIT_1STRING: "is1", + INIT_2STRING: "is2", + INIT_3STRING: "is3", + INIT_FILE: "if", + INIT_PROG: "iprog", + INITIALIZE_COLOR: "initc", + INITIALIZE_PAIR: "initp", + INSERT_CHARACTER: "ich1", + INSERT_LINE: "il1", + INSERT_PADDING: "ip", + KEY_A1: "ka1", + KEY_A3: "ka3", + KEY_B2: "kb2", + KEY_BACKSPACE: "kbs", + KEY_BEG: "kbeg", + KEY_BTAB: "kcbt", + KEY_C1: "kc1", + KEY_C3: "kc3", + KEY_CANCEL: "kcan", + KEY_CATAB: "ktbc", + KEY_CLEAR: "kclr", + KEY_CLOSE: "kclo", + KEY_COMMAND: "kcmd", + KEY_COPY: "kcpy", + KEY_CREATE: "kcrt", + KEY_CTAB: "kctab", + KEY_DC: "kdch1", + KEY_DL: "kdl1", + KEY_DOWN: "kcud1", + KEY_EIC: "krmir", + KEY_END: "kend", + KEY_ENTER: "kent", + KEY_EOL: "kel", + KEY_EOS: "ked", + KEY_EXIT: "kext", + KEY_F0: "kf0", + KEY_F1: "kf1", + KEY_F10: "kf10", + KEY_F11: "kf11", + KEY_F12: "kf12", + KEY_F13: "kf13", + KEY_F14: "kf14", + KEY_F15: "kf15", + KEY_F16: "kf16", + KEY_F17: "kf17", + KEY_F18: "kf18", + KEY_F19: "kf19", + KEY_F2: "kf2", + KEY_F20: "kf20", + KEY_F21: "kf21", + KEY_F22: "kf22", + KEY_F23: "kf23", + KEY_F24: "kf24", + KEY_F25: "kf25", + KEY_F26: "kf26", + KEY_F27: "kf27", + KEY_F28: "kf28", + KEY_F29: "kf29", + KEY_F3: "kf3", + KEY_F30: "kf30", + KEY_F31: "kf31", + KEY_F32: "kf32", + KEY_F33: "kf33", + KEY_F34: "kf34", + KEY_F35: "kf35", + KEY_F36: "kf36", + KEY_F37: "kf37", + KEY_F38: "kf38", + KEY_F39: "kf39", + KEY_F4: "kf4", + KEY_F40: "kf40", + KEY_F41: "kf41", + KEY_F42: "kf42", + KEY_F43: "kf43", + KEY_F44: "kf44", + KEY_F45: "kf45", + KEY_F46: "kf46", + KEY_F47: "kf47", + KEY_F48: "kf48", + KEY_F49: "kf49", + KEY_F5: "kf5", + KEY_F50: "kf50", + KEY_F51: "kf51", + KEY_F52: "kf52", + KEY_F53: "kf53", + KEY_F54: "kf54", + KEY_F55: "kf55", + KEY_F56: "kf56", + KEY_F57: "kf57", + KEY_F58: "kf58", + KEY_F59: "kf59", + KEY_F6: "kf6", + KEY_F60: "kf60", + KEY_F61: "kf61", + KEY_F62: "kf62", + KEY_F63: "kf63", + KEY_F7: "kf7", + KEY_F8: "kf8", + KEY_F9: "kf9", + KEY_FIND: "kfnd", + KEY_HELP: "khlp", + KEY_HOME: "khome", + KEY_IC: "kich1", + KEY_IL: "kil1", + KEY_LEFT: "kcub1", + KEY_LL: "kll", + KEY_MARK: "kmrk", + KEY_MESSAGE: "kmsg", + KEY_MOVE: "kmov", + KEY_NEXT: "knxt", + KEY_NPAGE: "knp", + KEY_OPEN: "kopn", + KEY_OPTIONS: "kopt", + KEY_PPAGE: "kpp", + KEY_PREVIOUS: "kprv", + KEY_PRINT: "kprt", + KEY_REDO: "krdo", + KEY_REFERENCE: "kref", + KEY_REFRESH: "krfr", + KEY_REPLACE: "krpl", + KEY_RESTART: "krst", + KEY_RESUME: "kres", + KEY_RIGHT: "kcuf1", + KEY_SAVE: "ksav", + KEY_SBEG: "kBEG", + KEY_SCANCEL: "kCAN", + KEY_SCOMMAND: "kCMD", + KEY_SCOPY: "kCPY", + KEY_SCREATE: "kCRT", + KEY_SDC: "kDC", + KEY_SDL: "kDL", + KEY_SELECT: "kslt", + KEY_SEND: "kEND", + KEY_SEOL: "kEOL", + KEY_SEXIT: "kEXT", + KEY_SF: "kind", + KEY_SFIND: "kFND", + KEY_SHELP: "kHLP", + KEY_SHOME: "kHOM", + KEY_SIC: "kIC", + KEY_SLEFT: "kLFT", + KEY_SMESSAGE: "kMSG", + KEY_SMOVE: "kMOV", + KEY_SNEXT: "kNXT", + KEY_SOPTIONS: "kOPT", + KEY_SPREVIOUS: "kPRV", + KEY_SPRINT: "kPRT", + KEY_SR: "kri", + KEY_SREDO: "kRDO", + KEY_SREPLACE: "kRPL", + KEY_SRIGHT: "kRIT", + KEY_SRSUME: "kRES", + KEY_SSAVE: "kSAV", + KEY_SSUSPEND: "kSPD", + KEY_STAB: "khts", + KEY_SUNDO: "kUND", + KEY_SUSPEND: "kspd", + KEY_UNDO: "kund", + KEY_UP: "kcuu1", + KEYPAD_LOCAL: "rmkx", + KEYPAD_XMIT: "smkx", + LAB_F0: "lf0", + LAB_F1: "lf1", + LAB_F10: "lf10", + LAB_F2: "lf2", + LAB_F3: "lf3", + LAB_F4: "lf4", + LAB_F5: "lf5", + LAB_F6: "lf6", + LAB_F7: "lf7", + LAB_F8: "lf8", + LAB_F9: "lf9", + LABEL_FORMAT: "fln", + LABEL_OFF: "rmln", + LABEL_ON: "smln", + META_OFF: "rmm", + META_ON: "smm", + MICRO_COLUMN_ADDRESS: "mhpa", + MICRO_DOWN: "mcud1", + MICRO_LEFT: "mcub1", + MICRO_RIGHT: "mcuf1", + MICRO_ROW_ADDRESS: "mvpa", + MICRO_UP: "mcuu1", + NEWLINE: "nel", + ORDER_OF_PINS: "porder", + ORIG_COLORS: "oc", + ORIG_PAIR: "op", + PAD_CHAR: "pad", + PARM_DCH: "dch", + PARM_DELETE_LINE: "dl", + PARM_DOWN_CURSOR: "cud", + PARM_DOWN_MICRO: "mcud", + PARM_ICH: "ich", + PARM_INDEX: "indn", + PARM_INSERT_LINE: "il", + PARM_LEFT_CURSOR: "cub", + PARM_LEFT_MICRO: "mcub", + PARM_RIGHT_CURSOR: "cuf", + PARM_RIGHT_MICRO: "mcuf", + PARM_RINDEX: "rin", + PARM_UP_CURSOR: "cuu", + PARM_UP_MICRO: "mcuu", + PKEY_KEY: "pfkey", + PKEY_LOCAL: "pfloc", + PKEY_XMIT: "pfx", + PLAB_NORM: "pln", + PRINT_SCREEN: "mc0", + PRTR_NON: "mc5p", + PRTR_OFF: "mc4", + PRTR_ON: "mc5", + PULSE: "pulse", + QUICK_DIAL: "qdial", + REMOVE_CLOCK: "rmclk", + REPEAT_CHAR: "rep", + REQ_FOR_INPUT: "rfi", + RESET_1STRING: "rs1", + RESET_2STRING: "rs2", + RESET_3STRING: "rs3", + RESET_FILE: "rf", + RESTORE_CURSOR: "rc", + ROW_ADDRESS: "vpa", + SAVE_CURSOR: "sc", + SCROLL_FORWARD: "ind", + SCROLL_REVERSE: "ri", + SELECT_CHAR_SET: "scs", + SET_ATTRIBUTES: "sgr", + SET_BACKGROUND: "setb", + SET_BOTTOM_MARGIN: "smgb", + SET_BOTTOM_MARGIN_PARM: "smgbp", + SET_CLOCK: "sclk", + SET_COLOR_PAIR: "scp", + SET_FOREGROUND: "setf", + SET_LEFT_MARGIN: "smgl", + SET_LEFT_MARGIN_PARM: "smglp", + SET_RIGHT_MARGIN: "smgr", + SET_RIGHT_MARGIN_PARM: "smgrp", + SET_TAB: "hts", + SET_TOP_MARGIN: "smgt", + SET_TOP_MARGIN_PARM: "smgtp", + SET_WINDOW: "wind", + START_BIT_IMAGE: "sbim", + START_CHAR_SET_DEF: "scsd", + STOP_BIT_IMAGE: "rbim", + STOP_CHAR_SET_DEF: "rcsd", + SUBSCRIPT_CHARACTERS: "subcs", + SUPERSCRIPT_CHARACTERS: "supcs", + TAB: "ht", + THESE_CAUSE_CR: "docr", + TO_STATUS_LINE: "tsl", + TONE: "tone", + UNDERLINE_CHAR: "uc", + UP_HALF_LINE: "hu", + USER0: "u0", + USER1: "u1", + USER2: "u2", + USER3: "u3", + USER4: "u4", + USER5: "u5", + USER6: "u6", + USER7: "u7", + USER8: "u8", + USER9: "u9", + WAIT_TONE: "wait", + XOFF_CHARACTER: "xoffc", + XON_CHARACTER: "xonc", + ZERO_MOTION: "zerom", + ALT_SCANCODE_ESC: "scesa", + BIT_IMAGE_CARRIAGE_RETURN: "bicr", + BIT_IMAGE_NEWLINE: "binel", + BIT_IMAGE_REPEAT: "birep", + CHAR_SET_NAMES: "csnm", + CODE_SET_INIT: "csin", + COLOR_NAMES: "colornm", + DEFINE_BIT_IMAGE_REGION: "defbi", + DEVICE_TYPE: "devt", + DISPLAY_PC_CHAR: "dispc", + END_BIT_IMAGE_REGION: "endbi", + ENTER_PC_CHARSET_MODE: "smpch", + ENTER_SCANCODE_MODE: "smsc", + EXIT_PC_CHARSET_MODE: "rmpch", + EXIT_SCANCODE_MODE: "rmsc", + GET_MOUSE: "getm", + KEY_MOUSE: "kmous", + MOUSE_INFO: "minfo", + PC_TERM_OPTIONS: "pctrm", + PKEY_PLAB: "pfxl", + REQ_MOUSE_POS: "reqmp", + SCANCODE_ESCAPE: "scesc", + SET0_DES_SEQ: "s0ds", + SET1_DES_SEQ: "s1ds", + SET2_DES_SEQ: "s2ds", + SET3_DES_SEQ: "s3ds", + SET_A_BACKGROUND: "setab", + SET_A_FOREGROUND: "setaf", + SET_COLOR_BAND: "setcolor", + SET_LR_MARGIN: "smglr", + SET_PAGE_LENGTH: "slines", + SET_TB_MARGIN: "smgtb", + ENTER_HORIZONTAL_HL_MODE: "ehhlm", + ENTER_LEFT_HL_MODE: "elhlm", + ENTER_LOW_HL_MODE: "elohlm", + ENTER_RIGHT_HL_MODE: "erhlm", + ENTER_TOP_HL_MODE: "ethlm", + ENTER_VERTICAL_HL_MODE: "evhlm", + SET_A_ATTRIBUTES: "sgr1", + SET_PGLEN_INCH: "slength", +}; + +/* + * the order of the variables are from the header file + */ + +export const VARORDER: Record<"booleans" | "numbers" | "strings", string[]> = { + booleans: [ + "AUTO_LEFT_MARGIN", + "AUTO_RIGHT_MARGIN", + "NO_ESC_CTLC", + "CEOL_STANDOUT_GLITCH", + "EAT_NEWLINE_GLITCH", + "ERASE_OVERSTRIKE", + "GENERIC_TYPE", + "HARD_COPY", + "HAS_META_KEY", + "HAS_STATUS_LINE", + "INSERT_NULL_GLITCH", + "MEMORY_ABOVE", + "MEMORY_BELOW", + "MOVE_INSERT_MODE", + "MOVE_STANDOUT_MODE", + "OVER_STRIKE", + "STATUS_LINE_ESC_OK", + "DEST_TABS_MAGIC_SMSO", + "TILDE_GLITCH", + "TRANSPARENT_UNDERLINE", + "XON_XOFF", + "NEEDS_XON_XOFF", + "PRTR_SILENT", + "HARD_CURSOR", + "NON_REV_RMCUP", + "NO_PAD_CHAR", + "NON_DEST_SCROLL_REGION", + "CAN_CHANGE", + "BACK_COLOR_ERASE", + "HUE_LIGHTNESS_SATURATION", + "COL_ADDR_GLITCH", + "CR_CANCELS_MICRO_MODE", + "HAS_PRINT_WHEEL", + "ROW_ADDR_GLITCH", + "SEMI_AUTO_RIGHT_MARGIN", + "CPI_CHANGES_RES", + "LPI_CHANGES_RES", + ], + numbers: [ + "COLUMNS", + "INIT_TABS", + "LINES", + "LINES_OF_MEMORY", + "MAGIC_COOKIE_GLITCH", + "PADDING_BAUD_RATE", + "VIRTUAL_TERMINAL", + "WIDTH_STATUS_LINE", + "NUM_LABELS", + "LABEL_HEIGHT", + "LABEL_WIDTH", + "MAX_ATTRIBUTES", + "MAXIMUM_WINDOWS", + "MAX_COLORS", + "MAX_PAIRS", + "NO_COLOR_VIDEO", + "BUFFER_CAPACITY", + "DOT_VERT_SPACING", + "DOT_HORZ_SPACING", + "MAX_MICRO_ADDRESS", + "MAX_MICRO_JUMP", + "MICRO_COL_SIZE", + "MICRO_LINE_SIZE", + "NUMBER_OF_PINS", + "OUTPUT_RES_CHAR", + "OUTPUT_RES_LINE", + "OUTPUT_RES_HORZ_INCH", + "OUTPUT_RES_VERT_INCH", + "PRINT_RATE", + "WIDE_CHAR_SIZE", + "BUTTONS", + "BIT_IMAGE_ENTWINING", + "BIT_IMAGE_TYPE", + ], + strings: [ + "BACK_TAB", + "BELL", + "CARRIAGE_RETURN", + "CHANGE_SCROLL_REGION", + "CLEAR_ALL_TABS", + "CLEAR_SCREEN", + "CLR_EOL", + "CLR_EOS", + "COLUMN_ADDRESS", + "COMMAND_CHARACTER", + "CURSOR_ADDRESS", + "CURSOR_DOWN", + "CURSOR_HOME", + "CURSOR_INVISIBLE", + "CURSOR_LEFT", + "CURSOR_MEM_ADDRESS", + "CURSOR_NORMAL", + "CURSOR_RIGHT", + "CURSOR_TO_LL", + "CURSOR_UP", + "CURSOR_VISIBLE", + "DELETE_CHARACTER", + "DELETE_LINE", + "DIS_STATUS_LINE", + "DOWN_HALF_LINE", + "ENTER_ALT_CHARSET_MODE", + "ENTER_BLINK_MODE", + "ENTER_BOLD_MODE", + "ENTER_CA_MODE", + "ENTER_DELETE_MODE", + "ENTER_DIM_MODE", + "ENTER_INSERT_MODE", + "ENTER_SECURE_MODE", + "ENTER_PROTECTED_MODE", + "ENTER_REVERSE_MODE", + "ENTER_STANDOUT_MODE", + "ENTER_UNDERLINE_MODE", + "ERASE_CHARS", + "EXIT_ALT_CHARSET_MODE", + "EXIT_ATTRIBUTE_MODE", + "EXIT_CA_MODE", + "EXIT_DELETE_MODE", + "EXIT_INSERT_MODE", + "EXIT_STANDOUT_MODE", + "EXIT_UNDERLINE_MODE", + "FLASH_SCREEN", + "FORM_FEED", + "FROM_STATUS_LINE", + "INIT_1STRING", + "INIT_2STRING", + "INIT_3STRING", + "INIT_FILE", + "INSERT_CHARACTER", + "INSERT_LINE", + "INSERT_PADDING", + "KEY_BACKSPACE", + "KEY_CATAB", + "KEY_CLEAR", + "KEY_CTAB", + "KEY_DC", + "KEY_DL", + "KEY_DOWN", + "KEY_EIC", + "KEY_EOL", + "KEY_EOS", + "KEY_F0", + "KEY_F1", + "KEY_F10", + "KEY_F2", + "KEY_F3", + "KEY_F4", + "KEY_F5", + "KEY_F6", + "KEY_F7", + "KEY_F8", + "KEY_F9", + "KEY_HOME", + "KEY_IC", + "KEY_IL", + "KEY_LEFT", + "KEY_LL", + "KEY_NPAGE", + "KEY_PPAGE", + "KEY_RIGHT", + "KEY_SF", + "KEY_SR", + "KEY_STAB", + "KEY_UP", + "KEYPAD_LOCAL", + "KEYPAD_XMIT", + "LAB_F0", + "LAB_F1", + "LAB_F10", + "LAB_F2", + "LAB_F3", + "LAB_F4", + "LAB_F5", + "LAB_F6", + "LAB_F7", + "LAB_F8", + "LAB_F9", + "META_OFF", + "META_ON", + "NEWLINE", + "PAD_CHAR", + "PARM_DCH", + "PARM_DELETE_LINE", + "PARM_DOWN_CURSOR", + "PARM_ICH", + "PARM_INDEX", + "PARM_INSERT_LINE", + "PARM_LEFT_CURSOR", + "PARM_RIGHT_CURSOR", + "PARM_RINDEX", + "PARM_UP_CURSOR", + "PKEY_KEY", + "PKEY_LOCAL", + "PKEY_XMIT", + "PRINT_SCREEN", + "PRTR_OFF", + "PRTR_ON", + "REPEAT_CHAR", + "RESET_1STRING", + "RESET_2STRING", + "RESET_3STRING", + "RESET_FILE", + "RESTORE_CURSOR", + "ROW_ADDRESS", + "SAVE_CURSOR", + "SCROLL_FORWARD", + "SCROLL_REVERSE", + "SET_ATTRIBUTES", + "SET_TAB", + "SET_WINDOW", + "TAB", + "TO_STATUS_LINE", + "UNDERLINE_CHAR", + "UP_HALF_LINE", + "INIT_PROG", + "KEY_A1", + "KEY_A3", + "KEY_B2", + "KEY_C1", + "KEY_C3", + "PRTR_NON", + "CHAR_PADDING", + "ACS_CHARS", + "PLAB_NORM", + "KEY_BTAB", + "ENTER_XON_MODE", + "EXIT_XON_MODE", + "ENTER_AM_MODE", + "EXIT_AM_MODE", + "XON_CHARACTER", + "XOFF_CHARACTER", + "ENA_ACS", + "LABEL_ON", + "LABEL_OFF", + "KEY_BEG", + "KEY_CANCEL", + "KEY_CLOSE", + "KEY_COMMAND", + "KEY_COPY", + "KEY_CREATE", + "KEY_END", + "KEY_ENTER", + "KEY_EXIT", + "KEY_FIND", + "KEY_HELP", + "KEY_MARK", + "KEY_MESSAGE", + "KEY_MOVE", + "KEY_NEXT", + "KEY_OPEN", + "KEY_OPTIONS", + "KEY_PREVIOUS", + "KEY_PRINT", + "KEY_REDO", + "KEY_REFERENCE", + "KEY_REFRESH", + "KEY_REPLACE", + "KEY_RESTART", + "KEY_RESUME", + "KEY_SAVE", + "KEY_SUSPEND", + "KEY_UNDO", + "KEY_SBEG", + "KEY_SCANCEL", + "KEY_SCOMMAND", + "KEY_SCOPY", + "KEY_SCREATE", + "KEY_SDC", + "KEY_SDL", + "KEY_SELECT", + "KEY_SEND", + "KEY_SEOL", + "KEY_SEXIT", + "KEY_SFIND", + "KEY_SHELP", + "KEY_SHOME", + "KEY_SIC", + "KEY_SLEFT", + "KEY_SMESSAGE", + "KEY_SMOVE", + "KEY_SNEXT", + "KEY_SOPTIONS", + "KEY_SPREVIOUS", + "KEY_SPRINT", + "KEY_SREDO", + "KEY_SREPLACE", + "KEY_SRIGHT", + "KEY_SRSUME", + "KEY_SSAVE", + "KEY_SSUSPEND", + "KEY_SUNDO", + "REQ_FOR_INPUT", + "KEY_F11", + "KEY_F12", + "KEY_F13", + "KEY_F14", + "KEY_F15", + "KEY_F16", + "KEY_F17", + "KEY_F18", + "KEY_F19", + "KEY_F20", + "KEY_F21", + "KEY_F22", + "KEY_F23", + "KEY_F24", + "KEY_F25", + "KEY_F26", + "KEY_F27", + "KEY_F28", + "KEY_F29", + "KEY_F30", + "KEY_F31", + "KEY_F32", + "KEY_F33", + "KEY_F34", + "KEY_F35", + "KEY_F36", + "KEY_F37", + "KEY_F38", + "KEY_F39", + "KEY_F40", + "KEY_F41", + "KEY_F42", + "KEY_F43", + "KEY_F44", + "KEY_F45", + "KEY_F46", + "KEY_F47", + "KEY_F48", + "KEY_F49", + "KEY_F50", + "KEY_F51", + "KEY_F52", + "KEY_F53", + "KEY_F54", + "KEY_F55", + "KEY_F56", + "KEY_F57", + "KEY_F58", + "KEY_F59", + "KEY_F60", + "KEY_F61", + "KEY_F62", + "KEY_F63", + "CLR_BOL", + "CLEAR_MARGINS", + "SET_LEFT_MARGIN", + "SET_RIGHT_MARGIN", + "LABEL_FORMAT", + "SET_CLOCK", + "DISPLAY_CLOCK", + "REMOVE_CLOCK", + "CREATE_WINDOW", + "GOTO_WINDOW", + "HANGUP", + "DIAL_PHONE", + "QUICK_DIAL", + "TONE", + "PULSE", + "FLASH_HOOK", + "FIXED_PAUSE", + "WAIT_TONE", + "USER0", + "USER1", + "USER2", + "USER3", + "USER4", + "USER5", + "USER6", + "USER7", + "USER8", + "USER9", + "ORIG_PAIR", + "ORIG_COLORS", + "INITIALIZE_COLOR", + "INITIALIZE_PAIR", + "SET_COLOR_PAIR", + "SET_FOREGROUND", + "SET_BACKGROUND", + "CHANGE_CHAR_PITCH", + "CHANGE_LINE_PITCH", + "CHANGE_RES_HORZ", + "CHANGE_RES_VERT", + "DEFINE_CHAR", + "ENTER_DOUBLEWIDE_MODE", + "ENTER_DRAFT_QUALITY", + "ENTER_ITALICS_MODE", + "ENTER_LEFTWARD_MODE", + "ENTER_MICRO_MODE", + "ENTER_NEAR_LETTER_QUALITY", + "ENTER_NORMAL_QUALITY", + "ENTER_SHADOW_MODE", + "ENTER_SUBSCRIPT_MODE", + "ENTER_SUPERSCRIPT_MODE", + "ENTER_UPWARD_MODE", + "EXIT_DOUBLEWIDE_MODE", + "EXIT_ITALICS_MODE", + "EXIT_LEFTWARD_MODE", + "EXIT_MICRO_MODE", + "EXIT_SHADOW_MODE", + "EXIT_SUBSCRIPT_MODE", + "EXIT_SUPERSCRIPT_MODE", + "EXIT_UPWARD_MODE", + "MICRO_COLUMN_ADDRESS", + "MICRO_DOWN", + "MICRO_LEFT", + "MICRO_RIGHT", + "MICRO_ROW_ADDRESS", + "MICRO_UP", + "ORDER_OF_PINS", + "PARM_DOWN_MICRO", + "PARM_LEFT_MICRO", + "PARM_RIGHT_MICRO", + "PARM_UP_MICRO", + "SELECT_CHAR_SET", + "SET_BOTTOM_MARGIN", + "SET_BOTTOM_MARGIN_PARM", + "SET_LEFT_MARGIN_PARM", + "SET_RIGHT_MARGIN_PARM", + "SET_TOP_MARGIN", + "SET_TOP_MARGIN_PARM", + "START_BIT_IMAGE", + "START_CHAR_SET_DEF", + "STOP_BIT_IMAGE", + "STOP_CHAR_SET_DEF", + "SUBSCRIPT_CHARACTERS", + "SUPERSCRIPT_CHARACTERS", + "THESE_CAUSE_CR", + "ZERO_MOTION", + "CHAR_SET_NAMES", + "KEY_MOUSE", + "MOUSE_INFO", + "REQ_MOUSE_POS", + "GET_MOUSE", + "SET_A_FOREGROUND", + "SET_A_BACKGROUND", + "PKEY_PLAB", + "DEVICE_TYPE", + "CODE_SET_INIT", + "SET0_DES_SEQ", + "SET1_DES_SEQ", + "SET2_DES_SEQ", + "SET3_DES_SEQ", + "SET_LR_MARGIN", + "SET_TB_MARGIN", + "BIT_IMAGE_REPEAT", + "BIT_IMAGE_NEWLINE", + "BIT_IMAGE_CARRIAGE_RETURN", + "COLOR_NAMES", + "DEFINE_BIT_IMAGE_REGION", + "END_BIT_IMAGE_REGION", + "SET_COLOR_BAND", + "SET_PAGE_LENGTH", + "DISPLAY_PC_CHAR", + "ENTER_PC_CHARSET_MODE", + "EXIT_PC_CHARSET_MODE", + "ENTER_SCANCODE_MODE", + "EXIT_SCANCODE_MODE", + "PC_TERM_OPTIONS", + "SCANCODE_ESCAPE", + "ALT_SCANCODE_ESC", + "ENTER_HORIZONTAL_HL_MODE", + "ENTER_LEFT_HL_MODE", + "ENTER_LOW_HL_MODE", + "ENTER_RIGHT_HL_MODE", + "ENTER_TOP_HL_MODE", + "ENTER_VERTICAL_HL_MODE", + "SET_A_ATTRIBUTES", + "SET_PGLEN_INCH", + ], +}; diff --git a/src/util/util/os-utils/TermInfo/TermInfo.ts b/src/util/util/os-utils/TermInfo/TermInfo.ts new file mode 100644 index 000000000..bc669960d --- /dev/null +++ b/src/util/util/os-utils/TermInfo/TermInfo.ts @@ -0,0 +1,50 @@ +// SOURCE: https://github.com/eistaa/parse-terminfo +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +import { openTerminfoBuffer } from "./openTerminfoBuffer"; +import { parseTerminfo, TermInfo } from "./parseTerminfo"; + +export class ParseOptions { + term?: string; + directories?: string[]; + debug: boolean = false; +} + +export function parse(opts?: ParseOptions): TermInfo { + let term; + + if (process.platform === "win32") + throw new Error("no terminfo for windows..."); + + // get term + if (opts?.term) { + term = String(opts.term); + } else { + if (process.env.TERM && process.env.TERM !== "") { + term = process.env.TERM; + } else { + throw new Error( + "No terminal specified (`opts.term`) and TERM is undefined", + ); + } + } + + if(opts?.debug) console.log("Parsing terminfo for", term); + + const bufferData = openTerminfoBuffer(term, opts); + const capabilities = parseTerminfo(bufferData.buffer, term, opts); + capabilities.path = bufferData.path; + + if(opts?.debug) console.log("Parsed terminfo for", term, ":", capabilities); + + return capabilities; +} + +export default { + VARIABLES: require("./Constants").ALL_VARS, + parse +}; diff --git a/src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts b/src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts new file mode 100644 index 000000000..6e71510cb --- /dev/null +++ b/src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts @@ -0,0 +1,104 @@ +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +import fs from "fs"; +import path from "path"; +import { ParseOptions } from "./TermInfo"; + +const DEFAULT_DB_DIRECTORIES = [ + "/etc/terminfo", + "/lib/terminfo", + "/usr/share/terminfo", +]; + +function isDirectory(directory: string) { + try { + return fs.statSync(path.normalize(directory.trim())).isDirectory(); + } catch (err) { + return false; + } +} + +function constructDBDirectories(dirs?: string[] | string) { + /* + * the ordering comes from manpage 'terminfo(5)' + */ + + const directories: string[] = []; + + // argument can be array or string + if (dirs) { + if (Array.isArray(dirs)) { + dirs.filter(isDirectory).forEach((dir) => directories.push(dir)); + } else { + if (isDirectory(dirs)) directories.push(dirs); + } + } + + // TERMINFO may exist + if (process.env.TERMINFO && isDirectory(process.env.TERMINFO)) + directories.push(process.env.TERMINFO); + + // there may be a local terminfo directory + if ( + process.env.HOME && + isDirectory(path.normalize(path.join(process.env.HOME, ".terminfo"))) + ) + directories.push(path.join(process.env.HOME, ".terminfo")); + + // TERMINFO_DIRS can contain a :-separated list of directories + if (process.env.TERMINFO_DIRS) { + const terminfoDirectories = process.env.TERMINFO_DIRS.split(":"); + terminfoDirectories + .filter(isDirectory) + .forEach((dir) => directories.push(dir)); + } + + // default to hardcoded directories + DEFAULT_DB_DIRECTORIES.filter(isDirectory).forEach((dir) => + directories.push(dir), + ); + + return directories; +} + +export function openTerminfoBuffer( + term: string, + opts: ParseOptions | undefined, +) { + // determine directories + const directories = constructDBDirectories(opts?.directories); + + if (opts?.debug) console.log("Directories:", directories); + + let filepath; + + if (directories.length === 0) + throw new Error("No terminfo database directories exist"); + + // use first valid directory + for (let i = 0; i < directories.length; i++) { + try { + filepath = path.join(directories[i], term.charAt(0), term); + if (fs.statSync(filepath).isFile()) { + if (opts?.debug) + console.log("Found terminfo data at", filepath); + break; + } + } catch (err) { + filepath = undefined; + } + } + + if (filepath === undefined) + throw new Error("Found no terminfo database for " + term); + + // read to buffer + return { + path: filepath, + buffer: fs.readFileSync(filepath), + }; +} diff --git a/src/util/util/os-utils/TermInfo/parseTerminfo.ts b/src/util/util/os-utils/TermInfo/parseTerminfo.ts new file mode 100644 index 000000000..71a4b933b --- /dev/null +++ b/src/util/util/os-utils/TermInfo/parseTerminfo.ts @@ -0,0 +1,159 @@ +"use strict"; + +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +import { ALL_VARS, VARORDER } from "./Constants"; + +/* + * based of on the format description in the term(5) manual page. + */ + +export class TermInfo { + description?: string; + term?: string[]; + capabilities: { + booleans: Record; + numbers: Record; + strings: Record; + }; + path: string; + integerSize: number; +} + +export function parseTerminfo( + buffer: Buffer, + term: string, + opts?: { debug: boolean }, +): TermInfo { + let offset = 0; + + function readInt() { + const result = buffer.readInt16LE(offset); + console.log("Read int @", offset, ":", result); + offset += 2; + return result; + } + + /// @type {{ description: string | undefined; term: string[] | undefined; capabilities: { booleans: {}; numbers: {}; strings: {}; }; }} + const result: TermInfo = { + capabilities: { + booleans: {}, + numbers: {}, + strings: {}, + }, + path: "", + description: undefined, + term: undefined, + integerSize: 0, + }; + + // check the magic number + const magic = readInt(); + + // if (magic !== 0x011a) + // throw new Error("invalid magic number in buffer for " + term + ": " + magic.toString(16)); + switch (magic) { + case 0x011a: + result.integerSize = 16; + break; + case 0x21e: + result.integerSize = 32; + break; + default: + throw new Error( + "invalid magic number in buffer for " + + term + + ": " + + magic.toString(16), + ); + } + + if (opts?.debug) console.log("Magic number:", magic, "Integer size:", result.integerSize); + + //offset += 2; + + // parse section sizes + const sizes = { + names: readInt(), + booleans: readInt(), + numbers: readInt(), + strings: readInt(), + table: readInt(), + }; + + if (opts?.debug) console.log("Section sizes:", sizes); + + //offset += 10; + + // parse names section + const names = buffer + .toString("ascii", offset, offset + sizes.names - 1) + .split("|"); + result.term = names[0].split("|"); + result.description = names[1]; + if (opts?.debug) + console.log("Got info:", { + term: result.term, + description: result.description, + }); + offset += sizes.names; + + // parse booleans + let boolean; + const numBools = Math.min(VARORDER.booleans.length, sizes.booleans); + for (let i = 0; i < numBools; i++) { + if (i >= VARORDER.booleans.length) { + if (opts?.debug) console.log("Read boolean overran length"); + continue; + } // doesn't (yet) support extended terminfo + + const data = buffer.readInt8(offset + i); + if (opts?.debug && data != 0 && data != 1) + console.log("Invalid boolean data:", data.toString(16)); + + boolean = !!data; + if (boolean) + result.capabilities.booleans[ALL_VARS[VARORDER.booleans[i]]] = true; + } + offset += sizes.booleans + ((offset + sizes.booleans) % 2); // padded to short boundary + + // parse numbers + let number; + const numNumbers = Math.min(VARORDER.numbers.length, sizes.numbers); + for (let i = 0; i < numNumbers; i++) { + if (i >= VARORDER.numbers.length) continue; // doesn't (yet) support extended terminfo + + number = buffer.readInt16LE(offset + 2 * i); + if (number !== -1) + result.capabilities.numbers[ALL_VARS[VARORDER.numbers[i]]] = number; + } + offset += 2 * sizes.numbers; + if (opts?.debug) + console.log("Read numbers up to", offset, ":", result.capabilities.numbers); + + // parse strings + let tableOffset, valueEnd; + const tableStart = offset + 2 * sizes.strings; + const numStrings = Math.min(VARORDER.strings.length, sizes.strings); + for (let i = 0; i < numStrings; i++) { + if (i >= VARORDER.strings.length) continue; // doesn't (yet) support extended terminfo + + tableOffset = buffer.readInt16LE(offset + 2 * i); + if (tableOffset !== -1) { + valueEnd = tableStart + tableOffset; + while (buffer[valueEnd++] !== 0); // string values are null terminated + result.capabilities.strings[ALL_VARS[VARORDER.strings[i]]] = + buffer.toString( + "ascii", + tableStart + tableOffset, + valueEnd - 1, + ); + } + } + + return result; +}