software_keyboard.cpp 58 KB

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