qt_software_keyboard.cpp 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646
  1. // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <QCursor>
  4. #include <QKeyEvent>
  5. #include <QScreen>
  6. #include "common/logging/log.h"
  7. #include "common/settings.h"
  8. #include "common/string_util.h"
  9. #include "core/core.h"
  10. #include "core/hid/emulated_controller.h"
  11. #include "core/hid/hid_core.h"
  12. #include "core/hid/hid_types.h"
  13. #include "core/hid/input_interpreter.h"
  14. #include "ui_qt_software_keyboard.h"
  15. #include "yuzu/applets/qt_software_keyboard.h"
  16. #include "yuzu/main.h"
  17. #include "yuzu/util/overlay_dialog.h"
  18. namespace {
  19. using namespace Service::AM::Applets;
  20. constexpr float BASE_HEADER_FONT_SIZE = 23.0f;
  21. constexpr float BASE_SUB_FONT_SIZE = 17.0f;
  22. constexpr float BASE_EDITOR_FONT_SIZE = 26.0f;
  23. constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f;
  24. constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f;
  25. constexpr float BASE_ICON_BUTTON_SIZE = 36.0f;
  26. [[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f;
  27. constexpr float BASE_HEIGHT = 720.0f;
  28. } // Anonymous namespace
  29. QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
  30. QWidget* parent, Core::System& system_, bool is_inline_,
  31. Core::Frontend::KeyboardInitializeParameters initialize_parameters_)
  32. : QDialog(parent), ui{std::make_unique<Ui::QtSoftwareKeyboardDialog>()}, system{system_},
  33. is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} {
  34. ui->setupUi(this);
  35. setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint |
  36. Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint);
  37. setWindowModality(Qt::WindowModal);
  38. setAttribute(Qt::WA_DeleteOnClose);
  39. setAttribute(Qt::WA_TranslucentBackground);
  40. keyboard_buttons = {{
  41. {{
  42. {
  43. ui->button_1,
  44. ui->button_2,
  45. ui->button_3,
  46. ui->button_4,
  47. ui->button_5,
  48. ui->button_6,
  49. ui->button_7,
  50. ui->button_8,
  51. ui->button_9,
  52. ui->button_0,
  53. ui->button_minus,
  54. ui->button_backspace,
  55. },
  56. {
  57. ui->button_q,
  58. ui->button_w,
  59. ui->button_e,
  60. ui->button_r,
  61. ui->button_t,
  62. ui->button_y,
  63. ui->button_u,
  64. ui->button_i,
  65. ui->button_o,
  66. ui->button_p,
  67. ui->button_slash,
  68. ui->button_return,
  69. },
  70. {
  71. ui->button_a,
  72. ui->button_s,
  73. ui->button_d,
  74. ui->button_f,
  75. ui->button_g,
  76. ui->button_h,
  77. ui->button_j,
  78. ui->button_k,
  79. ui->button_l,
  80. ui->button_colon,
  81. ui->button_apostrophe,
  82. ui->button_return,
  83. },
  84. {
  85. ui->button_z,
  86. ui->button_x,
  87. ui->button_c,
  88. ui->button_v,
  89. ui->button_b,
  90. ui->button_n,
  91. ui->button_m,
  92. ui->button_comma,
  93. ui->button_dot,
  94. ui->button_question,
  95. ui->button_exclamation,
  96. ui->button_ok,
  97. },
  98. {
  99. ui->button_shift,
  100. ui->button_shift,
  101. ui->button_space,
  102. ui->button_space,
  103. ui->button_space,
  104. ui->button_space,
  105. ui->button_space,
  106. ui->button_space,
  107. ui->button_space,
  108. ui->button_space,
  109. ui->button_space,
  110. ui->button_ok,
  111. },
  112. }},
  113. {{
  114. {
  115. ui->button_hash,
  116. ui->button_left_bracket,
  117. ui->button_right_bracket,
  118. ui->button_dollar,
  119. ui->button_percent,
  120. ui->button_circumflex,
  121. ui->button_ampersand,
  122. ui->button_asterisk,
  123. ui->button_left_parenthesis,
  124. ui->button_right_parenthesis,
  125. ui->button_underscore,
  126. ui->button_backspace_shift,
  127. },
  128. {
  129. ui->button_q_shift,
  130. ui->button_w_shift,
  131. ui->button_e_shift,
  132. ui->button_r_shift,
  133. ui->button_t_shift,
  134. ui->button_y_shift,
  135. ui->button_u_shift,
  136. ui->button_i_shift,
  137. ui->button_o_shift,
  138. ui->button_p_shift,
  139. ui->button_at,
  140. ui->button_return_shift,
  141. },
  142. {
  143. ui->button_a_shift,
  144. ui->button_s_shift,
  145. ui->button_d_shift,
  146. ui->button_f_shift,
  147. ui->button_g_shift,
  148. ui->button_h_shift,
  149. ui->button_j_shift,
  150. ui->button_k_shift,
  151. ui->button_l_shift,
  152. ui->button_semicolon,
  153. ui->button_quotation,
  154. ui->button_return_shift,
  155. },
  156. {
  157. ui->button_z_shift,
  158. ui->button_x_shift,
  159. ui->button_c_shift,
  160. ui->button_v_shift,
  161. ui->button_b_shift,
  162. ui->button_n_shift,
  163. ui->button_m_shift,
  164. ui->button_less_than,
  165. ui->button_greater_than,
  166. ui->button_plus,
  167. ui->button_equal,
  168. ui->button_ok_shift,
  169. },
  170. {
  171. ui->button_shift_shift,
  172. ui->button_shift_shift,
  173. ui->button_space_shift,
  174. ui->button_space_shift,
  175. ui->button_space_shift,
  176. ui->button_space_shift,
  177. ui->button_space_shift,
  178. ui->button_space_shift,
  179. ui->button_space_shift,
  180. ui->button_space_shift,
  181. ui->button_space_shift,
  182. ui->button_ok_shift,
  183. },
  184. }},
  185. }};
  186. numberpad_buttons = {{
  187. {
  188. ui->button_1_num,
  189. ui->button_2_num,
  190. ui->button_3_num,
  191. ui->button_backspace_num,
  192. },
  193. {
  194. ui->button_4_num,
  195. ui->button_5_num,
  196. ui->button_6_num,
  197. ui->button_ok_num,
  198. },
  199. {
  200. ui->button_7_num,
  201. ui->button_8_num,
  202. ui->button_9_num,
  203. ui->button_ok_num,
  204. },
  205. {
  206. nullptr,
  207. ui->button_0_num,
  208. nullptr,
  209. ui->button_ok_num,
  210. },
  211. }};
  212. all_buttons = {
  213. ui->button_1,
  214. ui->button_2,
  215. ui->button_3,
  216. ui->button_4,
  217. ui->button_5,
  218. ui->button_6,
  219. ui->button_7,
  220. ui->button_8,
  221. ui->button_9,
  222. ui->button_0,
  223. ui->button_minus,
  224. ui->button_backspace,
  225. ui->button_q,
  226. ui->button_w,
  227. ui->button_e,
  228. ui->button_r,
  229. ui->button_t,
  230. ui->button_y,
  231. ui->button_u,
  232. ui->button_i,
  233. ui->button_o,
  234. ui->button_p,
  235. ui->button_slash,
  236. ui->button_return,
  237. ui->button_a,
  238. ui->button_s,
  239. ui->button_d,
  240. ui->button_f,
  241. ui->button_g,
  242. ui->button_h,
  243. ui->button_j,
  244. ui->button_k,
  245. ui->button_l,
  246. ui->button_colon,
  247. ui->button_apostrophe,
  248. ui->button_z,
  249. ui->button_x,
  250. ui->button_c,
  251. ui->button_v,
  252. ui->button_b,
  253. ui->button_n,
  254. ui->button_m,
  255. ui->button_comma,
  256. ui->button_dot,
  257. ui->button_question,
  258. ui->button_exclamation,
  259. ui->button_ok,
  260. ui->button_shift,
  261. ui->button_space,
  262. ui->button_hash,
  263. ui->button_left_bracket,
  264. ui->button_right_bracket,
  265. ui->button_dollar,
  266. ui->button_percent,
  267. ui->button_circumflex,
  268. ui->button_ampersand,
  269. ui->button_asterisk,
  270. ui->button_left_parenthesis,
  271. ui->button_right_parenthesis,
  272. ui->button_underscore,
  273. ui->button_backspace_shift,
  274. ui->button_q_shift,
  275. ui->button_w_shift,
  276. ui->button_e_shift,
  277. ui->button_r_shift,
  278. ui->button_t_shift,
  279. ui->button_y_shift,
  280. ui->button_u_shift,
  281. ui->button_i_shift,
  282. ui->button_o_shift,
  283. ui->button_p_shift,
  284. ui->button_at,
  285. ui->button_return_shift,
  286. ui->button_a_shift,
  287. ui->button_s_shift,
  288. ui->button_d_shift,
  289. ui->button_f_shift,
  290. ui->button_g_shift,
  291. ui->button_h_shift,
  292. ui->button_j_shift,
  293. ui->button_k_shift,
  294. ui->button_l_shift,
  295. ui->button_semicolon,
  296. ui->button_quotation,
  297. ui->button_z_shift,
  298. ui->button_x_shift,
  299. ui->button_c_shift,
  300. ui->button_v_shift,
  301. ui->button_b_shift,
  302. ui->button_n_shift,
  303. ui->button_m_shift,
  304. ui->button_less_than,
  305. ui->button_greater_than,
  306. ui->button_plus,
  307. ui->button_equal,
  308. ui->button_ok_shift,
  309. ui->button_shift_shift,
  310. ui->button_space_shift,
  311. ui->button_1_num,
  312. ui->button_2_num,
  313. ui->button_3_num,
  314. ui->button_backspace_num,
  315. ui->button_4_num,
  316. ui->button_5_num,
  317. ui->button_6_num,
  318. ui->button_ok_num,
  319. ui->button_7_num,
  320. ui->button_8_num,
  321. ui->button_9_num,
  322. ui->button_0_num,
  323. };
  324. SetupMouseHover();
  325. if (!initialize_parameters.ok_text.empty()) {
  326. ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text));
  327. }
  328. ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text));
  329. ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text));
  330. current_text = initialize_parameters.initial_text;
  331. cursor_position = initialize_parameters.initial_cursor_position;
  332. SetTextDrawType();
  333. for (auto* button : all_buttons) {
  334. connect(button, &QPushButton::clicked, this, [this, button](bool) {
  335. if (is_inline) {
  336. InlineKeyboardButtonClicked(button);
  337. } else {
  338. NormalKeyboardButtonClicked(button);
  339. }
  340. });
  341. }
  342. // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
  343. if (system.IsPoweredOn()) {
  344. input_interpreter = std::make_unique<InputInterpreter>(system);
  345. }
  346. }
  347. QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() {
  348. StopInputThread();
  349. }
  350. void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) {
  351. if (isVisible()) {
  352. return;
  353. }
  354. MoveAndResizeWindow(pos, size);
  355. SetKeyboardType();
  356. SetPasswordMode();
  357. SetControllerImage();
  358. DisableKeyboardButtons();
  359. SetBackspaceOkEnabled();
  360. open();
  361. }
  362. void QtSoftwareKeyboardDialog::ShowTextCheckDialog(
  363. Service::AM::Applets::SwkbdTextCheckResult text_check_result,
  364. std::u16string text_check_message) {
  365. switch (text_check_result) {
  366. case SwkbdTextCheckResult::Success:
  367. case SwkbdTextCheckResult::Silent:
  368. default:
  369. break;
  370. case SwkbdTextCheckResult::Failure: {
  371. StopInputThread();
  372. OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message),
  373. QString{}, tr("OK"), Qt::AlignCenter);
  374. dialog.exec();
  375. StartInputThread();
  376. break;
  377. }
  378. case SwkbdTextCheckResult::Confirm: {
  379. StopInputThread();
  380. OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message),
  381. tr("Cancel"), tr("OK"), Qt::AlignCenter);
  382. if (dialog.exec() != QDialog::Accepted) {
  383. StartInputThread();
  384. break;
  385. }
  386. const auto& text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
  387. : ui->line_edit_osk->text();
  388. std::u16string text_s = Common::U16StringFromBuffer(text.utf16(), text.size());
  389. emit SubmitNormalText(SwkbdResult::Ok, std::move(text_s), true);
  390. break;
  391. }
  392. }
  393. }
  394. void QtSoftwareKeyboardDialog::ShowInlineKeyboard(
  395. Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) {
  396. MoveAndResizeWindow(pos, size);
  397. ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);"));
  398. ui->headerOSK->hide();
  399. ui->subOSK->hide();
  400. ui->inputOSK->hide();
  401. ui->charactersOSK->hide();
  402. ui->inputBoxOSK->hide();
  403. ui->charactersBoxOSK->hide();
  404. initialize_parameters.max_text_length = appear_parameters.max_text_length;
  405. initialize_parameters.min_text_length = appear_parameters.min_text_length;
  406. initialize_parameters.type = appear_parameters.type;
  407. initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags;
  408. initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button;
  409. initialize_parameters.enable_return_button = appear_parameters.enable_return_button;
  410. initialize_parameters.disable_cancel_button = appear_parameters.disable_cancel_button;
  411. SetKeyboardType();
  412. SetControllerImage();
  413. DisableKeyboardButtons();
  414. SetBackspaceOkEnabled();
  415. open();
  416. }
  417. void QtSoftwareKeyboardDialog::HideInlineKeyboard() {
  418. StopInputThread();
  419. QDialog::hide();
  420. }
  421. void QtSoftwareKeyboardDialog::InlineTextChanged(
  422. Core::Frontend::InlineTextParameters text_parameters) {
  423. current_text = text_parameters.input_text;
  424. cursor_position = text_parameters.cursor_position;
  425. SetBackspaceOkEnabled();
  426. }
  427. void QtSoftwareKeyboardDialog::ExitKeyboard() {
  428. StopInputThread();
  429. QDialog::done(QDialog::Accepted);
  430. }
  431. void QtSoftwareKeyboardDialog::open() {
  432. QDialog::open();
  433. row = 0;
  434. column = 0;
  435. switch (bottom_osk_index) {
  436. case BottomOSKIndex::LowerCase:
  437. case BottomOSKIndex::UpperCase: {
  438. const auto* const curr_button =
  439. keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column];
  440. // This is a workaround for setFocus() randomly not showing focus in the UI
  441. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  442. break;
  443. }
  444. case BottomOSKIndex::NumberPad: {
  445. const auto* const curr_button = numberpad_buttons[row][column];
  446. // This is a workaround for setFocus() randomly not showing focus in the UI
  447. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  448. break;
  449. }
  450. default:
  451. break;
  452. }
  453. StartInputThread();
  454. }
  455. void QtSoftwareKeyboardDialog::reject() {
  456. // Pressing the ESC key in a dialog calls QDialog::reject().
  457. // We will override this behavior to the "Cancel" action on the software keyboard.
  458. TranslateButtonPress(Core::HID::NpadButton::X);
  459. }
  460. void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) {
  461. if (!is_inline) {
  462. QDialog::keyPressEvent(event);
  463. return;
  464. }
  465. const auto entered_key = event->key();
  466. switch (entered_key) {
  467. case Qt::Key_Escape:
  468. QDialog::keyPressEvent(event);
  469. return;
  470. case Qt::Key_Backspace:
  471. switch (bottom_osk_index) {
  472. case BottomOSKIndex::LowerCase:
  473. ui->button_backspace->click();
  474. break;
  475. case BottomOSKIndex::UpperCase:
  476. ui->button_backspace_shift->click();
  477. break;
  478. case BottomOSKIndex::NumberPad:
  479. ui->button_backspace_num->click();
  480. break;
  481. default:
  482. break;
  483. }
  484. return;
  485. case Qt::Key_Return:
  486. switch (bottom_osk_index) {
  487. case BottomOSKIndex::LowerCase:
  488. ui->button_ok->click();
  489. break;
  490. case BottomOSKIndex::UpperCase:
  491. ui->button_ok_shift->click();
  492. break;
  493. case BottomOSKIndex::NumberPad:
  494. ui->button_ok_num->click();
  495. break;
  496. default:
  497. break;
  498. }
  499. return;
  500. case Qt::Key_Left:
  501. MoveTextCursorDirection(Direction::Left);
  502. return;
  503. case Qt::Key_Right:
  504. MoveTextCursorDirection(Direction::Right);
  505. return;
  506. default:
  507. break;
  508. }
  509. const auto entered_text = event->text();
  510. if (entered_text.isEmpty()) {
  511. return;
  512. }
  513. InlineTextInsertString(Common::U16StringFromBuffer(entered_text.utf16(), entered_text.size()));
  514. }
  515. void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) {
  516. QDialog::move(pos);
  517. QDialog::resize(size);
  518. // High DPI
  519. const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f;
  520. RescaleKeyboardElements(size.width(), size.height(), dpi_scale);
  521. }
  522. void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) {
  523. const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  524. const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  525. const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  526. const auto char_button_font_size =
  527. BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  528. const auto label_button_font_size =
  529. BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  530. QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal);
  531. QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal);
  532. QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal);
  533. QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal);
  534. QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size,
  535. QFont::Normal);
  536. ui->label_header->setFont(header_font);
  537. ui->label_sub->setFont(sub_font);
  538. ui->line_edit_osk->setFont(editor_font);
  539. ui->text_edit_osk->setFont(editor_font);
  540. ui->label_characters->setFont(sub_font);
  541. ui->label_characters_box->setFont(sub_font);
  542. ui->label_shift->setFont(label_button_font);
  543. ui->label_shift_shift->setFont(label_button_font);
  544. ui->label_cancel->setFont(label_button_font);
  545. ui->label_cancel_shift->setFont(label_button_font);
  546. ui->label_cancel_num->setFont(label_button_font);
  547. ui->label_enter->setFont(label_button_font);
  548. ui->label_enter_shift->setFont(label_button_font);
  549. ui->label_enter_num->setFont(label_button_font);
  550. for (auto* button : all_buttons) {
  551. if (button == ui->button_return || button == ui->button_return_shift) {
  552. button->setFont(label_button_font);
  553. continue;
  554. }
  555. if (button == ui->button_space || button == ui->button_space_shift) {
  556. button->setFont(label_button_font);
  557. continue;
  558. }
  559. if (button == ui->button_shift || button == ui->button_shift_shift) {
  560. button->setFont(label_button_font);
  561. button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) *
  562. (height / BASE_HEIGHT));
  563. continue;
  564. }
  565. if (button == ui->button_backspace || button == ui->button_backspace_shift ||
  566. button == ui->button_backspace_num) {
  567. button->setFont(label_button_font);
  568. button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) *
  569. (height / BASE_HEIGHT));
  570. continue;
  571. }
  572. if (button == ui->button_ok || button == ui->button_ok_shift ||
  573. button == ui->button_ok_num) {
  574. button->setFont(label_button_font);
  575. continue;
  576. }
  577. button->setFont(char_button_font);
  578. }
  579. }
  580. void QtSoftwareKeyboardDialog::SetKeyboardType() {
  581. switch (initialize_parameters.type) {
  582. case SwkbdType::Normal:
  583. case SwkbdType::Qwerty:
  584. case SwkbdType::Unknown3:
  585. case SwkbdType::Latin:
  586. case SwkbdType::SimplifiedChinese:
  587. case SwkbdType::TraditionalChinese:
  588. case SwkbdType::Korean:
  589. default: {
  590. bottom_osk_index = BottomOSKIndex::LowerCase;
  591. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  592. ui->verticalLayout_2->setStretch(0, 320);
  593. ui->verticalLayout_2->setStretch(1, 400);
  594. ui->gridLineOSK->setRowStretch(5, 94);
  595. ui->gridBoxOSK->setRowStretch(2, 81);
  596. break;
  597. }
  598. case SwkbdType::NumberPad: {
  599. bottom_osk_index = BottomOSKIndex::NumberPad;
  600. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  601. ui->verticalLayout_2->setStretch(0, 370);
  602. ui->verticalLayout_2->setStretch(1, 350);
  603. ui->gridLineOSK->setRowStretch(5, 144);
  604. ui->gridBoxOSK->setRowStretch(2, 131);
  605. break;
  606. }
  607. }
  608. }
  609. void QtSoftwareKeyboardDialog::SetPasswordMode() {
  610. switch (initialize_parameters.password_mode) {
  611. case SwkbdPasswordMode::Disabled:
  612. default:
  613. ui->line_edit_osk->setEchoMode(QLineEdit::Normal);
  614. break;
  615. case SwkbdPasswordMode::Enabled:
  616. ui->line_edit_osk->setEchoMode(QLineEdit::Password);
  617. break;
  618. }
  619. }
  620. void QtSoftwareKeyboardDialog::SetTextDrawType() {
  621. switch (initialize_parameters.text_draw_type) {
  622. case SwkbdTextDrawType::Line:
  623. case SwkbdTextDrawType::DownloadCode: {
  624. ui->topOSK->setCurrentIndex(0);
  625. if (initialize_parameters.max_text_length <= 10) {
  626. ui->gridLineOSK->setColumnStretch(0, 390);
  627. ui->gridLineOSK->setColumnStretch(1, 500);
  628. ui->gridLineOSK->setColumnStretch(2, 390);
  629. } else {
  630. ui->gridLineOSK->setColumnStretch(0, 130);
  631. ui->gridLineOSK->setColumnStretch(1, 1020);
  632. ui->gridLineOSK->setColumnStretch(2, 130);
  633. }
  634. if (is_inline) {
  635. return;
  636. }
  637. connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) {
  638. const auto is_valid = ValidateInputText(changed_string);
  639. const auto text_length = static_cast<u32>(changed_string.length());
  640. ui->label_characters->setText(QStringLiteral("%1/%2")
  641. .arg(text_length)
  642. .arg(initialize_parameters.max_text_length));
  643. ui->button_ok->setEnabled(is_valid);
  644. ui->button_ok_shift->setEnabled(is_valid);
  645. ui->button_ok_num->setEnabled(is_valid);
  646. ui->line_edit_osk->setFocus();
  647. });
  648. connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged,
  649. [this](int old_cursor_position, int new_cursor_position) {
  650. ui->button_backspace->setEnabled(
  651. initialize_parameters.enable_backspace_button && new_cursor_position > 0);
  652. ui->button_backspace_shift->setEnabled(
  653. initialize_parameters.enable_backspace_button && new_cursor_position > 0);
  654. ui->button_backspace_num->setEnabled(
  655. initialize_parameters.enable_backspace_button && new_cursor_position > 0);
  656. ui->line_edit_osk->setFocus();
  657. });
  658. connect(
  659. ui->line_edit_osk, &QLineEdit::returnPressed, this,
  660. [this] { TranslateButtonPress(Core::HID::NpadButton::Plus); }, Qt::QueuedConnection);
  661. ui->line_edit_osk->setPlaceholderText(
  662. QString::fromStdU16String(initialize_parameters.guide_text));
  663. ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text));
  664. ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length);
  665. ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position);
  666. ui->label_characters->setText(QStringLiteral("%1/%2")
  667. .arg(initialize_parameters.initial_text.size())
  668. .arg(initialize_parameters.max_text_length));
  669. break;
  670. }
  671. case SwkbdTextDrawType::Box:
  672. default: {
  673. ui->topOSK->setCurrentIndex(1);
  674. if (is_inline) {
  675. return;
  676. }
  677. connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] {
  678. if (static_cast<u32>(ui->text_edit_osk->toPlainText().length()) >
  679. initialize_parameters.max_text_length) {
  680. auto text_cursor = ui->text_edit_osk->textCursor();
  681. ui->text_edit_osk->setTextCursor(text_cursor);
  682. text_cursor.deletePreviousChar();
  683. }
  684. const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText());
  685. const auto text_length = static_cast<u32>(ui->text_edit_osk->toPlainText().length());
  686. ui->label_characters_box->setText(QStringLiteral("%1/%2")
  687. .arg(text_length)
  688. .arg(initialize_parameters.max_text_length));
  689. ui->button_ok->setEnabled(is_valid);
  690. ui->button_ok_shift->setEnabled(is_valid);
  691. ui->button_ok_num->setEnabled(is_valid);
  692. ui->text_edit_osk->setFocus();
  693. });
  694. connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] {
  695. const auto new_cursor_position = ui->text_edit_osk->textCursor().position();
  696. ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
  697. new_cursor_position > 0);
  698. ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
  699. new_cursor_position > 0);
  700. ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
  701. new_cursor_position > 0);
  702. ui->text_edit_osk->setFocus();
  703. });
  704. ui->text_edit_osk->setPlaceholderText(
  705. QString::fromStdU16String(initialize_parameters.guide_text));
  706. ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text));
  707. ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0
  708. ? QTextCursor::Start
  709. : QTextCursor::End);
  710. ui->label_characters_box->setText(QStringLiteral("%1/%2")
  711. .arg(initialize_parameters.initial_text.size())
  712. .arg(initialize_parameters.max_text_length));
  713. break;
  714. }
  715. }
  716. }
  717. void QtSoftwareKeyboardDialog::SetControllerImage() {
  718. const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
  719. const auto* player_1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
  720. const auto controller_type =
  721. handheld->IsConnected() ? handheld->GetNpadStyleIndex() : player_1->GetNpadStyleIndex();
  722. const QString theme = [] {
  723. if (QIcon::themeName().contains(QStringLiteral("dark")) ||
  724. QIcon::themeName().contains(QStringLiteral("midnight"))) {
  725. return QStringLiteral("_dark");
  726. } else {
  727. return QString{};
  728. }
  729. }();
  730. switch (controller_type) {
  731. case Core::HID::NpadStyleIndex::ProController:
  732. case Core::HID::NpadStyleIndex::GameCube:
  733. ui->icon_controller->setStyleSheet(
  734. QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
  735. ui->icon_controller_shift->setStyleSheet(
  736. QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
  737. ui->icon_controller_num->setStyleSheet(
  738. QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
  739. break;
  740. case Core::HID::NpadStyleIndex::JoyconDual:
  741. ui->icon_controller->setStyleSheet(
  742. QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
  743. ui->icon_controller_shift->setStyleSheet(
  744. QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
  745. ui->icon_controller_num->setStyleSheet(
  746. QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
  747. break;
  748. case Core::HID::NpadStyleIndex::JoyconLeft:
  749. ui->icon_controller->setStyleSheet(
  750. QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
  751. .arg(theme));
  752. ui->icon_controller_shift->setStyleSheet(
  753. QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
  754. .arg(theme));
  755. ui->icon_controller_num->setStyleSheet(
  756. QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
  757. .arg(theme));
  758. break;
  759. case Core::HID::NpadStyleIndex::JoyconRight:
  760. ui->icon_controller->setStyleSheet(
  761. QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
  762. .arg(theme));
  763. ui->icon_controller_shift->setStyleSheet(
  764. QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
  765. .arg(theme));
  766. ui->icon_controller_num->setStyleSheet(
  767. QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
  768. .arg(theme));
  769. break;
  770. case Core::HID::NpadStyleIndex::Handheld:
  771. ui->icon_controller->setStyleSheet(
  772. QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
  773. ui->icon_controller_shift->setStyleSheet(
  774. QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
  775. ui->icon_controller_num->setStyleSheet(
  776. QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
  777. break;
  778. default:
  779. break;
  780. }
  781. }
  782. void QtSoftwareKeyboardDialog::DisableKeyboardButtons() {
  783. switch (bottom_osk_index) {
  784. case BottomOSKIndex::LowerCase:
  785. case BottomOSKIndex::UpperCase:
  786. default: {
  787. for (const auto& keys : keyboard_buttons) {
  788. for (const auto& rows : keys) {
  789. for (auto* button : rows) {
  790. if (!button) {
  791. continue;
  792. }
  793. button->setEnabled(true);
  794. }
  795. }
  796. }
  797. const auto& key_disable_flags = initialize_parameters.key_disable_flags;
  798. ui->button_space->setDisabled(key_disable_flags.space);
  799. ui->button_space_shift->setDisabled(key_disable_flags.space);
  800. ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username);
  801. ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username);
  802. ui->button_slash->setDisabled(key_disable_flags.slash);
  803. ui->button_1->setDisabled(key_disable_flags.numbers);
  804. ui->button_2->setDisabled(key_disable_flags.numbers);
  805. ui->button_3->setDisabled(key_disable_flags.numbers);
  806. ui->button_4->setDisabled(key_disable_flags.numbers);
  807. ui->button_5->setDisabled(key_disable_flags.numbers);
  808. ui->button_6->setDisabled(key_disable_flags.numbers);
  809. ui->button_7->setDisabled(key_disable_flags.numbers);
  810. ui->button_8->setDisabled(key_disable_flags.numbers);
  811. ui->button_9->setDisabled(key_disable_flags.numbers);
  812. ui->button_0->setDisabled(key_disable_flags.numbers);
  813. ui->button_return->setEnabled(initialize_parameters.enable_return_button);
  814. ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button);
  815. break;
  816. }
  817. case BottomOSKIndex::NumberPad: {
  818. for (const auto& rows : numberpad_buttons) {
  819. for (auto* button : rows) {
  820. if (!button) {
  821. continue;
  822. }
  823. button->setEnabled(true);
  824. }
  825. }
  826. break;
  827. }
  828. }
  829. }
  830. void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() {
  831. if (is_inline) {
  832. ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length);
  833. ui->button_ok_shift->setEnabled(current_text.size() >=
  834. initialize_parameters.min_text_length);
  835. ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length);
  836. ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
  837. cursor_position > 0);
  838. ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
  839. cursor_position > 0);
  840. ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
  841. cursor_position > 0);
  842. } else {
  843. const auto text_length = [this] {
  844. if (ui->topOSK->currentIndex() == 1) {
  845. return static_cast<u32>(ui->text_edit_osk->toPlainText().length());
  846. } else {
  847. return static_cast<u32>(ui->line_edit_osk->text().length());
  848. }
  849. }();
  850. const auto normal_cursor_position = [this] {
  851. if (ui->topOSK->currentIndex() == 1) {
  852. return ui->text_edit_osk->textCursor().position();
  853. } else {
  854. return ui->line_edit_osk->cursorPosition();
  855. }
  856. }();
  857. ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length);
  858. ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length);
  859. ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length);
  860. ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
  861. normal_cursor_position > 0);
  862. ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
  863. normal_cursor_position > 0);
  864. ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
  865. normal_cursor_position > 0);
  866. }
  867. }
  868. bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) {
  869. const auto& key_disable_flags = initialize_parameters.key_disable_flags;
  870. const auto input_text_length = static_cast<u32>(input_text.length());
  871. if (input_text_length < initialize_parameters.min_text_length ||
  872. input_text_length > initialize_parameters.max_text_length) {
  873. return false;
  874. }
  875. if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) {
  876. return false;
  877. }
  878. if ((key_disable_flags.at || key_disable_flags.username) &&
  879. input_text.contains(QLatin1Char{'@'})) {
  880. return false;
  881. }
  882. if ((key_disable_flags.percent || key_disable_flags.username) &&
  883. input_text.contains(QLatin1Char{'%'})) {
  884. return false;
  885. }
  886. if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) {
  887. return false;
  888. }
  889. if ((key_disable_flags.backslash || key_disable_flags.username) &&
  890. input_text.contains(QLatin1Char('\\'))) {
  891. return false;
  892. }
  893. if (key_disable_flags.numbers &&
  894. std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) {
  895. return false;
  896. }
  897. if (bottom_osk_index == BottomOSKIndex::NumberPad &&
  898. std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) {
  899. return false;
  900. }
  901. return true;
  902. }
  903. void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() {
  904. switch (bottom_osk_index) {
  905. case BottomOSKIndex::LowerCase:
  906. bottom_osk_index = BottomOSKIndex::UpperCase;
  907. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  908. ui->button_shift_shift->setStyleSheet(
  909. QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);"
  910. "\nimage-position: left;"));
  911. ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
  912. ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
  913. break;
  914. case BottomOSKIndex::UpperCase:
  915. if (caps_lock_enabled) {
  916. caps_lock_enabled = false;
  917. ui->button_shift_shift->setStyleSheet(
  918. QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);"
  919. "\nimage-position: left;"));
  920. ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
  921. ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
  922. ui->label_shift_shift->setText(QStringLiteral("Caps Lock"));
  923. bottom_osk_index = BottomOSKIndex::LowerCase;
  924. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  925. } else {
  926. caps_lock_enabled = true;
  927. ui->button_shift_shift->setStyleSheet(
  928. QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);"
  929. "\nimage-position: left;"));
  930. ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
  931. ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
  932. ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off"));
  933. }
  934. break;
  935. case BottomOSKIndex::NumberPad:
  936. default:
  937. break;
  938. }
  939. }
  940. void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) {
  941. if (button == ui->button_ampersand) {
  942. if (ui->topOSK->currentIndex() == 1) {
  943. ui->text_edit_osk->insertPlainText(QStringLiteral("&"));
  944. } else {
  945. ui->line_edit_osk->insert(QStringLiteral("&"));
  946. }
  947. return;
  948. }
  949. if (button == ui->button_return || button == ui->button_return_shift) {
  950. if (ui->topOSK->currentIndex() == 1) {
  951. ui->text_edit_osk->insertPlainText(QStringLiteral("\n"));
  952. } else {
  953. ui->line_edit_osk->insert(QStringLiteral("\n"));
  954. }
  955. return;
  956. }
  957. if (button == ui->button_space || button == ui->button_space_shift) {
  958. if (ui->topOSK->currentIndex() == 1) {
  959. ui->text_edit_osk->insertPlainText(QStringLiteral(" "));
  960. } else {
  961. ui->line_edit_osk->insert(QStringLiteral(" "));
  962. }
  963. return;
  964. }
  965. if (button == ui->button_shift || button == ui->button_shift_shift) {
  966. ChangeBottomOSKIndex();
  967. return;
  968. }
  969. if (button == ui->button_backspace || button == ui->button_backspace_shift ||
  970. button == ui->button_backspace_num) {
  971. if (ui->topOSK->currentIndex() == 1) {
  972. auto text_cursor = ui->text_edit_osk->textCursor();
  973. ui->text_edit_osk->setTextCursor(text_cursor);
  974. text_cursor.deletePreviousChar();
  975. } else {
  976. ui->line_edit_osk->backspace();
  977. }
  978. return;
  979. }
  980. if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
  981. const auto& text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
  982. : ui->line_edit_osk->text();
  983. std::u16string text_s = Common::U16StringFromBuffer(text.utf16(), text.size());
  984. emit SubmitNormalText(SwkbdResult::Ok, std::move(text_s));
  985. return;
  986. }
  987. if (ui->topOSK->currentIndex() == 1) {
  988. ui->text_edit_osk->insertPlainText(button->text());
  989. } else {
  990. ui->line_edit_osk->insert(button->text());
  991. }
  992. // Revert the keyboard to lowercase if the shift key is active.
  993. if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
  994. // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase
  995. // if bottom_osk_index is UpperCase and caps_lock_enabled is true.
  996. caps_lock_enabled = true;
  997. ChangeBottomOSKIndex();
  998. }
  999. }
  1000. void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) {
  1001. if (!button->isEnabled()) {
  1002. return;
  1003. }
  1004. if (button == ui->button_ampersand) {
  1005. InlineTextInsertString(u"&");
  1006. return;
  1007. }
  1008. if (button == ui->button_return || button == ui->button_return_shift) {
  1009. InlineTextInsertString(u"\n");
  1010. return;
  1011. }
  1012. if (button == ui->button_space || button == ui->button_space_shift) {
  1013. InlineTextInsertString(u" ");
  1014. return;
  1015. }
  1016. if (button == ui->button_shift || button == ui->button_shift_shift) {
  1017. ChangeBottomOSKIndex();
  1018. return;
  1019. }
  1020. if (button == ui->button_backspace || button == ui->button_backspace_shift ||
  1021. button == ui->button_backspace_num) {
  1022. if (cursor_position <= 0 || current_text.empty()) {
  1023. cursor_position = 0;
  1024. return;
  1025. }
  1026. --cursor_position;
  1027. current_text.erase(cursor_position, 1);
  1028. SetBackspaceOkEnabled();
  1029. emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position);
  1030. return;
  1031. }
  1032. if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
  1033. emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position);
  1034. return;
  1035. }
  1036. InlineTextInsertString(
  1037. Common::U16StringFromBuffer(button->text().utf16(), button->text().size()));
  1038. // Revert the keyboard to lowercase if the shift key is active.
  1039. if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
  1040. // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase
  1041. // if bottom_osk_index is UpperCase and caps_lock_enabled is true.
  1042. caps_lock_enabled = true;
  1043. ChangeBottomOSKIndex();
  1044. }
  1045. }
  1046. void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) {
  1047. if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) {
  1048. return;
  1049. }
  1050. current_text.insert(cursor_position, string);
  1051. cursor_position += static_cast<s32>(string.size());
  1052. SetBackspaceOkEnabled();
  1053. emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position);
  1054. }
  1055. void QtSoftwareKeyboardDialog::SetupMouseHover() {
  1056. // setFocus() has a bug where continuously changing focus will cause the focus UI to
  1057. // mysteriously disappear. A workaround we have found is using the mouse to hover over
  1058. // the buttons to act in place of the button focus. As a result, we will have to set
  1059. // a blank cursor when hovering over all the buttons and set a no focus policy so the
  1060. // buttons do not stay in focus in addition to the mouse hover.
  1061. for (auto* button : all_buttons) {
  1062. button->setCursor(QCursor(Qt::BlankCursor));
  1063. button->setFocusPolicy(Qt::NoFocus);
  1064. }
  1065. }
  1066. template <Core::HID::NpadButton... T>
  1067. void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() {
  1068. const auto f = [this](Core::HID::NpadButton button) {
  1069. if (input_interpreter->IsButtonPressedOnce(button)) {
  1070. TranslateButtonPress(button);
  1071. }
  1072. };
  1073. (f(T), ...);
  1074. }
  1075. template <Core::HID::NpadButton... T>
  1076. void QtSoftwareKeyboardDialog::HandleButtonHold() {
  1077. const auto f = [this](Core::HID::NpadButton button) {
  1078. if (input_interpreter->IsButtonHeld(button)) {
  1079. TranslateButtonPress(button);
  1080. }
  1081. };
  1082. (f(T), ...);
  1083. }
  1084. void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button) {
  1085. switch (button) {
  1086. case Core::HID::NpadButton::A:
  1087. switch (bottom_osk_index) {
  1088. case BottomOSKIndex::LowerCase:
  1089. case BottomOSKIndex::UpperCase:
  1090. keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column]->click();
  1091. break;
  1092. case BottomOSKIndex::NumberPad:
  1093. numberpad_buttons[row][column]->click();
  1094. break;
  1095. default:
  1096. break;
  1097. }
  1098. break;
  1099. case Core::HID::NpadButton::B:
  1100. switch (bottom_osk_index) {
  1101. case BottomOSKIndex::LowerCase:
  1102. ui->button_backspace->click();
  1103. break;
  1104. case BottomOSKIndex::UpperCase:
  1105. ui->button_backspace_shift->click();
  1106. break;
  1107. case BottomOSKIndex::NumberPad:
  1108. ui->button_backspace_num->click();
  1109. break;
  1110. default:
  1111. break;
  1112. }
  1113. break;
  1114. case Core::HID::NpadButton::X:
  1115. if (is_inline) {
  1116. emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position);
  1117. } else {
  1118. const auto& text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
  1119. : ui->line_edit_osk->text();
  1120. std::u16string text_s = Common::U16StringFromBuffer(text.utf16(), text.size());
  1121. emit SubmitNormalText(SwkbdResult::Cancel, std::move(text_s));
  1122. }
  1123. break;
  1124. case Core::HID::NpadButton::Y:
  1125. switch (bottom_osk_index) {
  1126. case BottomOSKIndex::LowerCase:
  1127. ui->button_space->click();
  1128. break;
  1129. case BottomOSKIndex::UpperCase:
  1130. ui->button_space_shift->click();
  1131. break;
  1132. case BottomOSKIndex::NumberPad:
  1133. default:
  1134. break;
  1135. }
  1136. break;
  1137. case Core::HID::NpadButton::StickL:
  1138. case Core::HID::NpadButton::StickR:
  1139. switch (bottom_osk_index) {
  1140. case BottomOSKIndex::LowerCase:
  1141. ui->button_shift->click();
  1142. break;
  1143. case BottomOSKIndex::UpperCase:
  1144. ui->button_shift_shift->click();
  1145. break;
  1146. case BottomOSKIndex::NumberPad:
  1147. default:
  1148. break;
  1149. }
  1150. break;
  1151. case Core::HID::NpadButton::L:
  1152. MoveTextCursorDirection(Direction::Left);
  1153. break;
  1154. case Core::HID::NpadButton::R:
  1155. MoveTextCursorDirection(Direction::Right);
  1156. break;
  1157. case Core::HID::NpadButton::Plus:
  1158. switch (bottom_osk_index) {
  1159. case BottomOSKIndex::LowerCase:
  1160. ui->button_ok->click();
  1161. break;
  1162. case BottomOSKIndex::UpperCase:
  1163. ui->button_ok_shift->click();
  1164. break;
  1165. case BottomOSKIndex::NumberPad:
  1166. ui->button_ok_num->click();
  1167. break;
  1168. default:
  1169. break;
  1170. }
  1171. break;
  1172. case Core::HID::NpadButton::Left:
  1173. case Core::HID::NpadButton::StickLLeft:
  1174. case Core::HID::NpadButton::StickRLeft:
  1175. MoveButtonDirection(Direction::Left);
  1176. break;
  1177. case Core::HID::NpadButton::Up:
  1178. case Core::HID::NpadButton::StickLUp:
  1179. case Core::HID::NpadButton::StickRUp:
  1180. MoveButtonDirection(Direction::Up);
  1181. break;
  1182. case Core::HID::NpadButton::Right:
  1183. case Core::HID::NpadButton::StickLRight:
  1184. case Core::HID::NpadButton::StickRRight:
  1185. MoveButtonDirection(Direction::Right);
  1186. break;
  1187. case Core::HID::NpadButton::Down:
  1188. case Core::HID::NpadButton::StickLDown:
  1189. case Core::HID::NpadButton::StickRDown:
  1190. MoveButtonDirection(Direction::Down);
  1191. break;
  1192. default:
  1193. break;
  1194. }
  1195. }
  1196. void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
  1197. // Changes the row or column index depending on the direction.
  1198. auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) {
  1199. switch (direction) {
  1200. case Direction::Left:
  1201. column = (column + max_columns - 1) % max_columns;
  1202. break;
  1203. case Direction::Up:
  1204. row = (row + max_rows - 1) % max_rows;
  1205. break;
  1206. case Direction::Right:
  1207. column = (column + 1) % max_columns;
  1208. break;
  1209. case Direction::Down:
  1210. row = (row + 1) % max_rows;
  1211. break;
  1212. default:
  1213. break;
  1214. }
  1215. };
  1216. switch (bottom_osk_index) {
  1217. case BottomOSKIndex::LowerCase:
  1218. case BottomOSKIndex::UpperCase: {
  1219. const auto index = static_cast<std::size_t>(bottom_osk_index);
  1220. const auto* const prev_button = keyboard_buttons[index][row][column];
  1221. move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
  1222. auto* curr_button = keyboard_buttons[index][row][column];
  1223. while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
  1224. move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
  1225. curr_button = keyboard_buttons[index][row][column];
  1226. }
  1227. // This is a workaround for setFocus() randomly not showing focus in the UI
  1228. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  1229. break;
  1230. }
  1231. case BottomOSKIndex::NumberPad: {
  1232. const auto* const prev_button = numberpad_buttons[row][column];
  1233. move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
  1234. auto* curr_button = numberpad_buttons[row][column];
  1235. while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
  1236. move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
  1237. curr_button = numberpad_buttons[row][column];
  1238. }
  1239. // This is a workaround for setFocus() randomly not showing focus in the UI
  1240. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  1241. break;
  1242. }
  1243. default:
  1244. break;
  1245. }
  1246. }
  1247. void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) {
  1248. switch (direction) {
  1249. case Direction::Left:
  1250. if (is_inline) {
  1251. if (cursor_position <= 0) {
  1252. cursor_position = 0;
  1253. } else {
  1254. --cursor_position;
  1255. emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position);
  1256. }
  1257. } else {
  1258. if (ui->topOSK->currentIndex() == 1) {
  1259. ui->text_edit_osk->moveCursor(QTextCursor::Left);
  1260. } else {
  1261. ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1);
  1262. }
  1263. }
  1264. break;
  1265. case Direction::Right:
  1266. if (is_inline) {
  1267. if (cursor_position >= static_cast<s32>(current_text.size())) {
  1268. cursor_position = static_cast<s32>(current_text.size());
  1269. } else {
  1270. ++cursor_position;
  1271. emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position);
  1272. }
  1273. } else {
  1274. if (ui->topOSK->currentIndex() == 1) {
  1275. ui->text_edit_osk->moveCursor(QTextCursor::Right);
  1276. } else {
  1277. ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1);
  1278. }
  1279. }
  1280. break;
  1281. default:
  1282. break;
  1283. }
  1284. }
  1285. void QtSoftwareKeyboardDialog::StartInputThread() {
  1286. if (input_thread_running) {
  1287. return;
  1288. }
  1289. input_thread_running = true;
  1290. input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this);
  1291. }
  1292. void QtSoftwareKeyboardDialog::StopInputThread() {
  1293. input_thread_running = false;
  1294. if (input_thread.joinable()) {
  1295. input_thread.join();
  1296. }
  1297. if (input_interpreter) {
  1298. input_interpreter->ResetButtonStates();
  1299. }
  1300. }
  1301. void QtSoftwareKeyboardDialog::InputThread() {
  1302. while (input_thread_running) {
  1303. input_interpreter->PollInput();
  1304. HandleButtonPressedOnce<
  1305. Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
  1306. Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
  1307. Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
  1308. Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
  1309. Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
  1310. Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
  1311. Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
  1312. Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
  1313. Core::HID::NpadButton::StickRDown>();
  1314. HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
  1315. Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
  1316. Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
  1317. Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
  1318. Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
  1319. Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
  1320. Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
  1321. Core::HID::NpadButton::StickRDown>();
  1322. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  1323. }
  1324. }
  1325. QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
  1326. connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window,
  1327. &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection);
  1328. connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window,
  1329. &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection);
  1330. connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window,
  1331. &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection);
  1332. connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window,
  1333. &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection);
  1334. connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window,
  1335. &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection);
  1336. connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window,
  1337. &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection);
  1338. connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window,
  1339. &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection);
  1340. connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this,
  1341. &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection);
  1342. connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this,
  1343. &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection);
  1344. }
  1345. QtSoftwareKeyboard::~QtSoftwareKeyboard() = default;
  1346. void QtSoftwareKeyboard::InitializeKeyboard(
  1347. bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
  1348. std::function<void(Service::AM::Applets::SwkbdResult, std::u16string, bool)>
  1349. submit_normal_callback_,
  1350. std::function<void(Service::AM::Applets::SwkbdReplyType, std::u16string, s32)>
  1351. submit_inline_callback_) {
  1352. if (is_inline) {
  1353. submit_inline_callback = std::move(submit_inline_callback_);
  1354. } else {
  1355. submit_normal_callback = std::move(submit_normal_callback_);
  1356. }
  1357. LOG_INFO(Service_AM,
  1358. "\nKeyboardInitializeParameters:"
  1359. "\nok_text={}"
  1360. "\nheader_text={}"
  1361. "\nsub_text={}"
  1362. "\nguide_text={}"
  1363. "\ninitial_text={}"
  1364. "\nmax_text_length={}"
  1365. "\nmin_text_length={}"
  1366. "\ninitial_cursor_position={}"
  1367. "\ntype={}"
  1368. "\npassword_mode={}"
  1369. "\ntext_draw_type={}"
  1370. "\nkey_disable_flags={}"
  1371. "\nuse_blur_background={}"
  1372. "\nenable_backspace_button={}"
  1373. "\nenable_return_button={}"
  1374. "\ndisable_cancel_button={}",
  1375. Common::UTF16ToUTF8(initialize_parameters.ok_text),
  1376. Common::UTF16ToUTF8(initialize_parameters.header_text),
  1377. Common::UTF16ToUTF8(initialize_parameters.sub_text),
  1378. Common::UTF16ToUTF8(initialize_parameters.guide_text),
  1379. Common::UTF16ToUTF8(initialize_parameters.initial_text),
  1380. initialize_parameters.max_text_length, initialize_parameters.min_text_length,
  1381. initialize_parameters.initial_cursor_position, initialize_parameters.type,
  1382. initialize_parameters.password_mode, initialize_parameters.text_draw_type,
  1383. initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background,
  1384. initialize_parameters.enable_backspace_button,
  1385. initialize_parameters.enable_return_button,
  1386. initialize_parameters.disable_cancel_button);
  1387. emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters));
  1388. }
  1389. void QtSoftwareKeyboard::ShowNormalKeyboard() const {
  1390. emit MainWindowShowNormalKeyboard();
  1391. }
  1392. void QtSoftwareKeyboard::ShowTextCheckDialog(
  1393. Service::AM::Applets::SwkbdTextCheckResult text_check_result,
  1394. std::u16string text_check_message) const {
  1395. emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message));
  1396. }
  1397. void QtSoftwareKeyboard::ShowInlineKeyboard(
  1398. Core::Frontend::InlineAppearParameters appear_parameters) const {
  1399. LOG_INFO(Service_AM,
  1400. "\nInlineAppearParameters:"
  1401. "\nmax_text_length={}"
  1402. "\nmin_text_length={}"
  1403. "\nkey_top_scale_x={}"
  1404. "\nkey_top_scale_y={}"
  1405. "\nkey_top_translate_x={}"
  1406. "\nkey_top_translate_y={}"
  1407. "\ntype={}"
  1408. "\nkey_disable_flags={}"
  1409. "\nkey_top_as_floating={}"
  1410. "\nenable_backspace_button={}"
  1411. "\nenable_return_button={}"
  1412. "\ndisable_cancel_button={}",
  1413. appear_parameters.max_text_length, appear_parameters.min_text_length,
  1414. appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y,
  1415. appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y,
  1416. appear_parameters.type, appear_parameters.key_disable_flags.raw,
  1417. appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button,
  1418. appear_parameters.enable_return_button, appear_parameters.disable_cancel_button);
  1419. emit MainWindowShowInlineKeyboard(std::move(appear_parameters));
  1420. }
  1421. void QtSoftwareKeyboard::HideInlineKeyboard() const {
  1422. emit MainWindowHideInlineKeyboard();
  1423. }
  1424. void QtSoftwareKeyboard::InlineTextChanged(
  1425. Core::Frontend::InlineTextParameters text_parameters) const {
  1426. LOG_INFO(Service_AM,
  1427. "\nInlineTextParameters:"
  1428. "\ninput_text={}"
  1429. "\ncursor_position={}",
  1430. Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position);
  1431. emit MainWindowInlineTextChanged(std::move(text_parameters));
  1432. }
  1433. void QtSoftwareKeyboard::ExitKeyboard() const {
  1434. emit MainWindowExitKeyboard();
  1435. }
  1436. void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result,
  1437. std::u16string submitted_text, bool confirmed) const {
  1438. submit_normal_callback(result, submitted_text, confirmed);
  1439. }
  1440. void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type,
  1441. std::u16string submitted_text,
  1442. s32 cursor_position) const {
  1443. submit_inline_callback(reply_type, submitted_text, cursor_position);
  1444. }