Files
miniSCP/src/shared/rpc.ts
T
jgamboaandClaude 6bb4c3cd7d Merge main into feat/7css-skin + give the HTML context menu an Aero skin
main retired electrobun's native context menu (a documented no-op on Linux) in
favour of an HTML one on every platform. Merging that here needs the menu to
look right under the Win7 skin as well.

Conflicts, all resolved by keeping both sides:
- src/bun/index.ts — createAppRPC keeps the branch's mainWindow getter, drops
  main's removed contextMenu dep.
- ui/src/app/app.ts — imports both TitleBar and ContextMenu.
- ui/src/app/platform.ts — kept the branch's version (its IS_MAC_CHROME accounts
  for the frameless win7 chrome, and MOD_LABEL correctly keys on the real
  platform rather than the chrome), plus exporting IS_MAC and adding IS_LINUX
  for the menu's per-OS shell wording.

Aero menu (branch-only): 7.css has no menu component, so it is hand-authored
across both layers — the row <button>s shed their push-button slab in `skin`
(revert-layer only reaches `utilities` from there), and the square-cornered
slab, hairline border and gradient selection band are stated in `skinfix`.
Built strictly from existing --w7-* vars, never literal hex, so the
hand-written dark Win7 palette recolours it for free. Hover and the keyboard
cursor share one band (the component marks the cursor with `bg-accent`, which
is overridden alongside :hover) as a real Win7 menu does.

Also answers a question the merge raised: frameless windows DO work on Linux.
The GTK wrapper honours titleBarStyle "hidden" — it calls
gtk_window_set_decorated(false) and logs "GTK: Created window without
decorations (custom titlebar)". Verified on GNOME/Wayland and documented in
architecture.md.

Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (492.52 kB
initial, still under the 500 kB budget), app launches with the skin active,
frameless, no console errors and no GBM failures.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-15 14:59:02 +02:00

223 lines
8.5 KiB
TypeScript

