Discreet Log #12: Native Porting
21 Jul 2021
We released Cwtch 1.1 last week, our first Beta update, with a ton of bug fixes and even a few new features and the two reasons we’ve been able to move this fast even with such a small team are both the maturity of Cwtch now as a platform, but also, because Flutter has been working well as a cross-platform framework with quality high level widgets that let us build the UI rapidly. That said, Flutter having good cross-platform widgets doesn’t mean it’s seamless frictionless work to bring it to 3 platforms, especially when support for the Desktop ones are still very new to Flutter.
We’ve already blogged about building Android service support into the Cwtch Flutter app as one big and ongoing example of that work. Additionally, each platform handles notifications differently, and so on Android our notification work overlaps with our service support work, as reported above. Today I wanted to take a look at some other platform specific work we’ve done for Windows and Linux.
Windows
Icons
One of the first things we noticed is that Flutter Engine doesn’t natively support app icons. This should have been obvious as we had to manually specify it in AndroidManifest.xml as well, but we’re used to special Android exceptions from a framework that otherwise more seamlessly handles desktop. In Flutter’s case though, no platform’s icons are handled. For Windows, the generated platform specific C code in windows
has rudimentary support for setting a default icon. We could have just dropped our icon on top of the default one in windows/runner/resources
and called it a day, but it turned out that scaled very poorly and often looked terrible. It seems like the Windows OS code for resizing icons hasn’t been updated since the 90s. Thankfully I also remember Windows programming from the late 90s and knew that it was usually typical to supply a pre-resized set of icons to compensate for this. Unfortunately the auto-generated code from Flutter in windows/runner/win32_window.cpp
just populated the WNDCLASS
with one icon. Additionally, after looking at the Windows API docs, WNDCLASS
is deprecated and supposed to be replaced with WNDCLASSEX
that also has support for setting a “small” icon to be used in the application window bar. This all led to the following modifications to our windows/runner/win32_window.cpp
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASSEX window_class{};
window_class.cbSize = sizeof(WNDCLASSEX); // required new setup for WNDCLASSEX per API docs
...
int icon_sz = GetSystemMetrics(SM_CXICON);
int iconh_id = IDI_APP_ICON_32;
if (icon_sz > 128) {
iconh_id = IDI_APP_ICON_256;
} else if (icon_sz > 64) {
iconh_id = IDI_APP_ICON_128;
} else if (icon_sz > 48) {
iconh_id = IDI_APP_ICON_64;
} else if (icon_sz > 32) {
iconh_id = IDI_APP_ICON_48;
}
window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(iconh_id));
// ... repeat picking size for and setting small icon ...
RegisterClassEx(&window_class);
We also have to add all the new icons as resources to windows/runner/Runner.rc and windows/runner/resource.h.
With that done we’ve made Flutter on Windows behave in a much more Windows idiomatic way and gotten much better looking less scrunched icons on the Windows bar and window title bar.
Installer
Flutter does have a suggested path for packaging a Windows app with an MSIX installer however this produces a Universal Windows Platform (UWP) application, which has some limitations, including not being able to execute packaged binaries except through an exclusive API that seems to only have a C# implementation (blocking our launching of Tor). Also Microsoft has confirmed UWP is dying, losing support, and not on their roadmap any longer so it’s a double dead end for us.
To package our new Cwtch Flutter app we dusted off the NSIS installer code we had written for our old UI and updated it to work with the new one:
- windows/nsis/cwtch-installer.nsi
- Build instructions: this includes signing the .exe and signing the installer with our code signing cert.
With this work repurposed and updated, we could now generate simple but pleasant and easy to use Windows installers for Cwtch.
Flutter Engine Issue with Caps Lock
Flutter Engine launched on Windows at 2.0 with a bug where the right shift key was sticky, and once pressed, could not be turned off. A user side code work around was proposed, and we implemented it while we wait for the engine fix to be published.
This involved adding lib/widgets/rightshiftfixer.dart
and then using it by wrapping our main app widget in it in lib/main.dart
home: appState.cwtchInit == true ? ShiftRightFixer(child: ProfileMgrView()) : SplashView(),
We should be able to remove this awkward hack fix once the full fix is shipped in newer version of Flutter.
Windows Performance Problems
The Flutter Engine for Windows also appeared to have a big performance regression and only part of the fix was published, so we again manually had to update the generated code to use a new non efficiency broken run loop in windows/runner/run_loop.cpp
void RunLoop::Run() {
// Fix/workaround for Windows high CPU usage
// https://github.com/flutter/flutter/issues/78517#issuecomment-814843107
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Linux
Icon
Flutter on Linux uses GTK to do basic window building and Window Manager interface. Sadly, at the time we had Flutter generate the linux platform code it didn’t appear to include any support for icons. GTK as a framework however does, so it was a simple one line add to linux/my_application.cc
to have our Linux Flutter app have a proper icon.
gtk_window_set_icon_from_file(window, "./cwtch.png", NULL);
Linux Style Install
Flutter also has one supported way of packaging applications for Linux, and it is using Snaps. We’re slightly more fans of traditional packages (eg: .deb and .rpm) and Linux style system installs vs containers for user applications. It’s mostly a matter of preference.
The first problem before we could package our app like that however was that a Linux Flutter app cannot
run with its libraries and assets spread out in different directories, it’s hard coded to load those resources
from its executing directory. We had to read the Flutter engine code for Linux applications to see
how it was loading libraries and assets and thankfully the Flutter engine C structs do provide ways of
specifying the paths, even if in the Flutter generated code for your app it is
then hard coded to the directory of the executable. We made the following changes so that the app could be
run from the build directory, its own directory, from installed to $HOME
or installed to the
system under /usr
in Linux/my_application.cc
// Check if assets folder is relative to the executable or if we can use a system copy
struct stat info;
// if we're not in freshly compiled structure
if (stat(fl_dart_project_get_assets_path(project), &info ) != 0 ) {
if( stat("lib/cwtch", &info) == 0) {
// use local dir structure
project->assets_path = g_build_filename("data", "flutter_assets", nullptr);
project->aot_library_path = g_build_filename("lib", "libapp.so", nullptr);
project->icu_data_path = g_build_filename("data", "icudtl.dat", nullptr);
gtk_window_set_icon_from_file(window, "./cwtch.png", NULL);
} else if( stat("/usr/share/cwtch/data/flutter_assets", &info ) != 0 ) {
// if we're non in sys installed structure, use home dir structure
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
// /home/$USER/.local/share/cwtch/data/flutter_assets
project->assets_path = g_build_filename(homedir, ".local", "share", "cwtch", "data", "flutter_assets", nullptr);
// /home/$USER/.local/lib/cwtch/
project->aot_library_path = g_build_filename(homedir, ".local", "lib", "cwtch", "libapp.so", nullptr);
// /home/$USER/.local/share/cwtch/data
project->icu_data_path = g_build_filename(homedir, ".local", "share", "cwtch", "data", "icudtl.dat", nullptr);
gtk_window_set_icon_from_file(window, g_build_filename(homedir, ".local", "share", "icons", "cwtch.png", nullptr), NULL);
} else {
// else assume we are in sys installed structure
// /usr/share/cwtch/data/flutter_assets
project->assets_path = g_build_filename("/", "usr", "share", "cwtch", "data", "flutter_assets", nullptr);
// /usr/lib/cwtch
project->aot_library_path = g_build_filename("/", "usr", "lib", "cwtch", "libapp.so", nullptr);
// /usr/share/cwtch/data
project->icu_data_path = g_build_filename("/", "usr", "share", "cwtch", "data", "icudtl.dat", nullptr);
gtk_window_set_icon_from_file(window, "/usr/share/icons/cwtch.png", NULL);
}
}
printf("my_application.cc: using aot_library_path or '%s'\n", project->aot_library_path);
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
Since we were also shipping our own copy of libcwtch.so
we needed to launch the application with
a custom LD_LIBRARY_PATH
so our install packaging scripts moved the Flutter binary into the lib
folder
and dropped in a simple one line shell script to invoke it properly. Also, as per observed practice of other applications, our bundled copy of tor went into the lib
folder.
We wrote a script to assemble our actual release build directory with linux/package-release.sh, and you can see the rest of our invoke and install scripts in linux/.
Outstanding Platform Issues
Flutter on desktop is a new feature released this year and not everything works quite as expected. One issue we’re still waiting on Flutter to fix is that dragging scrolls bars is inverted, so dragging the scroll bar up scrolls down. We suspect this might be a mistake related to mobile gesture scroll dragging behaviour but regardless it’s a Flutter bug we hope they will address soon.
Additionally, we have received reports that in the current version of our app, Backspace stops working after a language change. This has purportedly been fixed upstream in Flutter, and we plan to upgrade the Flutter version on our build server for our next release to hopefully pass the fix on to our users.
Conclusion
Flutter provides some quality high level widgets that work cross-platform which has allowed us to rapidly build Cwtch, and improve it. We’re pleased with what we’ve seen so far, and excited with what it appears to be enabling us to deliver in the future. That said, it’s both a new tech, and there simply is no such thing as a perfect silver bullet for crossplatform app building. It still has some rough edges around some platform features which we’ve manually had to work around, and still have more work to do. Notification quality per platform still needs improving, as do other integrations like Windows system tray implementation.
If you’d like to see us keep going both in adding more features to Cwtch and on improving its platform specific integration, please donate!