Skip to content

Support Windows and macOS with a QFileDialog file picker - #11

Open
deathy wants to merge 4 commits into
omacom:masterfrom
deathy:portable-file-picker
Open

deathy wants to merge 4 commits into
omacom:masterfrom
deathy:portable-file-picker

Conversation

@deathy

@deathy deathy commented Sep 10, 2026

Copy link
Copy Markdown

Thanks for omacut — it's lovely, and I wanted it on my Windows machine.

It turned out to need very little, almost entirely because FilePicker was
already an abstraction with the portal sitting behind it. This adds a second
implementation on QFileDialog and lets the .pro files choose one per
platform. Linux is untouched by design: the portal path is unchanged, and every
new branch sits behind OMACUT_PORTAL_FILE_PICKER or Q_OS_WIN. Windows and
macOS both get their own native dialog out of it.

The one real compromise

The portal's choices combo is how the export dialog asks for a quality, and
native save dialogs have no field to put one in. So the choices ride in the
"Save as type" combo those dialogs already show — still one dialog, still one
pick, but it is a genuine difference in feel and you may well not like it.
exportFilters and scaleHeightForFilter are static and pure, so the pairing
is covered without driving a modal dialog.

Three portability fixes

These came out of getting the suite green on Windows, and two of them are in the
tests rather than the app:

  • replaceWithTemp uses std::rename, which replaces the destination on POSIX
    but fails on Windows when it already exists — which is exactly the
    re-export-over-a-previous-cut case the function exists for. MoveFileExW does
    it in one step. Linux behaviour is unchanged.
  • The tests join PATH with ':'. On Windows that collapses into one nonsense
    entry, so the deliberately-broken ffmpeg was never found, the real one ran,
    the export succeeded, and the two tests waiting for a failure signal waited
    forever.
  • That broken ffmpeg is a shebang script named ffmpeg. Windows looks
    executables up by extension and starts them with CreateProcess, which has no
    notion of a shebang, so it was neither found nor started.

ffmpeg::toolPath also looks in the application directory before the PATH, so
a self-contained build can ship ffmpeg beside the binary. A packaged install
still resolves through the PATH exactly as before.

Testing

  • Windows (MSVC 2022, Qt 6.8.1): 27/27 of the existing suite, 7/7 of the new one.
  • macOS (Qt 6.11, Homebrew): the same, via ./bin/build and ./bin/test.
  • By hand on Windows: open a file, trim, zoom the timeline, export.

bin/test needed one nudge to work on macOS at all — qmake produces an .app
bundle there, and the script ends in ./backend_tests, so it failed with "No
such file or directory" after a perfectly good build.

Worth pushing back on

The non-portal build links QtWidgets and constructs a QApplication, because
QFileDialog is a widget. If you'd rather not take that dependency, a
QtQuick.Dialogs FileDialog avoids widgets but inverts the control flow that
FilePicker has today. Happy to redo it that way, split this up, or drop any
piece of it — and equally happy if Windows just isn't somewhere you want to go.

Windows packaging (bundled ffmpeg, CI, archive layout) deliberately stays out of
this PR; it lives in https://github.com/deathy/omacut-windows.

deathy and others added 4 commits September 10, 2026 19:01
The portal picker talks to xdg-desktop-portal over D-Bus, which only
exists on Linux. FilePicker was already an abstract interface with a
single implementation behind it, so this adds a second one built on
QFileDialog and lets the .pro files choose per platform. Windows and
macOS both get their own native dialog out of it.

The one thing that does not carry over is the portal's "choices" combo,
which is how the export dialog asks for a quality. Native save dialogs
have no field to put that in, so the choices ride in the "Save as type"
combo they already show -- still one dialog, still one pick.

QFileDialog is a widget, so the non-portal build links QtWidgets and
constructs a QApplication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Exports encode to a sibling temp file and rename it over the target only
once ffmpeg succeeds, so a failed cut leaves any existing file alone.
POSIX rename() replaces the destination; the Windows CRT's refuses when
it exists, which is precisely the case this path is written for -- so on
Windows every re-export over a previous cut failed with "Could not write
the exported file" and left the .omacut-part.mp4 behind.

MoveFileExW with MOVEFILE_REPLACE_EXISTING does the replacement in one
step, and takes the path as UTF-16 rather than through QFile::encodeName.

Covered by the existing exportClipCanReplaceSourceFile and
failedExportPreservesExistingFile tests, which only exercised the POSIX
path before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A distro package declares ffmpeg as a dependency and finds it on the
PATH. A self-contained build has no package manager to lean on and ships
ffmpeg beside the executable instead, where findExecutable would never
look -- it only searches the PATH.

Check the application directory first, so a bundled pair is always the
one that gets used, and keep the PATH as the fallback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things stopped it on Windows, none of them the code under test:

PATH entries were joined with ':', so the directory holding the
deliberately-broken ffmpeg became part of one nonsense entry and was
never searched. The real ffmpeg ran instead, the export succeeded, and
the two tests waiting for a failure signal waited forever.

The broken ffmpeg was a shebang script named "ffmpeg". Windows finds
executables by extension and starts them with CreateProcess, which knows
nothing about shebangs, so it was neither found nor started. An
"ffmpeg.exe" holding no valid executable image fails to start for the
reason the test actually wants.

And exportStartFailureClearsBusy checked that the export is briefly busy
after a failed start, which holds only where a failed exec is reported
asynchronously. CreateProcess fails inline, so the cleanup has already
run by the time exportClip() returns. The assertions that matter still
run everywhere.

The build of the test binaries needed adjusting on both platforms too:
qmake produces a GUI-subsystem executable on Windows, leaving QtTest with
no stdout to print results to, and an .app bundle on macOS, which
bin/test looks straight past.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant