Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9809365
WIP: Linux and macOS support (backup, not finished)
KnifeLemon Oct 6, 2026
4e34275
Merge branch 'main' into wip/linux-macos
KnifeLemon Oct 6, 2026
5743636
macOS: package signs, tests pass, a clear alert when no window can open
KnifeLemon Oct 6, 2026
21e8c59
Linux and macOS packages run on every push to wip/linux-macos, with s…
KnifeLemon Oct 6, 2026
031da57
macOS: the menu bar icon no longer crashes Filee at start; Linux keep…
KnifeLemon Oct 6, 2026
aae7195
Finder and the file manager by name on macOS and Linux; HWPX read-bac…
KnifeLemon Oct 6, 2026
94462c0
Engine install guidance for macOS and Linux in the app's language
KnifeLemon Oct 6, 2026
a8715f5
macOS: Filee follows the system language when started from Finder
KnifeLemon Oct 6, 2026
8a7dff5
macOS: ask for Accessibility at start, remind on the home page, start…
KnifeLemon Oct 6, 2026
756ad77
macOS: ask for Accessibility once the main window is up, so the alert…
KnifeLemon Oct 6, 2026
bc158a2
macOS: Option+drag opens the donut over Finder; FILEE_TRACE_GESTURES …
KnifeLemon Oct 6, 2026
4704fe0
macOS: Filee answers "Convert with Filee" in Finder's right-click men…
KnifeLemon Oct 6, 2026
91a3115
Watch folders on macOS and Linux wait for a paused copy to finish
KnifeLemon Oct 6, 2026
36681be
Updates on the About page, no engine pre-ticked, search above the lis…
KnifeLemon Oct 6, 2026
72c3ef6
README: install for Windows, macOS and Linux, shorter features, scree…
KnifeLemon Oct 6, 2026
9bb5425
Version 1.7.0
KnifeLemon Oct 6, 2026
f8c35c9
CI: no platform warnings, watcher tests step through their own settle…
KnifeLemon Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*.md text
*.ps1 text eol=crlf
*.sh text eol=lf
*.plist text eol=lf

*.ttf binary
*.otf binary
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ concurrency:
cancel-in-progress: true

jobs:
portable:
uses: ./.github/workflows/portable.yml

build:
runs-on: windows-latest
steps:
Expand Down
198 changes: 198 additions & 0 deletions .github/workflows/portable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
name: Linux and macOS packages

on:
workflow_call:
inputs:
version:
type: string
default: ''
required: false
workflow_dispatch:
inputs:
version:
description: 'Package version (empty uses Directory.Build.props)'
type: string
required: false

permissions:
contents: read

jobs:
portable:
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
rid: linux-x64
- runner: ubuntu-24.04-arm
rid: linux-arm64
- runner: macos-15
rid: osx-arm64
- runner: macos-15-intel
rid: osx-x64
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: Linux desktop dependencies
if: runner.os == 'Linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ca-certificates libc6 libgcc-s1 libgssapi-krb5-2 libicu74 libssl3t64 libstdc++6 tzdata zlib1g \
libfontconfig1 libfreetype6 libx11-6 libice6 libsm6 libxrandr2 libxkbcommon0 libxi6 libxtst6 xvfb fonts-noto-cjk

- name: Fonts
run: ./build/fetch-fonts.ps1

- name: Restore engine downloads
uses: actions/cache@v4
with:
path: build/.cache/portable-downloads
key: portable-engines-${{ matrix.rid }}-${{ hashFiles('src/Filee.Engines/Infrastructure/engines.json') }}

