software_keyboard.cpp 57 KB

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