Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dist/
build/*
!build/icon.icns
!build/icon.png
!build/icon.ico
!build/icon-1024.png
!build/entitlements.mac.plist

Expand Down
Binary file added build/icon.ico
Binary file not shown.
38 changes: 28 additions & 10 deletions electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ function createWindow() {
win = new BrowserWindow({
width: b.width, height: b.height, x: b.x, y: b.y,
minWidth: 920, minHeight: 600,
titleBarStyle: 'hiddenInset',
titleBarStyle: process.platform === 'win32' ? 'default' : 'hiddenInset',
backgroundColor: '#0b0c0a',
vibrancy: 'sidebar',
visualEffectState: 'active',
...(process.platform === 'darwin' ? { vibrancy: 'sidebar', visualEffectState: 'active' } : {}),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
Expand Down Expand Up @@ -92,6 +91,11 @@ app.whenReady().then(() => {

// ---------- 截图直通车:监听系统截屏落盘,新截图推给渲染层浮出直通卡 ----------
function screenshotDir() {
if (process.platform === 'win32') {
// Windows: 用户截图目录,通常在 Pictures\Screenshots,不存在则回退桌面
const pics = path.join(os.homedir(), 'Pictures', 'Screenshots');
return fs.existsSync(pics) ? pics : path.join(os.homedir(), 'Desktop');
}
try {
const out = require('child_process').execSync('defaults read com.apple.screencapture location 2>/dev/null', { encoding: 'utf8' }).trim();
if (out) return out.startsWith('~') ? path.join(os.homedir(), out.slice(1)) : out;
Expand Down Expand Up @@ -289,8 +293,13 @@ ipcMain.handle('clip:image', (e, { path: p }) => {
});
ipcMain.handle('clip:file', (e, { path: p }) => new Promise((resolve) => {
const { execFile } = require('child_process');
// argv 传路径,避免拼进 AppleScript 字面量被注入
execFile('osascript', ['-e', 'on run argv', '-e', 'set the clipboard to (POSIX file (item 1 of argv))', '-e', 'end run', p], (err) => resolve({ ok: !err, error: err && err.message }));
if (process.platform === 'win32') {
// Windows: PowerShell Set-Clipboard 复制文件路径到剪贴板(资源管理器可粘贴)
execFile('powershell', ['-Command', `Set-Clipboard -Path "${p}"`], (err) => resolve({ ok: !err, error: err && err.message }));
} else {
// macOS: osascript 把 POSIX file 写入剪贴板
execFile('osascript', ['-e', 'on run argv', '-e', 'set the clipboard to (POSIX file (item 1 of argv))', '-e', 'end run', p], (err) => resolve({ ok: !err, error: err && err.message }));
}
}));

// 拖拽落盘:file-promise 类拖入(截图浮窗等)没有真实路径,把字节写进临时目录换路径
Expand Down Expand Up @@ -330,11 +339,20 @@ ipcMain.handle('pty:cwd', (e, { id }) => new Promise((resolve) => {
const p = terminals.get(id);
if (!p || !p.pid) return resolve({ ok: false });
const { exec } = require('child_process');
exec(`lsof -a -p ${p.pid} -d cwd -Fn`, { env: { ...process.env, LC_ALL: 'en_US.UTF-8' } }, (err, stdout) => {
if (err) return resolve({ ok: false });
const line = stdout.split('\n').find((l) => l.startsWith('n'));
resolve(line ? { ok: true, cwd: decodeLsofPath(line.slice(1)) } : { ok: false });
});
if (process.platform === 'win32') {
// Windows: PowerShell 通过 Get-CimInstance 查进程可执行路径,再取其目录
exec(`powershell -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${p.pid}').ExecutablePath"`, (err, stdout) => {
if (err || !stdout.trim()) return resolve({ ok: false });
const exePath = stdout.trim();
resolve({ ok: true, cwd: path.dirname(exePath) });
});
} else {
exec(`lsof -a -p ${p.pid} -d cwd -Fn`, { env: { ...process.env, LC_ALL: 'en_US.UTF-8' } }, (err, stdout) => {
if (err) return resolve({ ok: false });
const line = stdout.split('\n').find((l) => l.startsWith('n'));
resolve(line ? { ok: true, cwd: decodeLsofPath(line.slice(1)) } : { ok: false });
});
}
}));

// 取终端前台进程名(node-pty 维护):判断当前是裸 shell 还是正跑着 claude/codex 等程序
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 22 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
"rebuild": "electron-rebuild -f -w node-pty",
"build:milkdown": "esbuild src-vendor/milkdown-entry.js --bundle --format=iife --minify --outfile=public/vendor/milkdown/milkdown.js --loader:.svg=dataurl --loader:.woff=file --loader:.woff2=file --loader:.ttf=file --loader:.eot=file --asset-names=[name]",
"build:hljs": "esbuild src-vendor/hljs-entry.js --bundle --format=iife --minify --outfile=public/vendor/hljs/highlight.js && cp node_modules/marked/marked.min.js public/vendor/marked/marked.min.js && cp node_modules/highlight.js/styles/github-dark.min.css node_modules/highlight.js/styles/github.min.css public/vendor/hljs/styles/",
"check:vendor-patch": "grep -q '20===e.keyCode||229===e.keyCode' public/vendor/xterm/xterm.js",
"check:vendor-patch": "node -e \"const fs=require('fs'); const c=fs.readFileSync('public/vendor/xterm/xterm.js','utf8'); if(!c.includes('20===e.keyCode||229===e.keyCode')){console.error('xterm patch missing');process.exit(1)}\"",
"predist": "npm run check:vendor-patch",
"dist": "electron-builder --mac"
"dist": "electron-builder --mac",
"dist:win": "electron-builder --win --x64"
},
"build": {
"appId": "com.huashu.fanbox",
Expand Down Expand Up @@ -46,6 +47,25 @@
"dmg": {
"title": "FanBox",
"writeUpdateInfo": false
},
"win": {
"icon": "build/icon.ico",
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/icon.ico",
"uninstallerIcon": "build/icon.ico",
"installerHeaderIcon": "build/icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "翻箱 FanBox"
}
},
"type": "commonjs",
Expand Down