- name: Build and test native projects
run: |
./build/fetch-portable-engines.ps1 -Runtime '${{ matrix.rid }}' -Destination engines
dotnet build Filee.slnx -c Release
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$projects = @('Filee.Core.Tests', 'Filee.Platform.Tests', 'Filee.Engines.Tests', 'Filee.Cli.Tests', 'Filee.App.Tests')
foreach ($project in $projects) {
$testArguments = @('test', "tests/$project/$project.csproj", '--no-build', '-c', 'Release', '--logger', 'trx', '--results-directory', "TestResults/${{ matrix.rid }}",
'--filter', 'FullyQualifiedName!=Filee.App.Tests.ExplorerMenuTests.Every_native_import_exists_in_the_dll_it_names&FullyQualifiedName!=Filee.App.Tests.ExplorerMenuTests.Reading_the_registered_package_never_throws')
if ($IsLinux) {
xvfb-run -a env -u WAYLAND_DISPLAY XDG_SESSION_TYPE=x11 dotnet @testArguments
} else {
dotnet @testArguments
}
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

- name: Build portable package
env:
PACKAGE_VERSION: ${{ inputs.version }}
run: ./build/build-portable.ps1 -Runtime '${{ matrix.rid }}' -Version $env:PACKAGE_VERSION -SkipFonts

- name: Check archive and run native converters
run: |
$archives = @(Get-ChildItem Releases -Filter '*-${{ matrix.rid }}.tar.gz')
if ($archives.Count -ne 1) { throw 'Expected one portable archive.' }
$archive = $archives[0]
$expected = (Get-Content (Join-Path Releases ($archive.Name.Replace('.tar.gz', '-SHA256SUMS.txt')))).Split(' ')[0]
if ((Get-FileHash $archive.FullName -Algorithm SHA256).Hash -ne $expected) { throw 'Archive checksum mismatch.' }
$smoke = Join-Path $env:RUNNER_TEMP 'filee-smoke'
New-Item -ItemType Directory -Path $smoke | Out-Null
tar -xzf $archive.FullName -C $smoke
if ($LASTEXITCODE -ne 0) { throw 'Archive extraction failed.' }
$package = Join-Path $smoke ($archive.Name.Replace('.tar.gz', ''))
if ($IsMacOS) {
codesign --verify --deep --strict (Join-Path $package 'Filee.app')
if ($LASTEXITCODE -ne 0) { throw 'Extracted app signature did not verify.' }
}
$bin = if ($IsMacOS) { Join-Path $package 'Filee.app/Contents/MacOS' } else { $package }
$cli = Join-Path $bin 'filee-cli'
& $cli version
if ($LASTEXITCODE -ne 0) { throw 'CLI did not start.' }
& (Join-Path $bin 'engines/rhwp/rhwp') --version
if ($LASTEXITCODE -ne 0) { throw 'rhwp did not start.' }
& (Join-Path $bin 'engines/7zip/7zz') i | Out-Null
if ($LASTEXITCODE -ne 0) { throw '7-Zip did not start.' }
$inputFile = Join-Path $smoke 'sample.txt'
'Filee 한글 conversion smoke test' | Set-Content $inputFile -Encoding utf8NoBOM
& $cli convert $inputFile --to hwpx -o (Join-Path $smoke 'converted') --json
if ($LASTEXITCODE -ne 0 -or -not (Test-Path (Join-Path $smoke 'converted/sample.hwpx'))) { throw 'Published CLI conversion failed.' }
"FILEE_SMOKE_APP=$(Join-Path $bin 'Filee')" >> $env:GITHUB_ENV

- name: Launch native desktop app
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import os
import pathlib
import signal
import subprocess
import sys
import time

app = os.environ['FILEE_SMOKE_APP']
shots = pathlib.Path(os.environ['RUNNER_TEMP']) / 'filee-screens'
shots.mkdir(exist_ok=True)
command = [app, '--settings', '--install-engines=', '--context-menu=off', '--start-with-system=off']
if os.environ['RUNNER_OS'] == 'Linux':
command = ['xvfb-run', '-a', 'env', '-u', 'WAYLAND_DISPLAY', 'XDG_SESSION_TYPE=x11', *command]
log_path = pathlib.Path(os.environ['RUNNER_TEMP']) / 'filee-startup.log'
with log_path.open('w') as log:
process = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT, start_new_session=True)
try:
time.sleep(8)
if process.poll() is not None:
raise RuntimeError('The desktop app exited before the startup check.')
if os.environ['RUNNER_OS'] == 'macOS':
# The real screen with Filee's window, as a person would see it (uploaded with the results).
subprocess.run(['screencapture', '-x', str(shots / 'settings.png')], check=False, timeout=30)
# A second start forwards to the running Filee, which brings its window to the front.
try:
subprocess.run([app], check=False, timeout=30)
except subprocess.TimeoutExpired:
raise RuntimeError('A second start did not hand over to the running Filee.')
time.sleep(4)
subprocess.run(['screencapture', '-x', str(shots / 'second-start.png')], check=False, timeout=30)
if process.poll() is not None:
raise RuntimeError('The desktop app exited after a second start.')
quit_code = 'import os, sys; os.setsid(); os.execv(sys.argv[1], [sys.argv[1], "--quit"])'
subprocess.run([sys.executable, '-c', quit_code, app], check=True, timeout=30)
if process.wait(timeout=15) != 0:
raise RuntimeError('The desktop app did not exit cleanly.')
except BaseException:
log.flush()
print(log_path.read_text(errors='replace'), file=sys.stderr)
raise
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
PY

