Many of us love the clean, minimalist design language of GNOME and Libadwaita but are forced to work inside a Windows environment for specific local software, gaming, or corporate restrictions.
Over my summer break, I decided to tackle this friction head-on. I forked the official upstream GNOME repository for Papers (the modern successor to the classic Evince document viewer) and built a native Windows port.
This isn't running inside WSL, nor is it a web-based clone. It is a native port built directly on the Universal C Runtime (UCRT64) via MSYS2.
The application is a complex hybrid codebase composed of a C rendering core (libdocument, libview) and a Rust shell application. Here is how I decoupled it from Linux-only paradigms and brought full Libadwaita interface layouts to Windows.
🛠️ The Core Engineering Challenges
1. Architectural Abstraction via a Win32 PAL
Instead of fracturing the upstream source tree with endless inline #ifdef G_OS_WIN32 blocks—which completely ruins upstream maintainability—I implemented a dedicated Platform Abstraction Layer (PAL) pattern.
I extracted all platform-dependent GUI and rendering wrapper functions into a dedicated interface: libview/pps-platform.h. The build system (meson.build) dynamically selects either the standard Unix GTK backend (pps-platform-gtk.c) or the new Windows backend (pps-platform-win32.c) based on the host OS. This layer handles standard coordinate mapping and window hooks natively through the Windows API rather than relying on X11 or Wayland assumptions.
2. Gating POSIX and Unix-Specific Services
The upstream engine relies on Unix-specific file descriptor duplicate operations (fcntl and the F_DUPFD_CLOEXEC flag) for preview and job processing loops.
Since Windows handles handle duplication via DuplicateHandle and manages inheritance explicitly through SECURITY_ATTRIBUTES, I gated the POSIX operations:
#ifndef G_OS_WIN32
static int
pps_dupfd (int fd, int minfd)
{
int ret;
ret = fcntl (fd, F_DUPFD_CLOEXEC, minfd);
if (ret == -1 && errno == EINVAL) {
ret = fcntl (fd, F_DUPFD, minfd);
if (ret != -1) {
int flags;
flags = fcntl (ret, F_GETFD);
if (flags != -1)
fcntl (ret, F_SETFD, flags | FD_CLOEXEC);
}
}
return ret;
}
#else
static int
pps_dupfd (int fd, int minfd)
{
// File descriptor duplication is a POSIX-only feature
return -1;
}
#endif
Additionally, I gated Unix-only features such as D-Bus Keyring/Secret Service integration (the oo7 crate used for the keyring) and the Nautilus file manager extensions, substituting standard library alternatives.
📦 Packaging for Windows (The Fun Part)
Getting an MSYS2 GTK4/Rust application to run standalone on a clean Windows machine without requiring MSYS2 to be installed is a massive staging puzzle.
1. Solving the GIO MIME Type Engine
Normally, GIO uses the Linux system's /usr/share/mime database to recognize formats (like identifying .pdf as application/pdf). On a clean Windows install, this database doesn't exist, causing GIO to default to application/octet-stream (Unknown binary format), making the app refuse to open any files.
I resolved this with a dual approach:
-
Bundling the MIME database: The custom bundling script (
bundle.ps1) copies the compiled MSYS2share/mimefolder into the application's runtime folder. -
Registry-Level MIME Injection: The Inno* Setup installer (
setup.iss) registers explicitContent Typevalues directly in the Windows registry (e.g. mapping.djvutoimage/vnd.djvuand.cbrtoapplication/vnd.comicbook-rar).
This ensures GIO instantly recognizes PDF, DjVu, TIFF, CBR, and CBZ formats natively.
*setup.iss for Inno Setup installer was automated using Gemini 3.5 Flash
2. Dynamic Backend Path Resolution
At runtime, Papers loads document-reader plugins dynamically. The upstream paths were hardcoded to Unix system prefixes. I patched libdocument/pps-document-factory.c to dynamically locate the backends relative to the running module's location on Windows:
#ifdef G_OS_WIN32
if (!pps_backends_dir) {
g_autofree gchar *inst_dir = g_win32_get_package_installation_directory_of_module(NULL);
if (inst_dir) {
pps_backends_dir = g_build_filename(inst_dir, "lib", "papers", "6", "backends", NULL);
}
}
#endif
3. Evading Antivirus False Positives
Unsigned installers compiled with Inno Setup and containing Rust executables are heavily flagged by heuristic AV scanners due to generic packer signature matches.
To bypass this for our Winget submission:
- I configured Cargo to fully strip debug symbols and formatting strings in release mode (
strip = trueinCargo.toml). - Switched the installer packing method from
lzma2to standardzipinsetup.issto break generic heuristic detections.
đźš§ Active Areas of Focus / Call for Contributions
While the application is fully functional, open-source, and able to view documents natively, there are a few active areas of development we are optimizing:
- Tab Management: Currently, trying to open a new document opens it in a new window instead of a new tab within the same window.
- Open File Dialog: File-menu document loading occasionally fails to trigger the view redraw.
- Startup Performance: The initialization path currently takes around 3 seconds to complete, which we are looking to optimize by profiling GSettings loading.
🤝 Upstreaming and Community
We are currently in active discussion with the upstream GNOME Papers maintainers to integrate this cross-platform PAL work cleanly into the main repository.
- GitHub Repository: harshmishrahm01/papers-for-windows
- Winget PR: winget-pkgs #399816
- GNOME GitLab Discussion: GNOME Papers #714
If you are a Rust/GTK enthusiast, feel free to clone the repo, run the automated setup script (auto-setup.ps1), and send over a pull request!



Top comments (0)