Skip to content
Closed
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
91 changes: 91 additions & 0 deletions .github/workflows/flatpak.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Flatpak

on:
workflow_dispatch:
push:
branches-ignore:
- main
- master
Comment thread
coderabbitai[bot] marked this conversation as resolved.
paths:
- "src/**"
- "public/**"
- "electron/**"
- "flatpak/**"
- "package.json"
- "package-lock.json"
- "vite.config.ts"
- ".github/workflows/flatpak.yml"
pull_request:
paths:
- "src/**"
- "public/**"
- "electron/**"
- "flatpak/**"
- "package.json"
- "package-lock.json"
- "vite.config.ts"
- ".github/workflows/flatpak.yml"

jobs:
flatpak:
name: Build Flatpak
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"

# Install flatpak-node-generator from a pinned commit for reproducibility.
# The tool is a Python package (not a single script); pinning to an exact
# commit SHA prevents supply-chain surprises from upstream changes.
# To update: bump the commit SHA below to the desired upstream commit.
- name: Install flatpak-node-generator
run: |
pip3 install --quiet \
"git+https://github.com/flatpak/flatpak-builder-tools.git@5de461271d05f43f267b954ac6010b410378ae47#subdirectory=node"

- name: Generate offline npm sources
run: |
flatpak-node-generator npm package-lock.json \
-o flatpak/generated-sources.json

- name: Install Flatpak and flatpak-builder
run: |
sudo apt-get update -y
sudo apt-get install -y flatpak flatpak-builder

- name: Add Flathub remote
run: |
flatpak remote-add --user --if-not-exists flathub \
https://dl.flathub.org/repo/flathub.flatpakrepo

- name: Install Flatpak runtimes and SDK extensions
run: |
flatpak install --user -y flathub \
org.freedesktop.Platform//24.08 \
org.freedesktop.Sdk//24.08 \
org.electronjs.Electron2.BaseApp//24.08 \
org.freedesktop.Sdk.Extension.node20//24.08

- name: Build Flatpak bundle
run: |
flatpak-builder --user --install-deps-from=flathub \
--force-clean --repo=flatpak-repo \
flatpak-build flatpak/com.rein.app.yml

- name: Create .flatpak bundle file
run: |
flatpak build-bundle flatpak-repo rein.flatpak com.rein.app

- name: Upload Flatpak artifact
uses: actions/upload-artifact@v4
with:
name: rein-flatpak
path: rein.flatpak
retention-days: 7
67 changes: 64 additions & 3 deletions electron/main.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { app, BrowserWindow } = require('electron');
const { app, BrowserWindow, session, systemPreferences } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const http = require('http');
Expand Down Expand Up @@ -60,15 +60,46 @@ function createWindow() {
width: 1200,
height: 800,
show: false,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

// Grant screen-capture permission requests only from the trusted local
// origin and only for display-capture / screen. Without this handler
// Electron silently denies getDisplayMedia in packaged builds.
const TRUSTED_ORIGIN = 'http://localhost:3000';
mainWindow.webContents.session.setPermissionRequestHandler(
(webContents, permission, callback) => {
const origin = webContents.getURL();
const isScreenCapture = permission === 'display-capture' || permission === 'screen';
callback(origin.startsWith(TRUSTED_ORIGIN) && isScreenCapture);
}
);

// Allow permission checks (e.g. navigator.permissions.query) for the same
// trusted origin and screen-capture permissions only.
mainWindow.webContents.session.setPermissionCheckHandler(
(webContents, permission) => {
const origin = webContents ? webContents.getURL() : '';
const isScreenCapture = permission === 'display-capture' || permission === 'screen';
return origin.startsWith(TRUSTED_ORIGIN) && isScreenCapture;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);

mainWindow.loadURL('http://localhost:3000');

// Show when ready
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});

// Clear the reference so createWindow() can recreate it (macOS dock reopen)
mainWindow.on('closed', () => {
mainWindow = null;
});

