Android Scaling / Keyboard Problem
-
I've got an Android application that first gets the screen width and height by constructing a temporary QApplication, getting the screens() from the QApplication object, setting QT_SCALE_FACTOR via qputenv, then deleting the temporary QApplication.
Then, there's a construction of a second QApplication that uses the QT_SCALE_FACTOR environment setting correctly. I'm using QApplication because my app uses both QML (QtQuick) and QWidgets.
My problem is that the Android keyboard doesn't show up upon startup of the application when activating focus in a TextField (QML).
I know what is happening but just don't know what to do to fix it.
- Upon the first QApplication construction, the InputMethodManager is calling startInputInner to startup the input method correctly.
- But, this first QApplication object doesn't have the QT_SCALE_FACTOR environment variable set yet, so this object's GUI would scale things incorrectly.
- After qputenv("QT_SCALE_FACTOR", "1.5");, 1.5 as an example, and then deleting the first QApplication, I construct the second QApplication, but it's not calling startInputInner since Android already thinks that my application has an InputMethodManager that's already called startInputInner.
If I put the Android application in the background by swiping up from the bottom (white line swipe up), and then bring it back into the foreground, then the keyboard shows up correctly when a TextField gets focus. That's because my application relinquishes control of the Android keyboard when putting the app into the background, then re-establishes a new connection to the keyboard when my app is brought back into the foreground.
Unfortunately, I don't know how to get the Android screen's width and height without constructing a temporary QApplication to start with. Here is a snippet of the code. If anyone has any ideas on how to pre-call functionality within a Qt application to get the Android screen's width and height without constructing a QApplication or QGuiApplication, I'd like to know how. Thanks.
#ifdef Q_OS_MOBILE // Temporary QApplication to get width and height of screen 0 QApplication *temp = new QApplication(argc, argv); double width = temp->screens().at(0)->availableSize().width(); double height = temp->screens().at(0)->availableSize().height(); delete temp; double scaleWidth = width / 800.0; double scaleHeight = height / 480.0; qputenv("QT_SCALE_FACTOR", QString::number(min(scaleWidth, scaleHeight)).toLocal8Bit()); #endif // Primary QApplication for the real application QApplication app(argc, argv);
-
Well, I didn't find a way to not create a temporary QApplication to get the screen size, so I did the next best thing that I could think of and that is to write the QT_SCALE_FACTOR value out to a file on the Android device, then display a message to the user to "reopen the app", and then exit the app. After the user reopens the app, check to see if the file exists, if it exists then read the scale factor value, call qputenv with the scale factor value read from file. This avoids creating the temporary QApplication once the scale factor has been written to the file and re-read every time the app is opened. Here's the code to do all that:
#ifdef Q_OS_MOBILE const char *qtScaleFactor = "QT_SCALE_FACTOR"; // Check to see if the QT_SCALE_FACTOR environment variable is set and is not empty if (!qEnvironmentVariableIsSet(qtScaleFactor) || qEnvironmentVariableIsEmpty(qtScaleFactor)) { QString scaleFactor; // Try to read the QT_SCALE_FACTOR from file first if (!FileIO::readQtScaleFactor(scaleFactor)) { // Temporary QApplication to get width and height of screen 0 QApplication *temp = new QApplication(argc, argv); double width = temp->screens().at(0)->availableSize().width(); double height = temp->screens().at(0)->availableSize().height(); double scaleWidth = width / 800.0; double scaleHeight = height / 480.0; scaleFactor = QString::number(min(scaleWidth, scaleHeight)); qputenv(qtScaleFactor, scaleFactor.toLocal8Bit()); // Write the QT_SCALE_FACTOR to file bool writeSuccessful = FileIO::writeQtScaleFactor(scaleFactor); // If the write was successful, display a message to the user to reopen the "AppName" App if (writeSuccessful) { tempMsgBox("Screen Scale Factor Set\nfor \"AppName\" App.\n\nPlease Reopen \"AppName\" App.", nullptr, 5000, 36); // Delay and process events to display MessageBox QElapsedTimer elapsedTimer; elapsedTimer.start(); do { QCoreApplication::processEvents(); } while (elapsedTimer.elapsed() < 5500); elapsedTimer.invalidate(); delete temp; return 0; // Exits the App } delete temp; } else { // QT_SCALE_FACTOR was sucessfully read from file, so just set the environment variable qputenv(qtScaleFactor, scaleFactor.toLocal8Bit()); } } #endif // Primary QApplication for the real application QApplication app(argc, argv);
This code is only run on a mobile device (e.g. Android and iOS), not Windows or MacOS (Desktop). The deletion of the temporary QApplication was postponed until after the QMessageBox was displayed since it was an error not to have a QApplication before creating a QWidget. And, the QElapsedTimer based do-loop for calling QCoreApplication::processEvents() was necessary to display the QMessageBox, accept the OK button and/or timeout after 5 seconds, and dismiss the QMessageBox automatically.
This was my solution and I'm pretty happy with it. I hope it helps someone else.
-
FYI, I'm using Qt 6.5.5 (latest LTS version) and compiling for both the arm7a and arm8a Android devices. My host OS is Windows 11, but I believe the same issue would exist if using MacOS as the host. Using Qt Creator 11.0.3 (a slightly older version based on Qt 6.4.3 (MSVC2019, x86_64)). Everything else works as it should. The Android SDK 13, NDK 25, JDK 17, and OpenSSL in the Android Devices setting have all "Green" checkmarks. Not a problem there.
-
Well, I didn't find a way to not create a temporary QApplication to get the screen size, so I did the next best thing that I could think of and that is to write the QT_SCALE_FACTOR value out to a file on the Android device, then display a message to the user to "reopen the app", and then exit the app. After the user reopens the app, check to see if the file exists, if it exists then read the scale factor value, call qputenv with the scale factor value read from file. This avoids creating the temporary QApplication once the scale factor has been written to the file and re-read every time the app is opened. Here's the code to do all that:
#ifdef Q_OS_MOBILE const char *qtScaleFactor = "QT_SCALE_FACTOR"; // Check to see if the QT_SCALE_FACTOR environment variable is set and is not empty if (!qEnvironmentVariableIsSet(qtScaleFactor) || qEnvironmentVariableIsEmpty(qtScaleFactor)) { QString scaleFactor; // Try to read the QT_SCALE_FACTOR from file first if (!FileIO::readQtScaleFactor(scaleFactor)) { // Temporary QApplication to get width and height of screen 0 QApplication *temp = new QApplication(argc, argv); double width = temp->screens().at(0)->availableSize().width(); double height = temp->screens().at(0)->availableSize().height(); double scaleWidth = width / 800.0; double scaleHeight = height / 480.0; scaleFactor = QString::number(min(scaleWidth, scaleHeight)); qputenv(qtScaleFactor, scaleFactor.toLocal8Bit()); // Write the QT_SCALE_FACTOR to file bool writeSuccessful = FileIO::writeQtScaleFactor(scaleFactor); // If the write was successful, display a message to the user to reopen the "AppName" App if (writeSuccessful) { tempMsgBox("Screen Scale Factor Set\nfor \"AppName\" App.\n\nPlease Reopen \"AppName\" App.", nullptr, 5000, 36); // Delay and process events to display MessageBox QElapsedTimer elapsedTimer; elapsedTimer.start(); do { QCoreApplication::processEvents(); } while (elapsedTimer.elapsed() < 5500); elapsedTimer.invalidate(); delete temp; return 0; // Exits the App } delete temp; } else { // QT_SCALE_FACTOR was sucessfully read from file, so just set the environment variable qputenv(qtScaleFactor, scaleFactor.toLocal8Bit()); } } #endif // Primary QApplication for the real application QApplication app(argc, argv);
This code is only run on a mobile device (e.g. Android and iOS), not Windows or MacOS (Desktop). The deletion of the temporary QApplication was postponed until after the QMessageBox was displayed since it was an error not to have a QApplication before creating a QWidget. And, the QElapsedTimer based do-loop for calling QCoreApplication::processEvents() was necessary to display the QMessageBox, accept the OK button and/or timeout after 5 seconds, and dismiss the QMessageBox automatically.
This was my solution and I'm pretty happy with it. I hope it helps someone else.
-
-