README (12520B)
1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2 CROSS-COMPILE A GTK3 APP FROM LINUX TO WINDOWS 3 WITH NATIVE WINDOWS LOOK AND FEEL 4 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5 6 7 One of my research projects involves writing a Windows app for a 8 department at my school, but I only run Linux. I saved the steps for 9 building a GUI [GTK3] program on both Linux and Windows. This includes 10 the frills most Windows users expect, like a graphical installer, an 11 icon on the executable, and a program that looks like it was made for 12 Windows. 13 14 As a starter program, create the demo C program from [the GTK 15 documentation] and save it as `main.c'. 16 17 To compile the code on Linux, a simple 18 ┌──── 19 │ $ gcc -o main main.c `pkg-config --libs gtk+-3.0 --cflags gtk+-3.0` 20 └──── 21 will do the trick (you need pkg-config, GCC, and GTK3 installed). Test 22 the executable to make sure it works: 23 ┌──── 24 │ $ ./main 25 └──── 26 27 28 [GTK3] <https://www.gtk.org/> 29 30 [the GTK documentation] 31 <https://developer.gnome.org/gtk3/stable/gtk-getting-started.html> 32 33 34 Windows setup 35 ═════════════ 36 37 To build for Windows, you need to use [MinGW] to compile the 38 executable, and then you need to bundle the MinGW GTK dlls so they’re 39 available at runtime on Windows. 40 41 First install MinGW. If this step seems insurmountable, or if you’re 42 running Arch Linux or a similar distro, just skip to the [Automating 43 builds] section. 44 45 On Fedora, for example, you can install everything you need with this 46 command: 47 48 ┌──── 49 │ dnf install mingw64-gtk3 mingw32-binutils mingw32-nsiswrapper 50 └──── 51 52 53 [MinGW] <http://www.mingw.org/> 54 55 [Automating builds] See section Automating builds 56 57 Compiling for Windows 58 ───────────────────── 59 60 On a Linux system with MinGW installed you can compile the same 61 program with something like: 62 ┌──── 63 │ $ x86_64-w64-mingw32-gcc -o main main.c `mingw64-pkg-config --cflags gtk+-3.0 --libs gtk+-3.0` 64 └──── 65 66 You should now have an executable called `main.exe'. Obviously this 67 executable won’t work on Linux, but it also won’t work on Windows 68 until you make the GTK libraries available to it at runtime. 69 70 71 Bundle GTK libraries 72 ──────────────────── 73 74 Make a subdirectory called `windows' and copy the `.exe' into it. Now 75 copy the MinGW libraries over to the same folder. Unfortunately, 76 different Linux distributions have different locations (and sometimes 77 even different names) for the MinGW executables. On Fedora, they’re 78 found at `/usr/x86_64-w64-mingw32/sys-root/mingw/*'. 79 80 81 Test on Windows 82 ─────────────── 83 84 If you copy the whole `windows' folder to a Windows computer or VM, 85 the program should now run as expected. Magically enough, [WINE] is 86 also able to run the program from Linux. 87 88 However, the program looks bad because 89 • it uses GNOME’s Adwaita theme (which looks bad on Windows) 90 • it uses GTK to render the client-side decorations (the top of the 91 window with the exit button) 92 • there’s no icon for the program in Windows’ taskbar 93 • the program runs with a black command prompt window in the 94 background. 95 96 Furthermore, it’s not ideal to distribute the program by zipping up 97 this folder and sharing it. 98 99 100 [WINE] <https://en.wikipedia.org/wiki/Wine_(software)> 101 102 103 Fix Icon 104 ──────── 105 106 Download or create an icon. From within the code you can set the icon 107 for the program like this: 108 109 ┌──── 110 │ GdkPixbuf *icon; 111 │ icon = create_pixbuf("icon.ico"); 112 │ gtk_window_set_icon(GTK_WINDOW(window), icon); 113 └──── 114 The code for `create_pixbuf' is: 115 ┌──── 116 │ GdkPixbuf *create_pixbuf(const gchar * filename) { 117 │ 118 │ GdkPixbuf *pixbuf; 119 │ GError *error = NULL; 120 │ pixbuf = gdk_pixbuf_new_from_file(filename, &error); 121 │ 122 │ if (!pixbuf) { 123 │ 124 │ fprintf(stderr, "%s\n", error->message); 125 │ g_error_free(error); 126 │ } 127 │ 128 │ return pixbuf; 129 │ } 130 └──── 131 132 This will change the icon of the program while it’s running. It shows 133 up correctly in the Windows taskbar and window decorations, but it 134 won’t change the icon of the actual `.exe'. I still recommend 135 including this code because it works to set the icon on Linux desktop 136 environments. 137 138 To change the icon of the executable file on Windows, we have to use 139 MinGW’s windres utility. Create a file called “icon.rc” with the 140 contents: 141 142 ┌──── 143 │ id ICON "icon.ico" 144 └──── 145 146 and then run: 147 148 ┌──── 149 │ x86_64-w64-mingw32-windres icon.rc -O coff -o icon.res 150 └──── 151 152 Now we can add the `icon.res' resource file to the executable by 153 slightly modifying our GCC command: 154 155 ┌──── 156 │ $ x86_64-w64-mingw32-gcc -o main main.c icon.res `mingw64-pkg-config --cflags gtk+-3.0 --libs gtk+-3.0` 157 └──── 158 159 Now the actual `.exe' file will have an icon associated with it on 160 Windows. 161 162 163 Remove Command Prompt window 164 ──────────────────────────── 165 166 Just add the `-mwindows' flag to your compile command. 167 168 169 Fix Window Decorations 170 ────────────────────── 171 172 You can disable the GTK client-side decorations by adding the 173 following line to the code: 174 175 ┌──── 176 │ putenv("GTK_CSD=0"); 177 └──── 178 179 Now the program should use the normal Windows exit button and drag 180 bar. 181 182 183 Switch to a Windows GTK theme 184 ───────────────────────────── 185 186 Adwaita looks bad on Windows. Search [Gnome-Look] for a different GTK3 187 theme to use with your app. You can choose a flatter, lighter theme or 188 one specifically made to look like Windows. For this example, I chose 189 the “Windows10” theme. 190 191 Download and extract the theme, then copy it to the `share/themes/' 192 directory in the `windows/' directory we’ve been using. 193 194 Then create a file called `settings.ini' in a `etc/gtk-3.0/' folder 195 (also in the `windows/' directory) with the following contents: 196 197 ┌──── 198 │ [Settings] 199 │ gtk-theme-name=Windows10 200 └──── 201 202 Now the program should look like native Windows software: 203 204 <file:media/main_gtk_tutorial_screenshot.png> 205 206 207 [Gnome-Look] <https://www.gnome-look.org/> 208 209 210 Generate an installer 211 ───────────────────── 212 213 Here’s how to make an installer using a script called [nsiswrapper], 214 so your users don’t have to manually unzip this `windows/' 215 folder. Nsiswrapper generates an [NSIS] script and optionally compiles 216 it using `makensis'. 217 218 `cd' into the `windows/' directory, and run: 219 220 ┌──── 221 │ nsiswrapper --run main.exe ./* 222 └──── 223 224 This should generate a new file called `installer.exe', which you can 225 use to distribute the program. The installer is a standard GUI wizard 226 that Windows users will be accustomed to. It allows users to 227 optionally add a desktop shortcut, start menu entry, and taskbar 228 shortcut. 229 230 Notably, it also includes an uninstaller. 231 232 233 [nsiswrapper] <https://src.fedoraproject.org/rpms/mingw-nsiswrapper> 234 235 [NSIS] <https://sourceforge.net/projects/nsis/> 236 237 238 Automating builds 239 ═════════════════ 240 241 You may have noticed that I’ve been using Fedora to compile this 242 program. I’m actually running Arch Linux, but MinGW packages aren’t 243 easily available for Arch, so I’ve been using a [Linux container] to 244 take advantage of Fedora’s package system. 245 246 I’m using [Buildah] instead of Docker. I wrote a script to automate 247 the following tasks 248 249 • set up a named fedora container 250 • install the necessary packages using dnf 251 • save the container so I don’t have to download dependencies again 252 • compile the GTK program using the steps I’ve outlined so far 253 • generate the installer 254 255 Before you look at the script, I recommend reading the [buildah 256 introduction tutorial]. 257 258 ┌──── 259 │ #!/bin/bash 260 │ setup () { 261 │ # Create a fedora container 262 │ container=$(buildah from fedora) 263 │ 264 │ # Install dependencies 265 │ buildah run $container dnf -y install mingw64-gtk3 mingw32-binutils mingw32-nsiswrapper 266 │ 267 │ # Fix typo in mingw library 268 │ buildah run $container bash -c "sed -i -e 's/-Wl,-luuid/-luuid/g' /usr/x86_64-w64-mingw32/sys-root/mingw/lib/pkgconfig/gdk-3.0.pc" 269 │ 270 │ # Cache image to avoid re-downloading dependencies every time 271 │ buildah commit $container my-gtk-app 272 │ 273 │ # Clean up 274 │ buildah rm $container 275 │ } 276 │ 277 │ build () { 278 │ # Create a new container from the base one we created 279 │ container=$(buildah from localhost/my-gtk-app) 280 │ 281 │ # Folder to hold everything needed for the windows installer: 282 │ output_folder=windows 283 │ yes | rm -r $output_folder 284 │ mkdir $output_folder 285 │ 286 │ # Directory of your package in the container 287 │ folder=/root/app 288 │ 289 │ # Copy program into container 290 │ buildah copy $container . $folder 291 │ 292 │ # Name for the executable file on Windows 293 │ executable_name="Demo.exe" 294 │ 295 │ # make some folders we'll need 296 │ buildah run $container bash -c "cd $folder && mkdir -p $output_folder/share/themes $output_folder/etc/gtk-3.0" 297 │ 298 │ # generate a RC file with the windres utility to embed an icon into the .exe later on 299 │ buildah run $container bash -c "cd $folder && x86_64-w64-mingw32-windres icon.rc -O coff -o icon.res" 300 │ 301 │ # copy some project resources 302 │ buildah run $container bash -c "cd $folder && cp -r Windows10 $output_folder/share/themes && \ 303 │ cp settings.ini $output_folder/etc/gtk-3.0/ &&\ 304 │ cp icon.ico $output_folder" 305 │ 306 │ # Compile program 307 │ buildah run $container bash -c "cd $folder && x86_64-w64-mingw32-gcc -mwindows -o $executable_name main.c icon.res \`mingw64-pkg-config --cflags gtk+-3.0 --libs gtk+-3.0\`" 308 │ 309 │ # Copy executable into installation folder 310 │ buildah run $container bash -c "cd $folder && cp $executable_name $output_folder" 311 │ 312 │ # Copy mingw dlls into installation folder 313 │ # This part may need to be personalized 314 │ buildah run $container bash -c "yes | cp -r /usr/x86_64-w64-mingw32/sys-root/mingw/{bin/*.dll,share} $folder/$output_folder/" 315 │ 316 │ # Generate an installer 317 │ buildah run $container bash -ic "cd $folder/$output_folder && nsiswrapper --run $executable_name ./*" 318 │ 319 │ # Copy the output from the container 320 │ cp -ru $(buildah unshare buildah mount $container)$folder/$output_folder . 321 │ 322 │ # Clean up 323 │ buildah rm $container 324 │ } 325 │ 326 │ # This just checks whether the container already exists on your drive 327 │ buildah inspect localhost/my-gtk-app &>/dev/null 328 │ return_value=$? 329 │ 330 │ if [ $return_value -eq 1 ] 331 │ then 332 │ echo "Initial container setup" 333 │ setup 334 │ fi 335 │ 336 │ # Build project 337 │ build 338 └──── 339 340 341 [Linux container] <https://en.wikipedia.org/wiki/LXC> 342 343 [Buildah] <https://github.com/containers/buildah> 344 345 [buildah introduction tutorial] 346 <https://github.com/containers/buildah/blob/master/docs/tutorials/01-intro.md> 347 348 349 References 350 ══════════ 351 352 • [windows - How do I add an icon to a mingw-gcc compiled executable? 353 - Stack Ov…] 354 • [Disable client side decorations (GTK_CSD) by default on Windows 355 (win32) (#760…] 356 • [Compile for Windows on Linux | BlogCompiler] 357 358 359 [windows - How do I add an icon to a mingw-gcc compiled executable? - 360 Stack Ov…] 361 <https://stackoverflow.com/questions/708238/how-do-i-add-an-icon-to-a-mingw-gcc-compiled-executable> 362 363 [Disable client side decorations (GTK_CSD) by default on Windows (win32) 364 (#760…] <https://gitlab.gnome.org/GNOME/gtk/issues/760> 365 366 [Compile for Windows on Linux | BlogCompiler] 367 <https://www.blogcompiler.com/2010/07/11/compile-for-windows-on-linux/>