From 8e92a6f41a30e0166459670b6343c7dcb6abc872 Mon Sep 17 00:00:00 2001 From: pineappleEA Date: Fri, 2 Apr 2021 22:39:10 +0200 Subject: [PATCH] early-access version 1551 --- README.md | 2 +- dist/icons/overlay/arrow_left.png | Bin 0 -> 1490 bytes dist/icons/overlay/arrow_left_dark.png | Bin 0 -> 712 bytes dist/icons/overlay/arrow_right.png | Bin 0 -> 1394 bytes dist/icons/overlay/arrow_right_dark.png | Bin 0 -> 683 bytes dist/icons/overlay/button_A.png | Bin 0 -> 3494 bytes dist/icons/overlay/button_A_dark.png | Bin 0 -> 3167 bytes dist/icons/overlay/button_B.png | Bin 0 -> 3375 bytes dist/icons/overlay/button_B_dark.png | Bin 0 -> 2975 bytes dist/icons/overlay/button_L.png | Bin 0 -> 796 bytes dist/icons/overlay/button_L_dark.png | Bin 0 -> 745 bytes dist/icons/overlay/button_R.png | Bin 0 -> 1841 bytes dist/icons/overlay/button_R_dark.png | Bin 0 -> 1835 bytes dist/icons/overlay/button_X.png | Bin 0 -> 3968 bytes dist/icons/overlay/button_X_dark.png | Bin 0 -> 3530 bytes dist/icons/overlay/button_Y.png | Bin 0 -> 3337 bytes dist/icons/overlay/button_Y_dark.png | Bin 0 -> 2883 bytes dist/icons/overlay/button_minus.png | Bin 0 -> 2401 bytes dist/icons/overlay/button_minus_dark.png | Bin 0 -> 1969 bytes dist/icons/overlay/button_plus.png | Bin 0 -> 2497 bytes dist/icons/overlay/button_plus_dark.png | Bin 0 -> 2066 bytes dist/icons/overlay/button_press_stick.png | Bin 0 -> 5225 bytes .../icons/overlay/button_press_stick_dark.png | Bin 0 -> 3636 bytes dist/icons/overlay/controller_dual_joycon.png | Bin 0 -> 7312 bytes .../overlay/controller_dual_joycon_dark.png | Bin 0 -> 5889 bytes dist/icons/overlay/controller_handheld.png | Bin 0 -> 4645 bytes .../overlay/controller_handheld_dark.png | Bin 0 -> 3745 bytes dist/icons/overlay/controller_pro.png | Bin 0 -> 9493 bytes dist/icons/overlay/controller_pro_dark.png | Bin 0 -> 7488 bytes .../overlay/controller_single_joycon_left.png | Bin 0 -> 7489 bytes .../controller_single_joycon_left_a.png | Bin 0 -> 2609 bytes .../controller_single_joycon_left_a_dark.png | Bin 0 -> 2564 bytes .../controller_single_joycon_left_b.png | Bin 0 -> 2559 bytes .../controller_single_joycon_left_b_dark.png | Bin 0 -> 2383 bytes .../controller_single_joycon_left_dark.png | Bin 0 -> 6768 bytes .../controller_single_joycon_left_x.png | Bin 0 -> 2541 bytes .../controller_single_joycon_left_x_dark.png | Bin 0 -> 2392 bytes .../controller_single_joycon_left_y.png | Bin 0 -> 2641 bytes .../controller_single_joycon_left_y_dark.png | Bin 0 -> 2639 bytes .../controller_single_joycon_right.png | Bin 0 -> 7497 bytes .../controller_single_joycon_right_dark.png | Bin 0 -> 6729 bytes dist/icons/overlay/osk_button_B.png | Bin 0 -> 741 bytes dist/icons/overlay/osk_button_B_dark.png | Bin 0 -> 767 bytes .../overlay/osk_button_B_dark_disabled.png | Bin 0 -> 781 bytes dist/icons/overlay/osk_button_B_disabled.png | Bin 0 -> 791 bytes dist/icons/overlay/osk_button_Y.png | Bin 0 -> 726 bytes dist/icons/overlay/osk_button_Y_dark.png | Bin 0 -> 502 bytes .../overlay/osk_button_Y_dark_disabled.png | Bin 0 -> 694 bytes dist/icons/overlay/osk_button_Y_disabled.png | Bin 0 -> 699 bytes dist/icons/overlay/osk_button_backspace.png | Bin 0 -> 2919 bytes .../overlay/osk_button_backspace_dark.png | Bin 0 -> 2958 bytes dist/icons/overlay/osk_button_plus.png | Bin 0 -> 626 bytes dist/icons/overlay/osk_button_plus_dark.png | Bin 0 -> 676 bytes .../overlay/osk_button_plus_dark_disabled.png | Bin 0 -> 645 bytes .../overlay/osk_button_plus_disabled.png | Bin 0 -> 664 bytes dist/icons/overlay/osk_button_shift.png | Bin 0 -> 1876 bytes dist/icons/overlay/osk_button_shift_dark.png | Bin 0 -> 2003 bytes .../overlay/osk_button_shift_lock_off.png | Bin 0 -> 281 bytes .../overlay/osk_button_shift_lock_on.png | Bin 0 -> 274 bytes dist/icons/overlay/osk_button_shift_on.png | Bin 0 -> 1573 bytes .../overlay/osk_button_shift_on_dark.png | Bin 0 -> 1937 bytes dist/icons/overlay/overlay.qrc | 64 + dist/qt_themes/default/style.qss | 377 ++ dist/qt_themes/qdarkstyle/style.qss | 399 +- .../qdarkstyle_midnight_blue/style.qss | 439 ++- src/core/CMakeLists.txt | 1 + .../frontend/applets/software_keyboard.cpp | 148 +- src/core/frontend/applets/software_keyboard.h | 114 +- src/core/frontend/input_interpreter.cpp | 15 +- src/core/frontend/input_interpreter.h | 3 + src/core/hle/kernel/hle_ipc.cpp | 8 +- src/core/hle/kernel/hle_ipc.h | 10 + src/core/hle/service/am/am.cpp | 79 +- src/core/hle/service/am/am.h | 1 + src/core/hle/service/am/applets/applets.cpp | 18 +- src/core/hle/service/am/applets/applets.h | 10 +- .../hle/service/am/applets/controller.cpp | 5 +- src/core/hle/service/am/applets/controller.h | 4 +- src/core/hle/service/am/applets/error.cpp | 5 +- src/core/hle/service/am/applets/error.h | 4 +- .../service/am/applets/general_backend.cpp | 14 +- .../hle/service/am/applets/general_backend.h | 11 +- .../hle/service/am/applets/profile_select.cpp | 4 +- .../hle/service/am/applets/profile_select.h | 3 +- .../service/am/applets/software_keyboard.cpp | 1170 +++++- .../service/am/applets/software_keyboard.h | 187 +- .../am/applets/software_keyboard_types.h | 295 ++ .../hle/service/am/applets/web_browser.cpp | 5 +- src/core/hle/service/am/applets/web_browser.h | 4 +- src/core/hle/service/hid/controllers/npad.cpp | 8 +- src/core/settings.h | 1 + src/yuzu/CMakeLists.txt | 4 + src/yuzu/applets/error.cpp | 22 +- src/yuzu/applets/error.h | 2 +- src/yuzu/applets/software_keyboard.cpp | 1714 +++++++- src/yuzu/applets/software_keyboard.h | 285 +- src/yuzu/applets/software_keyboard.ui | 3503 +++++++++++++++++ src/yuzu/configuration/config.cpp | 2 + src/yuzu/configuration/configure_graphics.cpp | 19 +- src/yuzu/configuration/configure_graphics.ui | 41 +- src/yuzu/main.cpp | 241 +- src/yuzu/main.h | 35 +- src/yuzu/util/overlay_dialog.cpp | 249 ++ src/yuzu/util/overlay_dialog.h | 107 + src/yuzu/util/overlay_dialog.ui | 404 ++ 105 files changed, 9523 insertions(+), 513 deletions(-) create mode 100755 dist/icons/overlay/arrow_left.png create mode 100755 dist/icons/overlay/arrow_left_dark.png create mode 100755 dist/icons/overlay/arrow_right.png create mode 100755 dist/icons/overlay/arrow_right_dark.png create mode 100755 dist/icons/overlay/button_A.png create mode 100755 dist/icons/overlay/button_A_dark.png create mode 100755 dist/icons/overlay/button_B.png create mode 100755 dist/icons/overlay/button_B_dark.png create mode 100755 dist/icons/overlay/button_L.png create mode 100755 dist/icons/overlay/button_L_dark.png create mode 100755 dist/icons/overlay/button_R.png create mode 100755 dist/icons/overlay/button_R_dark.png create mode 100755 dist/icons/overlay/button_X.png create mode 100755 dist/icons/overlay/button_X_dark.png create mode 100755 dist/icons/overlay/button_Y.png create mode 100755 dist/icons/overlay/button_Y_dark.png create mode 100755 dist/icons/overlay/button_minus.png create mode 100755 dist/icons/overlay/button_minus_dark.png create mode 100755 dist/icons/overlay/button_plus.png create mode 100755 dist/icons/overlay/button_plus_dark.png create mode 100755 dist/icons/overlay/button_press_stick.png create mode 100755 dist/icons/overlay/button_press_stick_dark.png create mode 100755 dist/icons/overlay/controller_dual_joycon.png create mode 100755 dist/icons/overlay/controller_dual_joycon_dark.png create mode 100755 dist/icons/overlay/controller_handheld.png create mode 100755 dist/icons/overlay/controller_handheld_dark.png create mode 100755 dist/icons/overlay/controller_pro.png create mode 100755 dist/icons/overlay/controller_pro_dark.png create mode 100755 dist/icons/overlay/controller_single_joycon_left.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_a.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_a_dark.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_b.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_b_dark.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_dark.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_x.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_x_dark.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_y.png create mode 100755 dist/icons/overlay/controller_single_joycon_left_y_dark.png create mode 100755 dist/icons/overlay/controller_single_joycon_right.png create mode 100755 dist/icons/overlay/controller_single_joycon_right_dark.png create mode 100755 dist/icons/overlay/osk_button_B.png create mode 100755 dist/icons/overlay/osk_button_B_dark.png create mode 100755 dist/icons/overlay/osk_button_B_dark_disabled.png create mode 100755 dist/icons/overlay/osk_button_B_disabled.png create mode 100755 dist/icons/overlay/osk_button_Y.png create mode 100755 dist/icons/overlay/osk_button_Y_dark.png create mode 100755 dist/icons/overlay/osk_button_Y_dark_disabled.png create mode 100755 dist/icons/overlay/osk_button_Y_disabled.png create mode 100755 dist/icons/overlay/osk_button_backspace.png create mode 100755 dist/icons/overlay/osk_button_backspace_dark.png create mode 100755 dist/icons/overlay/osk_button_plus.png create mode 100755 dist/icons/overlay/osk_button_plus_dark.png create mode 100755 dist/icons/overlay/osk_button_plus_dark_disabled.png create mode 100755 dist/icons/overlay/osk_button_plus_disabled.png create mode 100755 dist/icons/overlay/osk_button_shift.png create mode 100755 dist/icons/overlay/osk_button_shift_dark.png create mode 100755 dist/icons/overlay/osk_button_shift_lock_off.png create mode 100755 dist/icons/overlay/osk_button_shift_lock_on.png create mode 100755 dist/icons/overlay/osk_button_shift_on.png create mode 100755 dist/icons/overlay/osk_button_shift_on_dark.png create mode 100755 dist/icons/overlay/overlay.qrc create mode 100755 src/core/hle/service/am/applets/software_keyboard_types.h create mode 100755 src/yuzu/applets/software_keyboard.ui create mode 100755 src/yuzu/util/overlay_dialog.cpp create mode 100755 src/yuzu/util/overlay_dialog.h create mode 100755 src/yuzu/util/overlay_dialog.ui diff --git a/README.md b/README.md index a0d1e474b..619598e82 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 1550. +This is the source code for early-access 1551. ## Legal Notice diff --git a/dist/icons/overlay/arrow_left.png b/dist/icons/overlay/arrow_left.png new file mode 100755 index 0000000000000000000000000000000000000000..a5d4fecfe603214504b78e3c27344d5414a50463 GIT binary patch literal 1490 zcmV;@1ugoCP)IM%rVPWl-7mjh@T>jPPwz^6bCpeW#rKYj}${gs91;PbZ9v2Aj zQv@-_K851AKsXb;gZqMHeJP*MKhcl4K=2bxjImD;6I>wJ3EshdQL?`8ae?4~UEvbZ z%*e>d$$rNLf&q>#F5v$fOqOL`Moe)5e)k6-_; zB-1o60eD%mUP)ZQp9v<@G%pd6EbceiZ1!;#aRK0f$Im0i*hR=suF|-Gw-daB`-)_} z^00`fuy?`eVz>%bOnk{>a7)V7Iv6)$8>Wali$7{~Sz=v5%49?wg+8 zY8~9;al-|=Tp~= zB)FcW3D~l%vqFfsC0~DYXlN)|7TRRb65$dzN#|_sPH=+jPtpKjn&x`|-U+!E6(drn zX}(KD39e5`0r)fQCR9$^w(SK?)BYl&hr@10!H6^f*tWf(>pB2D7-~D}MeOeE;hg_$ z7)BkyLm@V#T13;I%9drF7DAktdbJS00C=QQsnk4gO_4P1P2-#|=(2o~KhF7lg6vK9-CgMxnTRGeO`A!O_onUkRX4~0666-?xIdV4KCfw7 ziijSNc-}!Gnj9S+Ev>GuD!r*sHc}g~ZQFK*EQCmqok;I+PmsHdt0M#nvirC`qtONO z;Xv$C?H%*i1)>FN-SgPfK`E;JgABXLFU`jnAv+j9wW=k#=pD?#p@p3li8$nE3F zK}6fO=M2L*0N|KJd7;s094-_JvvEN7?4&2>JV9;|Pmj_j$j$Nmta5_9H@+MO?*=)S z%gwB>uPcY_+vT8~^EqADGmu0c7#KLBBC@ZyuX4_RiaWCJ*V}f1tkiN5$o;`Ck;ha* z4(x(v2YCR%QHgS8kORE|JC4YKUoq|$Ih)Pim&@g5`xQBqJKW=h9NH!M4ssTXBZvAe z1f269RYwl}YaUcb4jmX!8#xldfV#+$0S2~_HBGyVh$bb<$1<7Bp~1nyNFzrI7yxYB zmPRI`do!8Lq2b}-nYFdGNPb~S+- z8#0qjL}!+kmOc-vFSm&-FtEE^+`SL70PuRbT>dENzT70zzyM&|_75$PcW^I;)t77{ s4-7OR_YH2pg&1QO7-O>cWJV(Y0pEG)`=#zJdjJ3c07*qoM6N<$f`{eApa1{> literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/arrow_left_dark.png b/dist/icons/overlay/arrow_left_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..f73672a591fcf7a160735609f510818cceaf9ab7 GIT binary patch literal 712 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8khhnmSQK*5Dp-y;YjHK@;M7UB8wRq z_>O=u<5X=vX`rBFiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}CuAVNA zAr*7p-m&ZxaTI8M_(DoR!9sBQ1BL=NgWHUmV$%fqem^p7IQVX3b%FYxnLiCZ9GNdK zQd+Luth4w2>uvMwre0ioN^7$p)3pZK2RYxSoeV9Q|7mfo;`$9uzgzC#W<8gh|9YS5 z4yQO**6$yMVie_<)yM2)SDb(D-W}fq29Hj~z7aG?dH-%v)9%LyYT4GLzJI6mkd5t3 zLp$G{=LaUfxf`o;=K$9`X7+d75Bq#}G>YE&eTa7j)AywX?qLP*In5OwvV}GN_S&H= zoR`4*eO&?bsspvWGH37I1zOh!<|*HqD0=7iAzlxV=4ie<+Yf96ni|sh+i!=m_73w_ zX^{*`ao4L5s9R!PUm!j283u?wk$cxbASYEVSpkE?5u=W^rB@+@N}A zfmVTkO8&q3vvunjww-yCbZMIAw(p-`>@7%^I&-GMS2)juc|&5_^iAzPTMwSum)bw0 z=x~nN?!2_mZxn-b4UDxk|NMioQSpND0%fQN7?kFglbKHH`?+=!2 zxwC*PWp4iGHouHx>QC4#Czejwtp-$J`(AMV1nDO(*OC)WId?j~czN*$SSQ5D5Bonj zpSY(r;cJ*^{(;_!2Yw%z`DSmd*(a}S!a?nwPuKI`X4gx3Kih>}fxG;wt^&8YQr%wl g&o$q~{O>a|@GvwjJ-llQFx4@5y85}Sb4q9e0Mvs#3IG5A literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/arrow_right.png b/dist/icons/overlay/arrow_right.png new file mode 100755 index 0000000000000000000000000000000000000000..e47ee94bd8984106329e427b0d42b585eaa53b87 GIT binary patch literal 1394 zcmV-&1&#WNP)@&7SV-vGmA#=N^77#$ zM~+;%apT5H+I{U5CxGkq`g_1dpg+06^78T_7m%$7?%`fw4R>(J1>{oN4Q!WfaQk_` z)9L&&J3DLbpjg{j5CkVhsTd7nI08<9#Q@-yX znx3A%(r&k{Lbetdh?G+EzV9CZjv3U~wbuK!)>pA;lC9o@ftAV0$q#{_O^QY2gCGcg z!lL(O^8^FXZnvZ9>FKMTPRD4HMP$nN{ryTQD<;``U>G3Jn<4|pZ9#Sd7&aik030<~ za@_a*s#5A2DIz-q3~nMj1q|*XI|mFKkYB_c`METZodkvf^6xQ_UrqzrSzs6%`B|mZ zb;gA3G%&0oYprV{(lA&uR%GXaVHJ61X2we7OaQ|g^3u}MOMnq_rBZpOw+7TLC6mCgid?N$=eyl*9hlsy zUPKZ>&MYv%&6_t@s@3Y{ZnxV24)0ViB2C}-pXz4DpeHSA5@pQYrPvD9)dG zU>FMdM5R)BVid@^0ESg$&+~o*j*5sW@yVEF2Mw0IYJywh*f+fAP#%z750h)BXc%{-~UxoDPEE zZ2v(4oIph6Ory~_2gMeVbj}tNoM1$xTL32%5jnH4u<&&OoIv`xP<+UZJR}^*!9LHawS1Ywl+hxKLawZ<@Hcz?WedK7b3w1V4}=anEjb zu$bU=X*Qet?%usS54>);WLsP)CU}rOu852b7Ybk?Ib0}!W8+3qbZ&8R@pIGH&-H~y ztJT_@BrfE5f?KWD-aB{h%mZ(j)DMFTIRzdUF63~Fh>I&C-wcZjIh){Na2FO9K6{vR z<_0(oT*%3lT&-5yy|S`$33$t-epI-Sg9)zHYPcZMbB^ZTE*wrh(JOg$x7R zfD0J~wgne52y7EBWDK~6dkHvYxa7Mq3{NN7Q)YmJAlM}$b6A8683DEx7cu~BGcGs| zJOu9fFbqFQvZwpwEU*i>;3%*wxZvRL!9CnKU&T_x1;-|M9qv1ZOH#!Jhk)baMp5+r z;^N}R3AeRloB(ze7aZ7wgCN*dDwTf4L0qtWg8R5p6uoOwZyPRH44egAuy~6QkyEC) zzS!Qz{qN~wQ5&$}%mZ*Er?MbQto;)2D%09xz6Jz>%07*qoM6N<$f{avv AVE_OC literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/arrow_right_dark.png b/dist/icons/overlay/arrow_right_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..91cf83d2c57ecff0626bf14e3ed0d7f0ecf40c65 GIT binary patch literal 683 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8khhnmSQK*5Dp-y;YjHK@;M7UB8wRq z_>O=u<5X=vX`rBFiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}C8lEnW zAr*7p-Z||j;waPlQ8R#N-=zG+Mdus26U~ooa12P)tk|!x>*GG=`KMcFoNv9_si=7Q z|0e&?t~vSFcTbMDnmhALs@ZFe2Vw;*F`0XxUYc51|8YCN+dP8@-|Tldzwfni6n`L9 z*(LUXPnP-g+U;fH1TXmOm#02i?5?-Ri^T=^VNHdDmpm3%X^r zVOgbDX13Yq^&GCVnb$=N6v+M%{IS?DfA7xOJ=$v+$~S*~mS?nXLr-;9jN>uUw3tNJ z9kkNKfH`vkdF%b*Hf0k!*bZ z{>2~v1G5T`#x~^K(X?)V!+%us)b%Ip&fT#u=KJ#X`|)eGz=Xx%>FVdQ&MBb@08D%_ AHvj+t literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_A.png b/dist/icons/overlay/button_A.png new file mode 100755 index 0000000000000000000000000000000000000000..fd90f8b42a3c5d2bbcd4a446bb71690e672a6707 GIT binary patch literal 3494 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 P|5Sjjr2~Rs;d|?U@cf2? literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_A_dark.png b/dist/icons/overlay/button_A_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..d6b5514fabac904da9231749555b1c2c2700d070 GIT binary patch literal 3167 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 FV1lPxA(;RG literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_B.png b/dist/icons/overlay/button_B.png new file mode 100755 index 0000000000000000000000000000000000000000..e8927addc4dd0ba42485d890925e4d0e157c437e GIT binary patch literal 3375 zcmY*cc{J4D`+sBXg~<|QsfH+o$x;bfMoh^*(FslJtO{YIZ)dgg|64 zosvYQBwYc)8$^i4Zf)~H;?dtIrgLxICf@gos!2*<@AiJk4{v7OW=7!GB|zl=*-k5o zPgl**(6CM;y;MLK3KdrA;$b1NPm&I4v4@aQKcI9aivgGlTCm~Xvgq2W!Ai?ZN_I^T zC-g73o0_N6)G_2dUo_f9wJYSv$*iiU1Ax02t|6y?wy^c?a&rbluJfJ1cZT0_!Tw$` z96#&Jw}nev!j18W>dvLjp}Dz+E0&nPu&^+Mo0}Uy8y0dmu79QD2j}FN&&9nE0|SGn zP?o%mY^Br%k*;B1pc$glJxk=s4v!gkAW zCydE|jk0=gmPZ>5@XoIXysp45c5b2FR5deC9b2N)U~2K=9AF&5X2k>Zwx3{CX`jU+b&(Bdbg&6B^&=rHKr53oX4Z?~w-v2Q z7sWl>-9KnvQ*(#u(Ym`A-L*u7vQRClUOS|PqEfo1+Zash&?cVZS0R%gcWs&8%SHz*v zWTOm9HArXT2~uHRsc4ju$(1egdP(LSM}bYs%v?183A}M0xhyPzIxcRFE{$9V@{(~# zLSfDsm-m=HhLon;H<@S(61|4IWG`mGx z`1ZSgjF|;&VU-`Z3pkSYzb|b*R>SH|-SOFr{Hq5H#crtgxFyRJ_t4iQn=z;QZz$nN z=+izB2nNyu-Lsj{rb6pP)_USv9&w5@n3JBPt!K2qj z6tSUo59o2{C!$@|;{N<`&d_|w6ViNQcXP=|MO0$>nZZe%!JEDlz+ORfl73m=uV24D zS1NL{e1b2s=^7eFmW6E?-M%ee?LCn&KkshaNVG5;vH%R9=uWyW3FWf`r*deW7%Ua#`E@>*!NUC+f>`H0s^#(;+ zFf-Sd{oJ{8)XbQa<@c}L6>Vz;HYXl8+cTw<<>lvBih9v~#xj)xx*TTd#h)sjXexeh zmApAs@7dV*?E_1){!o{|v$5?{ElFlN#ZQ${dqF>mI7hpAn<{6A{hf7JH)sP_d<=6rfEsD%;Zn;D zyOZ^qCUJ8-aEY4dMbwf~w5u2D?CebIzD|yKrRtJXU$5yl5e|LY8NxAnIuw^Ae#i5& z=gRIralW{4x%2oP*D@;%Mph-KM5Rk=ey7KXxmCnr6#K~NXvG7%=?$l~-ORr?L1P zdx6kMihgbVhDx=w0t;d%xczk8D}Ymvmxreds69RBW64!GaEW*d_?RFmD1qW%wNn~; zh~jmx8RdP1Ou8l{;=Y^AFR3YWk(~Ld()RiL?3Er{>8PF_lO8a)K3`zg>gx z^eCq~-{VTj)^c@oYn>k~5rowqhaA_tG4VStFF&7MSXh{gBm8PKEAT361}myW5>w^y zZd~Ee@Ei_x8z>Ny*4q2iB%Wzjem!NMcL>jynh)hE z6bh;S);T@HNrGmD`=GoulG-x;=F{IB_)U}}_~aX#Eu~-iG39ge9AkdZr*MGVCM7W` zDZH_Pw1sYgF)_jffviH|rIQ7XLeCeuPI-FZyGA26PO(}_=?i$D(gNkyWfyJ*1zHe%3 z;%TcNhUJKTI(M!MEr0ec~L+)QMQXL^?8MDUqW@-;N+RP3uZ%nmE9}ewi7p)YbXH8f%;l;(pt=-8AhO+lb z9k<(bxS(nRToO(sv3l7Su6oRCGzPQrne^q=P0kS$UF_8bFktc+K8tfw)uPMZHbZc12q;F?uhZoczHKdA7!)+B1IT1AtjhyOgkq;j}v{|)0 z)I(ku7Z>m4C)hs=zU_zTs-+|9>6w9Lbz2(8Vo!p*5OrYjzrN*P4^H^#SfTJy*{(hU ztN33#2IP~pw6vYl5;k>^N7A-|2wqsPPq2XQuiF=~gu+4Rs{Q4@HwJQLXyYx%eBF~i z$66c-_>*{qS`1?2X=?|BaP99Qqh>MX@^qozNOHdF5%u{au>5F=$J6uIz1cYv|-(DAIvGx6 za*v^G^ct)zEiJPt^Kf{8Kxp;;$?f|QG1bMCoih6`^B+V-!u&oitdHv={fjA@=?D$+ zy#}2^mhb1g{Gk0`w}iw8R2CtOcn}oRlo?jIJeb{c1Sa(BYsHLNlk&{?5}P_@R#yX; zsQ#zGSJooAoB*4Yh1jC;rA-gBU`L1lYlxohE4f59jGfN;Xz+6bpbSlsW%_r+{s(Rt BTUP)8 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_B_dark.png b/dist/icons/overlay/button_B_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..3acbeddcd62aeba7c749697312ce1f142fd245ee GIT binary patch literal 2975 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 V0~d_hmf`>a002ovPDHLkV1fmYsowwq literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_L.png b/dist/icons/overlay/button_L.png new file mode 100755 index 0000000000000000000000000000000000000000..77838369eedad723c3058a39376666ce2af6f31a GIT binary patch literal 796 zcmeAS@N?(olHy`uVBq!ia0vp^DL@>Q{=-HE3585Bx znmx@Y(z-iZT>QJv(X|r0w{B%^jasX8 zYzK3K^nqnFbD4JVK2U35cyKLr?)Uv%^`9TvoZrs)X6DUchJFT^x&{0?Zl*n&~&g^qA z%=&m-L33$210Q3|`t-gtypw{e z5#={Ff2!NRDJgQ=`~?zub(}zRvl({#MYGt)M!qfEZ7A2jpK-_g9qCWc-cwwB@r9_! z;k!B>Js*3Y3(Vv?@FrY2e(!OQuY8s7b~&XfF-%GQd8E3-r?qVNUOS*h=8VdI!Gv5B zskbcxF-av>vN<_9IyHGlGZ)-{e|^J=`%yiHGiRQDcsEbnHBhJK-Ip&VsZB1%)h}$F zvFyG_uq3Sf>euW0dOpXV$fMPm-cI&L1-ZUXl zz=+Gq&GCc=ha{`wVuciumL7@)BR|U(2==-$v$L~rontD)XDGpwW%=6XzsrQQ&7FV$ z{*5|kWoK8$yGXkm-LA*KESFq={qV($6UB!l)z#J4J#W`JUGtB5*&Oe1F_IkknYl$V W=e(p{Pzf+4GkCiCxvX=px|QJWUCM!r5!7~ch8=@x_SD8TenU+ zR`JesDd5^s!MWCa!oy;ZGns1*1t)*rJL^09+^SQRFAx7*o_RaLPbrCY2j3l*YYpCu z`#&F$T);SI?)AU_!h84seAE%ayz2n-1F;LUj;~1lcHB|vk;-8X{RZpe_$^P;_=E&m zKZsQD#>CuMq-q{K;o`+DXFo)2V%1s}DcahlE9Ttn&XPe%8=I#D%e*P&x|EIVfdDJj7~ zruz+3-x#v_w$&H;c3yn_)#8TzjH=kxSCd}<3*+tnWZIY=X@39r2KW8<=U2r>PV*LC z!muu_e}fle%t^V#sZ2X2*dEeiy>Jf1wwMcI*Zo&K>Uc!tK<695u$jN=Bg6FB1s^LU z$++}1cN7X3@+lp2JYvxyL9yWFGjayq2NMpQ`{t+nJ!|{zoO`#|JdNA`={(EL+U?P& z_gc;M%My@}M7MaxmG6po+hebpUo%#h*<6)p`t@ILN5>V`vX*`wk{tKUIyWrlrIt+m RcVMz)@O1TaS?83{1ON!fH(~$) literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_R.png b/dist/icons/overlay/button_R.png new file mode 100755 index 0000000000000000000000000000000000000000..4e555395445f9db54c4beab20e09d2c51dc4f04c GIT binary patch literal 1841 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 U3YsB9nw9}wgaN!(%P#VN0Ffwzb^rhX literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_X_dark.png b/dist/icons/overlay/button_X_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..b2c83d0c1123d7f076dcabec447a78beb5be4e5c GIT binary patch literal 3530 zcmV;*4K?zKP)z#0HCifWd+&pb@pL3_`VtLa{|e zYlR@K0+t${8thE_S38|{T1Jt0LwKV>MA25oHzmdK5upX@qb-z& z|2P*K^W5j&eeO94=bO2cJLj&m_Rjs?^V)l@wIL)VBqSsxBqSsxBqSsxb%@^`@ETRU z0O$ww2l~d}cLUPnudl}c0#pEH@xK*7DX=U4+5zkok)x^XzgyazpsLypxB(ari~xFD zZlV-;4_FVZ6On&g_Ma4OLQqw`9JmL#6SyqJ&F=!92c8m<9Vu?R0j*Y0RqYJi4g3hW z*5~e=Vl%J`SS=!-`rVmUfvR4ts!>&~(BM+3s!LS0r{CXKU{TfH>IiwlSE<}_QdL)~ zYCpffzrdiX>8d(MRjYlKCON9r`7vhr{eAk1s*YCG|M;tgRSu}?J*w*2y-Of(id3~X z@HjBc@4k4%o4|cuJn{riP*oiR6yiMaw`Gn4Q$*wsHl3bj(^)d8YMQFf0oKs=#RTXG zJg3gJwVf@efjB}{wL9<%!FircHJgAjB628MN4^Uhzh15*^&>n%V=pjLL_SW&iLa+z zRULpcE_$Ym3C@^vv8sMO87IC8s;WbA#O&c$9|FjQz}u>NL!u6RIpwPARlqyI`F^zD z3Wx#2MC1dvJEmMvRqcawWOhq++pUZv!1W@s%gs(H6;xHb5?(Xium|{-h#WNCA!XN4 zRc#NvPAH}uE&*P0^PHN>o1#ZJbDuMlVG!^;Q{$AJa#i(KoPh)1pL<&=3VdHgUTVqE z^DkIcF9tpYI(gkz8>bBTrikoq&dAeKZhV?(1)-QW_$sipB_mIdP*t6P6JukAqeqW& z?AS45`?Rz)diCmMt9bxam4gS3Z#n_!(xnTLNW@kbvPI;%ratscP<3YKw_|Z6sPsT7 zIrryKRaIqVFCsiV_hAMM7+|XltbJoG(Z{03_E}k3%$+wE5o=%dL7=~g9B=GHFHgC@ z!Sb+P_a5E3cjCRq_Nq!WFG_WFwUzc`u^5Ggg~s-gNQ4I-e88rd;0(z8vZ)U}5mZ$# z1EzW5`;PD5!4+3rVQgPgQo{3pc-~6e%a$%9c06WmpFKXCi@$cUmA30KGd|+h0I!bF zp95!m;aEh-eJGc-v^3*=o_YEiN=izSYIob#ZLI&(dSm;{%uL3P8=F*{%@6^8*4PKu z1yv^&^@q0o)}?39o{Y;LXKY_xT}^&8A5~4Lz1rGZ78EQn-d{w>ot;a&cGdzB8e^iW zey#pSFOJZk0iOOR;GE9f+`<0+Z-3|Qx86>utu-&MVb7jD#`dFb9YxlaSqZh}f;3=u z{f$-yRn;!QWSc&lA`*#^`%o@ton`(nELyb4)in^{$dMyFz3ORW`%axYF>%sFS8XK0 z6m_!ePGkEB9S@vq*=LjV>(`Il@3`IAK6X6DiozAH+E}!35hqTZFt(pDYX;|?cb=;@ zlHgn*yY2?tg8s;|zZSW7;=Oe5(cRd7?b@|$+q%ujcJrpqY}~NX*!T4}T+fgpLyYY$ z@ulzFnxN{W^rd_@rG5MMDcp%F1}+@h6P;y(se{?izcS>pptKFt8`+O_uy5O-9EIru<}z zv3+G_B@5;+0AR`DC4Bz*=SKU92s3BR^m=&D4L5-WLDeZ<;mMRjq6{A~oNEV~KS6ET zvW5I;zN?|Z?~MEo*IaW=0=@H$-m2Ou=_|RaehVkoHc%Wse3<*E-0y0X$CQkY87yDE zoQ#fv%dj782) zj}<}tTJYfsw&x|=wQt9RxexmN%-I}P1Pyf3QD$Z)AA?QvE zKCDw&SxGc6YW>UKwrv})zxKLSpBCBCW(9qG`Qxs_Fx{|x*>cZ(C&|t>Dd^U%TY2r( z*Np8&gl^r;=NKw0E14f{7%>bME-)}w$(7AJGCf_%i9zA+6b-KG$hGk2aS-XR_z+MqK zW^3s0ElYlqV&~4Cy!gV4uJ)NeWBTbMbHs=du7b-dDk@wR(D0Zy!Cn}r?}FCU)R6aR zo~zNE8;9M<;30#*v~AX`S)6;Wc}C=x&0Ach>U+prU{6rr|D)B}H?2yLefmwzihMd3nakMF0Z_4&W>BfEFz~bceW!Ip0ez( zNlvHMS2dqnUzh%u_h+U_Y6p5ng@e zRkm*3YHS~gM3_B$w(B72zWw?#=JqkJ_L)C_KA(N|nX7#gu#_rXndLCn6Y1XFJit13Jm#w4MiML)k)w4tco8J_FkpTjOn_)K z>MEGkiaF-?F|Nw7 zHb>61rUxS$qwH82ix(}n(sn(nan6Jd@I+7%*$ouf_FGU;V4N}!PL8O}AnA+@cj^8B zZ?1onjT_CYkr@(gYB2#X1Fe4nG&m(BO7XkJuCGbcW=wN6&~C!b!*6ELph2$oDad#C zLu`Rk;MYxk=vnrSh?J}9PjMb)lA^Y@mZG8}F1yUU6jIieSzLGBb;&l}DAQ(4qwKRX z^b4~|^z`&}HoUiik++Pr(flkCX|kkfD%NdTPeAGzwE>Dmq}g9ZDg^C`vk;GOh49)4 zWxzK?WM6Yeo+m4b$bW!|ICFL(iJ`!xmc)bh%cG= zF3xIuA?9oZZWIxt#b;8zAZ!<)m;jf;2^hO^7KA-yx`Q7}HeZ5Mc`VT7L;^r5j+o7^ zMp&PeXK;(i9$+MplKG`pN(>m~R!s0gPyiA62QUU`bV0T=oZ~oRewfJ4zLvyN)hmHN z0vGw&j{tEH_^yb2l&AyWYn-WSZyZqrTVgIn>;XoK$iI?t;(Ha8_;upjz$U-@;v*Y@ z>yi}{0w5?r{I>jh;8#H4D^plRfqYz@w_8Rdv6%9X?T2-&UXs1ge^$s`FH}+Fvb_;-sq1Rn=Bg z7twQ6wU??!RrQ3g(zv5aRadIBCV5C>sOp8PI!9G2e3hzsDphrfs-}F&rNG_gnxf9i zw%IuAX9i_T?k_mGCeOxePo$hyD`;I*wJ(macLJBExcPtLHyV*{KZH|i;PgvGe97ny2?+@a2?+@a2?+@a2?-+q14Ad?$&WCMP5=M^07*qoM6N<$ Eg0H{T{{R30 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_Y.png b/dist/icons/overlay/button_Y.png new file mode 100755 index 0000000000000000000000000000000000000000..ca0de569df161c6492d7e42ed06f291b13a75322 GIT binary patch literal 3337 zcmV+k4fgVhP)k@0^qU?z?;6edpc_*x1f?4-{8zI7xJU>= zT`pH15k0{en+;%`k+Gv^@H>pLa-YvvV`S`}7_@@s{pYbIz9m zSPUS=?0f|20PvQgD1Y#Jy+I=aT3%3($CDO`L|%-Zl}TpjGeE)swzRdiy?FBE$t!vW zvb3O1r*jr#Yzu&Io1Kd!>WJtCP1DK&kRAanDQL8L-3DNi*}3Qe5gp*1Kc&YdkL3h) zyWNiv(QD|u;7?>OGsYJAe7@Z>omxiF2@@tb+S=MyF~)uk3+1IV1Yjc({YulckPL^G zc;oW&^6pSobq}m_J!vS6u_HwEh~Mw8kK31wgyJFkefc2_9^tVdrz4Tbv_K$mA|5BE z3hH*d-2gryqHmd*hlQx;oXsA<}H(E|+o4$aQaPUf8d6}FhMkfkWfdrqfwnCJn937V3U zvKB-1Ix`-K=so~{6ty>`$#uD0GXd-|DqaFYjIp2ie7^VkGt~VJj^2UO&yM}po0Gl?lKmuqfc2D&x5+1c46 zQ&Up|GDd>Z($etgqmKdrR8>VwY3%F#*M?2DN}@x88Ko6ii?Y7gue-g!-176 zS7Pkgu|oEdNCXQPE|eAi_O`aR-vL9e%76fG?+!uB&~&cr?U z+#@rEZ1zl=x1k6Vs+m6Sm*CZy!GU>@z8`d%&Tp>P_FHQk%tsf`Z#3 zk;oeW95VfO=gO5UNJ>gVK|z6#eQIhdl9Q8hB5Bz*SemNdWLp%bo>-Rs!X3fQC?n-)2I6!Fu9J|vu7haJ3FqJ z)2C05sgqS&TB_@1Qv5j`Qxmnd7dY(aB#v#|X$&pdRX0*REYL`IiA>?3+)V6hZUz^Y4U7l?W9T75L_tSlkh!oos4 z@W2B?ww!Ym7Z)QO4h!cZk-Vq|as&;B!#|SYXP~TGw+`2?UF+WvorC);R;=i^gXC~H z@a(hC3g@tU_ikvKCTuSo&iTWDye8MwTV&_YpU11OzA9{=pP!G(lPC9S6U%sQeSJM% zdF2)1_i6@_YQrOo^FTEt(Lg>h3 zK!Tt-IXRgC?ut8|IEh3eC@wAr=Um9XuMX0ZB}Q z1$8(a#@U#4TuaWiHE5R#t|( zx;i1-8#_pfqG0LLrNSdhn>KC2g$t&vUehCyNMwA%3EJM?j&ULP zvt}VLFHh*W>gwv4MyiZ}F&4j|M3f^V8JSd8R^sE2KNhxMwrm-)va+yf(IO%HP$(4B zSh9h{FDNWJ>1e}-4Z)s(dSdvNAL@H0YF(bfgGs`6joE*49>(mX-?J2Lb`S`|i6sW+WXcf(ozDVFh9-SE;C| z5WXE}5$a`(trnxLwxZkOfDCN@-6{d3o2X@W+V(*CzBe>TD#?!NP( z?23hQ$^LBm=eqY?s-U4zXeR*Er(q2tu7<!yrL6*Rg{vAGi^hY*ahvYMKjrtZ&4 zFA`G}r5J!^3s)0}c12N2ZhTU@pkA-{9Dp}u`?a}=*Q%?lZ(43euQJXx=-j5*Mjtw& zE#Xb*BBe|}cR`vFxZh{7b!4@L= zrC$DrDnzvOiI4n#f1eBX z>b8kM7mTq50FDC4&^wk0oO1+&gYw4E#iXR9U-W0B`(!1*-+vasd~|LgZZj~5F}C2? zv14cYGnBfbb#F8@G}Mk6GiDTk`;CZ~fRy3DI5!P3P7RX@2?jhtaVI8Fh-H6!y0p z;fsJ(IA`yu%bep(*PDB&q5fd<3 zPyq0Hy{8#tKOEwhG?fmA<9anTVd!H0>`kofK%{f_9Oco13Ak>SKzc%p;=v%+4pu zr$kf^;H~J|6NWQr1?^6Letr(;d>#?a1|YVrgFKEC(L0>;H;rmm`ywF(?T*Le8BIhF zan7eO#->32rB5AzLyWQgM6@qjVq+N+PS6{1I-TjNs^$<;F6VqaV=M=qJ6WWmb9wbq z`2VWhUjw*=&J{+#22c+mNJK$ZRf9wn^!a?2Dg(5!v9YnSv9YnSv9YnSF_ZrRVS7ty TfH!AO00000NkvXXu0mjfpua&z literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_Y_dark.png b/dist/icons/overlay/button_Y_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..0f3e4df25f8d8861e85fec40d0692a9b31ed9c08 GIT binary patch literal 2883 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_minus.png b/dist/icons/overlay/button_minus.png new file mode 100755 index 0000000000000000000000000000000000000000..7b315fe79df234a8e720fe37c3e1cdcef7c49991 GIT binary patch literal 2401 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 TlOYCn00000NkvXXu0mjfA(@M7 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_minus_dark.png b/dist/icons/overlay/button_minus_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..6dfcdc1b5f74b04a508c6c765854198df58173ba GIT binary patch literal 1969 zcmV;i2Tu5jP)3O2 zj5bZ=LBzC5h_5~f3BDN<6PiFwYJ35!m_njPR8$&;l%Qe|FaZ-2`T=d_qf8r=+Lk&4 zx8cvjzN6FWeB6(<&p!9w^-FH%=ALub+ByGO`+V%Z*McAjf*=TjAP9mWs17ULfZRx0 z0*nCbfZ^u%Wx%55=c49cfYZS7=HJu6C~%M%>GrnF+G8#m6CQyI<5LLF6mxLD@vcQ+9GL{l8|FXrD~6NB<+$kQu_SW z21$z~ZI{$2DouAZl>QknD1APQB5AXvSIcVQ%IlK0Ny>KbQp=knNvnV#0qaY*B@>SW zx8-=`shyybwgC5&9{Bs26TsKa>=&L!-{)zp8zc=$+7A4h{udMA1HjLfuC4REjMgGT zC0!2umijzTR?RcO7Bf56t)b$AHm{eDQvDmAx^WoTWM(gRW29)8E9q*|d(lA`6Vf~9 zCnbHf8zaR8m2^Ex%qvQ@p@u94{wV3XP7M_4awUBP_!Dqp3I9F7NnpL1J=gAml?y6q z4Jk60RqeaScmufB%nr13P^E%Ox{SDHw!?Xu)ht4W9t+YIWyIx?D+LAiX#!{@mN62>7CzJv`Tr*-x;fmB91B#kqa;ImdyI znc3mlcFNl2HiwCRLWt?XMZkS??Uef(dJFwK1Xh9#lD;(C4%xa~rP2Al#G4>*jsok< z?8I~%b4KWQ2{Ci94EXj;8?!{Hq_w2zoR`CXz!_4M9-O)%SA>3x5Hly|0e4Qf!97Bi z=Aym?xQ)UG)6xLePMw#OBSP;a#H@lL;G0u_bd69+mjW*X=X=@>W@HRlF?mItcZ7bK z5VK0o2X3DHgSVhto$Up4bJjU`>vENr^u6F^FKCN(X0{)EBXpait)M-=GV#STLJtsw z*QMBNW*-74K_x8*Uh}XObj+1zcEqd8{fv{HpmS~jziVg=k(FmX_z8NWlbxV*ZUl#G zs8aHZH@a?4;K34)2#x#!^|(i4L2{NYY^ zKe9Wwo2+d7>_%?COe z_^{7ofcsY9#}18|kw&AznKNf{y2#GFdGnY*Kl_rOuX+rhWslIIp&^EbhH|^<2ljhJ z=*2*-%SR0m-A_R=vo}dA&VsTWHnTUqUj04cWGCpH$H8wJX9!xJC%|th#1+614_iS; z2v}}rqb?C@X0H>^qb1pEW~1P{obXqUwu1I}XyS|Ka>8LEN=xCt;J2Ad`)^+Mf|mK~ zgqYx;W8nd3d%@hCbLS#azivI#Dv!%6ZmMPxpX)~CW`zDJCc~Zk&2^=J{ ziRI>PV2zob_5ANX>(9(y19o}a4bI{IsbWID^r~xseWa|Bfj9WxaZ$eE&iF)0c1@?1U!lu^=)gp<>4%r{Z}vq%pgoGX&f^mzYpdF*`GR z1lUo9uRi4-Gy6>|J62X)u30ki&!lR5LF+sP++b#nR(7tsAnc{UlSHzrRyjy22z#v6 z14@)^UQMd@xXtVkunG82)xLX(lfdWO6%z^w3Segc0JZ?r z7GxW6PLRZWzLSHCR@{~JVc-wM>%j?*0-rOpzjtb&cnM`mt4N{_dWyM2z$P<$sT(83 zv!FDu6Q2g2DczQWJOzBRTQQ*qf&w&e%dZ8#1H4ZD#gMPouSn(8-wD?|^H7J*C@{k;j0my~V6Ok~T|vMg7AONrPR1O40?Az9*?s zR*SB9N76l#A}`_ul0GPDhorHh(zM6hl6EQ8BoC%T(o#v=C7muR)$EK*x>sqAa;4Pn zF1J-AEg=;{-Aa9ipC{@VlmzB$FFHuRmKQ&GyJGz_dG4Xh87Dp@QdrB}a@|7YcX zi!`ivs`>pGX%X}PK4x~j%Jv6A5ClOG1VIo4L7jOI)Yq&OF`Z+|( literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_plus.png b/dist/icons/overlay/button_plus.png new file mode 100755 index 0000000000000000000000000000000000000000..4d8090d7dbecbb5ec5be0e945c29ce302efac58c GIT binary patch literal 2497 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 wA0;g{aQr^_y7O^ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_press_stick.png b/dist/icons/overlay/button_press_stick.png new file mode 100755 index 0000000000000000000000000000000000000000..6d0254d50ee535c54ae1f5576d1fbb53d3f4b488 GIT binary patch literal 5225 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_press_stick_dark.png b/dist/icons/overlay/button_press_stick_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..757d0ab292972636084bb75a461820f0d632f4d9 GIT binary patch literal 3636 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_dual_joycon_dark.png b/dist/icons/overlay/controller_dual_joycon_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..63e03eb4e0f1c692864c6af98aa0d349c83dc3a5 GIT binary patch literal 5889 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 IS?ulq0u7?|TmS$7 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_handheld.png b/dist/icons/overlay/controller_handheld.png new file mode 100755 index 0000000000000000000000000000000000000000..deb375011c85a5f4d615c08e33fcbb97f8532944 GIT binary patch literal 4645 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 F|6eLh>Gl8s literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_pro.png b/dist/icons/overlay/controller_pro.png new file mode 100755 index 0000000000000000000000000000000000000000..67cf86d5c46f43eedd96beacd789af20d94a07c1 GIT binary patch literal 9493 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 aGluAqkh6)lJK5kCCcsGF99gUDn(%+W&hcab literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_pro_dark.png b/dist/icons/overlay/controller_pro_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..7be655b961eba94566216874cf4b14c633c1e084 GIT binary patch literal 7488 zcmcIpi8qx0_n$G=$k-`{8e8^#$r6=)U!tNI$s<|IGWKB2K1#y3J71A!2v z$3rg=5{Xpw3GfZ}^a%1&41DNaMA1770*UmQ8(qE@Uc5<+K;Lr0jO=X9-@I|H!U2Y z9*^nE(-x7z5wA0w6cJuezYKv$qQMI>{D@q#uQ&qU8n*w+W`8Liw}F$zjh!xxU_*od zp6wpOmn!K7_>^IvFz_Q??n)2TG87l`+vwkG2pmZfpCuSS-e}V?7q(W zDB}u64y?-%$k++)oH*9KxcefXVFvV_;d&8a_x+}2(L#(q=>qBA*Wt`btVql~uoNN4Twoxy?VGlhsGj%~UVc}8B?KJEY`0qJpmwP~l!;m8i zyTvI2ef{Kq9E&LU7-Yo~ASD57**-K6@k)GvgzSSBPm`fm;KLl|y~2w)CR`7&Z+D{A z9(A%qm_dQ#+#YYQl^TYh=54EK%sz_IKyb7W+a5E$^D=PteE(`jM5@^-;n`f=%_Mo| zHLX7c2gJR^k}`d7?pCfF40uRBZzLmEfGz2jYktqw{^Pbg_pQ00-Vb+3$z?6`AqA(cvO=%Z7;`5`BH-vu<$Sn<_pgL9@QEjYtmdq)os$wmmT~+Q4w8_|f~JKe z2`2={TmIC!Y=@=)cwdM#M4Iaz%&3#Y+<0aY7D8*z@IK-|5xbcBS;+Z10@*_%=u^>coJj*Es-g3F*fPe2tegy1l+A!WZj;h)x`8m6 ze5sH2+Z^m-OX`NVRe0gjfv|uL!@?i$>2`S&EEDt{N8k<+ca}?Dz_a`I`A*6eT zvVM(*0=$W)5!8)QFo(T2glRCZP#PEt%D=&LW(IK3g}%zgDHyL+r~k1a&aKXqyRHt( z;BE~YGKA$K;!hYjtzam#iI_p+6HA!Un=GNE2h^DOpX-e%X@~1AAZ!r?|Go~Z=o#XQDcct80%J3Rn9&=vqZL)%Re-w4ejZ*`iwEG)kP_r zKK}-H$PYDN$S@}+VK~X9s?6Dn)C;XQ>z(>XBb6z~NSv_4ZTBiuKaS!b6i6%(p z$Sh0m8IP#0OM!oZn#2NY2MqR4yL?Vvm2!ss@&3_$iqw_o5~~ zIKj8g!1i467L;{81~!WXm{Lu$T&(`W%ZD*Af2JW6bg+hw*LLV!B!wmpoe_ElZ8dO`Dj;nKOC$YuKZp!4HI{wA1xH)|P z0wuS-6z`AI+_`dpUL18g`$gt(9tIHxIlkdcyS>4q78NH44WVj*kl@Pk(=t6wI00aP z+6;=nG+pgTIpflv;Q#2$%?DH@=*?qZ2M;WU;P0h=Yt3Tut)(Bo#pZ`Jle(B@pMo~N`>L%2Os5%G=6b&9@t$O>q_}7k?q9}F-U;1Qr@ih0m>>W=F zml{Xs#E2;K&9HWRTvhQ$!t+c+9jnE-+|d_C+l_Bgr<7UB|v`0`>i z*{GDNk^d(!rz`bh&fORC!nLOKpgP5O4Fz- zy5T0+(`VzN{6_%kele`l>o;PN!3f%6>UX+We=@!Z8^|2pQ1~=Qjbuvc-w%S09Z!4^ zC!ffS=Vw0wHmm$0FG4uR%#=$O{M{{P%_Ct!Y#w|k%p{dTc$ZBO$2KoA@W2PN>4V|q zpr!m&bp=fkV+u?R@SRn+PDOWr)0W~vS`J?3o_}=yFiPuiIuHAiaZw0MWLV?M8U2`0 zbXGxwC5RfD(8VAIT4a)_^U%RX4^kH9S!r&X=Y~F;pL99}BhGp#P4ePgP<>`BkQLcS z%UEFlQ^n(MnhWX~#g`8%Q-}dHC4nS+-b`!@+iSza6}xkOw+HmI zX&n%lF+Jjdp!t=1&MzGK;-ptld=n25o9NIxccc^hEbg_#v_P&Hf_X$Pdpt%T07kfa z-zLXqSN+UnRvcr_oBv9oL8OP+WeP71Mrq$qQrU#{^o3b5Enaen$#O;$V7L#cuX^yK zYOWl-Y(&{QMHeDC)f4r;qCeGB8a+KVRZ~Q;2RgN#lL+Vu57a|2{D_k|U<~+e@{O`a zIWe3y=ya~j=I7nh7xCwinom6D`N3?e#C+-6m(F0Mn5EDy@-rSb;#h(K7zwiFU4-N& z+>VFM-wRYJpNTHTOt>uHS$5t44WixN-fZ=dmt3H@gZ0qEx{4V{F%ogeeWH&MjR4s1 zs0*kKkXimJI!9e3vXQ+2_VL$gSX<^}jz-6|&?-K5R+i%uPrz0yG9DYqQEXKERIBr` zub$0?@*Q~(h1nFGO~>_5J2kktLc-MvQ4*yxUOZtc9eeC;pjIUCvhVA{QHX6#o2we~ zWCYdXIQL5$s+QDrY_K>++_L8T2}hooeOMX|T{t>SQ;F833RkA1_0c}K{+Dy(%MaC} zY@GyuwdMp1wE8>Ekgq6l)o&YE9aJslZ)0<#{_9}64o+R$B=*uaP%F+<`SaC4K6e(f zPj!n|-%q9IQEPrftq;)YjtcuKA%qmOo|SU{e5WR%8co2sY8NOeSm{5Glb)dez zSq|9|70`(l)t3FbM}h1sx4)JYJM@f%d6auPy)ZqV!1qs5a`pXcx|6={e2~t3E3YtX z)4|xz>r)?0^Z|68V(gLbkAupHJ~fiX#_MO)bxE(EY^O)deimEOeG(LC;OAq4G$>Ve z=6PE*L53Qi-EP$o2eRt`QBbGcVRaZ43Nd)Z^^x9Oyis9~f~lV3izll)?riSA0(tt@ z0MSuyS`*t>UASqqpi%cCzyHw_f02*BS$8tOZHW)heWyT8(!QgGc7|M7+M}w%c6%>= z3IzvPgm0ScP77dMUHBdoPyf{(S+OmTP-=7%6o@`HREcDKd-F=wa4}(&y};z2>{wJU zg>9|hXvR8bv-gj2>7F^TFX1r~j?L#~*7F?IUoV_0$^Mvr9D~x_Py5eghjGON;q#02 zWk=<5WQu5a7_lQaGecrqa1J)>|CM{NqN`1hyQgnNyyImLR?uyWzmlb0_cK(8K|cLK zv=O-E8l`pWReNvmdxc4Mrc!Joxl{ap(bb`TugCTri)lgq*D@J-U&wt_(Y;=7F=;z&v&WlDh`CsMresedld>yIfwgt46u8~0BkMNdhh8(FfEkn2{id+V=$0@slr9YuHf=H}U_1Jym$ z5b}W;v(Dm0y|gdf^HJBU!op-C3+y^r+WCE)xArYVqnJM{o(~*0g6j6Cu6i_Fee^4@ ze~Th+!`w~tz>SFCQd%3Wa4L3)yqjs#mO!}iD)xE}!ejSJQoE?8S4=_3aB#C$`xwB#HvFGh_=UyaG9>k{c`~;d{QQ9v*D7vg9ItZH zuC%HE$Q5w>?H^2SkLO)MG2*g;{iLw6Tj~SF>~u_+TmT=HXVH9Y-=_#|3HTG<)yvW? za$tav90W#^(&KFY`;5cl8=KD5`H6gIhU2R{?wt)X#LhG6fRa-L=zrtUh27* z_(yzMEH2`U^P?XdzwqSsf5kFWIp`Srpc+$>M>QwlKT+f3(!?fP`)$`(S1nX2{o>`K zj%2sJCg97ykVT+b`!Ptn zW4g8wD?mpKrM{~Jl;g`+ocdJXT@?g3bYa&T%@WV_Ce5|C@YS69E1+H5_z-in5Tm!O zA$YU$yw8Fuy5!KQeJR%u^!F<6d;H9IzY~=~|0OHZnL!#N<_h%W_BVG26+vy@gHlXW ztEv_)eo62EI@Xd*-2bovXIr~K(!JVWQZESEzNn_#)cCI5z27on?2)Uv%pvbU8LOn$ zA*-hWmK$)FTJ_r!CGn~G>PUSrcbm*cfyrUOHsCcA!F)r(*VDFMw`22yTG8AG-V6zJ z9nL`Oz`kB$1;;Ycn7464IX1rXQX)InF=53;e|V564Ke_8%4^ggR2SZKO~yT1_(0aP zDw6pSK{1dx@>CHHrG6s#VRAA!ehLxg-QyZ@#9|ZtleDNBICnG}sTi`}Txksp^;WhDGF(zj{M%0fuPQ8$2}k>5IPuR_@rP z=Z08|0?yKhh2%%_L&n7`!%r?O^P%if{s%?8obMk+gg*PWdq>|9*;Z|WevQmh91hgbZnd8H&f7+f z9Q8W_Ywz&+-88)`H&HfPh1-j7G(P6ejTSr3h1#XrH$bZ z(Ej=I)TlT0jN%rpM1(^pvwZ$U?e&lSIgG!mHA^9ThpBu%cjdWO?GEs|;+;;(3v5y| zLROj=URf_@q#5n%E!(|3gG)k#2`Y9qTrU zF(IWc8WAiJUFWZz zon)_b+9hV&XzMZqdoYJ z)_ve30}Z0$tM%qg_EMm_hSM%&qcM34W}F2`S6?$wYU8WAzsqvm; z{b`tqFsdzAmPEP_pGTv2%lpl+3YiWxVmEW(!T{ftO+rPUF}6O;;?pVA#bVnJ`WmWc z<2~nWq1B43NA>@?U$1r=XKRN?+`f8HH8EA<{$%&b%d|~R(s!rZc>Dsy>A!> zMhD&FNg9-6M%nj_%d;5@a(_D}CWuN5m(V&{BA4z026elM1jALo!SKXyzt9-}g)xY0 zukl0bEBl8(sD{$g1y-etPWRc_URcXLgY`R)ack)##F6gp&saKjn5P~Vm`T(ad{el? zXJ2yvYgl1^{rsy!M6XYwwaz$ufSNd zRjU?b9a0nLOYVs*>%H`Nm4gV0lJL6kqi=ks?IF9uR!Tm9W_Z!GMdBw=OP&uFHM5wq zZ>#mpjLk$}NM>s38{t@t(4XEsuTUW&q|heZ_<}z({qy;EvApJ8$c0XDFf#Q;dRn?}inMAB>@UkIp`8k_EU+3! z6oeP=XVcD-nTObC4oHJJJ<%D#Un>31Mu}xDkM-?x;y zpWi}RriSYUHF}&#m1%f@6l3~}J``M}NYGQilhy3mJCH#zbsbA>2mji#d-LnLF0r-b zX*HcDp-;*tP}95NKN+To`g4sr*cPT+Y;B1PT1FxBhBm8YCh^sg?;oD7C&SJ?r4%AewR>qKdZ> zT70^AnNq-QrfH4Rq5a5KWVV+}QzbHkvYH~NNVY&^vIujcgLo6YbE%cx@H zT>?{LMHNKP+RUbg+)Cd`8|qN-co*;#U^9^mUr{4f)+KStn7=LlL>BmQT9O$KCwe!S zEL}_F&K5emmdSt0#*3!uCa*8T{X^52vuSR&mZg&3{yIF9=OrYCxYNCo7d~AWP#0KE?OwUCre-Asv&Pw z)Cbo0&5Rxi?MCY^o?hbufByta2mXd#KfAt}f$^Dcgddg6sJ)+LI*qS%uz(Z|=pp<$o5**Te# zA_Z;Sts1Ql=7R2f29wXr?)f^B@hRlzv5z>Zr6!sxBa3YHlnpjPAq zY=JH@;4NXcsXl0gX5co?ZBKa{P@_d`7*@4o2+#!mgb~M)$;)om%FHd43b*(VR3@~w znc~N4PNR*tL@ky8X14;Je9l(J8=vv{`&5MHECFoQn#|sRfO|p1Cl}>gk%Qe$z#)(&H6BeWZV;nkxyVv)C>>a zgbnh8@gu0_1?QFCcj>5`@7+4VwJcLxbOPz&#Rj?vdPVX0^l= zU=B07lEi~QWP_OdoC#{g{PPBjCOjl*BO*3#@SMp*Xj%!GSqK&q^y`wIQtkps1_N`L zDO?zY#=!<%TlQE8i(;t4yN|mj(9&G`uQ#l zK{e9*`*D2?POh%97RNy%H<@P$p``ClBx|Lc(TM4x9 z1i5$mavH~7V1|Ks9M$t28m8J=I5QaPeGs1f1gBBAa$$63jl%r*8O1fI zs|!QS(_zid?xu-`xF0QE#G(5A#>kZEPex%SIT_Q*80#7ukVjEf+iDrrmGilCX?T3Xr$~}fj zQF?U7U5_=hLAIWuR8j=d5}Z}_lTJe6VsJy#FFO#TtuHY8`rAcUM-a;GHMJi{Q_HSB z%ntAZ;=ndGFw=~F9rYf=T>JY-FY) z{7N}SB#Uyqj>NCN$%8q8*%Le+SEfgmk+DZqb629}KszSBHtG?xAV7FE&!FrDi7Gf+q1jQXZDg8;YioH zWw^#FMYxx?$=@d!7((5vkMyV{9^FNjxld-?+@N@O)6f=fPtB}*Xg822kFZK|Kj>zN zwhyTrTDN;?Qk2+otWx?xVsjW3CL5;$*H~N5|1p=g^>&zYe3gz&_k4fas(VLtoBEDK zBj4bNNXsJ^qYXTf8blTArX^}FWjw*sX6{Jl*YdY7^g(A~;d087w8HnK5<&~tzTyIO z!FbmDK_w=1*Wq5a%ReHvFX1SDGeL`MU!Yl?L`1cHKsxBmjL|C4r6YBq=gfcwu6P{Kk(HD2SDAO^g+GGLI!hYdrq;c73csQ5jbX&)I< z-S7K_;5F4mdaWJT<~8v+8OvnD<}XN!1P*lQoD+lwq1gAHGi?#pUai|-y)qx(k6$k* zk+*(PoP}b2)iKO~8z87L#{ex``u+Q`(th3ObYhoN`65N*sb&R!D1q$F*%3UOZf+OS zL?n*wToAbE<7mf{bJ?N$1@dAbFXj|xvo%Rw6uQ2(`rtg?>X*abFieq^BsxK8w?Uz{ zovsK^oq=&HU%mR*5HZp-SxO4_WpQ{}Ogu%p1k8t=Ryr5^U|{v<&%OGYkgZxdcX5pU zeqPTRLZV*(aG|cbylvB#SbN&*!-aXrcqj);_0@a+N*G;WKv$e!ppSeSb+lVkQMn>(SA$B82mbv@Nr*Z>|hXAez7zjAWvhS9QpLP=LmIcL@%FQaTQ&#_z(Y$7b zc5`!+dRaWihCT;SdL2ydgiG)Znd#}v*sh2677bo`)DdFVl`5+j*wT^w@_gHPumijlvZ(ZS~szNY7b2)zN74ThU9h(7Mu;P1r z)N$y$Mq@7Iw!2!|g-gFq3tr}+#Q{|TH-s)m!u(1cEmaQkSVJ5-8F)EZj_;r}X*!BX z9wt)6uuDMnO#HKh*h_F_W#vu8+p$8M(f}sJqe}&87b3O!L6w8sJSQ|6NW1!EP6^~_ z+_y?=^B>@+^Uty;VJF$qEpbHSU&No(4>8BHK|J*Qo9v)Qha8x4wZW%%DaT%(o_X_N ze@YtLz(|>?5ftZrfa6Gi%Qit+HAiXi+5KmD5zfcQcg%r)!VS*UG*VO16)Aml@J1Dh zDdvL`YCCE#og-Zyy8n$pAhfu(84|%ag0br%04==l>D`I9=JpZWq3vmBoW*3}_4W1X z_&*_w}<`^9@@lBk}Zo~{B3 z{%};6Lj2faL5H2g>5X89+FoSqe*CeP_KS<_=`CI4Xun|qua%Y6;3iz0*a_#L6@nI= z=D}GM`@?W#DZn;GT8Rm||I++DS)Bz_2ENQcXzkfll!8%O6LeNCBLfp=?mR2`9UB`P zRFm7>KWq@Y`%SzXmiZ!{d`+W^0BJcvH3FuQqqaY%BsV;uWejVVfjNyivjL9&8bii+ zDv3l1pX*t#OE1p+RA6uf26t5Fr2u!x;T7@JcIA;*uIN(e(yPv8^4-MD_^#uc(Fom$J zz*rij+`;o2U!Wb!++9;)h1&LLHAIb7)t?XqZWBedQQIlOYIbQ}zSm52b#>LmQ{#|< zP|Ok6f_Xv)qHs9t7nhfp*Hs|Aqbr<6X`B{JXJEwStVW0$j~c^pdu*W+D}8rhxL{+H z0%Xl;r7XP;gH>yC`jpLGqST1TFB%57mrJu7K;hKs@z_dy!M*c5+&4l#BVfb%cZ=qM z;E7&Z#;ZGJ$i*HbFpXHF9GAOpqfD52v%zwPIiz^>(v|? z8Ih~;Jjm4ZkMa~AAp(((mS0y9bSQMpZDQvDADWLH&|YOYxS+A1$2^eEP>9z|?V$xV z`TmX+6BBE{dT&!4u=i0igp9FKp}-w5V?26-w~L)#cD`-p+>Age{A$-e|9tq4X9-?| z4c&}wsmAj{26%DDTpCWs8WOm5p5_<9*GSKWiPe{PCSq8wO z=234-UtgarP+wlDD8dwK3Y%6!FaP6_p?c<$ro zPg|y)D_)@f)sU79eVdC0Dg1)av03`MBKF|9gzwR#i5;ae(6qUOaLx|0s@Kf*PqoC( zqL1_~p@FDt;VpIF2~%^rHkx9pK4_z>o5;W*t_R#ISFVVzot5X4(PdBaoM=1!QtQlG zJ~&xGw~%vg)+aS+@0{evVOg_^(yf0|B?`E1)sPmB7n6*(Wyp)=8PD<8WiuNcx|4bd z>5iWAzGZMQDbk$(=So?^IY`z7z)A@MWF75Akav z*-SPJ)n{@5=AoiW_PGudJGEd!r)YCp5dG%HG91-^uC?8c;yb~VUn{qi26i_;Uj7e{ z$8*AJ20k<*(YcXhm*H+BrR5NWn8wO814_#YtqZ%}>iVcC56iDkTW$C9PT^wkp(%K~ zuMM2#A|gw9>{Nja$0IwG%Bx+^l@4G~nZx0xktvqV{8NVgx+dcIM^{PL$$% z>V^_waM*@|o${MO|N01f@|)$4PJqZC@fT_p_=Q)yev1@AqTT>MZj3Q1=2(1WXr194 z%=Gc{I3MKQmtPJArAypCSpFtK;?VMIx6C zK%hzGFG;n&B|h+O?#a1I@zSlvakw?&vQ~+$fI=L}sqNk2X|b8{CF-X86AT{#wt^sD$KJ z8CE;b{mYyrQ*dU@ERBdzcv?INsGPIsF9Lahe4u3Wmxl^gbuu~0tB^D0{7__*jrD(G zNq9R4!EWVZ*R~VrLJ(&t$jT-L1q6sLT~-X)pqmuxrslIz{ZOsKq!~<(&$V8Q*3fMs zV7fQp4K`^(N&dx2BlZD6f_crP1K##<;;I29T!0h?7%m^M0Ya-o+OBL&47Gq-{x3Du zCjyEwy)=A`Z=D0+J&2qeOXx?uJxjS3;q7&-BJkxkEJJ}gwk1&eig2sU)CW&bPsi8? zs>6%XcRAP%Ton=Y9|2bCTMae{IEa65!}eAmNe{GU?1ln(tB)&PK4ugXumw5PrO*u|i2JSqe@ZOzovDqZ~86mVFw zvK$;i7v>j)`EjyyK#8%;u~?f925m&ugXpucZ|_K_aPAi9|1EY;gYRWbq_h@daHE#= z7N`SWhV@-2L3Y#D->O-p~Rl*hO(e>Ld0^$C2Kq3y&PLx+c=2Y zNow!n;$j$by9fB&m&p+?;~bg$p`{3)nQviZI5IXi7P9qoSRZIvF|k9?;X~|6_~~4| z^LF=~e-k_>rj19X8vJ3fpkQ0Ho#A<&R7b_LIK4v#l9K$Kc9icak_4oMgL-eP3J0C* zq$q=-^4TXf6j(~Q27M^0`^~auikh-8F&W+=4n?d?MbFqYc-c}vzt)y88I^_Da8h~R zF~S_lOL<}qPD(@YBu@|8&zC_6dKFl=gsYrp6BBkbmN|X*bx`fWGSJrQHj0mTbad>8 z$mv9?Piut?EGfNQ@t7}Le5QHg1r9YeHDzx_@EB_|kOn;_KpNu+Mg>5NL+f@}lBw&) zRvMYGeA3cTeP|eQ*N7W46xZ=1r{;n@wzT4{tU4SunFK;bFWMBNmo}FRdcY!q$yfXwOZFjykca$^>K z@lNCfO|hi&x|N`}(~6Z|FHV(3U{FKjB*n-w$kl z{{f!lj4v-=V0^~gCj9~J;}sONYx=WQ@~)-v=&GDu%bv2r!!g2<7!b7st{L(%DC`k(mB^-}nw z2$n*;Ex<3u6TidT=Ga497XVsLaj>i^bI^M5i*#+v3674v0A1altAO5l9sskw^sVIc z!QdAqu)Ns?m+AmW_`P+o+TyvM)uNxAW=Q9GV<yRkv^6(t+hLP=r!@ltK;E zaC&Nzu=ZZqS1lt+p7^Ac;jyuOB_PK?$=atoS!6*!+61M+t=-exNW$uwC5A*SEBNC^ z!&ztfg(G&Sf{qrQ2gDTva+5&!^cqt9`ff?etBVf@UEmuNPI{4<8AZ+{!Aka_nH%O; zGxc<9PM(MTnyh)OR)tRyZ20~ajp)n-QDxd8R)1eMhanBM#DvOw`KiT*mY`SV%btQA z*@K#+H0O0~`s~L}zx1${rjPjDM1;p%W0Bi#)ecoQM6e)u5dE6=$P_)#8uLLY%i!hp zz{P7o)eyL%0uV(Gx}?4pQgSkYtd-%qL9I9t^tAs$G>T4^mSf7)a^Zj{9)GpL$hWHJ zt<5cJZ(_0@0TBPjRU41iS*wqXj;0sgFFU|>Q~J6HER5)ZQja%U<%qT$D48%${R_gUHX@3l_y_;Kw>y-=eH{}{M+lsr$1%O z&8s!K616fMBltl9{ICOJJE|Us_dVr%JYbYGP}v-rn4N;EHO!kjcb43=iG< z>HOpal~48T@t;3`Fs|9=$s#H%A*iE@4qx_9maz}QhnLSReXSHZ2McNxi(@mbS{5!Y zTa10ANR$@j(2#~T1Gy)uw@{zxX09rs&2RD`=bBk5RWcn+Iz^Aikc@Lw}==L9|($oINg8!xum{mlo7e6LZQhg_O4E6HR{+iS_^&K z*?Hku*un-lgVU=6{b>R~ChC1GIZWtfH7y${cDZ7;Outz;{1c3_Kj@ds_JZxu2zs*C z8F+PTYs7cmJ=*%7M5?&@b=KAc$>uy$lc{3yP9kgrc}3Xv`BJo5g@tdnqa}9&`J1m_ zv*{ZeI2@_`WqSH==yit%ko3|;WUG`(i|;~u;g><>gpmn+1b;A z9%vB7UiO{sw1oM60*N(#hz#+Yyo`#Sz{?6Nbu8Hkna1(CI~FBT2)uQb9Y{QRt!GL3 zWm3}-_cK&^Y(P3$m;<+C7J0b2L4k&P zq5AVbR#zIwXWn*xLt|}dfzc-gV|}BVD#gK!+=mS0!vt7sKYHp8N35*TP|@JhQbDe( zn@h!I=l7Vync7t{bYFiQT*?iFS z!v~j17W4RG@W0{5#skjS$@lh%eB;qeu#Gl+J|~(VGe%`)5BZO|c<(nKt{Oxb_Nt;9 zuJ$Xaa$A3_SuZrzAx?}1} zEAv|qmOgbN+^um|(HP8QRw&!~$M=!r7U+ck@R64(m0)3xWu`8yJVLx!$kc;v#ClFN z2*zH1nC3G<=t(5+0tSOu452N*zbiimpD}~exN5vxahMgfNU|CXj*&OOK%23$2SQyG zG@h7cs&Rj`;6Jcxyy`C^ESwL5HE%$*=SLlO<1f!I+sqzDnL~4C#9Q?^BHOEu4*}Se z-dzT5SL4wNwk04;^v-`jndIO)S_qA2w6lYL=b>+SK?(-~&B85Df`tzwrtrXwciSQQ z0t3G3U1jB^a^{10UTHfHY6n*df(gT1Y?0gGzc46Ln^f5#z!(L9CooLWW(osyRFMvhj8cx=)Z+8%?Cr#cePY5w*kW*q=kkRU|Fkn!3Qa{`Q{1CWNg zf=$c6Rs7KbbNuW@!Sv=0NFNvyt7q=)Rbr=gE=nCzWf@p7=7)wKy~CfYC>)KgGRXwR z;*0Bf&)|9pfAWC2K{!VYbL<9}=3YV!?ru@dv}uWj52w^ImdxMo|7udmn&9E8n<0|H z$6ob;p0@(>3kyevPyC#LcRKW-NayD_@8CKXx@fFtJ+V;M|+!U{0=mtZ~0 zxr!V#YndNib{mOID$V?2D)G3kM1fjj5LDzbt$uKDuoCdvl@XX( z^uWV!6AK%MKF`#v8kd9>c(}L(gB%<=Ob9&uF5ccV(-evlJ4wo!vd*f2v*A_YpwZO) zFW6f02?F1n0++;5tv0AjtedZA*MmWPEiEi8{Fe?g)dC8hEwEBo_?`SCcwzk$k0xlT-6`oXzx0~<0T(*YQ3o|k@axgVD?e*3* zX!W}uBP4^2e(RlHI#}evHF{21BbA}?h`*h)`iTFV ccJx-|K$Q{ody}?>Xo9|DFHyJO8u%PlluYdAJZl2mk=MwH3}8 ztciz3kRSZJ-j`AZD`<>`wW}cbB?tzlgMC<(l}8K!i1Zv5NWQv=EC|XFE!~MOk$7UP z-{l}6Ha1o(G$Je}z%MFDEAn!1(fU~gIPj@8&fGPwm{aU?$z@sQ^}}zsKb2x=l^!&u z6^keTk>-=FB$gLIId~5IdoY>@HiBvpY5UbBUf(Weva%hpW7!-Lb4jWwI+{GD)AXX! zlS60>YdcwQx#H`hR?S8?RFISNK5^HPsy!mnd&@@4@eZ5&>;4K4zvVtmSU!GZ?we`K zJ76HNA>!J#T|Yn$n4%a%eKpDYLrR9gUS7HuAYC8+y=GuwAU2@Bu*UlDO!GFr7Qd*d z4O1ir)xlMKkzbjE!r#c99=n+A}#qu#OumurpJ3Avx?)E*sJv!4K zd(hWpG`Kx9Or=t1a9@$+ctXNj7&D=cMqAEDJe+h%2w3Ur!d%Bpq?|j(Bu@hy65i@O@oQ{r;JI6K&8FAPLxmTs+DU`mV ztgNgY%SR(JIqWuzRVhcIWfZ%K0&I-is&K>LByXi_DJ4eHWfB+*8b9*d^Y-@dckSW% ziW9?eXJ>nu5`bx~#~atv(w8>hIw0x`yzq&T-9achzTv~4&)O{S-tHH>Z>2nO;T{RL zt_mmg-f<=bfB^`av9M9PoH8^tB%g9z7oynDbXbvc68R{c)$5;`jwZ3)g>}E!spOH` zNOvuYshXOaETDR_CIB{J=it!HmU0q`PW6v~*(x)_zYNzOJW-sS9@d#tgQ?G@1Tv1ceKc&pwOhZd3kv! zz^Q^d>)n|uWd(;|!TvB>X~+?vS5>k5rn9e{3e2GUG^bxa5=tKpL(wmsm+xzDZ?9bg zCyj*j0dE@;c`z?uSBG``4jaa2_8_KMtTVk#hqZ}rro(nnpMoMW&)pP@#rCDs9b9&P zT>ri$SN#wGp5BL*cD6`WaWaCchV6Qmq==;xo?##RO!n%|WBQPLbk>#Ky8T9Up0-=?KqV zIQdI$RK<^_r6rn87ISNBYxl#44+^KbT=!P}X<;$BH-V_mmbkzC8O5TmLG&Tp^o2O@et{iVWU z+_1;Z@eR)}c>xVT_JwrWqHQ_}Hj5%YU%3`tDT%YH!9P>i!$j)nh8!I&H{Ii9F z_#1Z*EV@rl1|l@-g5KUEF~WU-I?v{N;?s2+?3>)>cQ>GmS%*g!I`1|WXndMGvasOC zso4g1iA=wt1TX>Rxk+0-XbC@5t|%#_ZyfV1k*W5*U<)9rN3F~Ctn%0<@TQenz-gY7Etb{QNA(OV80 zu`4pDfkS!R79QoR3E-ES-UK2f#RZ}0c{AOc#)GV*wol9gytkr>Yc?e0UqfqTJ^Fu@Nudy3NCeG=exTU zzT@ftxCaEfheZ0y4I!0LU!4NIvFf8YT>gmRC5FUYv)g;achcAplgEfb5GJ7bA9*MW z|1F~f+@GWwL>KUb4hm8N7c>CykHo&+u>U!K*Bx@`h6zSsYTN>+4H@ET9?(Rivw{OCvuwu>@HV+J~(c@S?=v`j%u4r2adK+ZrU3}&hC+rXt+5i1@|?pzP{c}Bk|GVx&e&BU%r9lDjgU3 zB2dcR@t0RA+Ybl`==5G2Y+$IbG4mC5_jhka|yjO|4{uR8} zpJBZ`mtJjgSiCMSE&_!+#?{jygGWZFURvKQ&=0i2p3WRgwHZo?y5?5`sZ(fTtG)`U9FUt zCYjXaXOWp{4O55*1Ami_>iW8=B~A%%n%VlCzc~8p~wis5DiFa$_QaJAQAoo zMVUguFa%VVgfLZPi6kf$f(<|30__F`OCr>t1j^q;p8p9Cfr#~fC7wJDF?moa8)NMm;~Ehd z6OWA!fW*hgYvIB}@K>;r0a_8!LA3R=av(4jZDsCut$3}(J5+%!ODv1%quDvD+i^yJ zpu5~JR5hq8&nOdtA9us3XD?3$P~en&zz_c8m#Vb;0B1}k{2V4F(*IX=ph$v*SSWOO zL@FQePkkJvmL0crKXc)0EFuzUn99$C{?BOwq>D+kCpV)v_^@XJ=Q$|+Ia4sEf^HhX{ZAwZC z@HCM3DqiNgy1G8rbvBUf#PN_{uvn~nfKw5mt->8! z{cCD(Z*P{N>qc(E2Rk{9xL_3dI{iGKJyM2_)ka^qvUbV?Q`Y?_6*9Tsd6a>Z%)7h{ zU0Yl8mL<-&fA6bXCGBc15wo{0L(tJt3(`OdL+`6xx>Ad( z#Y(<})~3okht7c{>>dLL6Tvn#|5Sq--#w^c=$+0OnhAU` zJUTo)Y(Us`;1*`?8W|hsP!W=uu}e<#lm~&$qULc+*fU z6p=e6B#n{1dVyiyxUqbXDNeY3w5!Eb_482A`-IjiFqV$52a@IYG>0~7TA_(fRzzvz zDizDHA>FBZY>)8VZVUOmQlw>GM2gasF$#t9TrE!pUfW{BQL$%aA+y&0UDYQiM%@Q= z2Lc;je^~6Fv`%UWsc>Bot}Ets=?o-9b~B`dM~!^=LF-i$57bep2+ zIwkL2*=`@m0mL@@bd9|eL+yIiH}&A_soMA_rwpFyPR+WZRjWApxw*LqYI;^i?vO=z z_{Xq7sn)3GOCLW%v)9*>vG(p6r1$SQoQGJA!^NoxHMnC(&d}lFL%J5r?@loNfQKRn zEOnj7ca6S&{W=ehfQ8N<*45Q%%ht@-o9>Cl9Uz`dJNvA?y z46l}fH<-r>9)$W$bGW6c2?hzE`N=snQ3V|qR+zr_WY3Hig75Li&Vj*N1tMw6@A!Pe z^3~rs%MfW`W(X?sL?I-pg8Dp4)Cr_Gg=v!8_k8g4-e8|SpDSs=7@m;N*W+^g8$##) zM4QI+X2@BV@|{3`)2gdxe(yh(Lj^urFBxndJS>m1rcedRFWwIdU1(ksKOHykg*n1C zpkRn@|F`KtIDfM3*6bBi-sal_8e~N`SIa0A60{Kqg)nQs*CQJk!*YlPP2mUn8@-BC zNX3Qb0B{I-opbX(O&2S+kG;XYIVD*gT-%{4+VtbriL2>J3h(YE-?vk8ZH3%un>lC8 zg{^IDIF4beVg$9DO`HD>&tCJmE&_rK-br%$rz0?aegOcuZGtlH5H3 zs`&A+kSs)hnLsIq9RSxid1W(?;5>3+|w!UOy;r-Tf+cP9MX2Y1c zUr4~4OWeJr%AT1SzFx5JEbo5jpJR-b=_H$+;rZW3g@aVO+HYhD;*nsk4lw<`=l_H# zs`xE1+ZXKrBFYtgvYy3vj4F=7R?zS8PX$*E$qK0OjK;!ppJ`A1k^ z?RxjFiose9P>F8GX{x@Lk&`pJ>}91ek?kBw6-R^l8;sQ0T~1hQi|$^dr(5v6#o0ecI;YDHO_@%GfCD4?xVPHFExQ zZLpf>OmK%x?JxU&zvj!(pJe6aJnl%?=DdO%$s3_N)7PbRezU7wPu^cHsP&y8S1j8E wpK10towB+hZ$y4ND3sYVA8DviCUz)(LMt#_bmrg&_+x{htsSlEEd0{`23APY^Z)<= literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_b.png b/dist/icons/overlay/controller_single_joycon_left_b.png new file mode 100755 index 0000000000000000000000000000000000000000..7429450a33de84a48866309cb0ed7e65e33f7913 GIT binary patch literal 2559 zcmYLL3pCVQ7yl2L3WIRbm~f?p2AK(YGb4|XG|3|jWtwRSA#~A{46e*@J*Se!E6SrX zo}rQQsKLnK5~8`}dOu4O`u_U9wZ5~~Ip=rI-us-r*WPFEljQ7V0~3Xd0ssKBv&FiA zbJ$Mb0|iIiJvnu7+7)VLhuZ_T=slMb!S&u?TlY`^ka)AxA(>hdis0t{Fl)E4vq6Ng z2;Xb|Ktx1@Ht8xk^rCODzjn~I%l8*d;Gkf%9o7;T`CvZRCjfE&x3*U(a^*o|l@g8Q zOdRdZhqCG#%w+0oMVhAC5!*+%Wj>>6@hz;7Q+I|Ri$1sh%>-?Ad*IyBQ`cR?``YF) zY4$!ArIEL@3VB~E=2#lUzB~HbdlTQ>Y-2E5;|4!mH#L4ayA|4vT}|KAH~zNzJ#1lN ztp1zl>%a~k4@ZoOic+R10izs*2v-PCp8D%Pux*W%2OdNqz62QM#%d|ijjjIDg4&6d zsPXyyh<&}sRE@;(o=G~d9I)w3XW~Cg6Rp`#nU!)Qa#R|f{)wc}b{3!s?a7XMc@E%D z1aEvvkT@WS82WtW?jkHVC&v(r?E&fvUe`9aaax6ig>}wtG@pB0OQq3h0|scc$!`3! zdwkv1>6w|CgBT2^_Q?sSqNO5cK|w(Z3bH_KhWlpe^XJRkV`F3eaJr{7l|^ysbf~JE?7Bk}PbEylXK>0CJ3AOQ z?AfbVKkpVob)qj4Rnv%ssVE>%ChM9hLSk`1klAK%RvYzGbcGsMB zIp14KOG{Hh#8Vs|7`U!Ong3vP;qRKp#t|e^Reqi7H@`2(9??FeQ~o=+(p&W=!M>~H zKf)NajD8R@`5TE;kvCTuRzY*gH!%F=It*6Fb~boIB*jLZrzi@5j^0H!9_t~*o4?~3O%f}rCR0#|feh?$-&0EI%m z{ie=X?mf8UeKDGkl+S)qz|qmM9ca|lT~qe(^rVa3^^|T#v1)4v^&P9|iQbhH&ng+k zLm&J4F7^j=94C@v>aSjsKyFE}Pd_o?*^=M~)E5|YN-;k>R|$iZ18ZYo`J^nXlwFzOW!%*`fF9|m8s#D39Ncyi1De*iX&CUzw7 z4KEug{L;0_O!1dMueb8x;xC2n&GN7OvVKbNkC%ohcw?_eUL*CF(dDOGa#v+!WYm+0 zB@HiD^scQ<_fEL@$R@vS#w)bN5}M^LWZCi%G(SBdvjVcQn-WFX)}k1=l=5tfXhr2_ z;ql(RJYHA;iKHPkZhS6%(#ehi?Ip76D=I1^*>S*Ey{f^J{wk-C%aee0*VX(Hw(4Lx`0iN9~&Q6N(&o zQARvQX!Co%8_|9uVVJQO@OcsVl2vRI^SN7q`o7%VOC%EV#E4qHPEU7t_bIW?lj{|)+>Xi}&OiUC#{nPM zU;5D0Efen{27x^M%JtP_pDdDA7rL!_R3DIBZk{iLa9C)h5BGkf$3cevESih1 zIL3sB$z?L)FwJ)uKUK>ubD7vMIkMPjY#e0M-Q$iC26bACcd|Wlsdz12&VtF@|1ew5 zS(8&{Rb`JszBhv5&v@AEJvv-d{<-}&4sK*_elsPptuQ=;Z3p|21Qda3)K@`$oUYx} z!ci}@E-RQU>ws2xH{CcWo%A=bkUro-Q3oG_auGr8WeUW#D+-ItexCjNLt)|(k!NxsP@4E-6-Y{f1JlV3JRK!5=zP=N=wrYA} zS`Js z8EKSchu|VUwo6mg8Q-7A>dGvU5(s@@(e_hl<73w@;ucK)RYlQ-B5$B>(f-;PB)ORA9lx!e9_WM(;#$pSxXfSt7yw!#X3`#--%tP=nL literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_b_dark.png b/dist/icons/overlay/controller_single_joycon_left_b_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..3bd97a8eb695e6f9c2d7c0982970b76a900f7594 GIT binary patch literal 2383 zcmX|D30x9b6MsM+$%n4idZ3n}Yp$A>+F>50V2XK=VP4p38lp&EkZD-18B$~>Mrvrv zW?p&bQK<#n;gNnGc_rnQDWrxj>X+S%wfoKcy*Kmb&41p^Z+`QB?*h)l8KHz!0stVe zE*LKvr>;#!dD)7;uD(^q*2Otu@rp8{D~2*<`}NT-esKUOKUtd)j)}6iOsPe6@}(Y* zB2wdnV~>OQ_;|DM6Xdv%;OOIKQL$lMi7gTU#V#zy0e>p*U4CGsPT0?k@wz&4kyPI$ zLc^rY7)RK;+sqrj&Av`e@VJKpH}=iQXm`oajjkY(`Gi~b4#;y%TMp# zKhS@X8fu|c;%j=Qn>GqV-O(0!fG@z_>#O-~lvIPwG7#yuS?{*8T0XFgOIyLm%lE1G zVi{>^3+@JLA>g4$YyfLB-&OicTa00GadFj2^lzGxN@&YmwI>~XkjS@-x2jp~tINyF<~H=vDm6c}v$ONz&d$!Q*fY1e zQ_Obr#M7q>_RwbWJnj_rq6;!n+{EoW8z3 zd^-^;czk)PE7T&l@V%&NgEcxkJKGaQupIvIS|}9uY;2{hcMjNZQq=!emE9AOZjL%F zub`lzuAwph{CO|4{S~_Z?eY;e97v8_i@k|AM)-em-H`ih_L~e|lp%~s2wW4O;zFPTpE<=YC`Mw-Ze=%MXY?|@{L z1v0$!Ned4p_4(Hh927!lx1dE>S>V^%k%T|J1MFS%v2ue?eAZ*1M0hof#w`n;`@zGt-S=Zcr@ll z@6Z-hM!@J_3YZBVNgob}^T!*=KKlS?+y(2AXB{_VonFiR#X`5q!y;D)7Hv2mgZ&zD z{P=WPO&f*dLNfw{hxA++4E6zo`b#hQ_bZHiTHt%}NL^S|n$^*lm~I^K_4hZPALsA8 z+teg9K?UZY!Fp6wRM7R6h)`W2sWLZwyR9u9kGbJ2iXSv*wZB+irkaV*KdSK5Lsy0( zul79n{F7iegl^Q{0A9&Q@8!6D(aY`}RgB$|{;~de>7d)Wa|$Xa;&j3Ym;>O|%hzq= zutl$0-#o=8QlVeLHAK2&vBNH|JE7%NjAz35yhJK#q1=nR*sK)PK^wg<-MD+47mys0 zbbXgqx7A{sYtnU#?}Aqn(#-TpuKZWu+n#9jlpE}9#b8@}@Rl{BV@b1PKU6~{lB?Qb z$kmVr`<*u5aj{7eCrNqX6B*MV@ibBbXF5>RM&2AX%scNp-rqwj(C+(>vJEw8r+4QLJC5FWVUzp zF36K#%wt;7HS6#N#b@k0MN6BPS5|Zltw$0l@s6>tXa385dw|Gx%d~||r~r?`IN7_D z2CV@WP02O2O1#SQ-LHR8Otwx9GX!*bfldeY=C{(og8e+L;4tJBTjhnZp~c{;gIB>N zmdoM8w-3yqosUr-wObjT-6{TPSB|}5Dl)Pyz+~X$Tn*C;&`&|8wYCi+Is{APFI)$S!nWV7m z+{mxlN3RM|E#@Dt`N>7|IitzfY?hmAq6{b8A9#{kzHOmt&jU#Olx{N-Eh_*>$R{26 zT;F%Ss+cyJSM0;`ZO|IBcn1YSSuAY?f{CjK7w(Pi)EpQBP_afPpM0_A1yA88J4=Y? zl19CguNAerF&WJFs|WS<<_n?F!cAH-NmnNQG(>iuJ=WKoL53?PH=SfL{JXc4QWND< zozNlBpgvrumV7#{C%J|s=GV)un<#DsEXKpiZF=DBfXo^0aKK)FjNQQFKX@8z$2%#I znISjq7|cAQJ+D#z32UjrNaIN)^^!_8nY&)os=kXQgbD?z#H@b*1o*RNX6p@<-dM_O z&->d;8ff)`A~{RQOzZFy-WAJv`X%~^XBeUTItti?cZHd^4bD26ud_HHYYgn=td{)?*m`dQyE;=x z|Gv|wg>a1}IQ-rIJ1S$mlBC7UN9aoX%_Wz70_7K#W$cUdYJ^SaSy1LDy}A5$R8jW% N0azywOqF9$%HQ@KW+VUr literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_dark.png b/dist/icons/overlay/controller_single_joycon_left_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..24ed2c44c919f36ca9c4211ac1e4d03e70dc739c GIT binary patch literal 6768 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 cc&PhF!+An+!;Sb9@M{L>l8rQfH`;=tO#lD@ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_x_dark.png b/dist/icons/overlay/controller_single_joycon_left_x_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..119e3091af700fd7015099a98687973bdc5a2fd7 GIT binary patch literal 2392 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_y.png b/dist/icons/overlay/controller_single_joycon_left_y.png new file mode 100755 index 0000000000000000000000000000000000000000..24421d8b9126458f73acd55552992b3466255c8e GIT binary patch literal 2641 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_right_dark.png b/dist/icons/overlay/controller_single_joycon_right_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..afa80e6ef0f54acd62711c65abab9b9e7ad204a9 GIT binary patch literal 6729 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 ldcWMtNc(@e`la^d6613Y`I`V4 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_B.png b/dist/icons/overlay/osk_button_B.png new file mode 100755 index 0000000000000000000000000000000000000000..f4a04117857cd2c50ba830151e6ec7006622164a GIT binary patch literal 741 zcmVP000>X1^@s6#OZ}&00004b3#c}2nYxW zdP@ z-t+Fccka9Q0sGj;{{r)j>+9=7p65-GoM^Y($4Hh*t^rtYOp;_8 zz!d-odz@$ zeI%(@)tyS>IKBhmTE<=j@B)D8CIX-jKm_1KPMq0NsdU3Bk~aAhGCh#A7)8-&-hoD= zG1_jopQM6aw5+wqU6-NYdEOKpO-_?qO(B|tnQRd?x@#lwWH}S8Yej{X&{}a&j6mLFXOkJf_L#7 X+Klz_&aj%Q00000NkvXXu0mjfR$)gs literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_B_dark.png b/dist/icons/overlay/osk_button_B_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..2d2bffccaf7d650b43846a2b9b24254cebdc15ef GIT binary patch literal 767 zcmVP000>X1^@s6#OZ}&00004b3#c}2nYxW zdqUg?F z0suy%D7sxJ6na%n8X*2eqb>0T;IX8vnXQjwIbaj8t0PA-m&;uQ&L%J89uu`eq9v&V( zXl744?BA0hj+uQ(8UzM_5^x+i1>7nYi@8dr@(lRc;eDS7k#xTYq)le_s$4EN#>U3d zK)z!vF^^+r(=^Zd9~vASd@X4}(m*zw-B7Jo_W{wu!omrfRW2)(bTbJe0MGNT0B2g_ zGm=iXyk~&Cne_otya(Vgv8zV3GJo?+e@N~|So<9Hp002ovPDHLkV1itxR5<_u literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_B_dark_disabled.png b/dist/icons/overlay/osk_button_B_dark_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..93c102b1b977463f3ea2157c9c838b41ca2e83a9 GIT binary patch literal 781 zcmV+o1M>WdP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd|hr! zONaIlUSgU>mO8101S-4Z+O_Z`fe=I?K}A0fX>{lqROF$;gW6#?ily3DK= zbn|N(&rZ~)iN0^e2j zaXOv8KRY{n*7yAv4fKsD5d*A5ISG0)1f&34MDFEsxpv#OUjm;SxSt|LL}amv$To<` zn_{uJH9kHb13DUfk#S<4=gnJ|H3qabI*Y~P+e9MK31C^)*6{G~Pk@=3nM(v!)==_1 z?`~vr)k9N2rq2FORj<^2w}?oGh_nHxRP`dkudZRzahz+Bl>o%!@zFw|aFRc(<(P;( zX(Wft1_ZlnEJ}b{HmOvqR4$hXf#qi85G)yE2AeI%-y#5+OlHj()32&i1n)&Nlz>TN z%xT-U*LTV!^kvLuvxlm?XnP000>X1^@s6#OZ}&00004b3#c}2nYxW zd|z)H3lbIdeZSQhlTy|5B61iw0IaC$f{4sGj+5){?fv{uBID!Z zjjrnstLkMD*;8j<5x5hE;b4D%e{shmzV9D2#ykLy@AQPgYin&+CX-pJM>0@$V2qjm zO96m(W6adp*jPQ+Bmi939R}K~;$MMhz`n{sfF@u+aI~gIM>3hb30#j85#RS)MdW?W z9n5*2*BP}T2!c-F;f7!ZMaOZD#5F_07?T1b?PjytHW9g{s^3Kf^q?zUhgHiL=()=ha*hS&0)ds=5%HG$u*;4*G#B!2Ntazc-aiy#PMd&_Bl35s{g= zWGzJG&HViQ`t0m%g7UkmMr@sg<2boesWb#M)+Sq8THY=$E+zri*4EYs1_pisWV6{z zB9g4479Gc#j6EDc5Co&ZwW|2Mh+M5KRMi!cCZJJOPXZU&ngpooNIIRq5$gmX48uX! zbx*K0T8^pelWJl_%U1Aeb#-+pF1hIhHmC3u@JD;r!CXT_!-bxnp5=O^P0N`~W~opp zoB~EE{}=U8RMn9}p>TRT0f_k-CnqNlS!+`pqoNIHru;L0FvdJm)tu*fU$!e=!Ed`x V|F``x6RiLM002ovPDHLkV1k4+VpjkF literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_Y.png b/dist/icons/overlay/osk_button_Y.png new file mode 100755 index 0000000000000000000000000000000000000000..b08b4e26b9cde9477f9541ccfeba2f14cfc7ce7b GIT binary patch literal 726 zcmV;{0xA88P)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd zMZ_Y-RtkcRUw=R=QJXZT5o-fh@iT}ZB$Y`$&TL~0i`ry}AYp|WaOYUeB!p#VH&N_- z%X8mzo^$8kIT!xxzy6gZE9f}Rm@%fIsuzG$KuJWlRdrKDmQ{7p^SrNr1Q{P6FANP0 z-2<)zM|_Ui z=g^XfDJU)1d2+MdbeEgwvRD2g7a>WS_IRh`L&kbK)L zU8>b;7k%IVVvITO`~HgKIMcwjEVGE%y&$E$MO9s}ZM*2Y?z-bRgCg=I&B^lS)-fWo zowo>73WdTA0AtK$;B*gfZXKhln>`XCxyAs;`uINQLPTV_Px3+aCKsZrizKt?ZxoXJ zJDC~?f?%gyE)N6KSq2gLJ~A@WiR1XJh}5z?sygrc{!$)9U~l@a08@wRKhatkhUYdm zHljR(0cf>ap)qC_SUV)(gJoH>y#SC7NPw-at)InW@u_85gFu~R_~j{7)%hrjt~8s? zoqnBUzKqpsbyP$esyZVgwyGXaU&c=&@>WC^UDy5AulTNh1M>sj0H55+cmMzZ07*qo IM6N<$g0&S$+5i9m literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_Y_dark.png b/dist/icons/overlay/osk_button_Y_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..1fba9ca93dd607194f860d9f908f0493ff7701a8 GIT binary patch literal 502 zcmVP000>X1^@s6#OZ}&00004b3#c}2nYxW zdE5&Q=M32J4q7SUJ>R-#Rel^|k~Sje%M3B+}0c303Ux0&5H_r0?-!@iMv>glf( zLtsO8Nwjfhy1l6@0bvDrU<%D#Zbc{5*pVJGT&uZ`$}uDBc1JKFChQ7Q6vt zcK+1HZh<}^2Q(vcwtbMxhy;L>u9dz6^Y;DF;lJ=fo*hX5%hoe%$2rWIMXBk7e0U%Y zV9@&dfjf`4?P6qP{$!WNC2iT~bxG46ud_}@rd9Zqv*S>V@4*MD#Uv-ynGaHtOMX>V!Z07*qoM6N<$f{M+_Q2+n{ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_Y_dark_disabled.png b/dist/icons/overlay/osk_button_Y_dark_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..6ce53f9e436caa8b4510b3fa44b242787198b271 GIT binary patch literal 694 zcmV;n0!jUeP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd2emGB5}Z{!NChEOu;S36 zlL5h{-uGz9XbmD-Ew7>JA}*Z@#iD{(#4g=TcSo^CgLuEG;ed?(Xj1 z2PT2TDS0(u(eu2Sd_G_68ZtLGcdVzU=M`|S+Zh6z^?H4*P$+D7gd{820M7pdps9+g zju>Npx(o`CY*7>N3wRE!0$*DE)g-=3W6J<8#BqEtZ5Emw^5ow!Ywbo51mnQX7XO_f z2*$0oD?o2EC?X~d!@;zbOwe-3BJyBKT75D*JL`$aVoROgOeQno zge1RdtNK;7oD>g$AAnyh7NfqtzK6i^HaVkdD;aW(T`!eN*L>gqq^g&F-`^;eO2gLL zTMqx}G{}H!P(&8X<#N9nM#*cIjjs$F~8p+3XGAOp4c8M}e)B zL`1|(K98jNfR9dyh^%!;?pCjzkSL0l`O{eYRV|L=71v;9eSQ6VE|>FwVTb2iE|-ge zi@;fjC!Cs^df|d7faiHLz^21<6?jIYY1^rys&`$0JxD&Euhr}IF`$xWZliaqI+k`h z?m++wg~GO~j#z6S)A@{Qz#~P000>X1^@s6#OZ}&00004b3#c}2nYxW zd<)Tx%#!6D_zI}Ul+kmkPBf?L1i zx!-d?=l(huj&!8|CFvCmf?(JfQ&iP)5jg`K2ewspQ$*Gr$Ei(CO?^8QWN~pZmf?DX_>>%fp82u>Pf-T)U4S|P9zMbS0i_qUQv2HGny#=QPV0Klj* zX0=={C#|{-ab0%~7>#3*h`a<=fbU)YpK*MJ_Lc#RWwY5wiI5-&hE;W@Gp4HRp63-r z!u}`^N?s=op7*y4TE`KJ9 zqM{X|s^dN41CHaA0ir0n5Bw65d-;6+_wMfQ6W~;Lf~sD%LgIJOOV=xv%B1i6Ux6#7 zQfa+ftzHz7+ZO+b6+-)=^^}M#RIAki&+|4KjYb-H5$Eias#@nUz_x7=$N_f%!Z5rE zoJ%OkI*(D+%>)R*U|eaokMD~WA|h*jk`E+ReQSj{j#FdrVI5YjR4TP>g~Z*o``&lz zY^_$i0i3rPRP|X>S55(hVL0o$?j`nGOQ$AObt0jNk2^a%4->}sb>UTDEGeN&AJXac zWT8;_k!1RN=Y8MbYBrmBV4nVG)Kb-_&1Um*KLA*M#(KRz6h%=nZWSZI3EDs7XJgDe hRjqlR*WID5`UAl3&#)*bw|D>m002ovPDHLkV1l`nIKuz{ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_backspace.png b/dist/icons/overlay/osk_button_backspace.png new file mode 100755 index 0000000000000000000000000000000000000000..4ad284720ded558efda48766f900bc4426e3cd63 GIT binary patch literal 2919 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 RA&CG0002ovPDHLkV1hb2VV(d0 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_backspace_dark.png b/dist/icons/overlay/osk_button_backspace_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..19ac8847e42b79bc89d6219359f942926b248980 GIT binary patch literal 2958 zcmV;93vu*`P);VU>uUpy=TW+3)YCd zWks|lc6Mg(eRh3; zW+?6eLFnn}Ib?8f@O}VS#hv9~r{x3^LbManS^yWtoh1dZ;tt@4LZNUN5p6&!Phx19 z8Au3mI1#M}aCY2TQvOd%2xQy#kpMO^^BHkxN%sm$C5;Dordq)blYW*{NNd;re@ zI4`0&0hp;#W-l{esFd1jD%Zh=l#>1Y z|3ktG;s+tb*`DXE2XMGXnSXkox3s^%|7BCTQVvY)U*n~e7ZA}#=*jaofSy#I=!sPz zA;jeX9tY5&QRYq0^Uif$w`?d@iVtEFNJ{w`B3c8WEocp9-VI<;e}BI+7A(aVu?Qrk zydJ;<(EAMbr%WcZ$aURU4aG_cK;#11wtX{zyEVt|A08e)r(7<-ZYWktKq3{$w(Xml z`3IV1N{vQiaivmu(@3ZkEu#7wPeO=0nE7iuMVNVetya5W*REavHWe&Ik0@p!5<>ip zh_vJZuob|vxaBDn3bW!GTL{s#J26l%)Al6QAG4uloAyRg|=F)_9%dr02UL`(@Bvi6oIR4Ua?bt66Hb>$*1p z@J!?i$~f|jWr&$CQA%wHW9$j!8Ot_iUT$(}_q&;DbO!3~?moivygprdnE5u>b-#>c z%L9Zs6HP%LAOSOxF@f^={G3Lku@%5VojS~Xn^NjqhGGR~3i32DF3}X_X%eeMMg$T< z9P4@B769EEWtf@2-eU4JF(#2ud78u~k+wj&T<#Y#5{pDy0?nU4|CCH7vmHQ}Mwww|zSeP^yL7Ucf|;*}e|eg;6H%{i+e`gED1>O= zw{KrBg2~h53?hp32vlH!q?9?&^R@ywQDgieX1+`*^?*hVqhzz$s{uTrQ-YbF@G}!N ze}#%-4``#_$H7*`F#F#jC_SWUSsSUGZ&RoztN~+oKmS&&t|iiLQkS8 zFK3#|R+CVFPM8ee0tq2L;d$P30A_29`@Usa%au~=HEM)LR1&qiJb+JuY}-DIh&BPx z*-(6sh?bPgFG(tHNqx|Se_<{ zm1x|})ZX6SVP?J(Bd=Ks1QD$m92~q43A)T&mzd;fl1ParY}HUo4b|)QO91>)qyE)W z$~!flPYebd%3jiVK5-IF+UrxTR`(1I4J`%GuQA4rQp)dYJRdtITUxW(>|#W8WGD&_ z*eo_MFff?OWX=Qd7mabgCxrN_#`CdZx&c1wb7x^QrEbHWJ9qvaz!Cs&1s#Zper(&e z?uKoogmrpiI!QF;c2(DPUnQdR0K6M;EMn%LODR_cd_GdbK6*1^P2NS=Mp@ zL2ty$0C>>0?Xv%B?@%n z!f~A60=NRf+dyVA^JXb!F5vT_QLEK{1wDCcM6}XRKToMtsx$Lt%>1Orxa~yLE2aFn zMh)KtKCr_Q{PO6%&TBPloauSqx~{IStf5TWOhKa1PESlxqFCht%w9C^ zjovS~QKQC^j*gCpip64L-e*6FM4=zOIe6N5*C0pL0%t_5>%Pd$S778ah9Df~dESQ8PCIRZ$y_OVL?#dbD5W+K(KQHuk;5#@vYyH3^YaYG zO3@%vfdIg9oQIhCMvW3jd!E-Pg!rhjP$_|lTp$2YO5IIFw`i0&5kQ}m@)%Q@Qi2hS zKmg!4&aD7`6!du_It9Sy?(Xg*%%w{4OWgZSB&EC;z!zf9krI&DW}s0xm&;uT;PIGq zqy!}H83@409*<>NPcrkT;?9!dkGN+b03!|M?d|Os19&0sEGhm-#0=D=P$V zM3)0-#GNH&e-d;4YBJt==bh@DIdgUcSb>Buy9XQp1zs>)iA2UVssI2007*qoM6N<$ Ef|*-odH?_b literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_plus.png b/dist/icons/overlay/osk_button_plus.png new file mode 100755 index 0000000000000000000000000000000000000000..5baa5201e64079e4e89874a182ab789b75997fdc GIT binary patch literal 626 zcmV-&0*(ENP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd||N?<)0v`x*S5d58w`fi#6*C z0Nc*FyCjRbA$gu(7LjKFuFYE^fH&6KZjvO&bwLIg?Lb7HE(idCbrE@@s&%^_LCR0G z9*Ox0;0J&Z@m~)i+^d7IJmh`E&tNT0)Au2Sj;bC+VhRzt8v7ZHG3Is+U|ua-Yqw%f zL-OVs0b^~(982Xrn6@c^&UoUS`vw5bX7h6hVPz_AAAlWCNOlm>63MJ$W16N%)j*Pe z08H%O`xAaut>!ra>8WaK+UUJsD|duE&p)->?O&>TA%w6x&8_6>MImK%t&08ac+hAx zRsnqU-nU4;izNbZ7;_j^-K|-;h@M9wBC>-~FFK0~5qTVilox0_8eq&T@BM(}hZ@ME zx~@!M(tTed-dgIk=bYOh`8!%D0FW%KwOs&vi)2^T*Vfu@9bf_hfFwzdopT!i9%A+x z6#yQjX?i0`lJB#MIrU}i_xtU#7j2U4sOqKiJ{P000>X1^@s6#OZ}&00004b3#c}2nYxW zdSoDdPY zM*JZkL`e}@bwYqEM+5+%sw=MC38Fsu%P%7N!Jyr4e*;J+lb?5YcZXh8{Qyi2#-np4 zL1xEdQms}u#*O82`4=#ZTWiO6GG6Boq~FQe(V(?AZ#>T{tgo+!ai(E2HJi=W%*@Oa&+`(%d7>V2siUfo!Z5s1 zuh+NtTgk+i(ONqvBCD#pA|iQJJ>Bo8k0SC)M2eM4P000>X1^@s6#OZ}&00004b3#c}2nYxW zd zr9AS32TnDJOB+%W`~xxtoW;`7%@lML2gQJ>OASam#K_C#IOGR0TZzW094 z%jKTHC71j!%&f&)t#%vOQPqdQ9pDDg23p3Lqkg~NC>D!f|4CA>*Oxn;&NE;axEiys z2R!$Ee=n2C^cI$s%jH{%MB*iIchL@klPHSPVHlpyl?=b&HE{17f)Py-*$jf*udeQWL0xGrRP$-AzAF=i{5%U!qDZp37QN~N;ul&I<>;xBm-mOang zaY}#(7Zi-xb`_Qwyn{(pRoBK{h8|bPSTo| zh{#)21;F#Xb>O}4`zxyY&UFy**;No@%u&q7^YF?k>G%5$2D9iQ^sKcnTtx#=tyT|! z(2+R literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_plus_disabled.png b/dist/icons/overlay/osk_button_plus_disabled.png new file mode 100755 index 0000000000000000000000000000000000000000..c23e9d95d2a1abc572252e8d6ff8b0ff94e19151 GIT binary patch literal 664 zcmV;J0%!e+P)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd6N)mftqQ$M>>AiEl z=X~DfzI!fQ=tBQXa#qmy{Y7I;URCdj$W7oH&{WkU5jk)ir7`^%2wD1hs_AF|o(uSTPh48w3~ zr0Li}7LleEG8h$O5M#_+RRtgjf+gU6uh*Mb)pxc=z-L>+h{!=q;mlIimsW`5I5qmS z=sXG%iNteTQUI#e>JIS8;yD3&^ndp&XBkv=dwqTVu?=DX!Z6$f4lSO`z*QRnc+=^0 zp4b5J=M>%pR^tjL^g5YLKFDUXKjKVg_)shskK66`ZD5D#dlabZcDvnPn+5>O%~-G3 y7osT24@N}>xK96O{A7%IrK&a0^S(`MKGbj2k-*l{Sdi8L0000*!*o8`PDptBz8v1TB)fSxF@tX;p{NmYYE01WGuz+Zs zZ{%$N4u{jhgx-nr_YJ?T6BZemzX_KI0EFHmEzMo=1z%?GR?51`4RE){2t&+xUIXYK<2hMY&lw;V^k+@thuV~tlt~|lq_~B z1Y8$YL!lXd7ue#GxEH>tpv}@+XdwCc0}Kj|^aAX>x>P7Vmlf>;N9Jd_8*4B!$pM;Y zoD9Na9nO{&?}3kKk=zR*V&P^w$g*$-sDt)>0XgN2eoXS#$WTSYn$9@BlVzN{+DccT z=oZmtSBgAEd^Pv@9O>1KjK*FFTN)!Xis+J?IBy#o1Io_y;ZM6eR9}|13!GE?!RHNh z1%xss+{LeOc9D2RDVBpIgJN;%voqQ)>vz{Gw zjS>RZhIK${fHg*k2estxynSc=x1e^*!#7CkrLS5MVDPYA{`pV)aI zF6z`QoXe3*I~J`=Y01f;9Ce*wp=M06#uIN6w~6=5O+_kBa3I_>yO*&CWw+u^f6?gC zg=-|;p>*cX1E;MDqJH9}erlhvEO5~e7vZv{?~s9FtBjfDP@!S{0xxGYGd{5VF>FLo z_JQ(`GZ_P<5Y0KU-V$!_g!<(zD*UoX#^^O|;yxes_Ebriz-j-i0A z~h4)QZxLA$4Cc?skJcMX|h#%%sgZ=Z$J-{1_&=!uhrkFFXg4f&%larHae*h2`CRe|2A>s8AN1AB4D0>S$#Gla_#kndqk384FBGRK3(}N z+$OB2nz-f(42Nll6WRKXJTu0H|LDRt}~P9`n28jyKhl<>xK(aS#Bu0R>V zn6Z^_HZ@+`NuA;?cDNl-kzs;QLb=Af_Yk<_;Vxc$buFzWu?|&&N!C6I`c>_y_4Teu zY#MAy6CGAtJUa>ro=1+1{8GH^MKbX1(;FQ32<}X#zS5jzB*ZEDRS~RjbkhpYvQQO& zm7D&}RQT|SLmaNeo|@@bMoYrbH?k$NSy~S|Zng0OfuF#Mla85v2{M-BG?|t23tCfE zsn<#y9s1Lc@oreRH}*HS)5ex*jkVcNhVVU2M)gD(Tg_?pACB+~vgSQr^sca>;LDuQ z4x+8mgqa74&i2wRlo>Vqcs-ekF5`W+seC9Nb`;}kmYT2}G*rhg$vNPYs%N8ZY{1b# z-x00!@&40Tf0Yo?Ai%OUcGbul)n}yaLLV9(C%OGMzH<(8MS| z_cc6eA+W1D^T+c?V6DCZrJkQx;}pnR5X5xOl02Q+{nEWC@CfLIJYvMW%>WLGMz*FrzkxgrfwrU e!vDk2K~kx8ub7>PVmJQ-0+3b?mNgdW`~Lxl>4P5t literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_shift_dark.png b/dist/icons/overlay/osk_button_shift_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..c9cc5cd9afe9e8d81c1d8ad711329d715d5df606 GIT binary patch literal 2003 zcmb7F{Xf%-7yrzLMH|y*mvLim*jC~u595}H%|jMuD}|WGP%@brzRKqIFr#T)moAEM zr$VDuQ(`F(MOrC&Sb6Fhi*ieS>E7M{;QKwV*ZZ8;d7pFMznydPLxcTc2Id9;0KftR zNMYJ|T8kX0p4OjGx_fH_gyR!PhiWYc8Yk4|Yf=ODasU8+_g{cY32=;-G)pJ%NvE+{ z>Acvqc!0;_IV7bV>d5L$ z@0#|W`9aN~=GNDhz!&om2H;;_%+Gy4ifyJU84MxUqM6V4<(De)lhE1BC;{N1(#A>Ya~yj4CJO=J2Q(MUh8FSAE2QI%{MR^4yrs0?dJ5BM6n*5_`lz21&ny5Qa_{cMUi(vO~mYa=| zME>;K&ep*@X21zsm1jIU;?2y@kfjZUxZ;mSsde0F2e6kbgPNz6gT7@|oeM(5QWj$k z9?(AHWM{Z%9dwU5bw5E?NaqgcsHsQzPG-&_quk-IHy|Ks*l0NxcU5UNoOKfd{33`q za|s#!xmwEoo|B5w~te#fCYKbf6ZHe+%`=BSl66Y+~xQT7pfe`1uSHkNWHw-Z?QF)u~zZT z_W%=RH|0@X5Ia}JZxb!E<)T}70Gyv&%5!&D+&7%_mtQ^kp4d-3>7$cfgQ2~A-C~|z zglYn^Bj1GDD|aiuI2pp-b7V%(e?7H*YDRn?r~ue_QB}Wx!K3XFs}8C$Vv!oGp(5R% z6=14k)Kj2Yi!_*mLRI^y*=y6|S9dMc-grVh8Rhfku5XQLrq6Zd(*u*4+9ur**zzwd z?;3v4KuzTS4x3nrLviK4?u(;*ly38 z(@+L4ohh;&cHaZiBfP%`85tt#qn4PW@g|N;PhHb6f%ej$wb*aHQSx6o_UkAzlOjQO zDQ4oymN7s?#2bW}W3Xh&lMys)@iEU)vm8Nv7}>}XLY|lJ$H|Jg@GS6>mAVJ}Xe-8By!i*4IDH>CwcZ&H3qUlDQm>`~h;MeX@Zl4C&=SIBkY1C^}&J_f@2^(>)9M#paoT3pVzj^c)J z79SQ1DJBwMX2(&naA{NNj{c_-?GZ7;5s8G=r}tRL9Ctmu+1tQSQ}1or9WD~G3x`+M z8cPtEZ#A|Up^Sa!Z5=l4_!d343m4WW$_0E4_M=SbJn_mm>XeZ3XMWITn2WKBN@58E zQqYa*XOalmEs1ez>SY16OfD4#X#OSN;jy$thWe4pPL+vxSC&C)PW_hOr?~OAA{q?( zl@yog2Wp!(^Q($X{m!vY^nGU#N~_frzqObm?4krD~^F_rXstcMdTIC4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U1dAc}; zSoFT_+bG!NAmDnx%4C71X@cmj78mmn2Pa`Z*%-#CMn>BilV)3;5h&j7rpC8JcLAF>)x8K)9wiqv7gEK3(cW*RfUi!T@M7P1(fJu3kxw-Fg@6f$=v(@z% U>umVE8|Xp?Pgg&ebxsLQ0Bts2Bme*a literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_shift_lock_on.png b/dist/icons/overlay/osk_button_shift_lock_on.png new file mode 100755 index 0000000000000000000000000000000000000000..09077ab015f84fdee0e2303dcfeb2eb1e7b8480a GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1V0JzX3_ zEPCHgP~tt^C@8W>{+ok_*`-47FH{7W); zlckOQ)N`^`Z{^>6=Q`A6e7W~Np>l4iRAb;JN|g8RT9(yDXc))_(rxg-QKu2h*HJvm zek&pmt|RX10+%&B#!=cpTi4%r(fJ)Je9=77vrB8r+NT6B(@eEEn*X6r4;^K%yPlK8 zPG!-rzcbCYLwLC;=L475+DZbsIwd9S$R6L)?Jn%L>4I%LH>hssEzAc}Bntfju2Fex zTmz1>T#Zuq90xF-CxnchjO`Jw+SU7uS-O~3$j;pxyc`7p{;lE_K+_z73UFU%h#Pl4{yB(s;XDp5d;rJ@boTgsSGZO6*>L_ZqHygW_rOlnaggCDy z=T2EM3mS9>>Km&V|IPClHG3smbQZ8@*qc*i>37__y!Y5R;g zj%PYMq^^`-=^H%2B&5bb?%~1LW!O6-l(#mKlcDmpO(6r8MhZh^ zM;*o&Y7ZOUQ;v*PRUWFddr%SO^y`xUgnYC33t>X0t2zns7K<`5@JEyDF1Mt&vxtlq%Hc%~YE z-=!h?h3*!rF4C`kIi`oZQJORlqxBc_M-)#29cW}HbQYg@#`XAN4^TohO_QjvA-u5? z{NCCa8gRbsa7w8tId`TsgkE;|bi~tSdJ76WiOUVVoubTu&SZSe{{?b)!md!okky6I zqd*OBk0)|?J1>W~fwwk0a0wY8YDcnei^DZmc6eVkf7hg`76h5ESVWao3GO1y8>j@d z74ND=!9!gARYQ%xGgeZ~R{LR+T>T20H41IzYf@2&Du0rHHzdqz4lFOOZ0bYfZKYG- z(vD>M&BVe%+uf1n)XN{!M-0tSgS4fpNFNIoD;~l6qUeiTUn5Bka>1{CgL8c?SabL@ z*?x3^PTM5qQDnTlW?59^2>;0oG%^Xd2$#4uzRYdw5iKALnTXc3WVmhn=%jNZZxr@v zZH*!r>A%|OV}>qpZ}t`vcM=`b=mr z^QIr@X%k8Dsp&NxAH&V1Zz3th6U#dO^E#n2`K46jn@iKz>J){0s4QKPdqgA50^MQA`BQade1@LNdOXD^F8 zd2!-TmR&hEA=BTP;fys(J&hEVSf24jw YMJ6Iy=w)?(;H86zjz{qK5BjD2157^Pw*UYD literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_shift_on_dark.png b/dist/icons/overlay/osk_button_shift_on_dark.png new file mode 100755 index 0000000000000000000000000000000000000000..58e0d9cf41efc02d579aa2bfde0104e37d6849ca GIT binary patch literal 1937 zcmcIl2~$(q628{}VFCf;Lxjj~Fa`u!7}|qVy(;>3jjzB$lD_%`{trJvLz}(_r>z| z&%2Impjg^w4c9#Hvu;$Wc1;^~@5VS@_i}!84yCh;QI}s4e)B5UEMOF zo?PPBw3EAM;3vpHPHtfq8Cl(k~lQn&_MeR=2$lo0rNWUpCAY z3utsAMASK{ncYtuSJ#UtN8VbG?R{;ze%-7PxumAapXPh9Tt7K@wG#IlO@;Fzi5HN` zn9SD16dFlwezJeVj+)!*8>!DsF@jd;gA7gTn}^pT2+7*YpyhxrfE|W*2ef=r7kyTV3@u;IZ6EXPXPQC#8Wko*E{kTC0 zE`qGpeN!fK$K(ti=Vkv-I$`(6+*vb-6heDr)6seO4N6VQ_CBF8`$qNOP&I9e!frmL z+CRtk7#h$K(8a;QKBTVqGB^h}2jES)_~(&_SUq>X0-zg$H|>(27fioR)$G9gd{=@z zcxTBmrTce*Y{lS=$@6b#aIaL@oPLF6oF{30xbMo2SW4s^*RYB|l*~f7so;ji){Jgs*2i@yVrVlMo=?#E0#g)_nnT zm;t?RiEZF`edE`@Sv?DT#L1axO7^z$hH^&DihAmL=!wyfFH&J@>x}mQesh}fwC#Nv zKWO<~e-QXQS|_D>k^jr91!*JYeKxG*C^i0MR8rfR_6hNF`+my!7wm!Ll2foiC#=Qo z+8FOi6}^ciSA2!YE{bBOZ3Kmb{x##1xNbjT<*~(As8)lMNzFJinT}?k9HQ zy128*K<@rFyCaJiPl!7@Uf&wQ*2NrK#(y~4`AZ`GqhR4t&n40Xwuw@lL%hRWXl5z-Xv<_R+A*;^Pnr&|QkgitJlT60_`|n7XPmj&AQW_e={v*!pLGn;fJQJLY{)<0w<1Sl6oBIQV&79!y5v_`0od-jLu%K1Mzf}B$vwrDC%$hS7Xn7%&zf=3R^^Ul+ zG)zXdZnvI)Fl1m=7E`?#Mp@`qnTr<21m@%WW`{X)*}GPmPC6#X{7}2B*mGWhNE59Y zQiui6hHFNU7oy-&&pUynpE1x2meiQ2pS#yH0O0fkxr9as4ejxd3Bm^IN=&~zHoHtU zNawv)+#^oTG2t7=N*^9O7!nsNH%4__3%O85_Q6oNhD=T+KSGkJb*8e~V%6Y`vqVv> zVX+59MVPo7`zswdTMQH&#MG6U?$CPZP4}!)CH=nZA3c1z4I*pyNYk6c=i%=YfP4bI J+dSib{6ENObEyCT literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/overlay.qrc b/dist/icons/overlay/overlay.qrc new file mode 100755 index 000000000..d5a21ce10 --- /dev/null +++ b/dist/icons/overlay/overlay.qrc @@ -0,0 +1,64 @@ + + + arrow_left.png + arrow_left_dark.png + arrow_right.png + arrow_right_dark.png + button_minus.png + button_minus_dark.png + button_plus.png + button_plus_dark.png + button_A.png + button_A_dark.png + button_B.png + button_B_dark.png + button_X.png + button_X_dark.png + button_Y.png + button_Y_dark.png + button_L.png + button_L_dark.png + button_R.png + button_R_dark.png + button_press_stick.png + button_press_stick_dark.png + osk_button_B.png + osk_button_B_disabled.png + osk_button_B_dark.png + osk_button_B_dark_disabled.png + osk_button_Y.png + osk_button_Y_disabled.png + osk_button_Y_dark.png + osk_button_Y_dark_disabled.png + osk_button_backspace.png + osk_button_backspace_dark.png + osk_button_plus.png + osk_button_plus_disabled.png + osk_button_plus_dark.png + osk_button_plus_dark_disabled.png + osk_button_shift.png + osk_button_shift_dark.png + osk_button_shift_on.png + osk_button_shift_on_dark.png + osk_button_shift_lock_on.png + osk_button_shift_lock_off.png + controller_dual_joycon.png + controller_dual_joycon_dark.png + controller_pro.png + controller_pro_dark.png + controller_handheld.png + controller_handheld_dark.png + controller_single_joycon_left.png + controller_single_joycon_left_dark.png + controller_single_joycon_right.png + controller_single_joycon_right_dark.png + controller_single_joycon_left_a.png + controller_single_joycon_left_a_dark.png + controller_single_joycon_left_b.png + controller_single_joycon_left_b_dark.png + controller_single_joycon_left_x.png + controller_single_joycon_left_x_dark.png + controller_single_joycon_left_y.png + controller_single_joycon_left_y_dark.png + + diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 836dd25ca..3bc92b69d 100755 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -281,3 +281,380 @@ QWidget#controllerPlayer7, QWidget#controllerPlayer8 { background: transparent; } + +QDialog#QtSoftwareKeyboardDialog, +QStackedWidget#topOSK { + background: rgba(51, 51, 51, .9); +} + + +QDialog#OverlayDialog, +QStackedWidget#stackedDialog { + background: rgba(51, 51, 51, .7); +} + +QWidget#boxOSK, +QWidget#lineOSK, +QWidget#richDialog, +QWidget#lineDialog { + background: transparent; +} + +QStackedWidget#bottomOSK, +QWidget#contentDialog, +QWidget#contentRichDialog { + background: rgba(240, 240, 240, 1); +} + +QWidget#contentDialog, +QWidget#contentRichDialog { + margin: 5px; + border-radius: 6px; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog { + margin: 5px; + border-top: 2px solid rgba(44, 44, 44, 1); +} + +QWidget#legendOSKnum { + border-top: 1px solid rgba(44, 44, 44, 1); +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::vertical { + background: #cdcdcd; + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent; + border-radius: 4px; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::horizoncal { + background: #cdcdcd; + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent; + border-radius: 4px; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::handle { + background: #fff; + border-radius: 4px; + min-height: 5px; + min-width: 5px; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-page, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-page { + background: none; +} + +QWidget#inputOSK { + border-bottom: 3px solid rgba(255, 255, 255, .9); +} + +QWidget#inputOSK QLineEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#inputBoxOSK { + border: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#inputBoxOSK QTextEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#richDialog QTextBrowser { + background: transparent; + border: none; + padding: 35px 65px; +} + + +QWidget#lineOSK QLabel#label_header { + color: #f0f0f0; +} + +QWidget#lineOSK QLabel#label_sub, +QWidget#lineOSK QLabel#label_characters, +QWidget#boxOSK QLabel#label_characters_box { + color: #ccc; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + color: #888; +} + +QWidget#contentDialog QLabel#label_dialog { + padding: 20px 65px; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + padding: 0px 65px; +} + +QDialog#OverlayDialog QPushButton { + color: rgba(49, 79, 239, 1); + background: transparent; + border: none; + padding: 0px; + min-width: 0px; +} + +QDialog#OverlayDialog QPushButton:focus, +QDialog#OverlayDialog QPushButton:hover { + color: rgba(49, 79, 239, 1); + background: rgba(255, 255, 255, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; + outline: none; +} + +QDialog#OverlayDialog QPushButton:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton { + background: rgba(232, 232, 232, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background: rgba(218, 218, 218, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + color: rgba(240, 240, 240, 1); + background: rgba(44, 44, 44, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(240, 240, 240, 1); + background: rgba(49, 79, 239, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus, + +QDialog#QtSoftwareKeyboardDialog QPushButton:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover { + color: rgba(0, 0, 0, 1); + background: rgba(255, 255, 255, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_B.png); + qproperty-icon: url(:/overlay/osk_button_backspace.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_Y.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_plus.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_on.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_parenthesis, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_parenthesis { + padding-bottom: 7px; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#titleOSK QLabel { + background: transparent; + color: #ccc; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_L, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_num { + image: url(:/overlay/button_L.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_num { + image: url(:/overlay/arrow_left.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_R, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_num { + image: url(:/overlay/button_R.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_num { + image: url(:/overlay/arrow_right.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick, +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick_shift { + image: url(:/overlay/button_press_stick.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_X, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_num { + image: url(:/overlay/button_X.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_A, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_num { + image: url(:/overlay/button_A.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + color: rgba(164, 164, 164, 1); + background-color: rgba(218, 218, 218, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_slash:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_percent:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_1:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_2:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_3:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_4:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_5:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_6:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_7:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled { + color: rgba(164, 164, 164, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled { + background-image: url(:/overlay/osk_button_plus_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + background-image: url(:/overlay/osk_button_B_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled { + background-image: url(:/overlay/osk_button_Y_disabled.png); +} diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 2a1e8ddeb..8ce6d75f7 100755 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1560,7 +1560,400 @@ QWidget#controllerPlayer8 { background: transparent; } -/* touchscreen mapping widget */ -TouchScreenPreview { - qproperty-dotHighlightColor: #3daee9; +QDialog#QtSoftwareKeyboardDialog, +QStackedWidget#topOSK { + background: rgba(41, 41, 41, .9); +} + + +QDialog#OverlayDialog, +QStackedWidget#stackedDialog { + background: rgba(41, 41, 41, .7); +} + +QWidget#boxOSK, +QWidget#lineOSK, +QWidget#richDialog, +QWidget#lineDialog { + background: transparent; +} + +QStackedWidget#bottomOSK, +QWidget#contentDialog, +QWidget#contentRichDialog { + background: rgba(71, 69, 71, 1); +} + +QWidget#contentDialog, +QWidget#contentRichDialog { + margin: 5px; + border-radius: 6px; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog { + margin: 5px; + border-top: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#legendOSKnum { + border-top: 1px solid rgba(255, 255, 255, 1); +} + +QStackedWidget#stackedDialog QTextBrowser QWidget { + background: transparent; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar { + background: #2a2929; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-line { + border-image: none; +} + +QWidget#inputOSK { + border-bottom: 3px solid rgba(255, 255, 255, .9); +} + +QWidget#inputOSK QLineEdit { + background: transparent; + border: none; + color: #ccc; + padding: 0px; +} + +QWidget#inputBoxOSK { + border: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#inputBoxOSK QTextEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#richDialog QTextBrowser { + background: transparent; + border: none; + color: #fff; + padding: 35px 65px; +} + +QWidget#lineOSK QLabel#label_header { + color: #f0f0f0; +} + +QWidget#lineOSK QLabel#label_sub, +QWidget#lineOSK QLabel#label_characters, +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich, +QWidget#boxOSK QLabel#label_characters_box { + color: #ccc; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog, +QWidget#mainOSK, +QWidget#headerOSK, +QWidget#normalOSK, +QWidget#shiftOSK, +QWidget#numOSK, +QWidget#subOSK, +QWidget#inputOSK, +QWidget#inputBoxOSK, +QWidget#charactersOSK, +QWidget#charactersBoxOSK, +QWidget#legendOSK, +QWidget#legendOSK QWidget, +QWidget#legendOSKshift, +QWidget#legendOSKshift QWidget, +QWidget#legendOSKnum, +QWidget#legendOSKnum QWidget { + background: transparent; +} + +QWidget#contentDialog QLabel, +QWidget#legendOSK QLabel, +QWidget#legendOSKshift QLabel, +QWidget#legendOSKnum QLabel { + color: rgba(255, 255, 255, 1); +} + +QWidget#contentDialog QLabel#label_dialog { + padding: 20px 65px; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + padding: 0px 65px; +} + +QDialog#OverlayDialog QPushButton { + color: rgba(1, 253, 201, 1); + background: transparent; + border: none; + padding: 0px; + min-width: 0px; +} + +QDialog#OverlayDialog QPushButton:focus, +QDialog#OverlayDialog QPushButton:hover { + color: rgba(1, 253, 201, 1); + background: rgba(58, 61, 66, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#OverlayDialog QPushButton:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton { + color: rgba(255, 255, 255, 1); + background: rgba(80, 79, 80, 1); + border: 2px solid rgba(71, 69, 71, 1); + padding: 0px; + min-width: 0px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background: rgba(95, 94, 95, 1); + border: 2px solid rgba(71, 69, 71, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + color: rgba(240, 240, 240, 1); + background: rgba(255, 255, 255, 1); + border: 2px solid rgba(71, 69, 71, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(0, 0, 0, 1); + background: rgba(1, 253, 201, 1); + border: 2px solid rgba(71, 69, 71, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus, + +QDialog#QtSoftwareKeyboardDialog QPushButton:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover { + color: rgba(255, 255, 255, 1); + background: rgba(58, 61, 66, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_B_dark.png); + qproperty-icon: url(:/overlay/osk_button_backspace_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_Y_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(44, 44, 44, 1); + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_plus_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_on_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_parenthesis, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_parenthesis { + padding-bottom: 7px; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#titleOSK QLabel { + background: transparent; + color: #ccc; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_L, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_num { + image: url(:/overlay/button_L_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_num { + image: url(:/overlay/arrow_left_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_R, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_num { + image: url(:/overlay/button_R_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_num { + image: url(:/overlay/arrow_right_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick, +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick_shift { + image: url(:/overlay/button_press_stick_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_X, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_num { + image: url(:/overlay/button_X_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_A, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_num { + image: url(:/overlay/button_A_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + color: rgba(144, 144, 144, 1); + background-color: rgba(95, 94, 95, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_slash:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_percent:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_1:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_2:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_3:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_4:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_5:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_6:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_7:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled { + color: rgba(144, 144, 144, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled { + background-image: url(:/overlay/osk_button_plus_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + background-image: url(:/overlay/osk_button_B_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled { + background-image: url(:/overlay/osk_button_Y_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QFrame, +QDialog#QtSoftwareKeyboardDialog QFrame[frameShape="0"], +QDialog#OverlayDialog QFrame, +QDialog#OverlayDialog QFrame[frameShape="0"] { + border-radius: 0px; + border: none; } diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index a64037455..64e1ecbcc 100755 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -1,10 +1,10 @@ /* --------------------------------------------------------------------------- - Created by the qtsass compiler v0.1.1 + Created by the qtsass compiler v0.1.1 - The definitions are in the "qdarkstyle.qss._styles.scss" module + The definitions are in the "qdarkstyle.qss._styles.scss" module - WARNING! All changes made in this file will be lost! + WARNING! All changes made in this file will be lost! --------------------------------------------------------------------------- */ /* QDarkStyleSheet ----------------------------------------------------------- @@ -15,34 +15,34 @@ It is based on three selecting colors, three greyish (background) colors plus three whitish (foreground) colors. Each set of widgets of the same type have a header like this: - ------------------ - GroupName -------- - ------------------ + ------------------ + GroupName -------- + ------------------ And each widget is separated with a header like this: - QWidgetName ------ + QWidgetName ------ This makes more easy to find and change some css field. The basic configuration is described bellow. - BACKGROUND ----------- + BACKGROUND ----------- - Light (unpressed) - Normal (border, disabled, pressed, checked, toolbars, menus) - Dark (background) + Light (unpressed) + Normal (border, disabled, pressed, checked, toolbars, menus) + Dark (background) - FOREGROUND ----------- + FOREGROUND ----------- - Light (texts/labels) - Normal (not used yet) - Dark (disabled texts) + Light (texts/labels) + Normal (not used yet) + Dark (disabled texts) - SELECTION ------------ + SELECTION ------------ - Light (selection/hover/active) - Normal (selected) - Dark (selected disabled) + Light (selection/hover/active) + Normal (selected) + Dark (selected disabled) If a stranger configuration is required because of a bugfix or anything else, keep the comment on the line above so nobody changes it, including the @@ -2483,3 +2483,404 @@ QWidget#controllerPlayer7, QWidget#controllerPlayer8 { background: transparent; } + +QDialog#QtSoftwareKeyboardDialog, +QStackedWidget#topOSK { + background: rgba(15, 25, 34, .9); +} + +QDialog#OverlayDialog, +QStackedWidget#stackedDialog { + background: rgba(15, 25, 34, .7); +} + +QWidget#boxOSK, +QWidget#lineOSK, +QWidget#richDialog, +QWidget#lineDialog { + background: transparent; +} + +QStackedWidget#bottomOSK, +QWidget#contentDialog, +QWidget#contentRichDialog { + background: rgba(31, 41, 51, 1); +} + +QWidget#contentDialog, +QWidget#contentRichDialog { + margin: 5px; + border-radius: 6px; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog { + margin: 5px; + border-top: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#legendOSKnum { + border-top: 1px solid rgba(255, 255, 255, 1); +} + +QStackedWidget#stackedDialog QTextBrowser QWidget { + background: transparent; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar { + background: #19232d; + border: none; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-line { + border-image: none; +} + +QWidget#mainOSK QStackedWidget, +QDialog#OverlayDialog QStackedWidget { + border: none; + padding: 0px; +} + +QWidget#inputOSK { + border-bottom: 3px solid rgba(255, 255, 255, .9); +} + +QWidget#inputOSK QLineEdit { + background: transparent; + border: none; + color: #ccc; + padding: 0px; +} + +QWidget#inputBoxOSK { + border: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#inputBoxOSK QTextEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#richDialog QTextBrowser { + background: transparent; + border: none; + color: #fff; + padding: 35px 65px; +} + +QWidget#lineOSK QLabel#label_header { + color: #f0f0f0; +} + +QWidget#lineOSK QLabel#label_sub, +QWidget#lineOSK QLabel#label_characters, +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich, +QWidget#boxOSK QLabel#label_characters_box { + color: #ccc; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog, +QWidget#mainOSK, +QWidget#headerOSK, +QWidget#normalOSK, +QWidget#shiftOSK, +QWidget#numOSK, +QWidget#subOSK, +QWidget#inputOSK, +QWidget#inputBoxOSK, +QWidget#charactersOSK, +QWidget#charactersBoxOSK, +QWidget#legendOSK, +QWidget#legendOSK QWidget, +QWidget#legendOSKshift, +QWidget#legendOSKshift QWidget, +QWidget#legendOSKnum, +QWidget#legendOSKnum QWidget { + background: transparent; +} + +QWidget#contentDialog QLabel, +QWidget#legendOSK QLabel, +QWidget#legendOSKshift QLabel, +QWidget#legendOSKnum QLabel { + color: rgba(255, 255, 255, 1); +} + +QWidget#contentDialog QLabel#label_dialog { + padding: 20px 65px; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + padding: 0px 65px; +} + +QDialog#OverlayDialog QPushButton { + color: rgba(1, 253, 201, 1); + background: transparent; + border: none; + padding: 0px; + min-width: 0px; +} + +QDialog#OverlayDialog QPushButton:focus, +QDialog#OverlayDialog QPushButton:hover { + color: rgba(1, 253, 201, 1); + background: rgba(18, 33, 46, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#OverlayDialog QPushButton:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(110, 122, 130, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QLabel { + padding: 0px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton { + color: rgba(255, 255, 255, 1); + background: rgba(40, 51, 60, 1); + border: 2px solid rgba(31, 41, 51, 1); + border-radius: 0px; + padding: 0px; + min-width: 0px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background: rgba(55, 66, 75, 1); + border: 2px solid rgba(31, 41, 51, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + color: rgba(240, 240, 240, 1); + background: rgba(255, 255, 255, 1); + border: 2px solid rgba(31, 41, 51, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(0, 0, 0, 1); + background: rgba(1, 253, 201, 1); + border: 2px solid rgba(31, 41, 51, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus, + +QDialog#QtSoftwareKeyboardDialog QPushButton:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover { + color: rgba(255, 255, 255, 1); + background: rgba(18, 33, 46, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(110, 122, 130, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_B_dark.png); + qproperty-icon: url(:/overlay/osk_button_backspace_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_Y_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(44, 44, 44, 1); + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_plus_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_on_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_parenthesis, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_parenthesis { + padding-bottom: 7px; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#titleOSK QLabel { + background: transparent; + color: #ccc; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_L, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_num { + image: url(:/overlay/button_L_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_num { + image: url(:/overlay/arrow_left_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_R, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_num { + image: url(:/overlay/button_R_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_num { + image: url(:/overlay/arrow_right_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick, +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick_shift { + image: url(:/overlay/button_press_stick_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_X, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_num { + image: url(:/overlay/button_X_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_A, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_num { + image: url(:/overlay/button_A_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + color: rgba(144, 144, 144, 1); + background-color: rgba(55, 66, 75, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_slash:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_percent:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_1:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_2:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_3:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_4:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_5:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_6:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_7:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled { + color: rgba(144, 144, 144, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled { + background-image: url(:/overlay/osk_button_plus_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + background-image: url(:/overlay/osk_button_B_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled { + background-image: url(:/overlay/osk_button_Y_dark_disabled.png); +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0c1f5b0c8..5aa833d46 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -273,6 +273,7 @@ add_library(core STATIC hle/service/am/applets/profile_select.h hle/service/am/applets/software_keyboard.cpp hle/service/am/applets/software_keyboard.h + hle/service/am/applets/software_keyboard_types.h hle/service/am/applets/web_browser.cpp hle/service/am/applets/web_browser.h hle/service/am/applets/web_types.h diff --git a/src/core/frontend/applets/software_keyboard.cpp b/src/core/frontend/applets/software_keyboard.cpp index 856ed33da..12c76c9ee 100755 --- a/src/core/frontend/applets/software_keyboard.cpp +++ b/src/core/frontend/applets/software_keyboard.cpp @@ -1,29 +1,149 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/backend.h" +#include + +#include "common/logging/log.h" #include "common/string_util.h" #include "core/frontend/applets/software_keyboard.h" namespace Core::Frontend { + SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default; -void DefaultSoftwareKeyboardApplet::RequestText( - std::function)> out, - SoftwareKeyboardParameters parameters) const { - if (parameters.initial_text.empty()) - out(u"yuzu"); +DefaultSoftwareKeyboardApplet::~DefaultSoftwareKeyboardApplet() = default; - out(parameters.initial_text); +void DefaultSoftwareKeyboardApplet::InitializeKeyboard( + bool is_inline, KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + LOG_WARNING( + Service_AM, + "(STUBBED) called, backend requested to initialize the inline software keyboard."); + + submit_inline_callback = std::move(submit_inline_callback_); + } else { + LOG_WARNING( + Service_AM, + "(STUBBED) called, backend requested to initialize the normal software keyboard."); + + submit_normal_callback = std::move(submit_normal_callback_); + } + + parameters = std::move(initialize_parameters); + + LOG_INFO(Service_AM, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text), + Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text), + Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length, + parameters.min_text_length, parameters.initial_cursor_position, parameters.type, + parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw, + parameters.use_blur_background, parameters.enable_backspace_button, + parameters.enable_return_button, parameters.disable_cancel_button); } -void DefaultSoftwareKeyboardApplet::SendTextCheckDialog( - std::u16string error_message, std::function finished_check) const { +void DefaultSoftwareKeyboardApplet::ShowNormalKeyboard() const { LOG_WARNING(Service_AM, - "(STUBBED) called - Default fallback software keyboard does not support text " - "check! (error_message={})", - Common::UTF16ToUTF8(error_message)); - finished_check(); + "(STUBBED) called, backend requested to show the normal software keyboard."); + + SubmitNormalText(u"yuzu"); } + +void DefaultSoftwareKeyboardApplet::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to show the text check dialog."); +} + +void DefaultSoftwareKeyboardApplet::ShowInlineKeyboard( + InlineAppearParameters appear_parameters) const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to show the inline software keyboard."); + + LOG_INFO(Service_AM, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + std::thread([this] { SubmitInlineText(u"yuzu"); }).detach(); +} + +void DefaultSoftwareKeyboardApplet::HideInlineKeyboard() const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to hide the inline software keyboard."); +} + +void DefaultSoftwareKeyboardApplet::InlineTextChanged(InlineTextParameters text_parameters) const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to change the inline keyboard text."); + + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString, + text_parameters.input_text, text_parameters.cursor_position); +} + +void DefaultSoftwareKeyboardApplet::ExitKeyboard() const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to exit the software keyboard."); +} + +void DefaultSoftwareKeyboardApplet::SubmitNormalText(std::u16string text) const { + submit_normal_callback(Service::AM::Applets::SwkbdResult::Ok, text); +} + +void DefaultSoftwareKeyboardApplet::SubmitInlineText(std::u16string_view text) const { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + for (std::size_t index = 0; index < text.size(); ++index) { + submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString, + std::u16string(text.data(), text.data() + index + 1), + static_cast(index) + 1); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + + submit_inline_callback(Service::AM::Applets::SwkbdReplyType::DecidedEnter, std::u16string(text), + static_cast(text.size())); +} + } // namespace Core::Frontend diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h index f9b202664..506eb35bb 100755 --- a/src/core/frontend/applets/software_keyboard.h +++ b/src/core/frontend/applets/software_keyboard.h @@ -1,54 +1,116 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include -#include -#include -#include "common/bit_field.h" +#include + #include "common/common_types.h" +#include "core/hle/service/am/applets/software_keyboard_types.h" + namespace Core::Frontend { -struct SoftwareKeyboardParameters { - std::u16string submit_text; + +struct KeyboardInitializeParameters { + std::u16string ok_text; std::u16string header_text; std::u16string sub_text; std::u16string guide_text; std::u16string initial_text; - std::size_t max_length; - bool password; - bool cursor_at_beginning; + u32 max_text_length; + u32 min_text_length; + s32 initial_cursor_position; + Service::AM::Applets::SwkbdType type; + Service::AM::Applets::SwkbdPasswordMode password_mode; + Service::AM::Applets::SwkbdTextDrawType text_draw_type; + Service::AM::Applets::SwkbdKeyDisableFlags key_disable_flags; + bool use_blur_background; + bool enable_backspace_button; + bool enable_return_button; + bool disable_cancel_button; +}; - union { - u8 value; +struct InlineAppearParameters { + u32 max_text_length; + u32 min_text_length; + f32 key_top_scale_x; + f32 key_top_scale_y; + f32 key_top_translate_x; + f32 key_top_translate_y; + Service::AM::Applets::SwkbdType type; + Service::AM::Applets::SwkbdKeyDisableFlags key_disable_flags; + bool key_top_as_floating; + bool enable_backspace_button; + bool enable_return_button; + bool disable_cancel_button; +}; - BitField<1, 1, u8> disable_space; - BitField<2, 1, u8> disable_address; - BitField<3, 1, u8> disable_percent; - BitField<4, 1, u8> disable_slash; - BitField<6, 1, u8> disable_number; - BitField<7, 1, u8> disable_download_code; - }; +struct InlineTextParameters { + std::u16string input_text; + s32 cursor_position; }; class SoftwareKeyboardApplet { public: virtual ~SoftwareKeyboardApplet(); - virtual void RequestText(std::function)> out, - SoftwareKeyboardParameters parameters) const = 0; - virtual void SendTextCheckDialog(std::u16string error_message, - std::function finished_check) const = 0; + virtual void InitializeKeyboard( + bool is_inline, KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) = 0; + + virtual void ShowNormalKeyboard() const = 0; + + virtual void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const = 0; + + virtual void ShowInlineKeyboard(InlineAppearParameters appear_parameters) const = 0; + + virtual void HideInlineKeyboard() const = 0; + + virtual void InlineTextChanged(InlineTextParameters text_parameters) const = 0; + + virtual void ExitKeyboard() const = 0; }; class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet { public: - void RequestText(std::function)> out, - SoftwareKeyboardParameters parameters) const override; - void SendTextCheckDialog(std::u16string error_message, - std::function finished_check) const override; + ~DefaultSoftwareKeyboardApplet() override; + + void InitializeKeyboard( + bool is_inline, KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard(InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + +private: + void SubmitNormalText(std::u16string text) const; + void SubmitInlineText(std::u16string_view text) const; + + KeyboardInitializeParameters parameters; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; }; } // namespace Core::Frontend diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/frontend/input_interpreter.cpp index ec5fe660e..9f6a90e8f 100755 --- a/src/core/frontend/input_interpreter.cpp +++ b/src/core/frontend/input_interpreter.cpp @@ -12,7 +12,9 @@ InputInterpreter::InputInterpreter(Core::System& system) : npad{system.ServiceManager() .GetService("hid") ->GetAppletResource() - ->GetController(Service::HID::HidController::NPad)} {} + ->GetController(Service::HID::HidController::NPad)} { + ResetButtonStates(); +} InputInterpreter::~InputInterpreter() = default; @@ -25,6 +27,17 @@ void InputInterpreter::PollInput() { button_states[current_index] = button_state; } +void InputInterpreter::ResetButtonStates() { + previous_index = 0; + current_index = 0; + + button_states[0] = 0xFFFFFFFF; + + for (std::size_t i = 1; i < button_states.size(); ++i) { + button_states[i] = 0; + } +} + bool InputInterpreter::IsButtonPressed(HIDButton button) const { return (button_states[current_index] & (1U << static_cast(button))) != 0; } diff --git a/src/core/frontend/input_interpreter.h b/src/core/frontend/input_interpreter.h index 73fc47ffb..9495e3daf 100755 --- a/src/core/frontend/input_interpreter.h +++ b/src/core/frontend/input_interpreter.h @@ -66,6 +66,9 @@ public: /// Gets a button state from HID and inserts it into the array of button states. void PollInput(); + /// Resets all the button states to their defaults. + void ResetButtonStates(); + /** * Checks whether the button is pressed. * diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 161d9f782..2b363b1d9 100755 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -75,10 +75,14 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_ if (incoming) { // Populate the object lists with the data in the IPC request. for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { - copy_objects.push_back(handle_table.GetGeneric(rp.Pop())); + const u32 copy_handle{rp.Pop()}; + copy_handles.push_back(copy_handle); + copy_objects.push_back(handle_table.GetGeneric(copy_handle)); } for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) { - move_objects.push_back(handle_table.GetGeneric(rp.Pop())); + const u32 move_handle{rp.Pop()}; + move_handles.push_back(move_handle); + move_objects.push_back(handle_table.GetGeneric(move_handle)); } } else { // For responses we just ignore the handles, they're empty and will be populated when diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 9a769781b..2cfd857e4 100755 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -210,6 +210,14 @@ public: /// Helper function to test whether the output buffer at buffer_index can be written bool CanWriteBuffer(std::size_t buffer_index = 0) const; + Handle GetCopyHandle(std::size_t index) { + return copy_handles.at(index); + } + + Handle GetMoveHandle(std::size_t index) { + return move_handles.at(index); + } + template std::shared_ptr GetCopyObject(std::size_t index) { return DynamicObjectCast(copy_objects.at(index)); @@ -285,6 +293,8 @@ private: std::shared_ptr server_session; std::shared_ptr thread; // TODO(yuriks): Check common usage of this and optimize size accordingly + boost::container::small_vector move_handles; + boost::container::small_vector copy_handles; boost::container::small_vector, 8> move_objects; boost::container::small_vector, 8> copy_objects; boost::container::small_vector, 8> domain_objects; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index d91237cba..27633d6e4 100755 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -959,7 +959,7 @@ private: auto storage = applet->GetBroker().PopNormalDataToGame(); if (storage == nullptr) { - LOG_ERROR(Service_AM, + LOG_DEBUG(Service_AM, "storage is a nullptr. There is no data in the current normal channel"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_NO_DATA_IN_CHANNEL); @@ -990,7 +990,7 @@ private: auto storage = applet->GetBroker().PopInteractiveDataToGame(); if (storage == nullptr) { - LOG_ERROR(Service_AM, + LOG_DEBUG(Service_AM, "storage is a nullptr. There is no data in the current interactive channel"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_NO_DATA_IN_CHANNEL); @@ -1113,7 +1113,7 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) {2, nullptr, "AreAnyLibraryAppletsLeft"}, {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"}, {11, &ILibraryAppletCreator::CreateTransferMemoryStorage, "CreateTransferMemoryStorage"}, - {12, nullptr, "CreateHandleStorage"}, + {12, &ILibraryAppletCreator::CreateHandleStorage, "CreateHandleStorage"}, }; RegisterHandlers(functions); } @@ -1122,14 +1122,15 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default; void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + const auto applet_id = rp.PopRaw(); - const auto applet_mode = rp.PopRaw(); + const auto applet_mode = rp.PopRaw(); LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id, applet_mode); const auto& applet_manager{system.GetAppletManager()}; - const auto applet = applet_manager.GetApplet(applet_id); + const auto applet = applet_manager.GetApplet(applet_id, applet_mode); if (applet == nullptr) { LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); @@ -1147,9 +1148,18 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const u64 size{rp.Pop()}; + + const s64 size{rp.Pop()}; + LOG_DEBUG(Service_AM, "called, size={}", size); + if (size <= 0) { + LOG_ERROR(Service_AM, "size is less than or equal to 0"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + std::vector buffer(size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -1158,18 +1168,65 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { } void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_AM, "called"); - IPC::RequestParser rp{ctx}; - rp.SetCurrentOffset(3); - const auto handle{rp.Pop()}; + struct Parameters { + u8 permissions; + s64 size; + }; + + const auto parameters{rp.PopRaw()}; + const auto handle{ctx.GetCopyHandle(0)}; + + LOG_DEBUG(Service_AM, "called, permissions={}, size={}, handle={:08X}", parameters.permissions, + parameters.size, handle); + + if (parameters.size <= 0) { + LOG_ERROR(Service_AM, "size is less than or equal to 0"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } auto transfer_mem = system.CurrentProcess()->GetHandleTable().Get(handle); if (transfer_mem == nullptr) { - LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); + LOG_ERROR(Service_AM, "transfer_mem is a nullptr for handle={:08X}", handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + + const u8* const mem_begin = transfer_mem->GetPointer(); + const u8* const mem_end = mem_begin + transfer_mem->GetSize(); + std::vector memory{mem_begin, mem_end}; + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(system, std::move(memory)); +} + +void ILibraryAppletCreator::CreateHandleStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s64 size{rp.Pop()}; + const auto handle{ctx.GetCopyHandle(0)}; + + LOG_DEBUG(Service_AM, "called, size={}, handle={:08X}", size, handle); + + if (size <= 0) { + LOG_ERROR(Service_AM, "size is less than or equal to 0"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + + auto transfer_mem = + system.CurrentProcess()->GetHandleTable().Get(handle); + + if (transfer_mem == nullptr) { + LOG_ERROR(Service_AM, "transfer_mem is a nullptr for handle={:08X}", handle); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_UNKNOWN); return; diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index f6a453ab7..aefbdf0d5 100755 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -254,6 +254,7 @@ private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); + void CreateHandleStorage(Kernel::HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework { diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index e2f3b7563..5ddad851a 100755 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -241,31 +241,31 @@ void AppletManager::ClearAll() { frontend = {}; } -std::shared_ptr AppletManager::GetApplet(AppletId id) const { +std::shared_ptr AppletManager::GetApplet(AppletId id, LibraryAppletMode mode) const { switch (id) { case AppletId::Auth: - return std::make_shared(system, *frontend.parental_controls); + return std::make_shared(system, mode, *frontend.parental_controls); case AppletId::Controller: - return std::make_shared(system, *frontend.controller); + return std::make_shared(system, mode, *frontend.controller); case AppletId::Error: - return std::make_shared(system, *frontend.error); + return std::make_shared(system, mode, *frontend.error); case AppletId::ProfileSelect: - return std::make_shared(system, *frontend.profile_select); + return std::make_shared(system, mode, *frontend.profile_select); case AppletId::SoftwareKeyboard: - return std::make_shared(system, *frontend.software_keyboard); + return std::make_shared(system, mode, *frontend.software_keyboard); case AppletId::Web: case AppletId::Shop: case AppletId::OfflineWeb: case AppletId::LoginShare: case AppletId::WebAuth: - return std::make_shared(system, *frontend.web_browser); + return std::make_shared(system, mode, *frontend.web_browser); case AppletId::PhotoViewer: - return std::make_shared(system, *frontend.photo_viewer); + return std::make_shared(system, mode, *frontend.photo_viewer); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", static_cast(id)); - return std::make_shared(system, id); + return std::make_shared(system, id, mode); } } diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index b9a006317..26b482015 100755 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -62,6 +62,14 @@ enum class AppletId : u32 { MyPage = 0x1A, }; +enum class LibraryAppletMode : u32 { + AllForeground = 0, + Background = 1, + NoUI = 2, + BackgroundIndirectDisplay = 3, + AllForegroundInitiallyHidden = 4, +}; + class AppletDataBroker final { public: explicit AppletDataBroker(Kernel::KernelCore& kernel_); @@ -200,7 +208,7 @@ public: void SetDefaultAppletsIfMissing(); void ClearAll(); - std::shared_ptr GetApplet(AppletId id) const; + std::shared_ptr GetApplet(AppletId id, LibraryAppletMode mode) const; private: AppletFrontendSet frontend; diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index c2bfe698f..a33f05f97 100755 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/src/core/hle/service/am/applets/controller.cpp @@ -45,8 +45,9 @@ static Core::Frontend::ControllerParameters ConvertToFrontendParameters( }; } -Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +Controller::Controller(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ControllerApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} Controller::~Controller() = default; diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h index d4c9da7b1..07cb92bf9 100755 --- a/src/core/hle/service/am/applets/controller.h +++ b/src/core/hle/service/am/applets/controller.h @@ -106,7 +106,8 @@ static_assert(sizeof(ControllerSupportResultInfo) == 0xC, class Controller final : public Applet { public: - explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_); + explicit Controller(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ControllerApplet& frontend_); ~Controller() override; void Initialize() override; @@ -119,6 +120,7 @@ public: void ConfigurationComplete(); private: + LibraryAppletMode applet_mode; const Core::Frontend::ControllerApplet& frontend; Core::System& system; diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index 0c8b632e8..a9f0a9c95 100755 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -86,8 +86,9 @@ ResultCode Decode64BitError(u64 error) { } // Anonymous namespace -Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +Error::Error(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ErrorApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} Error::~Error() = default; diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h index a105cdb0c..a3e520cd4 100755 --- a/src/core/hle/service/am/applets/error.h +++ b/src/core/hle/service/am/applets/error.h @@ -25,7 +25,8 @@ enum class ErrorAppletMode : u8 { class Error final : public Applet { public: - explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_); + explicit Error(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ErrorApplet& frontend_); ~Error() override; void Initialize() override; @@ -40,6 +41,7 @@ public: private: union ErrorArguments; + LibraryAppletMode applet_mode; const Core::Frontend::ErrorApplet& frontend; ResultCode error_code = RESULT_SUCCESS; ErrorAppletMode mode = ErrorAppletMode::ShowError; diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index 4d1df5cbe..71016cce7 100755 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -37,8 +37,9 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) } } -Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +Auth::Auth(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::ParentalControlsApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} Auth::~Auth() = default; @@ -152,8 +153,9 @@ void Auth::AuthFinished(bool is_successful) { broker.SignalStateChanged(); } -PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +PhotoViewer::PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::PhotoViewerApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} PhotoViewer::~PhotoViewer() = default; @@ -202,8 +204,8 @@ void PhotoViewer::ViewFinished() { broker.SignalStateChanged(); } -StubApplet::StubApplet(Core::System& system_, AppletId id_) - : Applet{system_.Kernel()}, id{id_}, system{system_} {} +StubApplet::StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_) + : Applet{system_.Kernel()}, id{id_}, applet_mode{applet_mode_}, system{system_} {} StubApplet::~StubApplet() = default; diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index ba76ae3d3..d9e6d4384 100755 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -20,7 +20,8 @@ enum class AuthAppletType : u32 { class Auth final : public Applet { public: - explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_); + explicit Auth(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::ParentalControlsApplet& frontend_); ~Auth() override; void Initialize() override; @@ -32,6 +33,7 @@ public: void AuthFinished(bool is_successful = true); private: + LibraryAppletMode applet_mode; Core::Frontend::ParentalControlsApplet& frontend; Core::System& system; bool complete = false; @@ -50,7 +52,8 @@ enum class PhotoViewerAppletMode : u8 { class PhotoViewer final : public Applet { public: - explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_); + explicit PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::PhotoViewerApplet& frontend_); ~PhotoViewer() override; void Initialize() override; @@ -62,6 +65,7 @@ public: void ViewFinished(); private: + LibraryAppletMode applet_mode; const Core::Frontend::PhotoViewerApplet& frontend; bool complete = false; PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; @@ -70,7 +74,7 @@ private: class StubApplet final : public Applet { public: - explicit StubApplet(Core::System& system_, AppletId id_); + explicit StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_); ~StubApplet() override; void Initialize() override; @@ -82,6 +86,7 @@ public: private: AppletId id; + LibraryAppletMode applet_mode; Core::System& system; }; diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 77fba16c7..ab8b6fcc5 100755 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -15,9 +15,9 @@ namespace Service::AM::Applets { constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; -ProfileSelect::ProfileSelect(Core::System& system_, +ProfileSelect::ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, const Core::Frontend::ProfileSelectApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} ProfileSelect::~ProfileSelect() = default; diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h index 648d33a24..90f054030 100755 --- a/src/core/hle/service/am/applets/profile_select.h +++ b/src/core/hle/service/am/applets/profile_select.h @@ -33,7 +33,7 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco class ProfileSelect final : public Applet { public: - explicit ProfileSelect(Core::System& system_, + explicit ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, const Core::Frontend::ProfileSelectApplet& frontend_); ~ProfileSelect() override; @@ -47,6 +47,7 @@ public: void SelectionComplete(std::optional uuid); private: + LibraryAppletMode applet_mode; const Core::Frontend::ProfileSelectApplet& frontend; UserSelectionConfig config; diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index 79b209c6b..c24e228b2 100755 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -1,93 +1,80 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include "common/assert.h" #include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/software_keyboard.h" -#include "core/hle/result.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/software_keyboard.h" namespace Service::AM::Applets { namespace { -enum class Request : u32 { - Finalize = 0x4, - SetUserWordInfo = 0x6, - SetCustomizeDic = 0x7, - Calc = 0xa, - SetCustomizedDictionaries = 0xb, - UnsetCustomizedDictionaries = 0xc, - UnknownD = 0xd, - UnknownE = 0xe, -}; -constexpr std::size_t SWKBD_INLINE_INIT_SIZE = 0x8; -constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; -constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; -constexpr std::size_t DEFAULT_MAX_LENGTH = 500; -constexpr bool INTERACTIVE_STATUS_OK = false; -} // Anonymous namespace -static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( - KeyboardConfig config, std::u16string initial_text) { - Core::Frontend::SoftwareKeyboardParameters params{}; - params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - config.submit_text.data(), config.submit_text.size()); - params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - config.header_text.data(), config.header_text.size()); - params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(), - config.sub_text.size()); - params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(), - config.guide_text.size()); - params.initial_text = std::move(initial_text); - params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit; - params.password = static_cast(config.is_password); - params.cursor_at_beginning = static_cast(config.initial_cursor_position); - params.value = static_cast(config.keyset_disable_bitmask); +// The maximum number of UTF-16 characters that can be input into the swkbd text field. +constexpr u32 DEFAULT_MAX_TEXT_LENGTH = 500; - return params; +constexpr std::size_t REPLY_BASE_SIZE = sizeof(SwkbdState) + sizeof(SwkbdReplyType); +constexpr std::size_t REPLY_UTF8_SIZE = 0x7D4; +constexpr std::size_t REPLY_UTF16_SIZE = 0x3EC; + +constexpr const char* GetTextCheckResultName(SwkbdTextCheckResult text_check_result) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + return "Success"; + case SwkbdTextCheckResult::Failure: + return "Failure"; + case SwkbdTextCheckResult::Confirm: + return "Confirm"; + case SwkbdTextCheckResult::Silent: + return "Silent"; + default: + UNIMPLEMENTED_MSG("Unknown TextCheckResult={}", text_check_result); + return "Unknown"; + } } -SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, - const Core::Frontend::SoftwareKeyboardApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +void SetReplyBase(std::vector& reply, SwkbdState state, SwkbdReplyType reply_type) { + std::memcpy(reply.data(), &state, sizeof(SwkbdState)); + std::memcpy(reply.data() + sizeof(SwkbdState), &reply_type, sizeof(SwkbdReplyType)); +} + +} // Anonymous namespace + +SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::SoftwareKeyboardApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} SoftwareKeyboard::~SoftwareKeyboard() = default; void SoftwareKeyboard::Initialize() { - complete = false; - is_inline = false; - initial_text.clear(); - final_data.clear(); - Applet::Initialize(); - const auto keyboard_config_storage = broker.PopNormalDataToApplet(); - ASSERT(keyboard_config_storage != nullptr); - const auto& keyboard_config = keyboard_config_storage->GetData(); + LOG_INFO(Service_AM, "Initializing Software Keyboard Applet with LibraryAppletMode={}", + applet_mode); - if (keyboard_config.size() == SWKBD_INLINE_INIT_SIZE) { - is_inline = true; - return; + LOG_DEBUG(Service_AM, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + swkbd_applet_version = SwkbdAppletVersion{common_args.library_version}; + + switch (applet_mode) { + case LibraryAppletMode::AllForeground: + InitializeForeground(); + break; + case LibraryAppletMode::Background: + case LibraryAppletMode::BackgroundIndirectDisplay: + InitializeBackground(applet_mode); + break; + default: + UNREACHABLE_MSG("Invalid LibraryAppletMode={}", applet_mode); + break; } - - ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); - std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); - - const auto work_buffer_storage = broker.PopNormalDataToApplet(); - ASSERT_OR_EXECUTE(work_buffer_storage != nullptr, { return; }); - const auto& work_buffer = work_buffer_storage->GetData(); - - if (config.initial_string_size == 0) - return; - - std::vector string(config.initial_string_size); - std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset, - string.size() * 2); - initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()); } bool SoftwareKeyboard::TransactionComplete() const { @@ -95,106 +82,995 @@ bool SoftwareKeyboard::TransactionComplete() const { } ResultCode SoftwareKeyboard::GetStatus() const { - return RESULT_SUCCESS; + return status; } void SoftwareKeyboard::ExecuteInteractive() { - if (complete) + if (complete) { return; + } - const auto storage = broker.PopInteractiveDataToApplet(); - ASSERT(storage != nullptr); - const auto data = storage->GetData(); - if (!is_inline) { - const auto status = static_cast(data[0]); - if (status == INTERACTIVE_STATUS_OK) { - complete = true; - } else { - std::array string; - std::memcpy(string.data(), data.data() + 4, string.size() * 2); - frontend.SendTextCheckDialog( - Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), - [this] { broker.SignalStateChanged(); }); - } + if (is_background) { + ProcessInlineKeyboardRequest(); } else { - Request request{}; - std::memcpy(&request, data.data(), sizeof(Request)); - - switch (request) { - case Request::Finalize: - complete = true; - broker.SignalStateChanged(); - break; - case Request::Calc: { - broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{1})); - broker.SignalStateChanged(); - break; - } - default: - UNIMPLEMENTED_MSG("Request {:X} is not implemented", request); - break; - } + ProcessTextCheck(); } } void SoftwareKeyboard::Execute() { if (complete) { - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); - broker.SignalStateChanged(); return; } - const auto parameters = ConvertToFrontendParameters(config, initial_text); - if (!is_inline) { - frontend.RequestText( - [this](std::optional text) { WriteText(std::move(text)); }, parameters); + if (is_background) { + return; } + + ShowNormalKeyboard(); } -void SoftwareKeyboard::WriteText(std::optional text) { - std::vector output_main(SWKBD_OUTPUT_BUFFER_SIZE); +void SoftwareKeyboard::SubmitTextNormal(SwkbdResult result, std::u16string submitted_text) { + if (complete) { + return; + } - if (text.has_value()) { - std::vector output_sub(SWKBD_OUTPUT_BUFFER_SIZE); - - if (config.utf_8) { - const u64 size = text->size() + sizeof(u64); - const auto new_text = Common::UTF16ToUTF8(*text); - - std::memcpy(output_sub.data(), &size, sizeof(u64)); - std::memcpy(output_sub.data() + 8, new_text.data(), - std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8)); - - output_main[0] = INTERACTIVE_STATUS_OK; - std::memcpy(output_main.data() + 4, new_text.data(), - std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); - } else { - const u64 size = text->size() * 2 + sizeof(u64); - std::memcpy(output_sub.data(), &size, sizeof(u64)); - std::memcpy(output_sub.data() + 8, text->data(), - std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); - - output_main[0] = INTERACTIVE_STATUS_OK; - std::memcpy(output_main.data() + 4, text->data(), - std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4)); - } - - complete = !config.text_check; - final_data = output_main; - - if (complete) { - broker.PushNormalDataFromApplet( - std::make_shared(system, std::move(output_main))); - broker.SignalStateChanged(); - } else { - broker.PushInteractiveDataFromApplet( - std::make_shared(system, std::move(output_sub))); - } + if (swkbd_config_common.use_text_check && result == SwkbdResult::Ok) { + SubmitForTextCheck(submitted_text); } else { - output_main[0] = 1; - complete = true; - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(output_main))); - broker.SignalStateChanged(); + SubmitNormalOutputAndExit(result, submitted_text); } } + +void SoftwareKeyboard::SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position) { + if (complete) { + return; + } + + current_text = submitted_text; + current_cursor_position = cursor_position; + + if (inline_use_utf8) { + switch (reply_type) { + case SwkbdReplyType::ChangedString: + reply_type = SwkbdReplyType::ChangedStringUtf8; + break; + case SwkbdReplyType::MovedCursor: + reply_type = SwkbdReplyType::MovedCursorUtf8; + break; + case SwkbdReplyType::DecidedEnter: + reply_type = SwkbdReplyType::DecidedEnterUtf8; + break; + default: + break; + } + } + + if (use_changed_string_v2) { + switch (reply_type) { + case SwkbdReplyType::ChangedString: + reply_type = SwkbdReplyType::ChangedStringV2; + break; + case SwkbdReplyType::ChangedStringUtf8: + reply_type = SwkbdReplyType::ChangedStringUtf8V2; + break; + default: + break; + } + } + + if (use_moved_cursor_v2) { + switch (reply_type) { + case SwkbdReplyType::MovedCursor: + reply_type = SwkbdReplyType::MovedCursorV2; + break; + case SwkbdReplyType::MovedCursorUtf8: + reply_type = SwkbdReplyType::MovedCursorUtf8V2; + break; + default: + break; + } + } + + SendReply(reply_type); +} + +void SoftwareKeyboard::InitializeForeground() { + LOG_INFO(Service_AM, "Initializing Normal Software Keyboard Applet."); + + is_background = false; + + const auto swkbd_config_storage = broker.PopNormalDataToApplet(); + ASSERT(swkbd_config_storage != nullptr); + + const auto& swkbd_config_data = swkbd_config_storage->GetData(); + ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon)); + + std::memcpy(&swkbd_config_common, swkbd_config_data.data(), sizeof(SwkbdConfigCommon)); + + switch (swkbd_applet_version) { + case SwkbdAppletVersion::Version5: + case SwkbdAppletVersion::Version65542: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld)); + std::memcpy(&swkbd_config_old, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigOld)); + break; + case SwkbdAppletVersion::Version196615: + case SwkbdAppletVersion::Version262152: + case SwkbdAppletVersion::Version327689: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld2)); + std::memcpy(&swkbd_config_old2, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigOld2)); + break; + case SwkbdAppletVersion::Version393227: + case SwkbdAppletVersion::Version524301: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); + std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigNew)); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdConfig revision={} with size={}", swkbd_applet_version, + swkbd_config_data.size()); + ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); + std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigNew)); + break; + } + + const auto work_buffer_storage = broker.PopNormalDataToApplet(); + ASSERT(work_buffer_storage != nullptr); + + if (swkbd_config_common.initial_string_length == 0) { + InitializeFrontendKeyboard(); + return; + } + + const auto& work_buffer = work_buffer_storage->GetData(); + + std::vector initial_string(swkbd_config_common.initial_string_length); + + std::memcpy(initial_string.data(), + work_buffer.data() + swkbd_config_common.initial_string_offset, + swkbd_config_common.initial_string_length * sizeof(char16_t)); + + initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(initial_string.data(), + initial_string.size()); + + LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text)); + + InitializeFrontendKeyboard(); +} + +void SoftwareKeyboard::InitializeBackground(LibraryAppletMode applet_mode) { + LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet."); + + is_background = true; + + const auto swkbd_inline_initialize_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(swkbd_inline_initialize_arg_storage != nullptr); + + const auto& swkbd_inline_initialize_arg = swkbd_inline_initialize_arg_storage->GetData(); + ASSERT(swkbd_inline_initialize_arg.size() == sizeof(SwkbdInitializeArg)); + + std::memcpy(&swkbd_initialize_arg, swkbd_inline_initialize_arg.data(), + swkbd_inline_initialize_arg.size()); + + if (swkbd_initialize_arg.library_applet_mode_flag) { + ASSERT(applet_mode == LibraryAppletMode::Background); + } else { + ASSERT(applet_mode == LibraryAppletMode::BackgroundIndirectDisplay); + } +} + +void SoftwareKeyboard::ProcessTextCheck() { + const auto text_check_storage = broker.PopInteractiveDataToApplet(); + ASSERT(text_check_storage != nullptr); + + const auto& text_check_data = text_check_storage->GetData(); + ASSERT(text_check_data.size() == sizeof(SwkbdTextCheck)); + + SwkbdTextCheck swkbd_text_check; + + std::memcpy(&swkbd_text_check, text_check_data.data(), sizeof(SwkbdTextCheck)); + + std::u16string text_check_message = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_text_check.text_check_message.data(), swkbd_text_check.text_check_message.size()); + + LOG_INFO(Service_AM, "\nTextCheckResult: {}\nTextCheckMessage: {}", + GetTextCheckResultName(swkbd_text_check.text_check_result), + Common::UTF16ToUTF8(text_check_message)); + + switch (swkbd_text_check.text_check_result) { + case SwkbdTextCheckResult::Success: + SubmitNormalOutputAndExit(SwkbdResult::Ok, current_text); + break; + case SwkbdTextCheckResult::Failure: + ShowTextCheckDialog(SwkbdTextCheckResult::Failure, text_check_message); + break; + case SwkbdTextCheckResult::Confirm: + ShowTextCheckDialog(SwkbdTextCheckResult::Confirm, text_check_message); + break; + case SwkbdTextCheckResult::Silent: + default: + break; + } +} + +void SoftwareKeyboard::ProcessInlineKeyboardRequest() { + const auto request_data_storage = broker.PopInteractiveDataToApplet(); + ASSERT(request_data_storage != nullptr); + + const auto& request_data = request_data_storage->GetData(); + ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand)); + + SwkbdRequestCommand request_command; + + std::memcpy(&request_command, request_data.data(), sizeof(SwkbdRequestCommand)); + + switch (request_command) { + case SwkbdRequestCommand::Finalize: + RequestFinalize(request_data); + break; + case SwkbdRequestCommand::SetUserWordInfo: + RequestSetUserWordInfo(request_data); + break; + case SwkbdRequestCommand::SetCustomizeDic: + RequestSetCustomizeDic(request_data); + break; + case SwkbdRequestCommand::Calc: + RequestCalc(request_data); + break; + case SwkbdRequestCommand::SetCustomizedDictionaries: + RequestSetCustomizedDictionaries(request_data); + break; + case SwkbdRequestCommand::UnsetCustomizedDictionaries: + RequestUnsetCustomizedDictionaries(request_data); + break; + case SwkbdRequestCommand::SetChangedStringV2Flag: + RequestSetChangedStringV2Flag(request_data); + break; + case SwkbdRequestCommand::SetMovedCursorV2Flag: + RequestSetMovedCursorV2Flag(request_data); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdRequestCommand={}", request_command); + break; + } +} + +void SoftwareKeyboard::SubmitNormalOutputAndExit(SwkbdResult result, + std::u16string submitted_text) { + std::vector out_data(sizeof(SwkbdResult) + STRING_BUFFER_SIZE); + + if (swkbd_config_common.use_utf8) { + std::string utf8_submitted_text = Common::UTF16ToUTF8(submitted_text); + + LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-8 Submitted Text: {}", result, + utf8_submitted_text); + + std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); + std::memcpy(out_data.data() + sizeof(SwkbdResult), utf8_submitted_text.data(), + utf8_submitted_text.size()); + } else { + LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-16 Submitted Text: {}", result, + Common::UTF16ToUTF8(submitted_text)); + + std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); + std::memcpy(out_data.data() + sizeof(SwkbdResult), submitted_text.data(), + submitted_text.size() * sizeof(char16_t)); + } + + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); + + ExitKeyboard(); +} + +void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { + current_text = submitted_text; + + std::vector out_data(sizeof(u64) + STRING_BUFFER_SIZE); + + if (swkbd_config_common.use_utf8) { + std::string utf8_submitted_text = Common::UTF16ToUTF8(submitted_text); + const u64 buffer_size = sizeof(u64) + utf8_submitted_text.size(); + + LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, + utf8_submitted_text); + + std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); + std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), + utf8_submitted_text.size()); + } else { + const u64 buffer_size = sizeof(u64) + submitted_text.size() * sizeof(char16_t); + + LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, + Common::UTF16ToUTF8(submitted_text)); + + std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); + std::memcpy(out_data.data() + sizeof(u64), submitted_text.data(), buffer_size); + } + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(out_data))); +} + +void SoftwareKeyboard::SendReply(SwkbdReplyType reply_type) { + switch (reply_type) { + case SwkbdReplyType::FinishedInitialize: + ReplyFinishedInitialize(); + break; + case SwkbdReplyType::Default: + ReplyDefault(); + break; + case SwkbdReplyType::ChangedString: + ReplyChangedString(); + break; + case SwkbdReplyType::MovedCursor: + ReplyMovedCursor(); + break; + case SwkbdReplyType::MovedTab: + ReplyMovedTab(); + break; + case SwkbdReplyType::DecidedEnter: + ReplyDecidedEnter(); + break; + case SwkbdReplyType::DecidedCancel: + ReplyDecidedCancel(); + break; + case SwkbdReplyType::ChangedStringUtf8: + ReplyChangedStringUtf8(); + break; + case SwkbdReplyType::MovedCursorUtf8: + ReplyMovedCursorUtf8(); + break; + case SwkbdReplyType::DecidedEnterUtf8: + ReplyDecidedEnterUtf8(); + break; + case SwkbdReplyType::UnsetCustomizeDic: + ReplyUnsetCustomizeDic(); + break; + case SwkbdReplyType::ReleasedUserWordInfo: + ReplyReleasedUserWordInfo(); + break; + case SwkbdReplyType::UnsetCustomizedDictionaries: + ReplyUnsetCustomizedDictionaries(); + break; + case SwkbdReplyType::ChangedStringV2: + ReplyChangedStringV2(); + break; + case SwkbdReplyType::MovedCursorV2: + ReplyMovedCursorV2(); + break; + case SwkbdReplyType::ChangedStringUtf8V2: + ReplyChangedStringUtf8V2(); + break; + case SwkbdReplyType::MovedCursorUtf8V2: + ReplyMovedCursorUtf8V2(); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdReplyType={}", reply_type); + ReplyDefault(); + break; + } +} + +void SoftwareKeyboard::ChangeState(SwkbdState state) { + swkbd_state = state; + + ReplyDefault(); +} + +void SoftwareKeyboard::InitializeFrontendKeyboard() { + if (is_background) { + const auto& appear_arg = swkbd_calc_arg.appear_arg; + + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + appear_arg.ok_text.data(), appear_arg.ok_text.size()); + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + const s32 initial_cursor_position = + current_cursor_position > 0 ? current_cursor_position : 0; + + const auto text_draw_type = + max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{ok_text}, + .header_text{}, + .sub_text{}, + .guide_text{}, + .initial_text{current_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{appear_arg.type}, + .password_mode{SwkbdPasswordMode::Disabled}, + .text_draw_type{text_draw_type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .use_blur_background{false}, + .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + frontend.InitializeKeyboard( + true, std::move(initialize_parameters), {}, + [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) { + SubmitTextInline(reply_type, submitted_text, cursor_position); + }); + } else { + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size()); + + std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size()); + + std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size()); + + std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size()); + + const u32 max_text_length = + swkbd_config_common.max_text_length > 0 && + swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? swkbd_config_common.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length + ? swkbd_config_common.min_text_length + : 0; + + const s32 initial_cursor_position = [this] { + switch (swkbd_config_common.initial_cursor_position) { + case SwkbdInitialCursorPosition::Start: + default: + return 0; + case SwkbdInitialCursorPosition::End: + return static_cast(initial_text.size()); + } + }(); + + const auto text_draw_type = [this, max_text_length] { + switch (swkbd_config_common.text_draw_type) { + case SwkbdTextDrawType::Line: + default: + return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + case SwkbdTextDrawType::Box: + case SwkbdTextDrawType::DownloadCode: + return swkbd_config_common.text_draw_type; + } + }(); + + const auto enable_return_button = text_draw_type == SwkbdTextDrawType::Box + ? swkbd_config_common.enable_return_button + : false; + + const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227 + ? swkbd_config_new.disable_cancel_button + : false; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{ok_text}, + .header_text{header_text}, + .sub_text{sub_text}, + .guide_text{guide_text}, + .initial_text{initial_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{swkbd_config_common.type}, + .password_mode{swkbd_config_common.password_mode}, + .text_draw_type{text_draw_type}, + .key_disable_flags{swkbd_config_common.key_disable_flags}, + .use_blur_background{swkbd_config_common.use_blur_background}, + .enable_backspace_button{true}, + .enable_return_button{enable_return_button}, + .disable_cancel_button{disable_cancel_button}, + }; + + frontend.InitializeKeyboard(false, std::move(initialize_parameters), + [this](SwkbdResult result, std::u16string submitted_text) { + SubmitTextNormal(result, submitted_text); + }, + {}); + } +} + +void SoftwareKeyboard::ShowNormalKeyboard() { + frontend.ShowNormalKeyboard(); +} + +void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + frontend.ShowTextCheckDialog(text_check_result, text_check_message); +} + +void SoftwareKeyboard::ShowInlineKeyboard() { + if (swkbd_state != SwkbdState::InitializedIsHidden) { + return; + } + + ChangeState(SwkbdState::InitializedIsAppearing); + + const auto& appear_arg = swkbd_calc_arg.appear_arg; + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + Core::Frontend::InlineAppearParameters appear_parameters{ + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .key_top_scale_x{swkbd_calc_arg.key_top_scale_x}, + .key_top_scale_y{swkbd_calc_arg.key_top_scale_y}, + .key_top_translate_x{swkbd_calc_arg.key_top_translate_x}, + .key_top_translate_y{swkbd_calc_arg.key_top_translate_y}, + .type{appear_arg.type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .key_top_as_floating{swkbd_calc_arg.key_top_as_floating}, + .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + frontend.ShowInlineKeyboard(std::move(appear_parameters)); + + ChangeState(SwkbdState::InitializedIsShown); +} + +void SoftwareKeyboard::HideInlineKeyboard() { + if (swkbd_state != SwkbdState::InitializedIsShown) { + return; + } + + ChangeState(SwkbdState::InitializedIsDisappearing); + + frontend.HideInlineKeyboard(); + + ChangeState(SwkbdState::InitializedIsHidden); +} + +void SoftwareKeyboard::InlineTextChanged() { + Core::Frontend::InlineTextParameters text_parameters{ + .input_text{current_text}, + .cursor_position{current_cursor_position}, + }; + + frontend.InlineTextChanged(std::move(text_parameters)); +} + +void SoftwareKeyboard::ExitKeyboard() { + complete = true; + status = RESULT_SUCCESS; + + frontend.ExitKeyboard(); + + broker.SignalStateChanged(); +} + +/// Inline Software Keyboard Requests + +void SoftwareKeyboard::RequestFinalize(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: Finalize"); + + ChangeState(SwkbdState::NotInitialized); + + ExitKeyboard(); +} + +void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented."); +} + +void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented."); +} + +void SoftwareKeyboard::RequestCalc(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: Calc"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArg)); + + std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand), + sizeof(SwkbdCalcArg)); + + if (swkbd_calc_arg.flags.set_input_text) { + current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_calc_arg.input_text.data(), swkbd_calc_arg.input_text.size()); + } + + if (swkbd_calc_arg.flags.set_cursor_position) { + current_cursor_position = swkbd_calc_arg.cursor_position; + } + + if (swkbd_calc_arg.flags.set_utf8_mode) { + inline_use_utf8 = swkbd_calc_arg.utf8_mode; + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg.flags.unset_customize_dic) { + ReplyUnsetCustomizeDic(); + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg.flags.unset_user_word_info) { + ReplyReleasedUserWordInfo(); + } + + if (swkbd_state == SwkbdState::NotInitialized && swkbd_calc_arg.flags.set_initialize_arg) { + InitializeFrontendKeyboard(); + + ChangeState(SwkbdState::InitializedIsHidden); + + ReplyFinishedInitialize(); + } + + if (!swkbd_calc_arg.flags.set_initialize_arg && + (swkbd_calc_arg.flags.set_input_text || swkbd_calc_arg.flags.set_cursor_position)) { + InlineTextChanged(); + } + + if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg.flags.appear) { + ShowInlineKeyboard(); + return; + } + + if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg.flags.disappear) { + HideInlineKeyboard(); + return; + } +} + +void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented."); +} + +void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector& request_data) { + LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries"); + + ReplyUnsetCustomizedDictionaries(); +} + +void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); + + std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); +} + +void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); + + std::memcpy(&use_moved_cursor_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); +} + +/// Inline Software Keyboard Replies + +void SoftwareKeyboard::ReplyFinishedInitialize() { + LOG_DEBUG(Service_AM, "Sending Reply: FinishedInitialize"); + + std::vector reply(REPLY_BASE_SIZE + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::FinishedInitialize); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDefault() { + LOG_DEBUG(Service_AM, "Sending Reply: Default"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::Default); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedString() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedString"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedString); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursor() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursor"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursor); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedTab() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedTab"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedTabArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedTab); + + const SwkbdMovedTabArg moved_tab_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_tab_arg, + sizeof(SwkbdMovedTabArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDecidedEnter() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnter"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdDecidedEnterArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnter); + + const SwkbdDecidedEnterArg decided_enter_arg{ + .text_length{static_cast(current_text.size())}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &decided_enter_arg, + sizeof(SwkbdDecidedEnterArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyDecidedCancel() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedCancel"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedCancel); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyChangedStringUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDecidedEnterUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnterUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdDecidedEnterArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnterUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdDecidedEnterArg decided_enter_arg{ + .text_length{static_cast(current_text.size())}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &decided_enter_arg, + sizeof(SwkbdDecidedEnterArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyUnsetCustomizeDic() { + LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizeDic"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizeDic); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyReleasedUserWordInfo() { + LOG_DEBUG(Service_AM, "Sending Reply: ReleasedUserWordInfo"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ReleasedUserWordInfo); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyUnsetCustomizedDictionaries() { + LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizedDictionaries"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizedDictionaries); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedStringV2() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringV2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringV2); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorV2() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorV2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorV2); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedStringUtf8V2() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8V2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8V2); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorUtf8V2() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8V2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8V2); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index 1d260fef8..df3e36aed 100755 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -1,20 +1,14 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include -#include -#include - #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/service/am/am.h" +#include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" - -union ResultCode; +#include "core/hle/service/am/applets/software_keyboard_types.h" namespace Core { class System; @@ -22,45 +16,10 @@ class System; namespace Service::AM::Applets { -enum class KeysetDisable : u32 { - Space = 0x02, - Address = 0x04, - Percent = 0x08, - Slashes = 0x10, - Numbers = 0x40, - DownloadCode = 0x80, -}; - -struct KeyboardConfig { - INSERT_PADDING_BYTES(4); - std::array submit_text; - u16_le left_symbol_key; - u16_le right_symbol_key; - INSERT_PADDING_BYTES(1); - KeysetDisable keyset_disable_bitmask; - u32_le initial_cursor_position; - std::array header_text; - std::array sub_text; - std::array guide_text; - u32_le length_limit; - INSERT_PADDING_BYTES(4); - u32_le is_password; - INSERT_PADDING_BYTES(5); - bool utf_8; - bool draw_background; - u32_le initial_string_offset; - u32_le initial_string_size; - u32_le user_dictionary_offset; - u32_le user_dictionary_size; - bool text_check; - u64_le text_check_callback; -}; -static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size."); - class SoftwareKeyboard final : public Applet { public: - explicit SoftwareKeyboard(Core::System& system_, - const Core::Frontend::SoftwareKeyboardApplet& frontend_); + explicit SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::SoftwareKeyboardApplet& frontend_); ~SoftwareKeyboard() override; void Initialize() override; @@ -70,17 +29,139 @@ public: void ExecuteInteractive() override; void Execute() override; - void WriteText(std::optional text); + /** + * Submits the input text to the application. + * If text checking is enabled, the application will verify the input text. + * If use_utf8 is enabled, the input text will be converted to UTF-8 prior to being submitted. + * This should only be used by the normal software keyboard. + * + * @param result SwkbdResult enum + * @param submitted_text UTF-16 encoded string + */ + void SubmitTextNormal(SwkbdResult result, std::u16string submitted_text); + + /** + * Submits the input text to the application. + * If utf8_mode is enabled, the input text will be converted to UTF-8 prior to being submitted. + * This should only be used by the inline software keyboard. + * + * @param reply_type SwkbdReplyType enum + * @param submitted_text UTF-16 encoded string + * @param cursor_position The current position of the text cursor + */ + void SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position); private: - const Core::Frontend::SoftwareKeyboardApplet& frontend; + /// Initializes the normal software keyboard. + void InitializeForeground(); - KeyboardConfig config; - std::u16string initial_text; - bool complete = false; - bool is_inline = false; - std::vector final_data; + /// Initializes the inline software keyboard. + void InitializeBackground(LibraryAppletMode applet_mode); + + /// Processes the text check sent by the application. + void ProcessTextCheck(); + + /// Processes the inline software keyboard request command sent by the application. + void ProcessInlineKeyboardRequest(); + + /// Submits the input text and exits the applet. + void SubmitNormalOutputAndExit(SwkbdResult result, std::u16string submitted_text); + + /// Submits the input text for text checking. + void SubmitForTextCheck(std::u16string submitted_text); + + /// Sends a reply to the application after processing a request command. + void SendReply(SwkbdReplyType reply_type); + + /// Changes the inline keyboard state. + void ChangeState(SwkbdState state); + + /** + * Signals the frontend to initialize the software keyboard with common parameters. + * This initializes either the normal software keyboard or the inline software keyboard + * depending on the state of is_background. + * Note that this does not cause the keyboard to appear. + * Use the respective Show*Keyboard() functions to cause the respective keyboards to appear. + */ + void InitializeFrontendKeyboard(); + + /// Signals the frontend to show the normal software keyboard. + void ShowNormalKeyboard(); + + /// Signals the frontend to show the text check dialog. + void ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + /// Signals the frontend to show the inline software keyboard. + void ShowInlineKeyboard(); + + /// Signals the frontend to hide the inline software keyboard. + void HideInlineKeyboard(); + + /// Signals the frontend that the current inline keyboard text has changed. + void InlineTextChanged(); + + /// Signals both the frontend and application that the software keyboard is exiting. + void ExitKeyboard(); + + /// Inline Software Keyboard Requests + + void RequestFinalize(const std::vector& request_data); + void RequestSetUserWordInfo(const std::vector& request_data); + void RequestSetCustomizeDic(const std::vector& request_data); + void RequestCalc(const std::vector& request_data); + void RequestSetCustomizedDictionaries(const std::vector& request_data); + void RequestUnsetCustomizedDictionaries(const std::vector& request_data); + void RequestSetChangedStringV2Flag(const std::vector& request_data); + void RequestSetMovedCursorV2Flag(const std::vector& request_data); + + /// Inline Software Keyboard Replies + + void ReplyFinishedInitialize(); + void ReplyDefault(); + void ReplyChangedString(); + void ReplyMovedCursor(); + void ReplyMovedTab(); + void ReplyDecidedEnter(); + void ReplyDecidedCancel(); + void ReplyChangedStringUtf8(); + void ReplyMovedCursorUtf8(); + void ReplyDecidedEnterUtf8(); + void ReplyUnsetCustomizeDic(); + void ReplyReleasedUserWordInfo(); + void ReplyUnsetCustomizedDictionaries(); + void ReplyChangedStringV2(); + void ReplyMovedCursorV2(); + void ReplyChangedStringUtf8V2(); + void ReplyMovedCursorUtf8V2(); + + LibraryAppletMode applet_mode; + Core::Frontend::SoftwareKeyboardApplet& frontend; Core::System& system; + + SwkbdAppletVersion swkbd_applet_version; + + SwkbdConfigCommon swkbd_config_common; + SwkbdConfigOld swkbd_config_old; + SwkbdConfigOld2 swkbd_config_old2; + SwkbdConfigNew swkbd_config_new; + std::u16string initial_text; + + SwkbdState swkbd_state{SwkbdState::NotInitialized}; + SwkbdInitializeArg swkbd_initialize_arg; + SwkbdCalcArg swkbd_calc_arg; + bool use_changed_string_v2{false}; + bool use_moved_cursor_v2{false}; + bool inline_use_utf8{false}; + s32 current_cursor_position{}; + + std::u16string current_text; + + bool is_background{false}; + + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard_types.h b/src/core/hle/service/am/applets/software_keyboard_types.h new file mode 100755 index 000000000..72c71ef6f --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard_types.h @@ -0,0 +1,295 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::AM::Applets { + +static constexpr std::size_t MAX_OK_TEXT_LENGTH = 8; +static constexpr std::size_t MAX_HEADER_TEXT_LENGTH = 64; +static constexpr std::size_t MAX_SUB_TEXT_LENGTH = 128; +static constexpr std::size_t MAX_GUIDE_TEXT_LENGTH = 256; +static constexpr std::size_t STRING_BUFFER_SIZE = 0x7D4; + +enum class SwkbdAppletVersion : u32_le { + Version5 = 0x5, // 1.0.0 + Version65542 = 0x10006, // 2.0.0 - 2.3.0 + Version196615 = 0x30007, // 3.0.0 - 3.0.2 + Version262152 = 0x40008, // 4.0.0 - 4.1.0 + Version327689 = 0x50009, // 5.0.0 - 5.1.0 + Version393227 = 0x6000B, // 6.0.0 - 7.0.1 + Version524301 = 0x8000D, // 8.0.0+ +}; + +enum class SwkbdType : u32 { + Normal, + NumberPad, + Qwerty, + Unknown3, + Latin, + SimplifiedChinese, + TraditionalChinese, + Korean, +}; + +enum class SwkbdInitialCursorPosition : u32 { + Start, + End, +}; + +enum class SwkbdPasswordMode : u32 { + Disabled, + Enabled, +}; + +enum class SwkbdTextDrawType : u32 { + Line, + Box, + DownloadCode, +}; + +enum class SwkbdResult : u32 { + Ok, + Cancel, +}; + +enum class SwkbdTextCheckResult : u32 { + Success, + Failure, + Confirm, + Silent, +}; + +enum class SwkbdState : u32 { + NotInitialized = 0x0, + InitializedIsHidden = 0x1, + InitializedIsAppearing = 0x2, + InitializedIsShown = 0x3, + InitializedIsDisappearing = 0x4, +}; + +enum class SwkbdRequestCommand : u32 { + Finalize = 0x4, + SetUserWordInfo = 0x6, + SetCustomizeDic = 0x7, + Calc = 0xA, + SetCustomizedDictionaries = 0xB, + UnsetCustomizedDictionaries = 0xC, + SetChangedStringV2Flag = 0xD, + SetMovedCursorV2Flag = 0xE, +}; + +enum class SwkbdReplyType : u32 { + FinishedInitialize = 0x0, + Default = 0x1, + ChangedString = 0x2, + MovedCursor = 0x3, + MovedTab = 0x4, + DecidedEnter = 0x5, + DecidedCancel = 0x6, + ChangedStringUtf8 = 0x7, + MovedCursorUtf8 = 0x8, + DecidedEnterUtf8 = 0x9, + UnsetCustomizeDic = 0xA, + ReleasedUserWordInfo = 0xB, + UnsetCustomizedDictionaries = 0xC, + ChangedStringV2 = 0xD, + MovedCursorV2 = 0xE, + ChangedStringUtf8V2 = 0xF, + MovedCursorUtf8V2 = 0x10, +}; + +struct SwkbdKeyDisableFlags { + union { + u32 raw{}; + + BitField<1, 1, u32> space; + BitField<2, 1, u32> at; + BitField<3, 1, u32> percent; + BitField<4, 1, u32> slash; + BitField<5, 1, u32> backslash; + BitField<6, 1, u32> numbers; + BitField<7, 1, u32> download_code; + BitField<8, 1, u32> username; + }; +}; +static_assert(sizeof(SwkbdKeyDisableFlags) == 0x4, "SwkbdKeyDisableFlags has incorrect size."); + +struct SwkbdConfigCommon { + SwkbdType type{}; + std::array ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + INSERT_PADDING_BYTES(1); + SwkbdKeyDisableFlags key_disable_flags{}; + SwkbdInitialCursorPosition initial_cursor_position{}; + std::array header_text{}; + std::array sub_text{}; + std::array guide_text{}; + u32 max_text_length{}; + u32 min_text_length{}; + SwkbdPasswordMode password_mode{}; + SwkbdTextDrawType text_draw_type{}; + bool enable_return_button{}; + bool use_utf8{}; + bool use_blur_background{}; + INSERT_PADDING_BYTES(1); + u32 initial_string_offset{}; + u32 initial_string_length{}; + u32 user_dictionary_offset{}; + u32 user_dictionary_entries{}; + bool use_text_check{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdConfigCommon) == 0x3D4, "SwkbdConfigCommon has incorrect size."); + +#pragma pack(push, 4) +// SwkbdAppletVersion 0x5, 0x10006 +struct SwkbdConfigOld { + INSERT_PADDING_WORDS(1); + VAddr text_check_callback{}; +}; +static_assert(sizeof(SwkbdConfigOld) == 0x3E0 - sizeof(SwkbdConfigCommon), + "SwkbdConfigOld has incorrect size."); + +// SwkbdAppletVersion 0x30007, 0x40008, 0x50009 +struct SwkbdConfigOld2 { + INSERT_PADDING_WORDS(1); + VAddr text_check_callback{}; + std::array text_grouping{}; +}; +static_assert(sizeof(SwkbdConfigOld2) == 0x400 - sizeof(SwkbdConfigCommon), + "SwkbdConfigOld2 has incorrect size."); + +// SwkbdAppletVersion 0x6000B, 0x8000D +struct SwkbdConfigNew { + std::array text_grouping{}; + std::array customized_dictionary_set_entries{}; + u8 total_customized_dictionary_set_entries{}; + bool disable_cancel_button{}; + INSERT_PADDING_BYTES(15); +}; +static_assert(sizeof(SwkbdConfigNew) == 0x4C8 - sizeof(SwkbdConfigCommon), + "SwkbdConfigNew has incorrect size."); +#pragma pack(pop) + +struct SwkbdTextCheck { + SwkbdTextCheckResult text_check_result{}; + std::array text_check_message{}; +}; +static_assert(sizeof(SwkbdTextCheck) == 0x7D8, "SwkbdTextCheck has incorrect size."); + +struct SwkbdCalcArgFlags { + union { + u64 raw{}; + + BitField<0, 1, u64> set_initialize_arg; + BitField<1, 1, u64> set_volume; + BitField<2, 1, u64> appear; + BitField<3, 1, u64> set_input_text; + BitField<4, 1, u64> set_cursor_position; + BitField<5, 1, u64> set_utf8_mode; + BitField<6, 1, u64> unset_customize_dic; + BitField<7, 1, u64> disappear; + BitField<8, 1, u64> unknown; + BitField<9, 1, u64> set_key_top_translate_scale; + BitField<10, 1, u64> unset_user_word_info; + BitField<11, 1, u64> set_disable_hardware_keyboard; + }; +}; +static_assert(sizeof(SwkbdCalcArgFlags) == 0x8, "SwkbdCalcArgFlags has incorrect size."); + +struct SwkbdInitializeArg { + u32 unknown{}; + bool library_applet_mode_flag{}; + bool is_above_hos_500{}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size."); + +struct SwkbdAppearArg { + SwkbdType type{}; + std::array ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + bool disable_cancel_button{}; + SwkbdKeyDisableFlags key_disable_flags{}; + u32 max_text_length{}; + u32 min_text_length{}; + bool enable_return_button{}; + INSERT_PADDING_BYTES(3); + u32 flags{}; + INSERT_PADDING_WORDS(6); +}; +static_assert(sizeof(SwkbdAppearArg) == 0x48, "SwkbdAppearArg has incorrect size."); + +struct SwkbdCalcArg { + u32 unknown{}; + u16 calc_arg_size{}; + INSERT_PADDING_BYTES(2); + SwkbdCalcArgFlags flags{}; + SwkbdInitializeArg initialize_arg{}; + f32 volume{}; + s32 cursor_position{}; + SwkbdAppearArg appear_arg{}; + std::array input_text{}; + bool utf8_mode{}; + INSERT_PADDING_BYTES(1); + bool enable_backspace_button{}; + INSERT_PADDING_BYTES(3); + bool key_top_as_floating{}; + bool footer_scalable{}; + bool alpha_enabled_in_input_mode{}; + u8 input_mode_fade_type{}; + bool disable_touch{}; + bool disable_hardware_keyboard{}; + INSERT_PADDING_BYTES(8); + f32 key_top_scale_x{}; + f32 key_top_scale_y{}; + f32 key_top_translate_x{}; + f32 key_top_translate_y{}; + f32 key_top_bg_alpha{}; + f32 footer_bg_alpha{}; + f32 balloon_scale{}; + INSERT_PADDING_WORDS(4); + u8 se_group{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdCalcArg) == 0x4A0, "SwkbdCalcArg has incorrect size."); + +struct SwkbdChangedStringArg { + u32 text_length{}; + s32 dictionary_start_cursor_position{}; + s32 dictionary_end_cursor_position{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdChangedStringArg) == 0x10, "SwkbdChangedStringArg has incorrect size."); + +struct SwkbdMovedCursorArg { + u32 text_length{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdMovedCursorArg) == 0x8, "SwkbdMovedCursorArg has incorrect size."); + +struct SwkbdMovedTabArg { + u32 text_length{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdMovedTabArg) == 0x8, "SwkbdMovedTabArg has incorrect size."); + +struct SwkbdDecidedEnterArg { + u32 text_length{}; +}; +static_assert(sizeof(SwkbdDecidedEnterArg) == 0x4, "SwkbdDecidedEnterArg has incorrect size."); + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index 2ab420789..b28b849bc 100755 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -208,8 +208,9 @@ void ExtractSharedFonts(Core::System& system) { } // namespace -WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} +WebBrowser::WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::WebBrowserApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend(frontend_), system{system_} {} WebBrowser::~WebBrowser() = default; diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 04c274754..5eafbae7b 100755 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -25,7 +25,8 @@ namespace Service::AM::Applets { class WebBrowser final : public Applet { public: - WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_); + WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::WebBrowserApplet& frontend_); ~WebBrowser() override; @@ -63,6 +64,7 @@ private: void ExecuteWifi(); void ExecuteLobby(); + LibraryAppletMode applet_mode; const Core::Frontend::WebBrowserApplet& frontend; bool complete{false}; diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 70b9f3824..1df62f98e 100755 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -413,12 +413,16 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { lstick_entry.y = static_cast(stick_l_y_f * HID_JOYSTICK_MAX); } - if (controller_type == NPadControllerType::JoyLeft || - controller_type == NPadControllerType::JoyRight) { + if (controller_type == NPadControllerType::JoyLeft) { pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); } + if (controller_type == NPadControllerType::JoyRight) { + pad_state.right_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.right_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + } + if (controller_type == NPadControllerType::GameCube) { trigger_entry.l_analog = static_cast( button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); diff --git a/src/core/settings.h b/src/core/settings.h index a81016b23..6c03a6ea9 100755 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -139,6 +139,7 @@ struct Values { Setting vulkan_device; Setting resolution_factor{1}; + Setting fullscreen_mode; Setting aspect_ratio; Setting max_anisotropy; Setting use_frame_limit; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index b025ced1c..cc0790e07 100755 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(yuzu applets/profile_select.h applets/software_keyboard.cpp applets/software_keyboard.h + applets/software_keyboard.ui applets/web_browser.cpp applets/web_browser.h bootmanager.cpp @@ -143,6 +144,9 @@ add_executable(yuzu uisettings.h util/limitable_input_dialog.cpp util/limitable_input_dialog.h + util/overlay_dialog.cpp + util/overlay_dialog.h + util/overlay_dialog.ui util/sequence_dialog/sequence_dialog.cpp util/sequence_dialog/sequence_dialog.h util/url_request_interceptor.cpp diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp index 8ee03ddb3..085688cd4 100755 --- a/src/yuzu/applets/error.cpp +++ b/src/yuzu/applets/error.cpp @@ -19,11 +19,11 @@ QtErrorDisplay::~QtErrorDisplay() = default; void QtErrorDisplay::ShowError(ResultCode error, std::function finished) const { callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occurred.\nPlease try again or contact the developer of the " - "software.\n\nError Code: %1-%2 (0x%3)") + tr("Error Code: %1-%2 (0x%3)") .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\nPlease try again or contact the developer of the software.")); } void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, @@ -32,13 +32,14 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); emit MainWindowDisplayError( - tr("An error occurred on %1 at %2.\nPlease try again or contact the " - "developer of the software.\n\nError Code: %3-%4 (0x%5)") - .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) - .arg(date_time.toString(QStringLiteral("h:mm:ss A"))) + tr("Error Code: %1-%2 (0x%3)") .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " + "software.") + .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) + .arg(date_time.toString(QStringLiteral("h:mm:ss A")))); } void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, @@ -46,10 +47,11 @@ void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_te std::function finished) const { callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occurred.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") + tr("Error Code: %1-%2 (0x%3)") .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\n\n%1\n\n%2") .arg(QString::fromStdString(dialog_text)) .arg(QString::fromStdString(fullscreen_text))); } diff --git a/src/yuzu/applets/error.h b/src/yuzu/applets/error.h index b0932d895..8bd895a32 100755 --- a/src/yuzu/applets/error.h +++ b/src/yuzu/applets/error.h @@ -24,7 +24,7 @@ public: std::function finished) const override; signals: - void MainWindowDisplayError(QString error) const; + void MainWindowDisplayError(QString error_code, QString error_text) const; private: void MainWindowFinishedError(); diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index ab8cfd8ee..06bab08d4 100755 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -1,153 +1,1641 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include -#include -#include -#include -#include -#include -#include "core/hle/lock.h" +#include +#include +#include + +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "core/settings.h" +#include "ui_software_keyboard.h" #include "yuzu/applets/software_keyboard.h" #include "yuzu/main.h" +#include "yuzu/util/overlay_dialog.h" -QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator( - Core::Frontend::SoftwareKeyboardParameters parameters) - : parameters(std::move(parameters)) {} +namespace { -QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { - if (input.size() > static_cast(parameters.max_length)) { - return Invalid; - } - if (parameters.disable_space && input.contains(QLatin1Char{' '})) { - return Invalid; - } - if (parameters.disable_address && input.contains(QLatin1Char{'@'})) { - return Invalid; - } - if (parameters.disable_percent && input.contains(QLatin1Char{'%'})) { - return Invalid; - } - if (parameters.disable_slash && - (input.contains(QLatin1Char{'/'}) || input.contains(QLatin1Char{'\\'}))) { - return Invalid; - } - if (parameters.disable_number && - std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { - return Invalid; - } +using namespace Service::AM::Applets; - if (parameters.disable_download_code && std::any_of(input.begin(), input.end(), [](QChar c) { - return c == QLatin1Char{'O'} || c == QLatin1Char{'I'}; - })) { - return Invalid; - } +constexpr float BASE_HEADER_FONT_SIZE = 23.0f; +constexpr float BASE_SUB_FONT_SIZE = 17.0f; +constexpr float BASE_EDITOR_FONT_SIZE = 26.0f; +constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f; +constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f; +constexpr float BASE_ICON_BUTTON_SIZE = 36.0f; +[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f; +constexpr float BASE_HEIGHT = 720.0f; - return Acceptable; -} +} // Anonymous namespace QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( - QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_) - : QDialog(parent), parameters(std::move(parameters_)) { - layout = new QVBoxLayout; + QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_) + : QDialog(parent), ui{std::make_unique()}, system{system_}, + is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { + ui->setupUi(this); - header_label = new QLabel(QString::fromStdU16String(parameters.header_text)); - header_label->setFont({header_label->font().family(), 11, QFont::Bold}); - if (header_label->text().isEmpty()) - header_label->setText(tr("Enter text:")); + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_TranslucentBackground); - sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text)); - sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(), - sub_label->font().weight(), true}); - sub_label->setHidden(parameters.sub_text.empty()); + keyboard_buttons = {{ + {{ + { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + }, + { + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + }, + { + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_return, + }, + { + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + }, + { + ui->button_shift, + ui->button_shift, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_ok, + }, + }}, + {{ + { + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + }, + { + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + }, + { + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_return_shift, + }, + { + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + }, + { + ui->button_shift_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_ok_shift, + }, + }}, + }}; - guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text)); - guide_label->setHidden(parameters.guide_text.empty()); + numberpad_buttons = {{ + { + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + }, + { + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + }, + { + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_ok_num, + }, + { + nullptr, + ui->button_0_num, + nullptr, + ui->button_ok_num, + }, + }}; - length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length)); - length_label->setAlignment(Qt::AlignRight); - length_label->setFont({length_label->font().family(), 8}); + all_buttons = { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + ui->button_shift, + ui->button_space, + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_0_num, + }; - line_edit = new QLineEdit; - line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters)); - line_edit->setMaxLength(static_cast(parameters.max_length)); - line_edit->setText(QString::fromStdU16String(parameters.initial_text)); - line_edit->setCursorPosition( - parameters.cursor_at_beginning ? 0 : static_cast(parameters.initial_text.size())); - line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal); + SetupMouseHover(); - connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { - length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length)); - }); - - buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); - if (parameters.submit_text.empty()) { - buttons->addButton(QDialogButtonBox::Ok); - } else { - buttons->addButton(QString::fromStdU16String(parameters.submit_text), - QDialogButtonBox::AcceptRole); + if (!initialize_parameters.ok_text.empty()) { + ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); + } + + ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); + ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); + + current_text = initialize_parameters.initial_text; + cursor_position = initialize_parameters.initial_cursor_position; + + SetTextDrawType(); + + for (auto* button : all_buttons) { + connect(button, &QPushButton::clicked, this, [this, button](bool) { + if (is_inline) { + InlineKeyboardButtonClicked(button); + } else { + NormalKeyboardButtonClicked(button); + } + }); + } + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); } - connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject); - layout->addWidget(header_label); - layout->addWidget(sub_label); - layout->addWidget(guide_label); - layout->addWidget(length_label); - layout->addWidget(line_edit); - layout->addWidget(buttons); - setLayout(layout); - setWindowTitle(tr("Software Keyboard")); } -QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { + StopInputThread(); +} -void QtSoftwareKeyboardDialog::accept() { - text = line_edit->text().toStdU16String(); - QDialog::accept(); +void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { + if (isVisible()) { + return; + } + + MoveAndResizeWindow(pos, size); + + SetKeyboardType(); + SetPasswordMode(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + case SwkbdTextCheckResult::Silent: + default: + break; + case SwkbdTextCheckResult::Failure: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + QString{}, tr("OK"), Qt::AlignCenter); + dialog.exec(); + + StartInputThread(); + break; + } + case SwkbdTextCheckResult::Confirm: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + tr("Cancel"), tr("OK"), Qt::AlignCenter); + if (dialog.exec() == QDialog::Accepted) { + emit SubmitNormalText(SwkbdResult::Ok, current_text); + break; + } + + StartInputThread(); + break; + } + } +} + +void QtSoftwareKeyboardDialog::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { + MoveAndResizeWindow(pos, size); + + ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); + + ui->headerOSK->hide(); + ui->subOSK->hide(); + ui->inputOSK->hide(); + ui->charactersOSK->hide(); + ui->inputBoxOSK->hide(); + ui->charactersBoxOSK->hide(); + + initialize_parameters.max_text_length = appear_parameters.max_text_length; + initialize_parameters.min_text_length = appear_parameters.min_text_length; + initialize_parameters.type = appear_parameters.type; + initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; + initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; + initialize_parameters.enable_return_button = appear_parameters.enable_return_button; + initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; + + SetKeyboardType(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::HideInlineKeyboard() { + StopInputThread(); + QDialog::hide(); +} + +void QtSoftwareKeyboardDialog::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + current_text = text_parameters.input_text; + cursor_position = text_parameters.cursor_position; + + SetBackspaceOkEnabled(); +} + +void QtSoftwareKeyboardDialog::ExitKeyboard() { + StopInputThread(); + QDialog::done(QDialog::Accepted); +} + +void QtSoftwareKeyboardDialog::open() { + QDialog::open(); + + row = 0; + column = 0; + + const auto* const curr_button = + keyboard_buttons[static_cast(bottom_osk_index)][row][column]; + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + + StartInputThread(); } void QtSoftwareKeyboardDialog::reject() { - text.clear(); - QDialog::reject(); + // Pressing the ESC key in a dialog calls QDialog::reject(). + // We will override this behavior to the "Cancel" action on the software keyboard. + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + emit SubmitNormalText(SwkbdResult::Cancel, current_text); + } } -std::u16string QtSoftwareKeyboardDialog::GetText() const { - return text; +void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { + if (!is_inline) { + QDialog::keyPressEvent(event); + return; + } + + const auto entered_key = event->key(); + + switch (entered_key) { + case Qt::Key_Escape: + QDialog::keyPressEvent(event); + return; + case Qt::Key_Backspace: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Return: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Left: + MoveTextCursorDirection(Direction::Left); + return; + case Qt::Key_Right: + MoveTextCursorDirection(Direction::Right); + return; + default: + break; + } + + const auto entered_text = event->text(); + + if (entered_text.isEmpty()) { + return; + } + + InlineTextInsertString(entered_text.toStdU16String()); +} + +void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { + QDialog::move(pos); + QDialog::resize(size); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + RescaleKeyboardElements(size.width(), size.height(), dpi_scale); +} + +void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { + const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto char_button_font_size = + BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto label_button_font_size = + BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); + QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); + QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); + QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); + QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, + QFont::Normal); + + ui->label_header->setFont(header_font); + ui->label_sub->setFont(sub_font); + ui->line_edit_osk->setFont(editor_font); + ui->text_edit_osk->setFont(editor_font); + ui->label_characters->setFont(sub_font); + ui->label_characters_box->setFont(sub_font); + + ui->label_shift->setFont(label_button_font); + ui->label_shift_shift->setFont(label_button_font); + ui->label_cancel->setFont(label_button_font); + ui->label_cancel_shift->setFont(label_button_font); + ui->label_cancel_num->setFont(label_button_font); + ui->label_enter->setFont(label_button_font); + ui->label_enter_shift->setFont(label_button_font); + ui->label_enter_num->setFont(label_button_font); + + for (auto* button : all_buttons) { + if (button == ui->button_return || button == ui->button_return_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || + button == ui->button_ok_num) { + button->setFont(label_button_font); + continue; + } + + button->setFont(char_button_font); + } +} + +void QtSoftwareKeyboardDialog::SetKeyboardType() { + switch (initialize_parameters.type) { + case SwkbdType::Normal: + case SwkbdType::Qwerty: + case SwkbdType::Unknown3: + case SwkbdType::Latin: + case SwkbdType::SimplifiedChinese: + case SwkbdType::TraditionalChinese: + case SwkbdType::Korean: + default: { + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 320); + ui->verticalLayout_2->setStretch(1, 400); + + ui->gridLineOSK->setRowStretch(5, 94); + ui->gridBoxOSK->setRowStretch(2, 81); + break; + } + case SwkbdType::NumberPad: { + bottom_osk_index = BottomOSKIndex::NumberPad; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 370); + ui->verticalLayout_2->setStretch(1, 350); + + ui->gridLineOSK->setRowStretch(5, 144); + ui->gridBoxOSK->setRowStretch(2, 131); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetPasswordMode() { + switch (initialize_parameters.password_mode) { + case SwkbdPasswordMode::Disabled: + default: + ui->line_edit_osk->setEchoMode(QLineEdit::Normal); + break; + case SwkbdPasswordMode::Enabled: + ui->line_edit_osk->setEchoMode(QLineEdit::Password); + break; + } +} + +void QtSoftwareKeyboardDialog::SetTextDrawType() { + switch (initialize_parameters.text_draw_type) { + case SwkbdTextDrawType::Line: + case SwkbdTextDrawType::DownloadCode: { + ui->topOSK->setCurrentIndex(0); + + if (initialize_parameters.max_text_length <= 10) { + ui->gridLineOSK->setColumnStretch(0, 390); + ui->gridLineOSK->setColumnStretch(1, 500); + ui->gridLineOSK->setColumnStretch(2, 390); + } else { + ui->gridLineOSK->setColumnStretch(0, 130); + ui->gridLineOSK->setColumnStretch(1, 1020); + ui->gridLineOSK->setColumnStretch(2, 130); + } + + if (is_inline) { + return; + } + + connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { + const auto is_valid = ValidateInputText(changed_string); + + const auto text_length = static_cast(changed_string.length()); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, + [this](int old_cursor_position, int new_cursor_position) { + ui->button_backspace->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_shift->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_num->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::returnPressed, [this] { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + }); + + ui->line_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); + ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + case SwkbdTextDrawType::Box: + default: { + ui->topOSK->setCurrentIndex(1); + + if (is_inline) { + return; + } + + connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { + if (static_cast(ui->text_edit_osk->toPlainText().length()) > + initialize_parameters.max_text_length) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } + + const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); + + const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->text_edit_osk->setFocus(); + }); + + connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { + const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + + ui->text_edit_osk->setFocus(); + }); + + ui->text_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 + ? QTextCursor::Start + : QTextCursor::End); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetControllerImage() { + const auto controller_type = Settings::values.players.GetValue()[8].connected + ? Settings::values.players.GetValue()[8].controller_type + : Settings::values.players.GetValue()[0].controller_type; + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_dark"); + } else { + return QString{}; + } + }(); + + switch (controller_type) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + break; + case Settings::ControllerType::DualJoyconDetached: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + break; + case Settings::ControllerType::LeftJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::RightJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::Handheld: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + default: { + for (const auto& keys : keyboard_buttons) { + for (const auto& rows : keys) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + } + + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + ui->button_space->setDisabled(key_disable_flags.space); + ui->button_space_shift->setDisabled(key_disable_flags.space); + + ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); + + ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); + + ui->button_slash->setDisabled(key_disable_flags.slash); + + ui->button_1->setDisabled(key_disable_flags.numbers); + ui->button_2->setDisabled(key_disable_flags.numbers); + ui->button_3->setDisabled(key_disable_flags.numbers); + ui->button_4->setDisabled(key_disable_flags.numbers); + ui->button_5->setDisabled(key_disable_flags.numbers); + ui->button_6->setDisabled(key_disable_flags.numbers); + ui->button_7->setDisabled(key_disable_flags.numbers); + ui->button_8->setDisabled(key_disable_flags.numbers); + ui->button_9->setDisabled(key_disable_flags.numbers); + ui->button_0->setDisabled(key_disable_flags.numbers); + + ui->button_return->setEnabled(initialize_parameters.enable_return_button); + ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); + break; + } + case BottomOSKIndex::NumberPad: { + for (const auto& rows : numberpad_buttons) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { + if (is_inline) { + ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(current_text.size() >= + initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + } else { + const auto text_length = [this] { + if (ui->topOSK->currentIndex() == 1) { + return static_cast(ui->text_edit_osk->toPlainText().length()); + } else { + return static_cast(ui->line_edit_osk->text().length()); + } + }(); + + const auto normal_cursor_position = [this] { + if (ui->topOSK->currentIndex() == 1) { + return ui->text_edit_osk->textCursor().position(); + } else { + return ui->line_edit_osk->cursorPosition(); + } + }(); + + ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + } +} + +bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + const auto input_text_length = static_cast(input_text.length()); + + if (input_text_length < initialize_parameters.min_text_length || + input_text_length > initialize_parameters.max_text_length) { + return false; + } + + if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { + return false; + } + + if ((key_disable_flags.at || key_disable_flags.username) && + input_text.contains(QLatin1Char{'@'})) { + return false; + } + + if ((key_disable_flags.percent || key_disable_flags.username) && + input_text.contains(QLatin1Char{'%'})) { + return false; + } + + if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { + return false; + } + + if ((key_disable_flags.backslash || key_disable_flags.username) && + input_text.contains(QLatin1Char('\\'))) { + return false; + } + + if (key_disable_flags.numbers && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { + return false; + } + + if (bottom_osk_index == BottomOSKIndex::NumberPad && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { + return false; + } + + return true; +} + +void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + bottom_osk_index = BottomOSKIndex::UpperCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + break; + case BottomOSKIndex::UpperCase: + if (caps_lock_enabled) { + caps_lock_enabled = false; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); + + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + } else { + caps_lock_enabled = true; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_on.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off")); + } + break; + case BottomOSKIndex::NumberPad: + default: + break; + } +} + +void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) { + if (button == ui->button_ampersand) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("&")); + } else { + ui->line_edit_osk->insert(QStringLiteral("&")); + } + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("\n")); + } else { + ui->line_edit_osk->insert(QStringLiteral("\n")); + } + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral(" ")); + } else { + ui->line_edit_osk->insert(QStringLiteral(" ")); + } + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (ui->topOSK->currentIndex() == 1) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } else { + ui->line_edit_osk->backspace(); + } + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + if (ui->topOSK->currentIndex() == 1) { + emit SubmitNormalText(SwkbdResult::Ok, + ui->text_edit_osk->toPlainText().toStdU16String()); + } else { + emit SubmitNormalText(SwkbdResult::Ok, ui->line_edit_osk->text().toStdU16String()); + } + return; + } + + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(button->text()); + } else { + ui->line_edit_osk->insert(button->text()); + } + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { + if (!button->isEnabled()) { + return; + } + + if (button == ui->button_ampersand) { + InlineTextInsertString(u"&"); + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + InlineTextInsertString(u"\n"); + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + InlineTextInsertString(u" "); + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (cursor_position <= 0 || current_text.empty()) { + cursor_position = 0; + return; + } + + --cursor_position; + + current_text.erase(cursor_position, 1); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); + return; + } + + InlineTextInsertString(button->text().toStdU16String()); + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { + if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { + return; + } + + current_text.insert(cursor_position, string); + + cursor_position += static_cast(string.size()); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); +} + +void QtSoftwareKeyboardDialog::SetupMouseHover() { + // setFocus() has a bug where continuously changing focus will cause the focus UI to + // mysteriously disappear. A workaround we have found is using the mouse to hover over + // the buttons to act in place of the button focus. As a result, we will have to set + // a blank cursor when hovering over all the buttons and set a no focus policy so the + // buttons do not stay in focus in addition to the mouse hover. + for (auto* button : all_buttons) { + button->setCursor(QCursor(Qt::BlankCursor)); + button->setFocusPolicy(Qt::NoFocus); + } +} + +template +void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +template +void QtSoftwareKeyboardDialog::HandleButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { + switch (button) { + case HIDButton::A: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); + break; + case BottomOSKIndex::NumberPad: + numberpad_buttons[row][column]->click(); + break; + default: + break; + } + break; + case HIDButton::B: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + break; + case HIDButton::X: + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + if (ui->topOSK->currentIndex() == 1) { + emit SubmitNormalText(SwkbdResult::Cancel, + ui->text_edit_osk->toPlainText().toStdU16String()); + } else { + emit SubmitNormalText(SwkbdResult::Cancel, + ui->line_edit_osk->text().toStdU16String()); + } + } + break; + case HIDButton::Y: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_space->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_space_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::LStick: + case HIDButton::RStick: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_shift->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_shift_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::L: + MoveTextCursorDirection(Direction::Left); + break; + case HIDButton::R: + MoveTextCursorDirection(Direction::Right); + break; + case HIDButton::Plus: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + case HIDButton::RStickLeft: + MoveButtonDirection(Direction::Left); + break; + case HIDButton::DUp: + case HIDButton::LStickUp: + case HIDButton::RStickUp: + MoveButtonDirection(Direction::Up); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + case HIDButton::RStickRight: + MoveButtonDirection(Direction::Right); + break; + case HIDButton::DDown: + case HIDButton::LStickDown: + case HIDButton::RStickDown: + MoveButtonDirection(Direction::Down); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { + // Changes the row or column index depending on the direction. + auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { + switch (direction) { + case Direction::Left: + column = (column + max_columns - 1) % max_columns; + break; + case Direction::Up: + row = (row + max_rows - 1) % max_rows; + break; + case Direction::Right: + column = (column + 1) % max_columns; + break; + case Direction::Down: + row = (row + 1) % max_rows; + break; + default: + break; + } + }; + + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: { + const auto index = static_cast(bottom_osk_index); + + const auto* const prev_button = keyboard_buttons[index][row][column]; + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + auto* curr_button = keyboard_buttons[index][row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + curr_button = keyboard_buttons[index][row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + case BottomOSKIndex::NumberPad: { + const auto* const prev_button = numberpad_buttons[row][column]; + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + auto* curr_button = numberpad_buttons[row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + curr_button = numberpad_buttons[row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { + switch (direction) { + case Direction::Left: + if (is_inline) { + if (cursor_position <= 0) { + cursor_position = 0; + } else { + --cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Left); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); + } + } + break; + case Direction::Right: + if (is_inline) { + if (cursor_position >= static_cast(current_text.size())) { + cursor_position = static_cast(current_text.size()); + } else { + ++cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Right); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); + } + } + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); +} + +void QtSoftwareKeyboardDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } + + if (input_interpreter) { + input_interpreter->ResetButtonStates(); + } +} + +void QtSoftwareKeyboardDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + HandleButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } } QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { - connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window, - &GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window, - &GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection); - connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this, - &QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, + &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, + &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, + &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); } QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; -void QtSoftwareKeyboard::RequestText(std::function)> out, - Core::Frontend::SoftwareKeyboardParameters parameters) const { - text_output = std::move(out); - emit MainWindowGetText(parameters); +void QtSoftwareKeyboard::InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + submit_inline_callback = std::move(submit_inline_callback_); + } else { + submit_normal_callback = std::move(submit_normal_callback_); + } + + LOG_INFO(Service_AM, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(initialize_parameters.ok_text), + Common::UTF16ToUTF8(initialize_parameters.header_text), + Common::UTF16ToUTF8(initialize_parameters.sub_text), + Common::UTF16ToUTF8(initialize_parameters.guide_text), + Common::UTF16ToUTF8(initialize_parameters.initial_text), + initialize_parameters.max_text_length, initialize_parameters.min_text_length, + initialize_parameters.initial_cursor_position, initialize_parameters.type, + initialize_parameters.password_mode, initialize_parameters.text_draw_type, + initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, + initialize_parameters.enable_backspace_button, + initialize_parameters.enable_return_button, + initialize_parameters.disable_cancel_button); + + emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); } -void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, - std::function finished_check_) const { - finished_check = std::move(finished_check_); - emit MainWindowTextCheckDialog(error_message); +void QtSoftwareKeyboard::ShowNormalKeyboard() const { + emit MainWindowShowNormalKeyboard(); } -void QtSoftwareKeyboard::MainWindowFinishedText(std::optional text) { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - text_output(std::move(text)); +void QtSoftwareKeyboard::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + emit MainWindowShowTextCheckDialog(text_check_result, text_check_message); } -void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - finished_check(); +void QtSoftwareKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const { + LOG_INFO(Service_AM, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); +} + +void QtSoftwareKeyboard::HideInlineKeyboard() const { + emit MainWindowHideInlineKeyboard(); +} + +void QtSoftwareKeyboard::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) const { + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + emit MainWindowInlineTextChanged(std::move(text_parameters)); +} + +void QtSoftwareKeyboard::ExitKeyboard() const { + emit MainWindowExitKeyboard(); +} + +void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const { + submit_normal_callback(result, submitted_text); +} + +void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, + s32 cursor_position) const { + submit_inline_callback(reply_type, submitted_text, cursor_position); } diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 9e1094cce..1a03c098c 100755 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -1,54 +1,228 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include +#include +#include +#include + #include #include + #include "core/frontend/applets/software_keyboard.h" +enum class HIDButton : u8; + +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class QtSoftwareKeyboardDialog; +} + class GMainWindow; -class QDialogButtonBox; -class QLabel; -class QLineEdit; -class QVBoxLayout; -class QtSoftwareKeyboard; - -class QtSoftwareKeyboardValidator final : public QValidator { -public: - explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters); - State validate(QString& input, int& pos) const override; - -private: - Core::Frontend::SoftwareKeyboardParameters parameters; -}; class QtSoftwareKeyboardDialog final : public QDialog { Q_OBJECT public: - QtSoftwareKeyboardDialog(QWidget* parent, - Core::Frontend::SoftwareKeyboardParameters parameters); + QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_); ~QtSoftwareKeyboardDialog() override; - void accept() override; + void ShowNormalKeyboard(QPoint pos, QSize size); + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, + QSize size); + + void HideInlineKeyboard(); + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + + void ExitKeyboard(); + +signals: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + +public slots: + void open() override; void reject() override; - std::u16string GetText() const; +protected: + /// We override the keyPressEvent for inputting text into the inline software keyboard. + void keyPressEvent(QKeyEvent* event) override; private: - std::u16string text; + enum class Direction { + Left, + Up, + Right, + Down, + }; - QDialogButtonBox* buttons; - QLabel* header_label; - QLabel* sub_label; - QLabel* guide_label; - QLabel* length_label; - QLineEdit* line_edit; - QVBoxLayout* layout; + enum class BottomOSKIndex { + LowerCase, + UpperCase, + NumberPad, + }; - Core::Frontend::SoftwareKeyboardParameters parameters; + /** + * Moves and resizes the window to a specified position and size. + * + * @param pos Top-left window position + * @param size Window size + */ + void MoveAndResizeWindow(QPoint pos, QSize size); + + /** + * Rescales all keyboard elements to account for High DPI displays. + * + * @param width Window width + * @param height Window height + * @param dpi_scale Display scaling factor + */ + void RescaleKeyboardElements(float width, float height, float dpi_scale); + + /// Sets the keyboard type based on initialize_parameters. + void SetKeyboardType(); + + /// Sets the password mode based on initialize_parameters. + void SetPasswordMode(); + + /// Sets the text draw type based on initialize_parameters. + void SetTextDrawType(); + + /// Sets the controller image at the bottom left of the software keyboard. + void SetControllerImage(); + + /// Disables buttons based on initialize_parameters. + void DisableKeyboardButtons(); + + /// Changes whether the backspace or/and ok buttons should be enabled or disabled. + void SetBackspaceOkEnabled(); + + /** + * Validates the input text sent in based on the parameters in initialize_parameters. + * + * @param input_text Input text + * + * @returns True if the input text is valid, false otherwise. + */ + bool ValidateInputText(const QString& input_text); + + /// Switches between LowerCase and UpperCase (Shift and Caps Lock) + void ChangeBottomOSKIndex(); + + /// Processes a keyboard button click from the UI as normal keyboard input. + void NormalKeyboardButtonClicked(QPushButton* button); + + /// Processes a keyboard button click from the UI as inline keyboard input. + void InlineKeyboardButtonClicked(QPushButton* button); + + /** + * Inserts a string of arbitrary length into the current_text at the current cursor position. + * This is only used for the inline software keyboard. + */ + void InlineTextInsertString(std::u16string_view string); + + /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. + void SetupMouseHover(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonHold(); + + /** + * Translates a button press to focus or click a keyboard button. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + /** + * Moves the focus of a button in a certain direction. + * + * @param direction The direction to move. + */ + void MoveButtonDirection(Direction direction); + + /** + * Moves the text cursor in a certain direction. + * + * @param direction The direction to move. + */ + void MoveTextCursorDirection(Direction direction); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + Core::System& system; + + // True if it is the inline software keyboard. + bool is_inline; + + // Common software keyboard initialize parameters. + Core::Frontend::KeyboardInitializeParameters initialize_parameters; + + // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. + std::u16string current_text; + s32 cursor_position{0}; + + static constexpr std::size_t NUM_ROWS_NORMAL = 5; + static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; + static constexpr std::size_t NUM_ROWS_NUMPAD = 4; + static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; + + // Stores the normal keyboard layout. + std::array, NUM_ROWS_NORMAL>, 2> + keyboard_buttons; + // Stores the numberpad keyboard layout. + std::array, NUM_ROWS_NUMPAD> numberpad_buttons; + + // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. + std::array all_buttons; + + std::size_t row{0}; + std::size_t column{0}; + + BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; + std::atomic caps_lock_enabled{false}; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; }; class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { @@ -58,19 +232,54 @@ public: explicit QtSoftwareKeyboard(GMainWindow& parent); ~QtSoftwareKeyboard() override; - void RequestText(std::function)> out, - Core::Frontend::SoftwareKeyboardParameters parameters) const override; - void SendTextCheckDialog(std::u16string error_message, - std::function finished_check_) const override; + void InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; signals: - void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; - void MainWindowTextCheckDialog(std::u16string error_message) const; + void MainWindowInitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; + + void MainWindowShowNormalKeyboard() const; + + void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const; + + void MainWindowShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const; + + void MainWindowHideInlineKeyboard() const; + + void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; + + void MainWindowExitKeyboard() const; private: - void MainWindowFinishedText(std::optional text); - void MainWindowFinishedCheckDialog(); + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; - mutable std::function)> text_output; - mutable std::function finished_check; + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; }; diff --git a/src/yuzu/applets/software_keyboard.ui b/src/yuzu/applets/software_keyboard.ui new file mode 100755 index 000000000..b0a1fcde9 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.ui @@ -0,0 +1,3503 @@ + + + QtSoftwareKeyboardDialog + + + + 0 + 0 + 1280 + 720 + + + + Software Keyboard + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 17 + + + + 0/32 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 26 + 50 + false + + + + Qt::StrongFocus + + + + + + 32 + + + Enter Text + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 23 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 17 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 17 + + + + 0/500 + + + + + + + + + + + 0 + + + 14 + + + 9 + + + 14 + + + 9 + + + + + + 26 + + + + Qt::StrongFocus + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Shift + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + - + + + + + + + + 1 + 1 + + + + + 28 + + + + ' + + + + + + + + 1 + 1 + + + + + 28 + + + + / + + + + + + + + 1 + 1 + + + + + 28 + + + + ! + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + w + + + + + + + + 1 + 1 + + + + + 28 + + + + r + + + + + + + + 1 + 1 + + + + + 28 + + + + e + + + + + + + + 1 + 1 + + + + + 28 + + + + q + + + + + + + + 1 + 1 + + + + + 28 + + + + u + + + + + + + + 1 + 1 + + + + + 28 + + + + y + + + + + + + + 1 + 1 + + + + + 28 + + + + t + + + + + + + + 1 + 1 + + + + + 28 + + + + o + + + + + + + + 1 + 1 + + + + + 28 + + + + p + + + + + + + + 1 + 1 + + + + + 28 + + + + i + + + + + + + + 1 + 1 + + + + + 28 + + + + a + + + + + + + + 1 + 1 + + + + + 28 + + + + s + + + + + + + + 1 + 1 + + + + + 28 + + + + d + + + + + + + + 1 + 1 + + + + + 28 + + + + f + + + + + + + + 1 + 1 + + + + + 28 + + + + h + + + + + + + + 1 + 1 + + + + + 28 + + + + j + + + + + + + + 1 + 1 + + + + + 28 + + + + g + + + + + + + + 1 + 1 + + + + + 28 + + + + k + + + + + + + + 1 + 1 + + + + + 28 + + + + l + + + + + + + + 1 + 1 + + + + + 28 + + + + : + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + z + + + + + + + + 1 + 1 + + + + + 28 + + + + c + + + + + + + + 1 + 1 + + + + + 28 + + + + x + + + + + + + + 1 + 1 + + + + + 28 + + + + v + + + + + + + + 1 + 1 + + + + + 28 + + + + m + + + + + + + + 1 + 1 + + + + + 28 + + + + , + + + + + + + + 1 + 1 + + + + + 28 + + + + n + + + + + + + + 1 + 1 + + + + + 28 + + + + b + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + ? + + + + + + + + 1 + 1 + + + + + 28 + + + + . + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Caps Lock + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + _ + + + + + + + + 1 + 1 + + + + + 28 + + + + " + + + + + + + + 1 + 1 + + + + + 28 + + + + @ + + + + + + + + 1 + 1 + + + + + 28 + + + + = + + + + + + + + 1 + 1 + + + + + 28 + + + + && + + + + + + + + 1 + 1 + + + + + 28 + + + + * + + + + + + + + 1 + 1 + + + + + 28 + + + + ) + + + + + + + + 1 + 1 + + + + + 28 + + + + ( + + + + + + + + 1 + 1 + + + + + 28 + + + + W + + + + + + + + 1 + 1 + + + + + 28 + + + + R + + + + + + + + 1 + 1 + + + + + 28 + + + + E + + + + + + + + 1 + 1 + + + + + 28 + + + + Q + + + + + + + + 1 + 1 + + + + + 28 + + + + U + + + + + + + + 1 + 1 + + + + + 28 + + + + Y + + + + + + + + 1 + 1 + + + + + 28 + + + + T + + + + + + + + 1 + 1 + + + + + 28 + + + + O + + + + + + + + 1 + 1 + + + + + 28 + + + + P + + + + + + + + 1 + 1 + + + + + 28 + + + + I + + + + + + + + 1 + 1 + + + + + 28 + + + + A + + + + + + + + 1 + 1 + + + + + 28 + + + + S + + + + + + + + 1 + 1 + + + + + 28 + + + + D + + + + + + + + 1 + 1 + + + + + 28 + + + + F + + + + + + + + 1 + 1 + + + + + 28 + + + + H + + + + + + + + 1 + 1 + + + + + 28 + + + + J + + + + + + + + 1 + 1 + + + + + 28 + + + + G + + + + + + + + 1 + 1 + + + + + 28 + + + + K + + + + + + + + 1 + 1 + + + + + 28 + + + + L + + + + + + + + 1 + 1 + + + + + 28 + + + + ; + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + Z + + + + + + + + 1 + 1 + + + + + 28 + + + + C + + + + + + + + 1 + 1 + + + + + 28 + + + + X + + + + + + + + 1 + 1 + + + + + 28 + + + + V + + + + + + + + 1 + 1 + + + + + 28 + + + + M + + + + + + + + 1 + 1 + + + + + 28 + + + + < + + + + + + + + 1 + 1 + + + + + 28 + + + + N + + + + + + + + 1 + 1 + + + + + 28 + + + + B + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + > + + + + + + + + 1 + 1 + + + + + 28 + + + + # + + + + + + + + 1 + 1 + + + + + 28 + + + + ] + + + + + + + + 1 + 1 + + + + + 28 + + + + $ + + + + + + + + 1 + 1 + + + + + 28 + + + + [ + + + + + + + + 1 + 1 + + + + + 28 + + + + ^ + + + + + + + + 1 + 1 + + + + + 28 + + + + % + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + + + button_1 + button_2 + button_3 + button_4 + button_5 + button_6 + button_7 + button_8 + button_9 + button_0 + button_minus + button_backspace + button_q + button_w + button_e + button_r + button_t + button_y + button_u + button_i + button_o + button_p + button_slash + button_return + button_a + button_s + button_d + button_f + button_g + button_h + button_j + button_k + button_l + button_colon + button_apostrophe + button_z + button_x + button_c + button_v + button_b + button_n + button_m + button_comma + button_dot + button_question + button_exclamation + button_ok + button_shift + button_space + button_hash + button_left_bracket + button_right_bracket + button_dollar + button_percent + button_circumflex + button_ampersand + button_asterisk + button_left_parenthesis + button_right_parenthesis + button_underscore + button_backspace_shift + button_q_shift + button_w_shift + button_e_shift + button_r_shift + button_t_shift + button_y_shift + button_u_shift + button_i_shift + button_o_shift + button_p_shift + button_at + button_return_shift + button_a_shift + button_s_shift + button_d_shift + button_f_shift + button_g_shift + button_h_shift + button_j_shift + button_k_shift + button_l_shift + button_semicolon + button_quotation + button_z_shift + button_x_shift + button_c_shift + button_v_shift + button_b_shift + button_n_shift + button_m_shift + button_less_than + button_greater_than + button_plus + button_equal + button_ok_shift + button_shift_shift + button_space_shift + button_1_num + button_2_num + button_3_num + button_backspace_num + button_4_num + button_5_num + button_6_num + button_ok_num + button_7_num + button_8_num + button_9_num + button_0_num + + + + + + diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1d6155999..4301431f8 100755 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -771,6 +771,7 @@ void Config::ReadRendererValues() { ReadSettingGlobal(Settings::values.renderer_backend, QStringLiteral("backend"), 0); ReadSettingGlobal(Settings::values.renderer_debug, QStringLiteral("debug"), false); ReadSettingGlobal(Settings::values.vulkan_device, QStringLiteral("vulkan_device"), 0); + ReadSettingGlobal(Settings::values.fullscreen_mode, QStringLiteral("fullscreen_mode"), 0); ReadSettingGlobal(Settings::values.aspect_ratio, QStringLiteral("aspect_ratio"), 0); ReadSettingGlobal(Settings::values.max_anisotropy, QStringLiteral("max_anisotropy"), 0); ReadSettingGlobal(Settings::values.use_frame_limit, QStringLiteral("use_frame_limit"), true); @@ -1334,6 +1335,7 @@ void Config::SaveRendererValues() { Settings::values.renderer_backend.UsingGlobal(), 0); WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false); WriteSettingGlobal(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); + WriteSettingGlobal(QStringLiteral("fullscreen_mode"), Settings::values.fullscreen_mode, 0); WriteSettingGlobal(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); WriteSettingGlobal(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); WriteSettingGlobal(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 9ff32aec4..2ae5bddeb 100755 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -77,18 +77,25 @@ void ConfigureGraphics::SetConfiguration() { if (Settings::IsConfiguringGlobal()) { ui->api->setCurrentIndex(static_cast(Settings::values.renderer_backend.GetValue())); + ui->fullscreen_mode_combobox->setCurrentIndex(Settings::values.fullscreen_mode.GetValue()); ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); } else { ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); ConfigurationShared::SetHighlight(ui->api_layout, !Settings::values.renderer_backend.UsingGlobal()); + + ConfigurationShared::SetPerGameSetting(ui->fullscreen_mode_combobox, + &Settings::values.fullscreen_mode); + ConfigurationShared::SetHighlight(ui->fullscreen_mode_label, + !Settings::values.fullscreen_mode.UsingGlobal()); + ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox, &Settings::values.aspect_ratio); + ConfigurationShared::SetHighlight(ui->ar_label, + !Settings::values.aspect_ratio.UsingGlobal()); ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); - ConfigurationShared::SetHighlight(ui->ar_label, - !Settings::values.aspect_ratio.UsingGlobal()); ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal()); } @@ -107,6 +114,9 @@ void ConfigureGraphics::ApplyConfiguration() { if (Settings::values.vulkan_device.UsingGlobal()) { Settings::values.vulkan_device.SetValue(vulkan_device); } + if (Settings::values.fullscreen_mode.UsingGlobal()) { + Settings::values.fullscreen_mode.SetValue(ui->fullscreen_mode_combobox->currentIndex()); + } if (Settings::values.aspect_ratio.UsingGlobal()) { Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex()); } @@ -140,6 +150,8 @@ void ConfigureGraphics::ApplyConfiguration() { } } + ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode, + ui->fullscreen_mode_combobox); ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, ui->aspect_ratio_combobox); @@ -253,6 +265,7 @@ void ConfigureGraphics::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal()); + ui->fullscreen_mode_combobox->setEnabled(Settings::values.fullscreen_mode.UsingGlobal()); ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); ui->use_asynchronous_gpu_emulation->setEnabled( Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); @@ -278,6 +291,8 @@ void ConfigureGraphics::SetupPerGameUI() { ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label, Settings::values.aspect_ratio.GetValue(true)); + ConfigurationShared::SetColoredComboBox(ui->fullscreen_mode_combobox, ui->fullscreen_mode_label, + Settings::values.fullscreen_mode.GetValue(true)); ConfigurationShared::InsertGlobalItem( ui->api, static_cast(Settings::values.renderer_backend.GetValue(true))); } diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 58486eb1e..116915efd 100755 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -104,9 +104,48 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Fullscreen Mode: + + + + + + + + Borderless Windowed (Default) + + + + + Exclusive Fullscreen + + + + + + + - + 0 diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 06445b993..f66b6c476 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -101,6 +101,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/settings.h" #include "core/telemetry_session.h" #include "input_common/main.h" +#include "util/overlay_dialog.h" #include "video_core/gpu.h" #include "video_core/shader_notify.h" #include "yuzu/about_dialog.h" @@ -225,6 +226,8 @@ GMainWindow::GMainWindow() SetDiscordEnabled(UISettings::values.enable_discord_presence); discord_rpc->Update(); + RegisterMetaTypes(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -373,6 +376,55 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::RegisterMetaTypes() { + // Register integral and floating point types + qRegisterMetaType("u8"); + qRegisterMetaType("u16"); + qRegisterMetaType("u32"); + qRegisterMetaType("u64"); + qRegisterMetaType("u128"); + qRegisterMetaType("s8"); + qRegisterMetaType("s16"); + qRegisterMetaType("s32"); + qRegisterMetaType("s64"); + qRegisterMetaType("f32"); + qRegisterMetaType("f64"); + + // Register string types + qRegisterMetaType("std::string"); + qRegisterMetaType("std::wstring"); + qRegisterMetaType("std::u8string"); + qRegisterMetaType("std::u16string"); + qRegisterMetaType("std::u32string"); + qRegisterMetaType("std::string_view"); + qRegisterMetaType("std::wstring_view"); + qRegisterMetaType("std::u8string_view"); + qRegisterMetaType("std::u16string_view"); + qRegisterMetaType("std::u32string_view"); + + // Register applet types + + // Controller Applet + qRegisterMetaType("Core::Frontend::ControllerParameters"); + + // Software Keyboard Applet + qRegisterMetaType( + "Core::Frontend::KeyboardInitializeParameters"); + qRegisterMetaType( + "Core::Frontend::InlineAppearParameters"); + qRegisterMetaType("Core::Frontend::InlineTextParameters"); + qRegisterMetaType("Service::AM::Applets::SwkbdResult"); + qRegisterMetaType( + "Service::AM::Applets::SwkbdTextCheckResult"); + qRegisterMetaType("Service::AM::Applets::SwkbdReplyType"); + + // Web Browser Applet + qRegisterMetaType("Service::AM::Applets::WebExitReason"); + + // Register loader types + qRegisterMetaType("Core::System::ResultStatus"); +} + void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get()); @@ -412,25 +464,112 @@ void GMainWindow::ProfileSelectorSelectProfile() { emit ProfileSelectorFinishedSelection(uuid); } -void GMainWindow::SoftwareKeyboardGetText( - const Core::Frontend::SoftwareKeyboardParameters& parameters) { - QtSoftwareKeyboardDialog dialog(this, parameters); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint | - Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Rejected) { - emit SoftwareKeyboardFinishedText(std::nullopt); +void GMainWindow::SoftwareKeyboardInitialize( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) { + if (software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is already initialized!"); return; } - emit SoftwareKeyboardFinishedText(dialog.GetText()); + software_keyboard = new QtSoftwareKeyboardDialog(render_window, Core::System::GetInstance(), + is_inline, std::move(initialize_parameters)); + + if (is_inline) { + connect( + software_keyboard, &QtSoftwareKeyboardDialog::SubmitInlineText, this, + [this](Service::AM::Applets::SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position) { + emit SoftwareKeyboardSubmitInlineText(reply_type, submitted_text, cursor_position); + }, + Qt::QueuedConnection); + } else { + connect( + software_keyboard, &QtSoftwareKeyboardDialog::SubmitNormalText, this, + [this](Service::AM::Applets::SwkbdResult result, std::u16string submitted_text) { + emit SoftwareKeyboardSubmitNormalText(result, submitted_text); + }, + Qt::QueuedConnection); + } } -void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message) { - QMessageBox::warning(this, tr("Text Check Failed"), QString::fromStdU16String(error_message)); - emit SoftwareKeyboardFinishedCheckDialog(); +void GMainWindow::SoftwareKeyboardShowNormal() { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + const auto& layout = render_window->GetFramebufferLayout(); + + const auto x = layout.screen.left; + const auto y = layout.screen.top; + const auto w = layout.screen.GetWidth(); + const auto h = layout.screen.GetHeight(); + + software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); +} + +void GMainWindow::SoftwareKeyboardShowTextCheck( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->ShowTextCheckDialog(text_check_result, text_check_message); +} + +void GMainWindow::SoftwareKeyboardShowInline( + Core::Frontend::InlineAppearParameters appear_parameters) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + const auto& layout = render_window->GetFramebufferLayout(); + + const auto x = + static_cast(layout.screen.left + (0.5f * layout.screen.GetWidth() * + ((2.0f * appear_parameters.key_top_translate_x) + + (1.0f - appear_parameters.key_top_scale_x)))); + const auto y = + static_cast(layout.screen.top + (layout.screen.GetHeight() * + ((2.0f * appear_parameters.key_top_translate_y) + + (1.0f - appear_parameters.key_top_scale_y)))); + const auto w = static_cast(layout.screen.GetWidth() * appear_parameters.key_top_scale_x); + const auto h = static_cast(layout.screen.GetHeight() * appear_parameters.key_top_scale_y); + + software_keyboard->ShowInlineKeyboard(std::move(appear_parameters), + render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); +} + +void GMainWindow::SoftwareKeyboardHideInline() { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->HideInlineKeyboard(); +} + +void GMainWindow::SoftwareKeyboardInlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->InlineTextChanged(std::move(text_parameters)); +} + +void GMainWindow::SoftwareKeyboardExit() { + if (!software_keyboard) { + return; + } + + software_keyboard->ExitKeyboard(); + + software_keyboard = nullptr; } void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, @@ -976,6 +1115,10 @@ void GMainWindow::ConnectWidgetEvents() { connect(this, &GMainWindow::EmulationStopping, render_window, &GRenderWindow::OnEmulationStopping); + // Software Keyboard Applet + connect(this, &GMainWindow::EmulationStarting, this, &GMainWindow::SoftwareKeyboardExit); + connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit); + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); } @@ -2185,15 +2328,6 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); - qRegisterMetaType("Core::Frontend::ControllerParameters"); - qRegisterMetaType( - "Core::Frontend::SoftwareKeyboardParameters"); - qRegisterMetaType("Core::System::ResultStatus"); - qRegisterMetaType("std::string"); - qRegisterMetaType>("std::optional"); - qRegisterMetaType("std::string_view"); - qRegisterMetaType("Service::AM::Applets::WebExitReason"); - connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); @@ -2242,8 +2376,11 @@ void GMainWindow::OnExecuteProgram(std::size_t program_index) { BootGame(last_filename_booted, program_index); } -void GMainWindow::ErrorDisplayDisplayError(QString body) { - QMessageBox::critical(this, tr("Error Display"), body); +void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { + OverlayDialog dialog(render_window, Core::System::GetInstance(), error_code, error_text, + QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); + dialog.exec(); + emit ErrorDisplayFinished(); } @@ -2295,24 +2432,66 @@ void GMainWindow::ToggleFullscreen() { void GMainWindow::ShowFullscreen() { if (ui.action_Single_Window_Mode->isChecked()) { UISettings::values.geometry = saveGeometry(); + ui.menubar->hide(); statusBar()->hide(); - showFullScreen(); + + if (Settings::values.fullscreen_mode.GetValue() == 1) { + showFullScreen(); + return; + } + + hide(); + setWindowFlags(windowFlags() | Qt::FramelessWindowHint); + const auto screen_geometry = QApplication::desktop()->screenGeometry(this); + setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(), + screen_geometry.height() + 1); + raise(); + showNormal(); } else { UISettings::values.renderwindow_geometry = render_window->saveGeometry(); - render_window->showFullScreen(); + + if (Settings::values.fullscreen_mode.GetValue() == 1) { + render_window->showFullScreen(); + return; + } + + render_window->hide(); + render_window->setWindowFlags(windowFlags() | Qt::FramelessWindowHint); + const auto screen_geometry = QApplication::desktop()->screenGeometry(this); + render_window->setGeometry(screen_geometry.x(), screen_geometry.y(), + screen_geometry.width(), screen_geometry.height() + 1); + render_window->raise(); + render_window->showNormal(); } } void GMainWindow::HideFullscreen() { if (ui.action_Single_Window_Mode->isChecked()) { + if (Settings::values.fullscreen_mode.GetValue() == 1) { + showNormal(); + restoreGeometry(UISettings::values.geometry); + } else { + hide(); + setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint); + restoreGeometry(UISettings::values.geometry); + raise(); + show(); + } + statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); ui.menubar->show(); - showNormal(); - restoreGeometry(UISettings::values.geometry); } else { - render_window->showNormal(); - render_window->restoreGeometry(UISettings::values.renderwindow_geometry); + if (Settings::values.fullscreen_mode.GetValue() == 1) { + render_window->showNormal(); + render_window->restoreGeometry(UISettings::values.renderwindow_geometry); + } else { + render_window->hide(); + render_window->setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint); + render_window->restoreGeometry(UISettings::values.renderwindow_geometry); + render_window->raise(); + render_window->show(); + } } } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 04d37d4ae..7f1e50a5b 100755 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -37,9 +37,13 @@ enum class GameListRemoveTarget; enum class InstalledEntryType; class GameListPlaceholder; +class QtSoftwareKeyboardDialog; + namespace Core::Frontend { struct ControllerParameters; -struct SoftwareKeyboardParameters; +struct InlineAppearParameters; +struct InlineTextParameters; +struct KeyboardInitializeParameters; } // namespace Core::Frontend namespace DiscordRPC { @@ -57,8 +61,11 @@ class InputSubsystem; } namespace Service::AM::Applets { +enum class SwkbdResult : u32; +enum class SwkbdTextCheckResult : u32; +enum class SwkbdReplyType : u32; enum class WebExitReason : u32; -} +} // namespace Service::AM::Applets enum class EmulatedDirectoryTarget { NAND, @@ -128,8 +135,10 @@ signals: void ProfileSelectorFinishedSelection(std::optional uuid); - void SoftwareKeyboardFinishedText(std::optional text); - void SoftwareKeyboardFinishedCheckDialog(); + void SoftwareKeyboardSubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text); + void SoftwareKeyboardSubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position); void WebBrowserExtractOfflineRomFS(); void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); @@ -139,15 +148,24 @@ public slots: void OnExecuteProgram(std::size_t program_index); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); - void ErrorDisplayDisplayError(QString body); + void SoftwareKeyboardInitialize( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters); + void SoftwareKeyboardShowNormal(); + void SoftwareKeyboardShowTextCheck(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + void SoftwareKeyboardShowInline(Core::Frontend::InlineAppearParameters appear_parameters); + void SoftwareKeyboardHideInline(); + void SoftwareKeyboardInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + void SoftwareKeyboardExit(); + void ErrorDisplayDisplayError(QString error_code, QString error_text); void ProfileSelectorSelectProfile(); - void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); - void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, bool is_local); void OnAppFocusStateChanged(Qt::ApplicationState state); private: + void RegisterMetaTypes(); + void InitializeWidgets(); void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); @@ -334,6 +352,9 @@ private: // Disables the web applet for the rest of the emulated session bool disable_web_applet{}; + // Applets + QtSoftwareKeyboardDialog* software_keyboard = nullptr; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp new file mode 100755 index 000000000..4a33fd13b --- /dev/null +++ b/src/yuzu/util/overlay_dialog.cpp @@ -0,0 +1,249 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "ui_overlay_dialog.h" +#include "yuzu/util/overlay_dialog.h" + +namespace { + +constexpr float BASE_TITLE_FONT_SIZE = 14.0f; +constexpr float BASE_FONT_SIZE = 18.0f; +constexpr float BASE_WIDTH = 1280.0f; +constexpr float BASE_HEIGHT = 720.0f; + +} // Anonymous namespace + +OverlayDialog::OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text, + const QString& body_text, const QString& left_button_text, + const QString& right_button_text, Qt::Alignment alignment, + bool use_rich_text_) + : QDialog(parent), ui{std::make_unique()}, use_rich_text{use_rich_text_} { + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_TranslucentBackground); + + if (use_rich_text) { + InitializeRichTextDialog(title_text, body_text, left_button_text, right_button_text, + alignment); + } else { + InitializeRegularTextDialog(title_text, body_text, left_button_text, right_button_text, + alignment); + } + + MoveAndResizeWindow(); + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); + + StartInputThread(); + } +} + +OverlayDialog::~OverlayDialog() { + StopInputThread(); +} + +void OverlayDialog::InitializeRegularTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, + const QString& right_button_text, + Qt::Alignment alignment) { + ui->stackedDialog->setCurrentIndex(0); + + ui->label_title->setText(title_text); + ui->label_dialog->setText(body_text); + ui->button_cancel->setText(left_button_text); + ui->button_ok->setText(right_button_text); + + ui->label_dialog->setAlignment(alignment); + + if (title_text.isEmpty()) { + ui->label_title->hide(); + ui->verticalLayout_2->setStretch(0, 0); + ui->verticalLayout_2->setStretch(1, 219); + ui->verticalLayout_2->setStretch(2, 82); + } + + if (left_button_text.isEmpty()) { + ui->button_cancel->hide(); + ui->button_cancel->setEnabled(false); + } + + if (right_button_text.isEmpty()) { + ui->button_ok->hide(); + ui->button_ok->setEnabled(false); + } + + connect( + ui->button_cancel, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::reject(); + }, + Qt::QueuedConnection); + connect( + ui->button_ok, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::accept(); + }, + Qt::QueuedConnection); +} + +void OverlayDialog::InitializeRichTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, + const QString& right_button_text, + Qt::Alignment alignment) { + ui->stackedDialog->setCurrentIndex(1); + + ui->label_title_rich->setText(title_text); + ui->text_browser_dialog->setText(body_text); + ui->button_cancel_rich->setText(left_button_text); + ui->button_ok_rich->setText(right_button_text); + + // TODO (Morph/Rei): Replace this with something that works better + ui->text_browser_dialog->setAlignment(alignment); + + if (title_text.isEmpty()) { + ui->label_title_rich->hide(); + ui->verticalLayout_3->setStretch(0, 0); + ui->verticalLayout_3->setStretch(1, 438); + ui->verticalLayout_3->setStretch(2, 82); + } + + if (left_button_text.isEmpty()) { + ui->button_cancel_rich->hide(); + ui->button_cancel_rich->setEnabled(false); + } + + if (right_button_text.isEmpty()) { + ui->button_ok_rich->hide(); + ui->button_ok_rich->setEnabled(false); + } + + connect( + ui->button_cancel_rich, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::reject(); + }, + Qt::QueuedConnection); + connect( + ui->button_ok_rich, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::accept(); + }, + Qt::QueuedConnection); +} + +void OverlayDialog::MoveAndResizeWindow() { + const auto pos = parentWidget()->mapToGlobal(parentWidget()->rect().topLeft()); + const auto width = static_cast(parentWidget()->width()); + const auto height = static_cast(parentWidget()->height()); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto body_text_font_size = + BASE_FONT_SIZE * (((width / BASE_WIDTH) + (height / BASE_HEIGHT)) / 2.0f) / dpi_scale; + const auto button_text_font_size = BASE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont title_text_font(QStringLiteral("MS Shell Dlg 2"), title_text_font_size, QFont::Normal); + QFont body_text_font(QStringLiteral("MS Shell Dlg 2"), body_text_font_size, QFont::Normal); + QFont button_text_font(QStringLiteral("MS Shell Dlg 2"), button_text_font_size, QFont::Normal); + + if (use_rich_text) { + ui->label_title_rich->setFont(title_text_font); + ui->text_browser_dialog->setFont(body_text_font); + ui->button_cancel_rich->setFont(button_text_font); + ui->button_ok_rich->setFont(button_text_font); + } else { + ui->label_title->setFont(title_text_font); + ui->label_dialog->setFont(body_text_font); + ui->button_cancel->setFont(button_text_font); + ui->button_ok->setFont(button_text_font); + } + + QDialog::move(pos); + QDialog::resize(width, height); +} + +template +void OverlayDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void OverlayDialog::TranslateButtonPress(HIDButton button) { + QPushButton* left_button = use_rich_text ? ui->button_cancel_rich : ui->button_cancel; + QPushButton* right_button = use_rich_text ? ui->button_ok_rich : ui->button_ok; + + // TODO (Morph): Handle QTextBrowser text scrolling + // TODO (Morph): focusPrevious/NextChild() doesn't work well with the rich text dialog, fix it + + switch (button) { + case HIDButton::A: + case HIDButton::B: + if (left_button->hasFocus()) { + left_button->click(); + } else if (right_button->hasFocus()) { + right_button->click(); + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + focusPreviousChild(); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + focusNextChild(); + break; + default: + break; + } +} + +void OverlayDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&OverlayDialog::InputThread, this); +} + +void OverlayDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } +} + +void OverlayDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h new file mode 100755 index 000000000..e8c388bd0 --- /dev/null +++ b/src/yuzu/util/overlay_dialog.h @@ -0,0 +1,107 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include + +#include "common/common_types.h" + +enum class HIDButton : u8; + +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class OverlayDialog; +} + +/** + * An OverlayDialog is an interactive dialog that accepts controller input (while a game is running) + * This dialog attempts to replicate the look and feel of the Nintendo Switch's overlay dialogs and + * provide some extra features such as embedding HTML/Rich Text content in a QTextBrowser. + * The OverlayDialog provides 2 modes: one to embed regular text into a QLabel and another to embed + * HTML/Rich Text content into a QTextBrowser. + */ +class OverlayDialog final : public QDialog { + Q_OBJECT + +public: + explicit OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text, + const QString& body_text, const QString& left_button_text, + const QString& right_button_text, + Qt::Alignment alignment = Qt::AlignCenter, bool use_rich_text_ = false); + ~OverlayDialog() override; + +private: + /** + * Initializes a text dialog with a QLabel storing text. + * Only use this for short text as the dialog buttons would be squashed with longer text. + * + * @param title_text Title text to be displayed + * @param body_text Main text to be displayed + * @param left_button_text Left button text. If empty, the button is hidden and disabled + * @param right_button_text Right button text. If empty, the button is hidden and disabled + * @param alignment Main text alignment + */ + void InitializeRegularTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, + const QString& right_button_text, Qt::Alignment alignment); + + /** + * Initializes a text dialog with a QTextBrowser storing text. + * This is ideal for longer text or rich text content. A scrollbar is shown for longer text. + * + * @param title_text Title text to be displayed + * @param body_text Main text to be displayed + * @param left_button_text Left button text. If empty, the button is hidden and disabled + * @param right_button_text Right button text. If empty, the button is hidden and disabled + * @param alignment Main text alignment + */ + void InitializeRichTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, const QString& right_button_text, + Qt::Alignment alignment); + + /// Moves and resizes the dialog to be fully overlayed on top of the parent window. + void MoveAndResizeWindow(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Translates a button press to focus or click either the left or right buttons. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + bool use_rich_text; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; +}; diff --git a/src/yuzu/util/overlay_dialog.ui b/src/yuzu/util/overlay_dialog.ui new file mode 100755 index 000000000..32080438b --- /dev/null +++ b/src/yuzu/util/overlay_dialog.ui @@ -0,0 +1,404 @@ + + + OverlayDialog + + + + 0 + 0 + 1280 + 720 + + + + Dialog + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 14 + + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 18 + + + + Qt::AlignCenter + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 18 + + + + Cancel + + + + + + + + 0 + 0 + + + + + 18 + + + + OK + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 14 + + + + + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 18 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 18 + + + + Cancel + + + + + + + + 0 + 0 + + + + + 18 + + + + OK + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + +