diff --git a/src/core/patcher.ts b/src/core/patcher.ts
index a3f961f..e2607f1 100644
--- a/src/core/patcher.ts
+++ b/src/core/patcher.ts
@@ -16,6 +16,8 @@ export function applyPatches(mainScript: HTMLScriptElement) {
console.group("[ ropeswing-patcher ]");
for (let extension of extensions) {
+ if (!extension.patches) continue;
+
for (let patch of extension.patches) {
if (patch.executable) continue;
// TODO: Using `as string` is bad, but TypeScript wasn't having it with my replace typings, and this works
@@ -26,3 +28,16 @@ export function applyPatches(mainScript: HTMLScriptElement) {
console.groupEnd();
}
+
+export function executePostload() {
+ console.group("[ ropeswing-postload ]");
+
+ for (let extension of extensions) {
+ if (!extension.onLoad) continue;
+
+ extension.onLoad();
+ console.log(`executed onLoad of ${extension.manifest.name}`);
+ }
+
+ console.groupEnd();
+}
diff --git a/src/ext/housekeeper.ts b/src/ext/housekeeper.ts
new file mode 100644
index 0000000..ccfe6bc
--- /dev/null
+++ b/src/ext/housekeeper.ts
@@ -0,0 +1,45 @@
+export default {
+ patches: [
+ {
+ find: /Windows 96 main
/,
+ replace: (match) => `${match}ropeswing ${ROPESWING_COMMIT}
`,
+ },
+ {
+ find: /Other trademarks and logos are property of their respective owners\./,
+ replace: (match) => `${match}\n\nropeswing ${ROPESWING_COMMIT}, an uwunet project.`,
+ executable: "C:/system/local/bin/about-ui",
+ }
+ ],
+ onLoad() {
+ if (localStorage["ropeswing-welcome"] !== "true") {
+ localStorage["ropeswing-welcome"] = "true";
+ w96.WApplication.execAsync(this.dialog(w96.WApplication), []);
+ }
+ },
+ manifest: {
+ name: "housekeeper",
+ description: "basic ropeswing info, e.g. welcome dialog, version listing",
+ authors: ["redstonekasi", "Beef"],
+ },
+ core: true,
+ dialog: (WApplication: typeof w96.WApplication) => new (class WelcomeDialog extends WApplication {
+ async main(argv: string[]) {
+ super.main(argv);
+ // @ts-expect-error
+ const popup = this.createWindow({
+ title: "ropeswing",
+ initialHeight: 120,
+ initialWidth: 260,
+ resizable: false,
+ bodyClass: "dlg-run-box",
+ controlBoxStyle: "WS_CBX_CLOSE",
+ });
+ const body = popup.getBodyContainer();
+ body.innerHTML = `
ropeswing has been installed!
Check your system settings in order to configure it.
`;
+ body.querySelector("button")?.addEventListener("click", () => popup.close(false));
+
+ popup.setPosition(window.innerWidth / 2 - 130, window.innerHeight / 2 - 60);
+ popup.show();
+ }
+ })(),
+} as Extension;
diff --git a/src/ext/royalmail/index.ts b/src/ext/royalmail/index.ts
index 6046613..2173e27 100644
--- a/src/ext/royalmail/index.ts
+++ b/src/ext/royalmail/index.ts
@@ -25,14 +25,14 @@ export default {
authors: ["Beef"],
},
core: true,
- app: (WApplication: any, components: Record) => class RoyalmailApplet extends WApplication {
+ app: (WApplication: typeof w96.WApplication, components: Record) => class RoyalmailApplet extends WApplication {
constructor() {
super();
}
// TODO: Check that this whole thing disposes properly.
// By all right, it should, however I am unsure if the weird stuff I do with the pages will mess anything up.
- public main = (argv: string[]) => useRoot((dispose) => {
+ public main = async (argv: string[]) => useRoot((dispose) => {
// HACK: Persist the original onterminated behaviour because we are an applet, but also dispose our reactive root
before("onterminated", this, dispose, true);
if ((super.main(argv), document.querySelector(".royalmail-applet"))) return;
diff --git a/src/ext/treebranch.ts b/src/ext/treebranch.ts
index dce36c1..8583e57 100644
--- a/src/ext/treebranch.ts
+++ b/src/ext/treebranch.ts
@@ -16,10 +16,12 @@ export default {
async getFileContent(path: string) {
let content = await w96.FS.readstr(path);
- for (const ext of extensions) {
- for (let patch of ext.patches) {
+ for (const extension of extensions) {
+ if (!extension.patches) continue;
+
+ for (let patch of extension.patches) {
if (patch.executable !== path) continue;
- content = content.replace(patch.find, contextify(patch.replace, ext.manifest.name) as string);
+ content = content.replace(patch.find, contextify(patch.replace, extension.manifest.name) as string);
}
}
diff --git a/src/ext/version.ts b/src/ext/version.ts
deleted file mode 100644
index 9a232b5..0000000
--- a/src/ext/version.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export default {
- patches: [
- {
- find: /Windows 96 main
/,
- replace: (match) => `${match}ropeswing ${ROPESWING_COMMIT}
`,
- },
- {
- find: /Other trademarks and logos are property of their respective owners\./,
- replace: (match) => `${match}\n\nropeswing ${ROPESWING_COMMIT}, an uwunet project.`,
- executable: "C:/system/local/bin/about-ui",
- }
- ],
- manifest: {
- name: "version",
- description: "show ropeswing version info on desktop",
- authors: ["redstonekasi"],
- },
- core: true,
-} as Extension;
diff --git a/src/index.ts b/src/index.ts
index 2d3858b..ffb3f15 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,6 @@
import { instead } from "spitroast";
import { writeLine } from "@lib/console";
-import { applyPatches } from "@core/patcher";
+import { applyPatches, executePostload } from "@core/patcher";
import api from "@core/api";
// W96 like to do this, let's follow the convention
@@ -23,35 +23,8 @@ const unpatch = instead("appendChild", document.head, (args, orig) => {
writeLine("booting original!");
orig(...args);
- if (localStorage["ropeswing-welcome"] !== "true") {
- localStorage["ropeswing-welcome"] = "true";
- w96.evt.sys.on("init-complete", () =>
- w96.WApplication.execAsync(
- new (class extends w96.WApplication {
- main(argv) {
- super.main(argv);
- const popup = this.createWindow({
- title: "ropeswing",
- initialHeight: 120,
- initialWidth: 260,
- resizable: false,
- bodyClass: "dlg-run-box",
- controlBoxStyle: "WS_CBX_CLOSE",
- });
- const body = popup.getBodyContainer();
- body.innerHTML = `ropeswing has been installed!
Check your system settings in order to configure it.
`;
- body.querySelector("button").addEventListener("click", () => {
- popup.close();
- });
- popup.setPosition(window.innerWidth / 2 - 130, window.innerHeight / 2 - 60);
- popup.show();
- }
- })(),
- null,
- null
- )
- );
- }
+ writeLine("assigning postload init event...");
+ w96.evt.sys.on("init-complete", executePostload);
});
console.log("kernel done!");
diff --git a/src/types/core.d.ts b/src/types/core.d.ts
index 985239f..c1a02a1 100644
--- a/src/types/core.d.ts
+++ b/src/types/core.d.ts
@@ -10,7 +10,11 @@ interface Patch {
}
interface Extension {
- patches: Patch[];
+ patches?: Patch[];
+ /** Runs *after* w96 has initialised */
+ onLoad?: () => void;
+ /** Currently unused. */
+ onUnload?: () => void;
manifest: {
name: string;
description: string;
diff --git a/src/types/w96.d.ts b/src/types/w96.d.ts
index 0cf79fa..db421fe 100644
--- a/src/types/w96.d.ts
+++ b/src/types/w96.d.ts
@@ -1,5 +1,135 @@
+// adapted from https://git.sys36.net/windows-96/utilities/api-typings/-/blob/master/win96.d.ts
+
+interface EmitterEvent {
+ name: string;
+ callback: Function;
+ type: "recurring" | "single";
+}
+
+interface EventEmitter {
+ eventQueue: EmitterEvent[];
+ on(evtName: string, callback: Function): void;
+ once(evtName: string, callback: Function): void;
+ emit(evtName: string, ...args: any): void;
+}
+
+interface WindowParams {
+ initialX?: number;
+ initialY?: number;
+ minHeight?: number;
+ minWidth?: number;
+ initialHeight?: number;
+ initialWidth?: number;
+ title?: string;
+ resizable?: boolean;
+ draggable?: boolean;
+ taskbar?: boolean;
+ icon?: string;
+ center?: boolean;
+ body?: string;
+ bodyClass?: string;
+ windowClass?: string;
+ controlBoxStyle?: "WS_CBX_MINMAXCLOSE" | "WS_CBX_CLOSE" | "WS_CBX_MINCLOSE" | "WS_CBX_NONE";
+ mobResize?: boolean;
+ iframeFix?: boolean;
+ animations?: {
+ windowOpen: string;
+ windowClose: string;
+ };
+ wndContainer?: HTMLDivElement;
+}
+
+declare class StandardWindow {
+ readonly id: string;
+ readonly useIcon: boolean;
+ readonly wndObject: HTMLDivElement;
+ readonly animations: {};
+ readonly params: WindowParams;
+ readonly isRegistered: boolean;
+ readonly appbarRegistered: boolean;
+ readonly shown: boolean;
+ readonly _wmStylingAllowed: boolean;
+ readonly maximized: boolean;
+ readonly minimized: boolean;
+ readonly uiUpdating: boolean;
+ readonly windowIcon: string;
+ readonly title: string;
+ readonly maximizeInfo: {
+ x: string;
+ y: string;
+ h: string;
+ w: string;
+ };
+
+ onclose: (e: { canceled: boolean }) => void;
+ onload: () => void;
+ ondarkenelements: () => void;
+ onlightenelements: () => void;
+ _ext: {
+ windowSnap: {
+ snapped: boolean;
+ originalSize: null;
+ };
+ };
+ showTitlebarMenu(e: MouseEvent): void;
+ registerWindow(): void;
+ registerAppBar(): void;
+ setTitle(text: string): void;
+ setHtml(text: string): void;
+ randomizePosition(): void;
+ show(): void;
+ center(current: boolean): void;
+ activate(): void;
+ toggleMinimize(): void;
+ toggleMaximize(): void;
+ close(ignoreEvents: boolean): void;
+ darkenElements(): void;
+ lightenElements(): void;
+ setWindowIcon(icon_url: string): HTMLDivElement;
+ setControlBoxStyle(cbstyle: "WS_CBX_CLOSE" | "WS_CBX_MINCLOSE" | "WS_CBX_MINMAXCLOSE" | "WS_CBX_NONE"): void;
+ setSize(w: number, h: number, ignoreThemeOffsets?: boolean): void;
+ setPosition(x: number, y: number): void;
+ getBounds(): {
+ x: number;
+ y: number;
+ height: number;
+ width: number;
+ };
+ getComputedBounds(): DOMRect;
+ getBodyContainer(): HTMLDivElement;
+ constructor(params: WindowParams);
+}
+
+declare class WApplication {
+ readonly appId: number;
+ readonly appWindow: StandardWindow;
+ readonly windows: StandardWindow[];
+ onterminated: (result: any) => void;
+ _running: boolean;
+ _terminating: boolean;
+ _appResult: any;
+ setAppResult(v: any): void;
+ terminate(): void;
+ createWindow(params: WindowParams, isAppWindow: boolean): StandardWindow;
+ main(argv: string[]): Promise;
+ ontermination(): void;
+}
+
interface W96API {
FS: {
readstr(path: string): Promise;
};
+ evt: {
+ fs: EventEmitter;
+ sys: EventEmitter;
+ wm: EventEmitter;
+ ui: EventEmitter;
+ };
+ WApplication: {
+ new(): WApplication;
+ prototype: WApplication;
+
+ kill(app_id: number, force: boolean): void;
+ execAsync(instance: WApplication, args: string[]): Promise;
+ };
}