Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,45 @@ jobs:
- name: Build macOS (debug, no codesign)
working-directory: patch_app
run: flutter build macos --debug

# Linux counterpart of build-macos: compiles patch_core as a cdylib and links
# it into the GTK app the same way a release build does, so Linux-specific
# link/plugin regressions fail the PR instead of first surfacing at
# release-tag time. Also the only CI job that compiles patch_core's Linux
# `cfg(target_os = "linux")` paths (ALSA/midir). Keep the apt deps in step
# with release.yml's "Install Linux build deps".
build-linux:
name: Linux — native build smoke test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config \
libgtk-3-dev libasound2-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev

# cargokit invokes cargo during `flutter build`, so the same pinned Rust
# as the `rust` job must be present.
- name: Install Rust 1.95.0
uses: dtolnay/rust-toolchain@1.95.0

- uses: Swatinem/rust-cache@v2

- name: Install Flutter 3.44.1
uses: subosito/flutter-action@v2
with:
flutter-version: 3.44.1
channel: stable
cache: true

- name: Pub get
working-directory: patch_app
run: flutter pub get

# Debug build: we only need compile-and-link to succeed, not an artifact.
- name: Build Linux (debug)
working-directory: patch_app
run: flutter build linux --debug
61 changes: 60 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ name: Release
# (CODE_SIGN_IDENTITY = "-"), so first launch after opening the DMG shows a
# Gatekeeper warning (right-click → Open, or
# `xattr -dr com.apple.quarantine Patch.app`); Windows is unsigned, so the
# installer trips SmartScreen ("More info → Run anyway"). Real codesigning/
# installer trips SmartScreen ("More info → Run anyway"); Linux ships a
# portable AppImage (`chmod +x` then run — no install step). Real codesigning/
# notarization would need certs added as repo secrets — out of scope here.
#
# NB the release name comes from the tag. Keep the tag aligned with
Expand Down Expand Up @@ -40,10 +41,24 @@ jobs:
platform: macos
- os: windows-latest
platform: windows
- os: ubuntu-latest
platform: linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

# Flutter Linux desktop needs GTK + the CMake/Ninja toolchain; patch-core
# links ALSA (midir) and audioplayers_linux links GStreamer. macOS/Windows
# get their MIDI/audio backends from the OS (CoreMIDI/WinMM), so this is
# Linux-only. Keep in step with ci.yml's build-linux job.
- name: Install Linux build deps
if: matrix.platform == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config \
libgtk-3-dev libasound2-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev

# cargokit compiles patch-core (cdylib/staticlib) during `flutter build`,
# so the Rust toolchain must be present. macOS links CoreMIDI, Windows
# WinMM — neither needs an extra system package (ALSA is Linux-only).
Expand Down Expand Up @@ -125,13 +140,56 @@ jobs:
/DMyOutputDir="${{ github.workspace }}" `
patch_app\windows\installer\patch.iss

# ── Linux ────────────────────────────────────────────────────────────
- name: Build Linux
if: matrix.platform == 'linux'
working-directory: patch_app
run: flutter build linux --release

- name: Package Linux (AppImage)
if: matrix.platform == 'linux'
working-directory: patch_app
# A portable AppImage (single executable, no install) mirrors the
# "download & run" UX of the macOS .dmg / Windows .exe. appimagetool is
# itself an AppImage; APPIMAGE_EXTRACT_AND_RUN avoids needing FUSE on the
# runner.
env:
APPIMAGE_EXTRACT_AND_RUN: 1
run: |
BUNDLE=build/linux/x64/release/bundle
APPDIR=AppDir
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin"
cp -r "$BUNDLE"/. "$APPDIR/usr/bin/"
cp assets/icon/icon_master.png "$APPDIR/patch.png"
cat > "$APPDIR/patch.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Name=Patch
Exec=patch_app
Icon=patch
Categories=AudioVideo;Network;Utility;
Terminal=false
EOF
cat > "$APPDIR/AppRun" <<'EOF'
#!/bin/bash
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/usr/bin/patch_app" "$@"
EOF
chmod +x "$APPDIR/AppRun"
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APPDIR" \
"$GITHUB_WORKSPACE/Patch-${{ steps.version.outputs.version }}-linux-x86_64.AppImage"

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: patch-${{ matrix.platform }}
path: |
Patch-*.dmg
Patch-*.exe
Patch-*.AppImage
if-no-files-found: error

release:
Expand All @@ -152,5 +210,6 @@ jobs:
files: |
artifacts/*.dmg
artifacts/*.exe
artifacts/*.AppImage
generate_release_notes: true
draft: true
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ flutter run -d macos
# iOS / iPad (device or simulator)
flutter run -d <device-id>

# Windows / Linux — generate the platform folder first if missing
flutter create --platforms=windows . # or linux
# Windows / Linux (platform folders are committed, like macOS/iOS)
flutter run -d windows # or linux
```

Prebuilt downloads for tagged releases are attached to the [GitHub Releases](https://github.com/vk0eppel/Patch/releases) page: a `.dmg` (macOS), an installer `.exe` (Windows), and a portable `.AppImage` (Linux — `chmod +x` then run, no install step). All are unsigned; see the notes in `.github/workflows/release.yml`.

The first build is slow because Cargokit cross-compiles the Rust engine for the target. Subsequent runs are fast.

The engine generates a `patch.toml` config file on first run in the platform data directory:
Expand Down
3 changes: 3 additions & 0 deletions patch_app/.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ migration:
- platform: windows
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
- platform: linux
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42

# User provided section

Expand Down
12 changes: 12 additions & 0 deletions patch_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ bool _positionOnScreen(Offset pos, Size windowSize) {
void main() async {
WidgetsFlutterBinding.ensureInitialized();

// TEMP DEADZONE PROBE — prints every pointer-down that reaches Flutter's
// engine, with window-local coords. If clicking the Audio tab's dead centre
// prints nothing, the event is swallowed natively before Flutter. Remove.
WidgetsBinding.instance.pointerRouter.addGlobalRoute((event) {
if (event is PointerDownEvent) {
debugPrint(
'PROBE pointerDown x=${event.position.dx.toStringAsFixed(1)} '
'y=${event.position.dy.toStringAsFixed(1)}',
);
}
});

// Lock to landscape on iPad — the fixed-width multi-panel layout is unusable
// in portrait. Done before runApp so it's in effect for the first frame. No-op
// on desktop and iPhone (see shouldLockLandscape).
Expand Down
1 change: 1 addition & 0 deletions patch_app/linux/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flutter/ephemeral
128 changes: 128 additions & 0 deletions patch_app/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)

# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "patch_app")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.patch.app")

# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)

# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")

# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()

# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()

# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()

# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})

# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)

# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")

# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)


# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)


# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()

# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)

set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")

install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)

install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)

install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)

foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)

# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)

# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)

# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
Loading
Loading