qt_software_keyboard.cpp 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674
  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 "hid_core/frontend/emulated_controller.h"
  11. #include "hid_core/frontend/input_interpreter.h"
  12. #include "hid_core/hid_core.h"
  13. #include "hid_core/hid_types.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. ui->button_left_optional_num,
  207. ui->button_0_num,
  208. ui->button_right_optional_num,
  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_left_optional_num,
  323. ui->button_0_num,
  324. ui->button_right_optional_num,
  325. };
  326. SetupMouseHover();
  327. if (!initialize_parameters.ok_text.empty()) {
  328. ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text));
  329. }
  330. ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text));
  331. ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text));
  332. ui->button_left_optional_num->setText(QChar{initialize_parameters.left_optional_symbol_key});
  333. ui->button_right_optional_num->setText(QChar{initialize_parameters.right_optional_symbol_key});
  334. current_text = initialize_parameters.initial_text;
  335. cursor_position = initialize_parameters.initial_cursor_position;
  336. SetTextDrawType();
  337. for (auto* button : all_buttons) {
  338. connect(button, &QPushButton::clicked, this, [this, button](bool) {
  339. if (is_inline) {
  340. InlineKeyboardButtonClicked(button);
  341. } else {
  342. NormalKeyboardButtonClicked(button);
  343. }
  344. });
  345. }
  346. // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
  347. if (system.IsPoweredOn()) {
  348. input_interpreter = std::make_unique<InputInterpreter>(system);
  349. }
  350. }
  351. QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() {
  352. StopInputThread();
  353. }
  354. void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) {
  355. if (isVisible()) {
  356. return;
  357. }
  358. MoveAndResizeWindow(pos, size);
  359. SetKeyboardType();
  360. SetPasswordMode();
  361. SetControllerImage();
  362. DisableKeyboardButtons();
  363. SetBackspaceOkEnabled();
  364. open();
  365. }
  366. void QtSoftwareKeyboardDialog::ShowTextCheckDialog(
  367. Service::AM::Applets::SwkbdTextCheckResult text_check_result,
  368. std::u16string text_check_message) {
  369. switch (text_check_result) {
  370. case SwkbdTextCheckResult::Success:
  371. case SwkbdTextCheckResult::Silent:
  372. default:
  373. break;
  374. case SwkbdTextCheckResult::Failure: {
  375. StopInputThread();
  376. OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message),
  377. QString{}, tr("OK"), Qt::AlignCenter);
  378. dialog.exec();
  379. StartInputThread();
  380. break;
  381. }
  382. case SwkbdTextCheckResult::Confirm: {
  383. StopInputThread();
  384. OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message),
  385. tr("Cancel"), tr("OK"), Qt::AlignCenter);
  386. if (dialog.exec() != QDialog::Accepted) {
  387. StartInputThread();
  388. break;
  389. }
  390. const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
  391. : ui->line_edit_osk->text();
  392. auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
  393. emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str), true);
  394. break;
  395. }
  396. }
  397. }
  398. void QtSoftwareKeyboardDialog::ShowInlineKeyboard(
  399. Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) {
  400. MoveAndResizeWindow(pos, size);
  401. ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);"));
  402. ui->headerOSK->hide();
  403. ui->subOSK->hide();
  404. ui->inputOSK->hide();
  405. ui->charactersOSK->hide();
  406. ui->inputBoxOSK->hide();
  407. ui->charactersBoxOSK->hide();
  408. initialize_parameters.max_text_length = appear_parameters.max_text_length;
  409. initialize_parameters.min_text_length = appear_parameters.min_text_length;
  410. initialize_parameters.type = appear_parameters.type;
  411. initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags;
  412. initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button;
  413. initialize_parameters.enable_return_button = appear_parameters.enable_return_button;
  414. initialize_parameters.disable_cancel_button = appear_parameters.disable_cancel_button;
  415. SetKeyboardType();
  416. SetControllerImage();
  417. DisableKeyboardButtons();
  418. SetBackspaceOkEnabled();
  419. open();
  420. }
  421. void QtSoftwareKeyboardDialog::HideInlineKeyboard() {
  422. StopInputThread();
  423. QDialog::hide();
  424. }
  425. void QtSoftwareKeyboardDialog::InlineTextChanged(
  426. Core::Frontend::InlineTextParameters text_parameters) {
  427. current_text = text_parameters.input_text;
  428. cursor_position = text_parameters.cursor_position;
  429. SetBackspaceOkEnabled();
  430. }
  431. void QtSoftwareKeyboardDialog::ExitKeyboard() {
  432. StopInputThread();
  433. QDialog::done(QDialog::Accepted);
  434. }
  435. void QtSoftwareKeyboardDialog::open() {
  436. QDialog::open();
  437. row = 0;
  438. column = 0;
  439. switch (bottom_osk_index) {
  440. case BottomOSKIndex::LowerCase:
  441. case BottomOSKIndex::UpperCase: {
  442. const auto* const curr_button =
  443. keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column];
  444. // This is a workaround for setFocus() randomly not showing focus in the UI
  445. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  446. break;
  447. }
  448. case BottomOSKIndex::NumberPad: {
  449. const auto* const curr_button = numberpad_buttons[row][column];
  450. // This is a workaround for setFocus() randomly not showing focus in the UI
  451. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  452. break;
  453. }
  454. default:
  455. break;
  456. }
  457. StartInputThread();
  458. }
  459. void QtSoftwareKeyboardDialog::reject() {
  460. // Pressing the ESC key in a dialog calls QDialog::reject().
  461. // We will override this behavior to the "Cancel" action on the software keyboard.
  462. TranslateButtonPress(Core::HID::NpadButton::X);
  463. }
  464. void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) {
  465. if (!is_inline) {
  466. QDialog::keyPressEvent(event);
  467. return;
  468. }
  469. const auto entered_key = event->key();
  470. switch (entered_key) {
  471. case Qt::Key_Escape:
  472. QDialog::keyPressEvent(event);
  473. return;
  474. case Qt::Key_Backspace:
  475. switch (bottom_osk_index) {
  476. case BottomOSKIndex::LowerCase:
  477. ui->button_backspace->click();
  478. break;
  479. case BottomOSKIndex::UpperCase:
  480. ui->button_backspace_shift->click();
  481. break;
  482. case BottomOSKIndex::NumberPad:
  483. ui->button_backspace_num->click();
  484. break;
  485. default:
  486. break;
  487. }
  488. return;
  489. case Qt::Key_Return:
  490. switch (bottom_osk_index) {
  491. case BottomOSKIndex::LowerCase:
  492. ui->button_ok->click();
  493. break;
  494. case BottomOSKIndex::UpperCase:
  495. ui->button_ok_shift->click();
  496. break;
  497. case BottomOSKIndex::NumberPad:
  498. ui->button_ok_num->click();
  499. break;
  500. default:
  501. break;
  502. }
  503. return;
  504. case Qt::Key_Left:
  505. MoveTextCursorDirection(Direction::Left);
  506. return;
  507. case Qt::Key_Right:
  508. MoveTextCursorDirection(Direction::Right);
  509. return;
  510. default:
  511. break;
  512. }
  513. const auto entered_text = event->text();
  514. if (entered_text.isEmpty()) {
  515. return;
  516. }
  517. InlineTextInsertString(Common::U16StringFromBuffer(entered_text.utf16(), entered_text.size()));
  518. }
  519. void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) {
  520. QDialog::move(pos);
  521. QDialog::resize(size);
  522. // High DPI
  523. const float dpi_scale = screen()->logicalDotsPerInch() / 96.0f;
  524. RescaleKeyboardElements(size.width(), size.height(), dpi_scale);
  525. }
  526. void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) {
  527. const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  528. const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  529. const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  530. const auto char_button_font_size =
  531. BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  532. const auto label_button_font_size =
  533. BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  534. QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal);
  535. QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal);
  536. QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal);
  537. QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal);
  538. QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size,
  539. QFont::Normal);
  540. ui->label_header->setFont(header_font);
  541. ui->label_sub->setFont(sub_font);
  542. ui->line_edit_osk->setFont(editor_font);
  543. ui->text_edit_osk->setFont(editor_font);
  544. ui->label_characters->setFont(sub_font);
  545. ui->label_characters_box->setFont(sub_font);
  546. ui->label_shift->setFont(label_button_font);
  547. ui->label_shift_shift->setFont(label_button_font);
  548. ui->label_cancel->setFont(label_button_font);
  549. ui->label_cancel_shift->setFont(label_button_font);
  550. ui->label_cancel_num->setFont(label_button_font);
  551. ui->label_enter->setFont(label_button_font);
  552. ui->label_enter_shift->setFont(label_button_font);
  553. ui->label_enter_num->setFont(label_button_font);
  554. for (auto* button : all_buttons) {
  555. if (button == ui->button_return || button == ui->button_return_shift) {
  556. button->setFont(label_button_font);
  557. continue;
  558. }
  559. if (button == ui->button_space || button == ui->button_space_shift) {
  560. button->setFont(label_button_font);
  561. continue;
  562. }
  563. if (button == ui->button_shift || button == ui->button_shift_shift) {
  564. button->setFont(label_button_font);
  565. button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) *
  566. (height / BASE_HEIGHT));
  567. continue;
  568. }
  569. if (button == ui->button_backspace || button == ui->button_backspace_shift ||
  570. button == ui->button_backspace_num) {
  571. button->setFont(label_button_font);
  572. button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) *
  573. (height / BASE_HEIGHT));
  574. continue;
  575. }
  576. if (button == ui->button_ok || button == ui->button_ok_shift ||
  577. button == ui->button_ok_num) {
  578. button->setFont(label_button_font);
  579. continue;
  580. }
  581. button->setFont(char_button_font);
  582. }
  583. }
  584. void QtSoftwareKeyboardDialog::SetKeyboardType() {
  585. switch (initialize_parameters.type) {
  586. case SwkbdType::Normal:
  587. case SwkbdType::Qwerty:
  588. case SwkbdType::Unknown3:
  589. case SwkbdType::Latin:
  590. case SwkbdType::SimplifiedChinese:
  591. case SwkbdType::TraditionalChinese:
  592. case SwkbdType::Korean:
  593. default: {
  594. bottom_osk_index = BottomOSKIndex::LowerCase;
  595. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  596. ui->verticalLayout_2->setStretch(0, 320);
  597. ui->verticalLayout_2->setStretch(1, 400);
  598. ui->gridLineOSK->setRowStretch(5, 94);
  599. ui->gridBoxOSK->setRowStretch(2, 81);
  600. break;
  601. }
  602. case SwkbdType::NumberPad: {
  603. bottom_osk_index = BottomOSKIndex::NumberPad;
  604. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  605. ui->verticalLayout_2->setStretch(0, 370);
  606. ui->verticalLayout_2->setStretch(1, 350);
  607. ui->gridLineOSK->setRowStretch(5, 144);
  608. ui->gridBoxOSK->setRowStretch(2, 131);
  609. break;
  610. }
  611. }
  612. }
  613. void QtSoftwareKeyboardDialog::SetPasswordMode() {
  614. switch (initialize_parameters.password_mode) {
  615. case SwkbdPasswordMode::Disabled:
  616. default:
  617. ui->line_edit_osk->setEchoMode(QLineEdit::Normal);
  618. break;
  619. case SwkbdPasswordMode::Enabled:
  620. ui->line_edit_osk->setEchoMode(QLineEdit::Password);
  621. break;
  622. }
  623. }
  624. void QtSoftwareKeyboardDialog::SetTextDrawType() {
  625. switch (initialize_parameters.text_draw_type) {
  626. case SwkbdTextDrawType::Line:
  627. case SwkbdTextDrawType::DownloadCode: {
  628. ui->topOSK->setCurrentIndex(0);
  629. if (initialize_parameters.max_text_length <= 10) {
  630. ui->gridLineOSK->setColumnStretch(0, 390);
  631. ui->gridLineOSK->setColumnStretch(1, 500);
  632. ui->gridLineOSK->setColumnStretch(2, 390);
  633. } else {
  634. ui->gridLineOSK->setColumnStretch(0, 130);
  635. ui->gridLineOSK->setColumnStretch(1, 1020);
  636. ui->gridLineOSK->setColumnStretch(2, 130);
  637. }
  638. if (is_inline) {
  639. return;
  640. }
  641. connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) {
  642. const auto is_valid = ValidateInputText(changed_string);
  643. const auto text_length = static_cast<u32>(changed_string.length());
  644. ui->label_characters->setText(QStringLiteral("%1/%2")
  645. .arg(text_length)
  646. .arg(initialize_parameters.max_text_length));
  647. ui->button_ok->setEnabled(is_valid);
  648. ui->button_ok_shift->setEnabled(is_valid);
  649. ui->button_ok_num->setEnabled(is_valid);
  650. ui->line_edit_osk->setFocus();
  651. });
  652. connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged,
  653. [this](int old_cursor_position, int new_cursor_position) {
  654. ui->button_backspace->setEnabled(
  655. initialize_parameters.enable_backspace_button && new_cursor_position > 0);
  656. ui->button_backspace_shift->setEnabled(
  657. initialize_parameters.enable_backspace_button && new_cursor_position > 0);
  658. ui->button_backspace_num->setEnabled(
  659. initialize_parameters.enable_backspace_button && new_cursor_position > 0);
  660. ui->line_edit_osk->setFocus();
  661. });
  662. connect(
  663. ui->line_edit_osk, &QLineEdit::returnPressed, this,
  664. [this] { TranslateButtonPress(Core::HID::NpadButton::Plus); }, Qt::QueuedConnection);
  665. ui->line_edit_osk->setPlaceholderText(
  666. QString::fromStdU16String(initialize_parameters.guide_text));
  667. ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text));
  668. ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length);
  669. ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position);
  670. ui->label_characters->setText(QStringLiteral("%1/%2")
  671. .arg(initialize_parameters.initial_text.size())
  672. .arg(initialize_parameters.max_text_length));
  673. break;
  674. }
  675. case SwkbdTextDrawType::Box:
  676. default: {
  677. ui->topOSK->setCurrentIndex(1);
  678. if (is_inline) {
  679. return;
  680. }
  681. connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] {
  682. if (static_cast<u32>(ui->text_edit_osk->toPlainText().length()) >
  683. initialize_parameters.max_text_length) {
  684. auto text_cursor = ui->text_edit_osk->textCursor();
  685. ui->text_edit_osk->setTextCursor(text_cursor);
  686. text_cursor.deletePreviousChar();
  687. }
  688. const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText());
  689. const auto text_length = static_cast<u32>(ui->text_edit_osk->toPlainText().length());
  690. ui->label_characters_box->setText(QStringLiteral("%1/%2")
  691. .arg(text_length)
  692. .arg(initialize_parameters.max_text_length));
  693. ui->button_ok->setEnabled(is_valid);
  694. ui->button_ok_shift->setEnabled(is_valid);
  695. ui->button_ok_num->setEnabled(is_valid);
  696. ui->text_edit_osk->setFocus();
  697. });
  698. connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] {
  699. const auto new_cursor_position = ui->text_edit_osk->textCursor().position();
  700. ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
  701. new_cursor_position > 0);
  702. ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
  703. new_cursor_position > 0);
  704. ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
  705. new_cursor_position > 0);
  706. ui->text_edit_osk->setFocus();
  707. });
  708. ui->text_edit_osk->setPlaceholderText(
  709. QString::fromStdU16String(initialize_parameters.guide_text));
  710. ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text));
  711. ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0
  712. ? QTextCursor::Start
  713. : QTextCursor::End);
  714. ui->label_characters_box->setText(QStringLiteral("%1/%2")
  715. .arg(initialize_parameters.initial_text.size())
  716. .arg(initialize_parameters.max_text_length));
  717. break;
  718. }
  719. }
  720. }
  721. void QtSoftwareKeyboardDialog::SetControllerImage() {
  722. const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
  723. const auto* player_1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
  724. const auto controller_type =
  725. handheld->IsConnected() ? handheld->GetNpadStyleIndex() : player_1->GetNpadStyleIndex();
  726. const QString theme = [] {
  727. if (QIcon::themeName().contains(QStringLiteral("dark")) ||
  728. QIcon::themeName().contains(QStringLiteral("midnight"))) {
  729. return QStringLiteral("_dark");
  730. } else {
  731. return QString{};
  732. }
  733. }();
  734. switch (controller_type) {
  735. case Core::HID::NpadStyleIndex::Fullkey:
  736. case Core::HID::NpadStyleIndex::GameCube:
  737. ui->icon_controller->setStyleSheet(
  738. QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
  739. ui->icon_controller_shift->setStyleSheet(
  740. QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
  741. ui->icon_controller_num->setStyleSheet(
  742. QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
  743. break;
  744. case Core::HID::NpadStyleIndex::JoyconDual:
  745. ui->icon_controller->setStyleSheet(
  746. QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
  747. ui->icon_controller_shift->setStyleSheet(
  748. QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
  749. ui->icon_controller_num->setStyleSheet(
  750. QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
  751. break;
  752. case Core::HID::NpadStyleIndex::JoyconLeft:
  753. ui->icon_controller->setStyleSheet(
  754. QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
  755. .arg(theme));
  756. ui->icon_controller_shift->setStyleSheet(
  757. QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
  758. .arg(theme));
  759. ui->icon_controller_num->setStyleSheet(
  760. QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
  761. .arg(theme));
  762. break;
  763. case Core::HID::NpadStyleIndex::JoyconRight:
  764. ui->icon_controller->setStyleSheet(
  765. QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
  766. .arg(theme));
  767. ui->icon_controller_shift->setStyleSheet(
  768. QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
  769. .arg(theme));
  770. ui->icon_controller_num->setStyleSheet(
  771. QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
  772. .arg(theme));
  773. break;
  774. case Core::HID::NpadStyleIndex::Handheld:
  775. ui->icon_controller->setStyleSheet(
  776. QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
  777. ui->icon_controller_shift->setStyleSheet(
  778. QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
  779. ui->icon_controller_num->setStyleSheet(
  780. QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
  781. break;
  782. default:
  783. break;
  784. }
  785. }
  786. void QtSoftwareKeyboardDialog::DisableKeyboardButtons() {
  787. switch (bottom_osk_index) {
  788. case BottomOSKIndex::LowerCase:
  789. case BottomOSKIndex::UpperCase:
  790. default: {
  791. for (const auto& keys : keyboard_buttons) {
  792. for (const auto& rows : keys) {
  793. for (auto* button : rows) {
  794. if (!button) {
  795. continue;
  796. }
  797. button->setEnabled(true);
  798. }
  799. }
  800. }
  801. const auto& key_disable_flags = initialize_parameters.key_disable_flags;
  802. ui->button_space->setDisabled(key_disable_flags.space);
  803. ui->button_space_shift->setDisabled(key_disable_flags.space);
  804. ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username);
  805. ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username);
  806. ui->button_slash->setDisabled(key_disable_flags.slash);
  807. ui->button_1->setDisabled(key_disable_flags.numbers);
  808. ui->button_2->setDisabled(key_disable_flags.numbers);
  809. ui->button_3->setDisabled(key_disable_flags.numbers);
  810. ui->button_4->setDisabled(key_disable_flags.numbers);
  811. ui->button_5->setDisabled(key_disable_flags.numbers);
  812. ui->button_6->setDisabled(key_disable_flags.numbers);
  813. ui->button_7->setDisabled(key_disable_flags.numbers);
  814. ui->button_8->setDisabled(key_disable_flags.numbers);
  815. ui->button_9->setDisabled(key_disable_flags.numbers);
  816. ui->button_0->setDisabled(key_disable_flags.numbers);
  817. ui->button_return->setEnabled(initialize_parameters.enable_return_button);
  818. ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button);
  819. break;
  820. }
  821. case BottomOSKIndex::NumberPad: {
  822. for (const auto& rows : numberpad_buttons) {
  823. for (auto* button : rows) {
  824. if (!button) {
  825. continue;
  826. }
  827. button->setEnabled(true);
  828. }
  829. }
  830. const auto enable_left_optional = initialize_parameters.left_optional_symbol_key != '\0';
  831. const auto enable_right_optional = initialize_parameters.right_optional_symbol_key != '\0';
  832. ui->button_left_optional_num->setEnabled(enable_left_optional);
  833. ui->button_left_optional_num->setVisible(enable_left_optional);
  834. ui->button_right_optional_num->setEnabled(enable_right_optional);
  835. ui->button_right_optional_num->setVisible(enable_right_optional);
  836. break;
  837. }
  838. }
  839. }
  840. void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() {
  841. if (is_inline) {
  842. ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length);
  843. ui->button_ok_shift->setEnabled(current_text.size() >=
  844. initialize_parameters.min_text_length);
  845. ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length);
  846. ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
  847. cursor_position > 0);
  848. ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
  849. cursor_position > 0);
  850. ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
  851. cursor_position > 0);
  852. } else {
  853. const auto text_length = [this] {
  854. if (ui->topOSK->currentIndex() == 1) {
  855. return static_cast<u32>(ui->text_edit_osk->toPlainText().length());
  856. } else {
  857. return static_cast<u32>(ui->line_edit_osk->text().length());
  858. }
  859. }();
  860. const auto normal_cursor_position = [this] {
  861. if (ui->topOSK->currentIndex() == 1) {
  862. return ui->text_edit_osk->textCursor().position();
  863. } else {
  864. return ui->line_edit_osk->cursorPosition();
  865. }
  866. }();
  867. ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length);
  868. ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length);
  869. ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length);
  870. ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
  871. normal_cursor_position > 0);
  872. ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
  873. normal_cursor_position > 0);
  874. ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
  875. normal_cursor_position > 0);
  876. }
  877. }
  878. bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) {
  879. const auto& key_disable_flags = initialize_parameters.key_disable_flags;
  880. const auto input_text_length = static_cast<u32>(input_text.length());
  881. if (input_text_length < initialize_parameters.min_text_length ||
  882. input_text_length > initialize_parameters.max_text_length) {
  883. return false;
  884. }
  885. if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) {
  886. return false;
  887. }
  888. if ((key_disable_flags.at || key_disable_flags.username) &&
  889. input_text.contains(QLatin1Char{'@'})) {
  890. return false;
  891. }
  892. if ((key_disable_flags.percent || key_disable_flags.username) &&
  893. input_text.contains(QLatin1Char{'%'})) {
  894. return false;
  895. }
  896. if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) {
  897. return false;
  898. }
  899. if ((key_disable_flags.backslash || key_disable_flags.username) &&
  900. input_text.contains(QLatin1Char('\\'))) {
  901. return false;
  902. }
  903. if (key_disable_flags.numbers &&
  904. std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) {
  905. return false;
  906. }
  907. if (bottom_osk_index == BottomOSKIndex::NumberPad &&
  908. std::any_of(input_text.begin(), input_text.end(), [this](QChar c) {
  909. return !c.isDigit() && c != QChar{initialize_parameters.left_optional_symbol_key} &&
  910. c != QChar{initialize_parameters.right_optional_symbol_key};
  911. })) {
  912. return false;
  913. }
  914. return true;
  915. }
  916. void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() {
  917. switch (bottom_osk_index) {
  918. case BottomOSKIndex::LowerCase:
  919. bottom_osk_index = BottomOSKIndex::UpperCase;
  920. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  921. ui->button_shift_shift->setStyleSheet(
  922. QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);"
  923. "\nimage-position: left;"));
  924. ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
  925. ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
  926. break;
  927. case BottomOSKIndex::UpperCase:
  928. if (caps_lock_enabled) {
  929. caps_lock_enabled = false;
  930. ui->button_shift_shift->setStyleSheet(
  931. QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);"
  932. "\nimage-position: left;"));
  933. ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
  934. ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
  935. ui->label_shift_shift->setText(QStringLiteral("Caps Lock"));
  936. bottom_osk_index = BottomOSKIndex::LowerCase;
  937. ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
  938. } else {
  939. caps_lock_enabled = true;
  940. ui->button_shift_shift->setStyleSheet(
  941. QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);"
  942. "\nimage-position: left;"));
  943. ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
  944. ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
  945. ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off"));
  946. }
  947. break;
  948. case BottomOSKIndex::NumberPad:
  949. default:
  950. break;
  951. }
  952. }
  953. void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) {
  954. if (button == ui->button_ampersand) {
  955. if (ui->topOSK->currentIndex() == 1) {
  956. ui->text_edit_osk->insertPlainText(QStringLiteral("&"));
  957. } else {
  958. ui->line_edit_osk->insert(QStringLiteral("&"));
  959. }
  960. return;
  961. }
  962. if (button == ui->button_return || button == ui->button_return_shift) {
  963. if (ui->topOSK->currentIndex() == 1) {
  964. ui->text_edit_osk->insertPlainText(QStringLiteral("\n"));
  965. } else {
  966. ui->line_edit_osk->insert(QStringLiteral("\n"));
  967. }
  968. return;
  969. }
  970. if (button == ui->button_space || button == ui->button_space_shift) {
  971. if (ui->topOSK->currentIndex() == 1) {
  972. ui->text_edit_osk->insertPlainText(QStringLiteral(" "));
  973. } else {
  974. ui->line_edit_osk->insert(QStringLiteral(" "));
  975. }
  976. return;
  977. }
  978. if (button == ui->button_shift || button == ui->button_shift_shift) {
  979. ChangeBottomOSKIndex();
  980. return;
  981. }
  982. if (button == ui->button_backspace || button == ui->button_backspace_shift ||
  983. button == ui->button_backspace_num) {
  984. if (ui->topOSK->currentIndex() == 1) {
  985. auto text_cursor = ui->text_edit_osk->textCursor();
  986. ui->text_edit_osk->setTextCursor(text_cursor);
  987. text_cursor.deletePreviousChar();
  988. } else {
  989. ui->line_edit_osk->backspace();
  990. }
  991. return;
  992. }
  993. if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
  994. const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
  995. : ui->line_edit_osk->text();
  996. auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
  997. emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str));
  998. return;
  999. }
  1000. if (ui->topOSK->currentIndex() == 1) {
  1001. ui->text_edit_osk->insertPlainText(button->text());
  1002. } else {
  1003. ui->line_edit_osk->insert(button->text());
  1004. }
  1005. // Revert the keyboard to lowercase if the shift key is active.
  1006. if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
  1007. // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase
  1008. // if bottom_osk_index is UpperCase and caps_lock_enabled is true.
  1009. caps_lock_enabled = true;
  1010. ChangeBottomOSKIndex();
  1011. }
  1012. }
  1013. void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) {
  1014. if (!button->isEnabled()) {
  1015. return;
  1016. }
  1017. if (button == ui->button_ampersand) {
  1018. InlineTextInsertString(u"&");
  1019. return;
  1020. }
  1021. if (button == ui->button_return || button == ui->button_return_shift) {
  1022. InlineTextInsertString(u"\n");
  1023. return;
  1024. }
  1025. if (button == ui->button_space || button == ui->button_space_shift) {
  1026. InlineTextInsertString(u" ");
  1027. return;
  1028. }
  1029. if (button == ui->button_shift || button == ui->button_shift_shift) {
  1030. ChangeBottomOSKIndex();
  1031. return;
  1032. }
  1033. if (button == ui->button_backspace || button == ui->button_backspace_shift ||
  1034. button == ui->button_backspace_num) {
  1035. if (cursor_position <= 0 || current_text.empty()) {
  1036. cursor_position = 0;
  1037. return;
  1038. }
  1039. --cursor_position;
  1040. current_text.erase(cursor_position, 1);
  1041. SetBackspaceOkEnabled();
  1042. emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position);
  1043. return;
  1044. }
  1045. if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
  1046. emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position);
  1047. return;
  1048. }
  1049. const auto button_text = button->text();
  1050. InlineTextInsertString(Common::U16StringFromBuffer(button_text.utf16(), button_text.size()));
  1051. // Revert the keyboard to lowercase if the shift key is active.
  1052. if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
  1053. // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase
  1054. // if bottom_osk_index is UpperCase and caps_lock_enabled is true.
  1055. caps_lock_enabled = true;
  1056. ChangeBottomOSKIndex();
  1057. }
  1058. }
  1059. void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) {
  1060. if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) {
  1061. return;
  1062. }
  1063. current_text.insert(cursor_position, string);
  1064. cursor_position += static_cast<s32>(string.size());
  1065. SetBackspaceOkEnabled();
  1066. emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position);
  1067. }
  1068. void QtSoftwareKeyboardDialog::SetupMouseHover() {
  1069. // setFocus() has a bug where continuously changing focus will cause the focus UI to
  1070. // mysteriously disappear. A workaround we have found is using the mouse to hover over
  1071. // the buttons to act in place of the button focus. As a result, we will have to set
  1072. // a blank cursor when hovering over all the buttons and set a no focus policy so the
  1073. // buttons do not stay in focus in addition to the mouse hover.
  1074. for (auto* button : all_buttons) {
  1075. button->setCursor(QCursor(Qt::BlankCursor));
  1076. button->setFocusPolicy(Qt::NoFocus);
  1077. }
  1078. }
  1079. template <Core::HID::NpadButton... T>
  1080. void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() {
  1081. const auto f = [this](Core::HID::NpadButton button) {
  1082. if (input_interpreter->IsButtonPressedOnce(button)) {
  1083. TranslateButtonPress(button);
  1084. }
  1085. };
  1086. (f(T), ...);
  1087. }
  1088. template <Core::HID::NpadButton... T>
  1089. void QtSoftwareKeyboardDialog::HandleButtonHold() {
  1090. const auto f = [this](Core::HID::NpadButton button) {
  1091. if (input_interpreter->IsButtonHeld(button)) {
  1092. TranslateButtonPress(button);
  1093. }
  1094. };
  1095. (f(T), ...);
  1096. }
  1097. void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button) {
  1098. switch (button) {
  1099. case Core::HID::NpadButton::A:
  1100. switch (bottom_osk_index) {
  1101. case BottomOSKIndex::LowerCase:
  1102. case BottomOSKIndex::UpperCase:
  1103. keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column]->click();
  1104. break;
  1105. case BottomOSKIndex::NumberPad:
  1106. numberpad_buttons[row][column]->click();
  1107. break;
  1108. default:
  1109. break;
  1110. }
  1111. break;
  1112. case Core::HID::NpadButton::B:
  1113. switch (bottom_osk_index) {
  1114. case BottomOSKIndex::LowerCase:
  1115. ui->button_backspace->click();
  1116. break;
  1117. case BottomOSKIndex::UpperCase:
  1118. ui->button_backspace_shift->click();
  1119. break;
  1120. case BottomOSKIndex::NumberPad:
  1121. ui->button_backspace_num->click();
  1122. break;
  1123. default:
  1124. break;
  1125. }
  1126. break;
  1127. case Core::HID::NpadButton::X:
  1128. if (is_inline) {
  1129. emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position);
  1130. } else {
  1131. const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
  1132. : ui->line_edit_osk->text();
  1133. auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
  1134. emit SubmitNormalText(SwkbdResult::Cancel, std::move(text_str));
  1135. }
  1136. break;
  1137. case Core::HID::NpadButton::Y:
  1138. switch (bottom_osk_index) {
  1139. case BottomOSKIndex::LowerCase:
  1140. ui->button_space->click();
  1141. break;
  1142. case BottomOSKIndex::UpperCase:
  1143. ui->button_space_shift->click();
  1144. break;
  1145. case BottomOSKIndex::NumberPad:
  1146. default:
  1147. break;
  1148. }
  1149. break;
  1150. case Core::HID::NpadButton::StickL:
  1151. case Core::HID::NpadButton::StickR:
  1152. switch (bottom_osk_index) {
  1153. case BottomOSKIndex::LowerCase:
  1154. ui->button_shift->click();
  1155. break;
  1156. case BottomOSKIndex::UpperCase:
  1157. ui->button_shift_shift->click();
  1158. break;
  1159. case BottomOSKIndex::NumberPad:
  1160. default:
  1161. break;
  1162. }
  1163. break;
  1164. case Core::HID::NpadButton::L:
  1165. MoveTextCursorDirection(Direction::Left);
  1166. break;
  1167. case Core::HID::NpadButton::R:
  1168. MoveTextCursorDirection(Direction::Right);
  1169. break;
  1170. case Core::HID::NpadButton::Plus:
  1171. switch (bottom_osk_index) {
  1172. case BottomOSKIndex::LowerCase:
  1173. ui->button_ok->click();
  1174. break;
  1175. case BottomOSKIndex::UpperCase:
  1176. ui->button_ok_shift->click();
  1177. break;
  1178. case BottomOSKIndex::NumberPad:
  1179. ui->button_ok_num->click();
  1180. break;
  1181. default:
  1182. break;
  1183. }
  1184. break;
  1185. case Core::HID::NpadButton::Left:
  1186. case Core::HID::NpadButton::StickLLeft:
  1187. case Core::HID::NpadButton::StickRLeft:
  1188. MoveButtonDirection(Direction::Left);
  1189. break;
  1190. case Core::HID::NpadButton::Up:
  1191. case Core::HID::NpadButton::StickLUp:
  1192. case Core::HID::NpadButton::StickRUp:
  1193. MoveButtonDirection(Direction::Up);
  1194. break;
  1195. case Core::HID::NpadButton::Right:
  1196. case Core::HID::NpadButton::StickLRight:
  1197. case Core::HID::NpadButton::StickRRight:
  1198. MoveButtonDirection(Direction::Right);
  1199. break;
  1200. case Core::HID::NpadButton::Down:
  1201. case Core::HID::NpadButton::StickLDown:
  1202. case Core::HID::NpadButton::StickRDown:
  1203. MoveButtonDirection(Direction::Down);
  1204. break;
  1205. default:
  1206. break;
  1207. }
  1208. }
  1209. void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
  1210. // Changes the row or column index depending on the direction.
  1211. auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) {
  1212. switch (direction) {
  1213. case Direction::Left:
  1214. column = (column + max_columns - 1) % max_columns;
  1215. break;
  1216. case Direction::Up:
  1217. row = (row + max_rows - 1) % max_rows;
  1218. break;
  1219. case Direction::Right:
  1220. column = (column + 1) % max_columns;
  1221. break;
  1222. case Direction::Down:
  1223. row = (row + 1) % max_rows;
  1224. break;
  1225. default:
  1226. break;
  1227. }
  1228. };
  1229. // Store the initial row and column.
  1230. const auto initial_row = row;
  1231. const auto initial_column = column;
  1232. switch (bottom_osk_index) {
  1233. case BottomOSKIndex::LowerCase:
  1234. case BottomOSKIndex::UpperCase: {
  1235. const auto index = static_cast<std::size_t>(bottom_osk_index);
  1236. const auto* const prev_button = keyboard_buttons[index][row][column];
  1237. move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
  1238. auto* curr_button = keyboard_buttons[index][row][column];
  1239. while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
  1240. // If we returned back to where we started from, break the loop.
  1241. if (row == initial_row && column == initial_column) {
  1242. break;
  1243. }
  1244. move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
  1245. curr_button = keyboard_buttons[index][row][column];
  1246. }
  1247. // This is a workaround for setFocus() randomly not showing focus in the UI
  1248. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  1249. break;
  1250. }
  1251. case BottomOSKIndex::NumberPad: {
  1252. const auto* const prev_button = numberpad_buttons[row][column];
  1253. move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
  1254. auto* curr_button = numberpad_buttons[row][column];
  1255. while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
  1256. // If we returned back to where we started from, break the loop.
  1257. if (row == initial_row && column == initial_column) {
  1258. break;
  1259. }
  1260. move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
  1261. curr_button = numberpad_buttons[row][column];
  1262. }
  1263. // This is a workaround for setFocus() randomly not showing focus in the UI
  1264. QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
  1265. break;
  1266. }
  1267. default:
  1268. break;
  1269. }
  1270. }
  1271. void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) {
  1272. switch (direction) {
  1273. case Direction::Left:
  1274. if (is_inline) {
  1275. if (cursor_position <= 0) {
  1276. cursor_position = 0;
  1277. } else {
  1278. --cursor_position;
  1279. emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position);
  1280. }
  1281. } else {
  1282. if (ui->topOSK->currentIndex() == 1) {
  1283. ui->text_edit_osk->moveCursor(QTextCursor::Left);
  1284. } else {
  1285. ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1);
  1286. }
  1287. }
  1288. break;
  1289. case Direction::Right:
  1290. if (is_inline) {
  1291. if (cursor_position >= static_cast<s32>(current_text.size())) {
  1292. cursor_position = static_cast<s32>(current_text.size());
  1293. } else {
  1294. ++cursor_position;
  1295. emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position);
  1296. }
  1297. } else {
  1298. if (ui->topOSK->currentIndex() == 1) {
  1299. ui->text_edit_osk->moveCursor(QTextCursor::Right);
  1300. } else {
  1301. ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1);
  1302. }
  1303. }
  1304. break;
  1305. default:
  1306. break;
  1307. }
  1308. }
  1309. void QtSoftwareKeyboardDialog::StartInputThread() {
  1310. if (input_thread_running) {
  1311. return;
  1312. }
  1313. input_thread_running = true;
  1314. input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this);
  1315. }
  1316. void QtSoftwareKeyboardDialog::StopInputThread() {
  1317. input_thread_running = false;
  1318. if (input_thread.joinable()) {
  1319. input_thread.join();
  1320. }
  1321. if (input_interpreter) {
  1322. input_interpreter->ResetButtonStates();
  1323. }
  1324. }
  1325. void QtSoftwareKeyboardDialog::InputThread() {
  1326. while (input_thread_running) {
  1327. input_interpreter->PollInput();
  1328. HandleButtonPressedOnce<
  1329. Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
  1330. Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
  1331. Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
  1332. Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
  1333. Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
  1334. Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
  1335. Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
  1336. Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
  1337. Core::HID::NpadButton::StickRDown>();
  1338. HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
  1339. Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
  1340. Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
  1341. Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
  1342. Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
  1343. Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
  1344. Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
  1345. Core::HID::NpadButton::StickRDown>();
  1346. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  1347. }
  1348. }
  1349. QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
  1350. connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window,
  1351. &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection);
  1352. connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window,
  1353. &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection);
  1354. connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window,
  1355. &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection);
  1356. connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window,
  1357. &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection);
  1358. connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window,
  1359. &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection);
  1360. connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window,
  1361. &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection);
  1362. connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window,
  1363. &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection);
  1364. connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this,
  1365. &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection);
  1366. connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this,
  1367. &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection);
  1368. }
  1369. QtSoftwareKeyboard::~QtSoftwareKeyboard() = default;
  1370. void QtSoftwareKeyboard::InitializeKeyboard(
  1371. bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
  1372. SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) {
  1373. if (is_inline) {
  1374. submit_inline_callback = std::move(submit_inline_callback_);
  1375. } else {
  1376. submit_normal_callback = std::move(submit_normal_callback_);
  1377. }
  1378. LOG_INFO(Service_AM,
  1379. "\nKeyboardInitializeParameters:"
  1380. "\nok_text={}"
  1381. "\nheader_text={}"
  1382. "\nsub_text={}"
  1383. "\nguide_text={}"
  1384. "\ninitial_text={}"
  1385. "\nmax_text_length={}"
  1386. "\nmin_text_length={}"
  1387. "\ninitial_cursor_position={}"
  1388. "\ntype={}"
  1389. "\npassword_mode={}"
  1390. "\ntext_draw_type={}"
  1391. "\nkey_disable_flags={}"
  1392. "\nuse_blur_background={}"
  1393. "\nenable_backspace_button={}"
  1394. "\nenable_return_button={}"
  1395. "\ndisable_cancel_button={}",
  1396. Common::UTF16ToUTF8(initialize_parameters.ok_text),
  1397. Common::UTF16ToUTF8(initialize_parameters.header_text),
  1398. Common::UTF16ToUTF8(initialize_parameters.sub_text),
  1399. Common::UTF16ToUTF8(initialize_parameters.guide_text),
  1400. Common::UTF16ToUTF8(initialize_parameters.initial_text),
  1401. initialize_parameters.max_text_length, initialize_parameters.min_text_length,
  1402. initialize_parameters.initial_cursor_position, initialize_parameters.type,
  1403. initialize_parameters.password_mode, initialize_parameters.text_draw_type,
  1404. initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background,
  1405. initialize_parameters.enable_backspace_button,
  1406. initialize_parameters.enable_return_button,
  1407. initialize_parameters.disable_cancel_button);
  1408. emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters));
  1409. }
  1410. void QtSoftwareKeyboard::ShowNormalKeyboard() const {
  1411. emit MainWindowShowNormalKeyboard();
  1412. }
  1413. void QtSoftwareKeyboard::ShowTextCheckDialog(
  1414. Service::AM::Applets::SwkbdTextCheckResult text_check_result,
  1415. std::u16string text_check_message) const {
  1416. emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message));
  1417. }
  1418. void QtSoftwareKeyboard::ShowInlineKeyboard(
  1419. Core::Frontend::InlineAppearParameters appear_parameters) const {
  1420. LOG_INFO(Service_AM,
  1421. "\nInlineAppearParameters:"
  1422. "\nmax_text_length={}"
  1423. "\nmin_text_length={}"
  1424. "\nkey_top_scale_x={}"
  1425. "\nkey_top_scale_y={}"
  1426. "\nkey_top_translate_x={}"
  1427. "\nkey_top_translate_y={}"
  1428. "\ntype={}"
  1429. "\nkey_disable_flags={}"
  1430. "\nkey_top_as_floating={}"
  1431. "\nenable_backspace_button={}"
  1432. "\nenable_return_button={}"
  1433. "\ndisable_cancel_button={}",
  1434. appear_parameters.max_text_length, appear_parameters.min_text_length,
  1435. appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y,
  1436. appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y,
  1437. appear_parameters.type, appear_parameters.key_disable_flags.raw,
  1438. appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button,
  1439. appear_parameters.enable_return_button, appear_parameters.disable_cancel_button);
  1440. emit MainWindowShowInlineKeyboard(std::move(appear_parameters));
  1441. }
  1442. void QtSoftwareKeyboard::HideInlineKeyboard() const {
  1443. emit MainWindowHideInlineKeyboard();
  1444. }
  1445. void QtSoftwareKeyboard::InlineTextChanged(
  1446. Core::Frontend::InlineTextParameters text_parameters) const {
  1447. LOG_INFO(Service_AM,
  1448. "\nInlineTextParameters:"
  1449. "\ninput_text={}"
  1450. "\ncursor_position={}",
  1451. Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position);
  1452. emit MainWindowInlineTextChanged(std::move(text_parameters));
  1453. }
  1454. void QtSoftwareKeyboard::ExitKeyboard() const {
  1455. emit MainWindowExitKeyboard();
  1456. }
  1457. void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result,
  1458. std::u16string submitted_text, bool confirmed) const {
  1459. submit_normal_callback(result, submitted_text, confirmed);
  1460. }
  1461. void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type,
  1462. std::u16string submitted_text,
  1463. s32 cursor_position) const {
  1464. submit_inline_callback(reply_type, submitted_text, cursor_position);
  1465. }