// Debug only if needed
mainWindow.webContents.on('did-fail-load', (e, code, desc) => {
console.log("LOAD FAILED:", code, desc);
Expand All @@ -77,12 +108,42 @@ function createWindow() {

// App start
app.whenReady().then(async () => {
// On macOS, screen-recording permission cannot be requested programmatically
// via the Electron API (askForMediaAccess does not support 'screen').
// If the permission has not been granted yet, show a dialog directing the
// user to enable it in System Preferences → Privacy & Security → Screen Recording.
if (process.platform === 'darwin') {
const status = systemPreferences.getMediaAccessStatus('screen');
if (status !== 'granted') {
const { dialog } = require('electron');
dialog.showMessageBoxSync({
type: 'warning',
title: 'Screen Recording Permission Required',
message: 'Rein needs Screen Recording permission to mirror your display.',
detail:
'Please open System Preferences → Privacy & Security → Screen Recording ' +
'and enable Rein, then restart the app.',
buttons: ['OK'],
});
}
}

await startServer();
createWindow();
});

// Cleanup
app.on('window-all-closed', () => {
if (serverProcess) serverProcess.kill();
if (process.platform !== 'darwin') app.quit();
// On macOS the server keeps running so the app can be reopened via the
// dock without restarting the Nitro process. Only kill the server and
// quit on platforms where closing all windows means the app is done.
if (process.platform !== 'darwin') {
if (serverProcess) serverProcess.kill();
app.quit();
}
});

// macOS: re-create window when dock icon is clicked
app.on('activate', () => {
if (!mainWindow) createWindow();
});
8 changes: 8 additions & 0 deletions flatpak/com.rein.app.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[Desktop Entry]
Name=Rein
Comment=Wireless trackpad, keyboard and screen mirror
Exec=run.sh
Icon=com.rein.app
Type=Application
Categories=Utility;
StartupWMClass=rein
40 changes: 40 additions & 0 deletions flatpak/com.rein.app.metainfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.rein.app</id>
<name>Rein</name>
<summary>Use your phone as a wireless trackpad, keyboard and screen mirror for your PC</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>

<description>
<p>
Rein turns your mobile device into a wireless trackpad, keyboard, and screen
mirror for your desktop. Connect over your local network — no cable, no
Bluetooth pairing required.
</p>
<ul>
<li>Trackpad: use your phone screen as a precise touchpad</li>
<li>Keyboard: type on your phone and the input appears on your PC</li>
<li>Screen Mirror: view your desktop screen on your mobile device in real time</li>
</ul>
</description>

<launchable type="desktop-id">com.rein.app.desktop</launchable>

<url type="homepage">https://github.com/anomalyco/opencode</url>
<url type="bugtracker">https://github.com/anomalyco/opencode/issues</url>

<releases>
<release version="1.0.0" date="2026-03-08">
<description>
<p>Initial Flatpak release of Rein.</p>
<ul>
<li>Wireless trackpad, keyboard and screen mirror over local network</li>
<li>Fixed screen capture permissions in packaged Electron executables</li>
</ul>
</description>
</release>
</releases>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<content_rating type="oars-1.1"/>
</component>
100 changes: 100 additions & 0 deletions flatpak/com.rein.app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
app-id: com.rein.app
runtime: org.freedesktop.Platform
runtime-version: '24.08'
sdk: org.freedesktop.Sdk
base: org.electronjs.Electron2.BaseApp
base-version: '24.08'
sdk-extensions:
- org.freedesktop.Sdk.Extension.node20
command: run.sh
separate-locales: false

finish-args:
- --share=ipc
# X11 as default display; use Xwayland when running under a Wayland compositor.
# Electron native Wayland support is still experimental.
- --socket=x11
# Wayland socket is included so the XDG Desktop Portal (ScreenCast) is
# reachable for getDisplayMedia even in an Xwayland session.
- --socket=wayland
- --socket=pulseaudio
- --share=network
# DRI device access required for hardware-accelerated rendering and screen capture
- --device=dri
# Allow the app to talk to the XDG Desktop Portal (ScreenCast interface)
# Note: org.freedesktop.portal.Desktop is the D-Bus service name; the
# ScreenCast interface is exposed under it.
- --talk-name=org.freedesktop.portal.Desktop
- --env=ELECTRON_TRASH=gio
# Proper cursor scaling on HiDPI displays under Wayland
- --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons
Comment thread
coderabbitai[bot] marked this conversation as resolved.

build-options:
append-path: /usr/lib/sdk/node20/bin
env:
NPM_CONFIG_LOGLEVEL: info

modules:
- name: rein
buildsystem: simple
subdir: rein
build-options:
env:
XDG_CACHE_HOME: /run/build/rein/flatpak-node/cache
npm_config_cache: /run/build/rein/flatpak-node/npm-cache
npm_config_nodedir: /usr/lib/sdk/node20
npm_config_offline: 'true'
build-commands:
# Install npm dependencies (offline, from pre-downloaded cache)
- npm install --offline

# package.json already contains "linux": { "target": "dir" } so no patch
# is needed. The jq line is intentionally omitted: reading and writing the
# same file in a single shell redirect would corrupt it.

# Build the Vite/TanStack frontend
- npm run build

# Package with electron-builder for Linux (unpacked dir, no network)
- |
. ../flatpak-node/electron-builder-arch-args.sh
npm run dist -- $ELECTRON_BUILDER_ARCH_ARGS --linux --dir

# Bundle the unpacked app into /app/main
- cp -a dist/linux-unpacked /app/main

# Bundle Node.js runtime so electron/main.cjs can spawn it at runtime
- install -Dm755 /usr/lib/sdk/node20/bin/node /app/bin/node

# Install the app launcher wrapper
- install -Dm755 -t /app/bin/ ../run.sh

# Install AppStream metadata
- install -Dm644 flatpak/com.rein.app.metainfo.xml \
/app/share/metainfo/com.rein.app.metainfo.xml

# Install desktop file and icon
- install -Dm644 flatpak/com.rein.app.desktop \
/app/share/applications/com.rein.app.desktop
- install -Dm644 public/app_icon/Icon.svg \
/app/share/icons/hicolor/scalable/apps/com.rein.app.svg

sources:
- type: dir
path: ..
dest: rein
skip:
- .git
- node_modules
- dist
- .output

# Generated npm sources (produced by flatpak-node-generator)
# Run: flatpak-node-generator npm package-lock.json -o flatpak/generated-sources.json
- generated-sources.json

# Wrapper script to launch the Electron app via zypak
- type: script
dest-filename: run.sh
commands:
- zypak-wrapper.sh /app/main/rein "$@"
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true
},
"linux": {
"target": "dir",
"category": "Utility",
"executableName": "rein"
}
}
}