mirror of
https://github.com/imgfloat/client.git
synced 2026-02-04 19:49:26 +00:00
Migrate from personal repo
This commit is contained in:
12
.gitattributes
vendored
Normal file
12
.gitattributes
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
res/icon/macos.icns filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/banner.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/128x128.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/16x16.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/256x256.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/appicon.ico filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/32x32.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/48x48.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/512x512.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/64x64.png filter=lfs diff=lfs merge=lfs -text
|
||||||
47
.github/workflows/ci.yml
vendored
Normal file
47
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Electron CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['**']
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- run: npm ci
|
||||||
|
- name: Check version tag
|
||||||
|
id: version_guard
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
- name: Build (no publish)
|
||||||
|
if: github.ref != 'refs/heads/master'
|
||||||
|
run: npx electron-builder --publish=never
|
||||||
|
- name: Build & publish
|
||||||
|
if: github.ref == 'refs/heads/master' && steps.version_guard.outputs.exists == 'false'
|
||||||
|
run: npx electron-builder --publish=always
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Publish skipped
|
||||||
|
if: github.ref == 'refs/heads/master' && steps.version_guard.outputs.exists == 'true'
|
||||||
|
run: echo "Version already released — skipping publish"
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/target
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
*.iml
|
||||||
|
local/
|
||||||
|
*.log
|
||||||
14
.prettierrc
Normal file
14
.prettierrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-java"],
|
||||||
|
"printWidth":120,
|
||||||
|
"tabWidth":4,
|
||||||
|
"useTabs":false,
|
||||||
|
"semi":true,
|
||||||
|
"singleQuote":false,
|
||||||
|
"trailingComma":"all",
|
||||||
|
"bracketSpacing":true,
|
||||||
|
"arrowParens":"always",
|
||||||
|
"requirePragma":false,
|
||||||
|
"insertPragma":false,
|
||||||
|
"proseWrap":"always"
|
||||||
|
}
|
||||||
31
Makefile
Normal file
31
Makefile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
.ONESHELL:
|
||||||
|
.POSIX:
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := run
|
||||||
|
|
||||||
|
ELECTRON := $(shell \
|
||||||
|
if [ -f /etc/os-release ] && grep -q '^ID=nixos' /etc/os-release; then \
|
||||||
|
echo electron; \
|
||||||
|
else \
|
||||||
|
echo "npx electron"; \
|
||||||
|
fi)
|
||||||
|
|
||||||
|
node_modules: package-lock.json
|
||||||
|
npm install
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run:
|
||||||
|
$(ELECTRON) src/main.js
|
||||||
|
|
||||||
|
.PHONY: run-x
|
||||||
|
run-x:
|
||||||
|
./util/run-xorg $(ELECTRON)
|
||||||
|
|
||||||
|
.PHONY: run-wl
|
||||||
|
run-wl:
|
||||||
|
./util/run-wl $(ELECTRON)
|
||||||
|
|
||||||
|
.PHONY: fix
|
||||||
|
fix: node_modules
|
||||||
|
./node_modules/.bin/prettier --write src
|
||||||
|
|
||||||
4303
package-lock.json
generated
Normal file
4303
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
package.json
Normal file
68
package.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "imgfloat-client",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Electron wrapper for the Imgfloat overlay",
|
||||||
|
"main": "src/main.js",
|
||||||
|
"build": {
|
||||||
|
"appId": "dev.kruhlmann.imgfloat",
|
||||||
|
"productName": "Imgfloat",
|
||||||
|
"files": [
|
||||||
|
"src/main.js",
|
||||||
|
"res/**"
|
||||||
|
],
|
||||||
|
"publish": {
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "Kruhlmann",
|
||||||
|
"repo": "imgfloat-j"
|
||||||
|
},
|
||||||
|
"asar": false,
|
||||||
|
"directories": {
|
||||||
|
"output": "dist"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"AppImage"
|
||||||
|
],
|
||||||
|
"category": "Utility",
|
||||||
|
"icon": "res/icon/linux"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"nsis"
|
||||||
|
],
|
||||||
|
"icon": "res/icon/appicon.ico"
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": true,
|
||||||
|
"perMachine": true,
|
||||||
|
"allowElevation": true,
|
||||||
|
"allowToChangeInstallationDirectory": false,
|
||||||
|
"createDesktopShortcut": true,
|
||||||
|
"createStartMenuShortcut": true,
|
||||||
|
"shortcutName": "Imgfloat",
|
||||||
|
"installerIcon": "res/icon/appicon.ico",
|
||||||
|
"uninstallerIcon": "res/icon/appicon.ico"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"icon": "res/icon/macos.icns",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "dmg",
|
||||||
|
"arch": [
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"electron-updater": "^6.7.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^35.7.5",
|
||||||
|
"electron-builder": "^24.13.3",
|
||||||
|
"prettier": "^3.7.4",
|
||||||
|
"prettier-plugin-java": "^2.7.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
res/banner.png
LFS
Normal file
BIN
res/banner.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/appicon.ico
LFS
Normal file
BIN
res/icon/appicon.ico
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/128x128.png
LFS
Normal file
BIN
res/icon/linux/128x128.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/16x16.png
LFS
Normal file
BIN
res/icon/linux/16x16.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/256x256.png
LFS
Normal file
BIN
res/icon/linux/256x256.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/32x32.png
LFS
Normal file
BIN
res/icon/linux/32x32.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/48x48.png
LFS
Normal file
BIN
res/icon/linux/48x48.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/512x512.png
LFS
Normal file
BIN
res/icon/linux/512x512.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/linux/64x64.png
LFS
Normal file
BIN
res/icon/linux/64x64.png
LFS
Normal file
Binary file not shown.
BIN
res/icon/macos.icns
LFS
Normal file
BIN
res/icon/macos.icns
LFS
Normal file
Binary file not shown.
16
shell.nix
Normal file
16
shell.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.electron
|
||||||
|
pkgs.mesa
|
||||||
|
pkgs.nodejs
|
||||||
|
pkgs.openbox
|
||||||
|
pkgs.vulkan-loader
|
||||||
|
pkgs.wayland
|
||||||
|
pkgs.wayland-protocols
|
||||||
|
pkgs.weston
|
||||||
|
pkgs.xorg.xorgserver
|
||||||
|
pkgs.xwayland
|
||||||
|
];
|
||||||
|
}
|
||||||
112
src/main.js
Normal file
112
src/main.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const { app, BrowserWindow } = require("electron");
|
||||||
|
const { autoUpdater } = require("electron-updater");
|
||||||
|
|
||||||
|
const initialWindowWidthPx = 960;
|
||||||
|
const initialWindowHeightPx = 640;
|
||||||
|
|
||||||
|
let canvasSizeInterval;
|
||||||
|
function clearCanvasSizeInterval() {
|
||||||
|
if (canvasSizeInterval) {
|
||||||
|
clearInterval(canvasSizeInterval);
|
||||||
|
canvasSizeInterval = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoResizeWindow(win, lastSize) {
|
||||||
|
if (win.isDestroyed()) {
|
||||||
|
return lastSize;
|
||||||
|
}
|
||||||
|
const newSize = await win.webContents.executeJavaScript(`(() => {
|
||||||
|
const canvas = document.getElementById('broadcast-canvas');
|
||||||
|
if (!canvas) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
width: Math.round(rect.width),
|
||||||
|
height: Math.round(rect.height),
|
||||||
|
};
|
||||||
|
})();`);
|
||||||
|
|
||||||
|
if (!newSize?.width || !newSize?.height) {
|
||||||
|
return lastSize;
|
||||||
|
}
|
||||||
|
if (lastSize.width === newSize.width && lastSize.height === newSize.height) {
|
||||||
|
return lastSize;
|
||||||
|
}
|
||||||
|
console.info(
|
||||||
|
`Window size did not match canvas old: ${lastSize.width}x${lastSize.height} new: ${newSize.width}x${newSize.height}. Resizing.`,
|
||||||
|
);
|
||||||
|
win.setContentSize(newSize.width, newSize.height, false);
|
||||||
|
win.setResizable(false);
|
||||||
|
return newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostNavigationLoad(win, url, broadcastRect) {
|
||||||
|
url = url || win.webContents.getURL();
|
||||||
|
let pathname;
|
||||||
|
try {
|
||||||
|
pathname = new URL(url).pathname;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to parse URL: ${url}`, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isBroadcast = /\/view\/[^/]+\/broadcast\/?$/.test(pathname);
|
||||||
|
|
||||||
|
console.info(`Navigation to ${url} detected. Is broadcast: ${isBroadcast}`);
|
||||||
|
if (isBroadcast) {
|
||||||
|
clearCanvasSizeInterval();
|
||||||
|
console.info("Setting up auto-resize for broadcast window.");
|
||||||
|
canvasSizeInterval = setInterval(() => {
|
||||||
|
autoResizeWindow(win, broadcastRect).then((newSize) => {
|
||||||
|
broadcastRect = newSize;
|
||||||
|
});
|
||||||
|
}, 750);
|
||||||
|
autoResizeWindow(win, broadcastRect).then((newSize) => {
|
||||||
|
broadcastRect = newSize;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearCanvasSizeInterval();
|
||||||
|
win.setSize(initialWindowWidthPx, initialWindowHeightPx, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow(version) {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: initialWindowWidthPx,
|
||||||
|
height: initialWindowHeightPx,
|
||||||
|
transparent: true,
|
||||||
|
frame: true,
|
||||||
|
backgroundColor: "#00000000",
|
||||||
|
alwaysOnTop: false,
|
||||||
|
icon: path.join(__dirname, "../resources/assets/icon/appicon.ico"),
|
||||||
|
webPreferences: { backgroundThrottling: false },
|
||||||
|
});
|
||||||
|
win.setMenu(null);
|
||||||
|
win.setTitle(`Imgfloat Client v${version}`);
|
||||||
|
|
||||||
|
return win;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
if (process.env.CI) {
|
||||||
|
process.on("uncaughtException", (err) => {
|
||||||
|
console.error("Uncaught exception:", err);
|
||||||
|
app.exit(1);
|
||||||
|
});
|
||||||
|
setTimeout(() => app.quit(), 3000);
|
||||||
|
}
|
||||||
|
autoUpdater.checkForUpdatesAndNotify();
|
||||||
|
|
||||||
|
let broadcastRect = { width: 0, height: 0 };
|
||||||
|
const version = app.getVersion();
|
||||||
|
const win = createWindow(version);
|
||||||
|
win.loadURL(process.env["IMGFLOAT_CHANNELS_URL"] || "https://imgfloat.kruhlmann.dev/channels");
|
||||||
|
win.webContents.on("did-finish-load", () => onPostNavigationLoad(win, undefined, broadcastRect));
|
||||||
|
win.webContents.on("did-navigate", (_, url) => onPostNavigationLoad(win, url, broadcastRect));
|
||||||
|
win.webContents.on("did-navigate-in-page", (_, url) => onPostNavigationLoad(win, url, broadcastRect));
|
||||||
|
win.on("page-title-updated", (e) => e.preventDefault());
|
||||||
|
win.on("closed", clearCanvasSizeInterval);
|
||||||
|
});
|
||||||
36
util/run-wl
Executable file
36
util/run-wl
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ELECTRON="$1"
|
||||||
|
APP_ENTRY="src/main.js"
|
||||||
|
SIZE="1280x800"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kill "$ELECTRON_PID" "$WESTON_PID" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
weston \
|
||||||
|
--backend=x11-backend.so \
|
||||||
|
--width="${SIZE%x*}" \
|
||||||
|
--height="${SIZE#*x}" \
|
||||||
|
--xwayland &
|
||||||
|
WESTON_PID=$!
|
||||||
|
|
||||||
|
# Wait for Weston to create its socket
|
||||||
|
for i in $(seq 1 50); do
|
||||||
|
SOCKET=$(ls "$XDG_RUNTIME_DIR"/wayland-* 2>/dev/null | head -n1 || true)
|
||||||
|
[ -n "$SOCKET" ] && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "${SOCKET:-}" ] || exit 1
|
||||||
|
|
||||||
|
export WAYLAND_DISPLAY="$(basename "$SOCKET")"
|
||||||
|
export ELECTRON_OZONE_PLATFORM_HINT=wayland
|
||||||
|
export NIXOS_OZONE_WL=1
|
||||||
|
|
||||||
|
"$ELECTRON" "$APP_ENTRY" &
|
||||||
|
ELECTRON_PID=$!
|
||||||
|
|
||||||
|
wait
|
||||||
58
util/run-xorg
Executable file
58
util/run-xorg
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ELECTRON="$1"
|
||||||
|
APP_ENTRY="src/main.js"
|
||||||
|
SCREEN="1280x800"
|
||||||
|
|
||||||
|
for d in 99 98 97 96 95; do
|
||||||
|
if ! xdpyinfo -display ":$d" >/dev/null 2>&1; then
|
||||||
|
DISP="$d"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "${DISP:-}" ] || {
|
||||||
|
echo "No free DISPLAY found" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Using DISPLAY=:$DISP"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kill "$ELECTRON_PID" "$OPENBOX_PID" "$XEPHYR_PID" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
Xephyr ":$DISP" -screen "$SCREEN" -resizeable -ac &
|
||||||
|
XEPHYR_PID=$!
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
DISPLAY=":$DISP" openbox &
|
||||||
|
OPENBOX_PID=$!
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
DISPLAY=":$DISP" "${ELECTRON}" "$APP_ENTRY" &
|
||||||
|
ELECTRON_PID=$!
|
||||||
|
DISPLAY=":$DISP" xsetroot -solid "#009999"
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
if ! kill -0 "$ELECTRON_PID" 2>/dev/null; then
|
||||||
|
echo "Electron exited — killing Xephyr"
|
||||||
|
kill "$XEPHYR_PID" 2>/dev/null || true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! kill -0 "$XEPHYR_PID" 2>/dev/null; then
|
||||||
|
echo "Xephyr exited — killing Electron"
|
||||||
|
kill "$ELECTRON_PID" 2>/dev/null || true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
|
||||||
|
wait "$ELECTRON_PID" 2>/dev/null || true
|
||||||
|
wait "$XEPHYR_PID" 2>/dev/null || true
|
||||||
Reference in New Issue
Block a user