import type {
AppSettings,
ConnectionConfig,
ConnectionsFile,
EntryType,
FileEntry,
Result,
SavedConnection,
SshConfigHost,
Stack,
TransferSnapshot,
} from "./types";
/** Everything an editor window needs to know about the file it hosts. */
export interface EditorOpenContext {
sessionId: string;
path: string;
name: string;
sessionTitle: string;
}
// Structurally equivalent to electrobun's ElectrobunRPCSchema, written in
// resolved form so this file stays importable from the Angular bundle without
// pulling in bun-only type modules.
//
// bun.requests = calls the webview makes, handled in src/bun/rpc-handlers.ts
// webview.messages = push events from bun, handled in ui rpc.service.ts
//
// ONE schema serves both window kinds (main + editor): electrobun allows
// partial handler sets, and messages nobody listens to are silently dropped,
// so each side only implements/hears what applies to it.
export type AppRPC = {
bun: {
requests: {
// sessions
connect: {
params: { config: ConnectionConfig };
response: Result<{ sessionId: string; homeDir: string; fsKey: string }>;
};
disconnect: { params: { sessionId: string }; response: Result };
// filesystem (uniform across "local" and sftp sessions)
listDir: {
params: { sessionId: string; path: string | null }; // null -> home dir
response: Result<{ path: string; entries: FileEntry[] }>;
};
// Local dirs to fs.watch for auto-refresh (the active tab's local panels);
// a change pushes pathRefresh. Remote panels are polled UI-side instead.
setWatchedDirs: { params: { dirs: string[] }; response: Result };
mkdir: { params: { sessionId: string; path: string }; response: Result };
createFile: { params: { sessionId: string; path: string }; response: Result };
rename: {
params: { sessionId: string; oldPath: string; newPath: string };
response: Result;
};
deleteEntries: {
params: { sessionId: string; entries: { path: string; type: EntryType }[] };
response: Result;
};
chmod: {
params: { sessionId: string; paths: string[]; mode: number };
response: Result;
};
// transfers
enqueueTransfers: {
params: {
srcSessionId: string;
items: { path: string; type: EntryType }[];
dstSessionId: string;
dstDir: string;
};
response: Result<{ jobIds: string[] }>;
};
cancelTransfer: { params: { jobId: string }; response: Result };
dismissTransfer: { params: { jobId: string }; response: Result };
// connection store
getConnections: {
params: Record<string, never>;
response: Result<{ store: ConnectionsFile }>;
};
upsertConnection: {
params: { connection: SavedConnection };
response: Result<{ id: string }>;
};
deleteConnection: { params: { id: string }; response: Result };
upsertStack: { params: { stack: Stack }; response: Result<{ id: string }> };
deleteStack: { params: { id: string }; response: Result };
/** Native file picker + parse of a WinSCP.ini export into the store. */
importWinScp: {
params: Record<string, never>;
response: Result<{ connections: number; stacks: number; skipped: number }>;
};
getSshConfigHosts: {
params: Record<string, never>;
response: Result<{ hosts: SshConfigHost[] }>;
};
// settings
getSettings: {
params: Record<string, never>;
response: Result<{ settings: AppSettings }>;
};
saveSettings: { params: { settings: AppSettings }; response: Result };
// file editing
readFile: {
params: { sessionId: string; path: string };
response: Result<{ content: string }>;
};
writeFile: {
params: { sessionId: string; path: string; content: string };
response: Result;
};
editExternal: {
params: { sessionId: string; path: string };
response: Result<{ watching: boolean }>;
};
pickEditorApp: {
params: Record<string, never>;
response: Result<{ path: string | null }>;
};
pickKeyFile: {
params: Record<string, never>;
response: Result<{ path: string | null }>;
};
getEditLeftovers: {
params: Record<string, never>;
response: Result<{ names: string[] }>;
};
clearEditLeftovers: { params: Record<string, never>; response: Result };
// local shell integration
/**
* Shell actions the context menu triggers on a LOCAL path. The menu
* itself is HTML (ui/src/app/context-menu.ts) — electrobun's native
* context menu is a no-op on Linux — but these three still need the
* bun side's Utils.
*/
shellItemAction: {
params: { action: "reveal" | "open" | "copyPath"; path: string };
response: Result;
};
moveToTrash: { params: { path: string }; response: Result };
/** Native About box (name, Build n, commit, engine versions). */
showAbout: { params: Record<string, never>; response: Result };
/** Windows drive roots for the local panel ("C:\", …); empty elsewhere. */
listDrives: { params: Record<string, never>; response: Result<{ drives: string[] }> };
// editor windows
/** Main window only: open (or focus) a native editor window. */
openEditor: { params: EditorOpenContext; response: Result };
/** Editor windows only — handled by that window's own rpc closure. */
editorSave: { params: { content: string }; response: Result };
editorClose: { params: Record<string, never>; response: Result };
// connection-manager window
/** Main window: open (or focus) the single connection-manager window. */
openConnectionManager: { params: Record<string, never>; response: Result };
/** Manager window only: connect/open-stack run in the MAIN window. */
managerConnect: { params: { id: string }; response: Result };
managerOpenStack: { params: { id: string }; response: Result };
managerClose: { params: Record<string, never>; response: Result };
// custom window chrome (win7 skin — the window is frameless, so the
// webview's title bar owns the controls). Served by every window kind.
windowMinimize: { params: Record<string, never>; response: Result };
/** Maximize/restore in one call; responds with the resulting state. */
windowToggleMaximize: {
params: Record<string, never>;
response: Result<{ maximized: boolean }>;
};
windowClose: { params: Record<string, never>; response: Result };
/** First paint: the title bar needs the state before any resize fires. */
windowIsMaximized: {
params: Record<string, never>;
response: Result<{ maximized: boolean }>;
};
// auto-updater (electrobun Updater)
/** Main window: re-run the update check on demand (menu item). */
checkForUpdate: {
params: Record<string, never>;
response: Result<{ updateAvailable: boolean; version: string }>;
};
/** Download the pending update (patch-chain, else full). Progress arrives via updateStatus. */
downloadUpdate: { params: Record<string, never>; response: Result };
/** Swap in the downloaded update and relaunch — quits the app, so may not resolve. */
applyUpdate: { params: Record<string, never>; response: Result };
};
messages: {
/** Editor → bun draft mirror; safety net for the non-cancellable native close. */
editorState: { dirty: boolean; content: string };
};
};
webview: {
requests: Record<never, never>;
messages: {
transferUpdate: TransferSnapshot;
sessionClosed: { sessionId: string; reason: string };
menuAction: { action: string };
/** bun → editor windows when the main window saves settings. */
settingsChanged: { settings: AppSettings };
/** bun → main after an editor save, so matching panels refresh. */
pathRefresh: { sessionId: string; dir: string };
/** bun → one editor: run the in-window close flow (dirty prompt). */
editorCloseRequest: Record<string, never>;
/** bun → main: the modal connection manager opened/closed. */
managerState: { open: boolean };
/**
* bun → any window: maximize state changed, so the custom title bar can
* swap its maximize/restore glyph. electrobun has no maximize event —
* this is derived from the window's `resize` event (see windows.ts
* trackMaximizeState), which also covers OS-initiated Aero Snap.
*/
windowState: { maximized: boolean };
/** bun → main: the manager window changed the connection store. */
connectionsChanged: Record<string, never>;
/**
* bun → main: auto-updater lifecycle, drives the tab-bar update pill.
* "ready" is not a phase — the UI derives it from downloadUpdate resolving ok.
*/
updateStatus: {
phase: "available" | "downloading" | "error";
version?: string;
progress?: number;
error?: string;
};
};
};
};