- name: Collect Filee logs
if: always()
shell: bash
run: |
mkdir -p "$RUNNER_TEMP/filee-screens"
for logs in "$HOME/Library/Application Support/Filee/logs" "$HOME/.config/Filee/logs"; do
if [ -d "$logs" ]; then cp -R "$logs" "$RUNNER_TEMP/filee-screens/"; fi
done

- name: Upload packages
uses: actions/upload-artifact@v4
with:
name: portable-${{ matrix.rid }}
path: |
Releases/*-${{ matrix.rid }}.tar.gz
Releases/*-${{ matrix.rid }}-SHA256SUMS.txt
if-no-files-found: error

- name: Upload test diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: unix-test-results-${{ matrix.rid }}
path: |
TestResults/${{ matrix.rid }}/
tests/Filee.App.Tests/bin/Release/net10.0/screenshots/
${{ runner.temp }}/filee-startup.log
${{ runner.temp }}/filee-screens/
if-no-files-found: ignore
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ permissions:
contents: write

jobs:
portable:
uses: ./.github/workflows/portable.yml
with:
version: ${{ github.ref_name }}

windows:
needs: portable
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -64,6 +70,13 @@ jobs:
shell: pwsh
run: ./build/build-installer.ps1 -Version ${{ steps.version.outputs.value }} -Portable

- name: Download Linux and macOS packages
uses: actions/download-artifact@v4
with:
pattern: portable-*
merge-multiple: true
path: Releases

- name: Publish the GitHub release
shell: pwsh
env:
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@ version it publishes under "What's Changed" in the GitHub release notes, above t
Add a `## <version>` section before tagging a release. Write what someone using Filee notices: what's new, where to
find it, what it does and doesn't do.

## 1.7.0

### macOS and Linux (preview)

- Filee runs on macOS 14 or later (Apple silicon and Intel) and on Linux (x64 and ARM64). Download the package for
your system from this release and run `install.sh` in it. These are previews: the macOS app isn't notarized yet,
so macOS asks you to allow it once under System Settings → Privacy & Security.
- macOS: hold Option and drag files to open the donut. Filee asks for the Accessibility permission the gesture needs
and starts it as soon as you switch Filee on. "Convert with Filee" is in Finder's right-click menu.
- Linux: the drag gesture works in an X11 session (on Ubuntu, "Ubuntu on Xorg" on the login screen). On Wayland, use
the drop zone and the right-click action in Nautilus, Nemo, Thunar or Dolphin.
- FFmpeg and Ghostscript come from Homebrew on macOS and from your distribution's packages on Linux. Filee finds them
by itself.

### New

- Search boxes above the profiles and the available presets (Donut toolbar) and above the preset list. A profile is
also found by an extension it handles: type "png" to find the image donut.
- "Never trigger in these apps" (Shortcuts) holds the apps as tags. Type a name, with the running apps suggested, or
pick one from the apps that have a window open.
- The update settings are on the About page now: the automatic check, Check now and the update button.
- The first-run engine choice starts with nothing ticked, so only what you pick is downloaded.

### Fixes

- On macOS and Linux, watch folders wait for a copy that pauses to finish instead of converting it half-written.

## 1.6.0

### New in the preset editor for audio
Expand Down
42 changes: 41 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,51 @@ Thanks for helping! Filee is built to be easy to extend: most contributions touc
DOCX. XLSX ↔ CSV and anything → HWPX work without either). Without `-Only` it also downloads LibreOffice with
H2Orestart and Java (~420 MB), which only older formats such as DOC, XLS and PPT need. Engines in the
repository's `engines/` folder are picked up by debug builds; users download the large ones from the app.
Filee never uses programs installed on the system (Microsoft Office, a system LibreOffice, tools on `PATH`).
Filee can also discover supported engines on `PATH` and in their standard installation folders. Settings >
Engines lets you choose a specific copy. Microsoft Office is not used.
4. `dotnet run --project src/Filee.App`, `dotnet test`.

Any IDE works (Visual Studio 2022+, Rider, VS Code with C# Dev Kit).

## Linux and macOS development

These ports are development builds until native desktop tests are complete. Install .NET 10 and PowerShell 7,
then build on the target OS. Supported package targets are `linux-x64`, `linux-arm64`, `osx-x64` and `osx-arm64`.

```sh
pwsh build/fetch-fonts.ps1
pwsh build/fetch-portable-engines.ps1 -Runtime linux-x64 -Destination engines
dotnet test Filee.slnx -c Release
pwsh build/build-portable.ps1 -Runtime linux-x64
```

Use the matching runtime on a Mac or ARM64 Linux machine. Packaging runs on Linux/macOS so archives retain
executable permissions. Each archive contains the app, CLI, rhwp, 7-Zip, installation instructions and per-user
install/uninstall scripts. `Filee-<version>-<rid>-SHA256SUMS.txt` verifies the archive. macOS bundles use
`com.filee.app` and a development ad-hoc signature by default; public distribution still needs Developer ID signing
and notarization. `-SigningIdentity` can supply a signing identity, but does not perform notarization.

- **Linux:** drag gestures require X11. Install the Avalonia desktop dependencies (fontconfig, FreeType, X11,
Xrandr, Xi, Xtst, XkbCommon, ICE and SM) and CJK fonts for document comparisons. File actions are installed for
Thunar, Nautilus, Nemo and Dolphin. The first enabled keyboard shortcut uses Thunar's native custom action;
close Thunar before changing or pausing that shortcut and reopen it afterwards. Global selected-file queries
and hold gestures are not supported. Wayland can use file actions, the drop zone, watch folders and CLI.
- **macOS:** grant Filee Accessibility permission for gestures and Finder Automation permission for selection.
Keep the development `.app` at a stable path while testing these permissions. Finder integration is a Quick
Action under Services; auto-start uses a per-user LaunchAgent.
- **Engines:** packages are selected by OS and architecture. Linux LibreOffice/Ghostscript and macOS
FFmpeg/Ghostscript currently use system or user-selected installations. Linux ARM64 has no Unhwp native binary.
The macOS LibreOffice package does not include H2Orestart; native rhwp/Unhwp provide the supported HWP routes.
Use Settings > Engines to inspect actual availability before comparing formats.

The reusable `portable.yml` workflow builds and tests all four targets. Native platform tests skip on other OSes.
Before calling a port ready, verify the installed package, CLI conversion, Finder/Thunar selection with Unicode
and spaced paths, drag and keyboard gestures, permissions denied/granted, Retina/scaling, tray, login startup,
engine downloads, watch folders, and uninstall. CI GUI startup alone does not validate these interactions.

The website lives in the adjacent `../filee-web` repository. Its downloads must match real release assets and
keep development status visible until native validation is complete.

## Where things live

Read [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) first. In short:
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
parameters of a member is fine (CS1573). -->
<NoWarn>$(NoWarn);CS1591;CS1573</NoWarn>

<Version>1.6.0</Version>
<Version>1.7.0</Version>
<Product>Filee</Product>
<Authors>Filee contributors</Authors>
<Copyright>Copyright (c) Filee contributors</Copyright>
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
<PackageVersion Include="ExcelNumberFormat" Version="1.1.0" />
<!-- 5.2+ requires SkiaSharp 4 (see PDFtoImage above); 5.1.1 uses SkiaSharp 3.119 and HarfBuzzSharp 8.3 like Avalonia. -->
<PackageVersion Include="Svg.Skia" Version="5.1.1" />
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.4" />
<!-- Managed zstd / bzip2 for unpacking downloaded engines (conda packages). -->
<PackageVersion Include="SharpCompress" Version="1.0.0" />
<PackageVersion Include="ExcelDataReader" Version="3.9.0" />
Expand Down
3 changes: 3 additions & 0 deletions Filee.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<Project Path="src/Filee.Core/Filee.Core.csproj" />
<Project Path="src/Filee.Engines/Filee.Engines.csproj" />
<Project Path="src/Filee.Platform.Windows/Filee.Platform.Windows.csproj" />
<Project Path="src/Filee.Platform.Linux/Filee.Platform.Linux.csproj" />
<Project Path="src/Filee.Platform.MacOS/Filee.Platform.MacOS.csproj" />
<Project Path="src/Filee.App/Filee.App.csproj" />
<Project Path="src/Filee.Cli/Filee.Cli.csproj" />
</Folder>
Expand All @@ -11,6 +13,7 @@
<Project Path="tests/Filee.Engines.Tests/Filee.Engines.Tests.csproj" />
<Project Path="tests/Filee.App.Tests/Filee.App.Tests.csproj" />
<Project Path="tests/Filee.Cli.Tests/Filee.Cli.Tests.csproj" />
<Project Path="tests/Filee.Platform.Tests/Filee.Platform.Tests.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path="Directory.Build.props" />
Expand Down
Loading
Loading