From 86674b6090cc43a4535536e87fcba28a5666e5a6 Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Fri, 24 Aug 2018 20:55:03 -0700 Subject: [PATCH] :wrench: Move to ES6 modules --- .gitignore | 2 +- package-lock.json | 38 ++++++++- package.json | 6 +- src/.github/issue_template.md | 3 - src/es6.js | 26 ++++++ src/index.js | 7 +- src/preload.js | 38 +-------- src/renderer/app-state.js | 7 ++ src/renderer/buttons.js | 41 +++++++++ src/renderer/index.html | 5 +- src/renderer/listeners.js | 41 +++++++++ src/renderer/renderer.js | 156 ++++++++-------------------------- src/state.js | 58 ++++++++++++- 13 files changed, 260 insertions(+), 168 deletions(-) delete mode 100644 src/.github/issue_template.md create mode 100644 src/es6.js create mode 100644 src/renderer/app-state.js create mode 100644 src/renderer/buttons.js create mode 100644 src/renderer/listeners.js diff --git a/.gitignore b/.gitignore index 7f3f2a6..3ea165f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules out -src/renderer/images +src/images .DS_Store diff --git a/package-lock.json b/package-lock.json index 57624b6..c8c52ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "windows95", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1531,6 +1531,11 @@ } } }, + "electron-is-dev": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz", + "integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4=" + }, "electron-osx-sign": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz", @@ -2545,6 +2550,14 @@ "assert-plus": "^1.0.0" } }, + "github-url-to-object": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/github-url-to-object/-/github-url-to-object-4.0.4.tgz", + "integrity": "sha512-1Ri1pR8XTfzLpbtPz5MlW/amGNdNReuExPsbF9rxLsBfO1GH9RtDBamhJikd0knMWq3RTTQDbTtw0GGvvEAJEA==", + "requires": { + "is-url": "^1.1.0" + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -2901,6 +2914,11 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -4650,6 +4668,24 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, + "update-electron-app": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/update-electron-app/-/update-electron-app-1.3.0.tgz", + "integrity": "sha512-OXfcmeenpjMyzXmadZ6NqxnrpPpiLji0sLUpXkexfX97XM8Gnk4iLovk4TlK4N8dzlETWdm9klgMmo9HpRbK7Q==", + "requires": { + "electron-is-dev": "^0.3.0", + "github-url-to-object": "^4.0.4", + "is-url": "^1.2.4", + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "username": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/username/-/username-3.0.0.tgz", diff --git a/package.json b/package.json index d44d5de..0bd57ec 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "package": "electron-forge package", "make": "electron-forge make", "publish": "electron-forge publish", - "lint": "standard \"src/**/*.js\"" + "lint": "standard \"src/**/*.mjs\"" }, "keywords": [], "author": "Felix Rieseberg, felix@felixrieseberg.com", @@ -19,6 +19,7 @@ }, "standard": { "globals": [ + "appState", "V86Starter", "windows95" ], @@ -29,7 +30,8 @@ "dependencies": { "electron-default-menu": "^1.0.1", "electron-squirrel-startup": "^1.0.0", - "fs-extra": "^7.0.0" + "fs-extra": "^7.0.0", + "update-electron-app": "^1.3.0" }, "devDependencies": { "@electron-forge/cli": "^6.0.0-beta.22", diff --git a/src/.github/issue_template.md b/src/.github/issue_template.md deleted file mode 100644 index 0196eb4..0000000 --- a/src/.github/issue_template.md +++ /dev/null @@ -1,3 +0,0 @@ -⚠️ Thank you for reporting an issue! - -Before we go any further, understand that I probably won't fullfil feature requests. I will however _gladly_ help you make a pull request if you're willing to play with Javascript! \ No newline at end of file diff --git a/src/es6.js b/src/es6.js new file mode 100644 index 0000000..9c44429 --- /dev/null +++ b/src/es6.js @@ -0,0 +1,26 @@ +const { protocol } = require('electron') +const fs = require('fs-extra') +const path = require('path') + +const ES6_PATH = path.join(__dirname, 'renderer') + +protocol.registerStandardSchemes(['es6']) + +async function setupProtocol () { + protocol.registerBufferProtocol('es6', async (req, cb) => { + console.log(req) + + try { + const filePath = path.join(ES6_PATH, req.url.replace('es6://', '' )) + const fileContent = await fs.readFile(filePath.replace('.js/', '.js')) + + cb({ mimeType: 'text/javascript', data: fileContent }) + } catch (error) { + console.warn(error) + } + }) +} + +module.exports = { + setupProtocol +} diff --git a/src/index.js b/src/index.js index ad0bb5b..40845e8 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ const path = require('path') const { clearCaches } = require('./cache') const { createMenu } = require('./menu') +const { setupProtocol } = require('./es6') if (require('electron-squirrel-startup')) { // eslint-disable-line global-require app.quit() @@ -16,13 +17,13 @@ const createWindow = () => { width: 1280, height: 800, useContentSize: true, - nodeIntegration: false, webPreferences: { + nodeIntegration: false, preload: path.join(__dirname, 'preload.js') } }) - mainWindow.loadURL(`file://${__dirname}/renderer/index.html?system=win98`) + mainWindow.loadURL(`file://${__dirname}/renderer/index.html`) mainWindow.on('closed', () => { mainWindow = null @@ -30,8 +31,10 @@ const createWindow = () => { } app.on('ready', async () => { + await setupProtocol() await createMenu() await clearCaches() + createWindow() }) diff --git a/src/preload.js b/src/preload.js index 2136803..6118b97 100644 --- a/src/preload.js +++ b/src/preload.js @@ -1,43 +1,13 @@ const { remote } = require('electron') const fs = require('fs-extra') -const { STATE_PATH, getState, resetState } = require('./state') +const { STATE_PATH, resetState, restoreState, saveState } = require('./state') window.windows95 = { STATE_PATH, - + restoreState, resetState, + saveState, - async saveState () { - return new Promise((resolve) => { - if (!window.emulator || !window.emulator.save_state) { - return resolve() - } - - window.emulator.save_state(async (error, newState) => { - if (error) { - console.log(error) - return - } - - await fs.outputFile(STATE_PATH, Buffer.from(newState)) - - console.log(`Saved state to ${STATE_PATH}`) - - resolve() - }) - }) - }, - - async restoreState () { - try { - window.emulator.restore_state(getState()) - } catch (error) { - console.log(`Could not read state file. Maybe none exists?`, error) - } - }, - - quit () { - remote.app.quit() - } + quit: () => remote.app.quit() } diff --git a/src/renderer/app-state.js b/src/renderer/app-state.js new file mode 100644 index 0000000..9047cc1 --- /dev/null +++ b/src/renderer/app-state.js @@ -0,0 +1,7 @@ +export function setupState () { + window.appState = { + cursorCaptured: false, + floppyFile: null, + bootFresh: false + } +} diff --git a/src/renderer/buttons.js b/src/renderer/buttons.js new file mode 100644 index 0000000..e9fa035 --- /dev/null +++ b/src/renderer/buttons.js @@ -0,0 +1,41 @@ +const $ = document.querySelector.bind(document) + +export function setupButtons (start) { + // Start + $('.btn-start').addEventListener('click', () => start()) + + // Reset + $('#reset').addEventListener('click', () => windows95.resetState()) + + $('#discard-state').addEventListener('click', () => { + window.appState.bootFresh = true + + start('win95') + }) + + // Floppy + $('#floppy').addEventListener('click', () => { + $('#file-input').click() + }) + + // Floppy (Hidden Input) + $('#file-input').addEventListener('change', (event) => { + window.appState.floppyFile = event.target.files && event.target.files.length > 0 + ? event.target.files[0] + : null + + if (window.appState.floppyFile) { + $('#floppy-path').innerHTML = `Inserted Floppy Disk: ${window.appState.floppyFile.path}` + } + }) +} + +export function toggleButtons (forceTo) { + const buttonElements = $('#buttons') + + if (buttonElements.style.display !== 'none' || forceTo === false) { + buttonElements.style.display = 'none' + } else { + buttonElements.style.display = undefined + } +} diff --git a/src/renderer/index.html b/src/renderer/index.html index a5db04c..7ace776 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -11,6 +11,7 @@ +
@@ -41,8 +42,8 @@
- diff --git a/src/renderer/listeners.js b/src/renderer/listeners.js new file mode 100644 index 0000000..1ed4dd7 --- /dev/null +++ b/src/renderer/listeners.js @@ -0,0 +1,41 @@ +export function setupCloseListener () { + window.appState.isQuitting = false + + const handleClose = async () => { + await windows95.saveState() + window.appState.isQuitting = true + windows95.quit() + } + + window.onbeforeunload = (event) => { + if (window.appState.isQuitting) return + + handleClose() + event.preventDefault() + event.returnValue = false + } +} + +export function setupEscListener () { + document.onkeydown = function (evt) { + evt = evt || window.event + if (evt.keyCode === 27) { + if (window.appState.cursorCaptured) { + window.appState.cursorCaptured = false + document.exitPointerLock() + } else { + window.appState.cursorCaptured = true + window.emulator.lock_mouse() + } + } + } +} + +export function setupClickListener () { + document.addEventListener('click', () => { + if (!window.appState.cursorCaptured) { + window.appState.cursorCaptured = true + window.emulator.lock_mouse() + } + }) +} diff --git a/src/renderer/renderer.js b/src/renderer/renderer.js index 0066055..54c9a48 100644 --- a/src/renderer/renderer.js +++ b/src/renderer/renderer.js @@ -1,24 +1,15 @@ -const $ = document.querySelector.bind(document) -const $$ = document.querySelectorAll.bind(document) +import { setupState } from 'es6://app-state.js' +import { setupClickListener, setupEscListener, setupCloseListener } from 'es6://listeners.js' +import { toggleButtons, setupButtons } from 'es6://buttons.js' -const BUTTONS = $('#buttons') +setupState() -let cursorCaptured = false -let floppyFile = null -let bootFresh = false - -const OPTIONS = { - win95: { - hda: { - url: './images/windows95.img', - async: true, - size: 242049024 - } - } -} - -async function main (id) { - const opts = Object.assign({ +/** + * The main method executing the VM. + */ +async function main () { + // New v86 instance + window.emulator = new V86Starter({ memory_size: 64 * 1024 * 1024, screen_container: document.getElementById('emulator'), bios: { @@ -27,126 +18,51 @@ async function main (id) { vga_bios: { url: './bios/vgabios.bin' }, + hda: { + url: '../images/windows96.img', + async: true, + size: 242049024 + }, + hdb: { + url: '../images/windows95.img', + async: true, + size: 242049024 + }, fda: { - buffer: floppyFile || undefined + buffer: window.appState.floppyFile || undefined }, boot_order: 0x132 - }, OPTIONS[id]) - - // New v86 instance - window.emulator = new V86Starter(opts) + }) // High DPI support if (navigator.userAgent.includes('Windows')) { - var scale = window.devicePixelRatio; - window.emulator.screen_adapter.set_scale(scale,scale); + const scale = window.devicePixelRatio + + window.emulator.screen_adapter.set_scale(scale, scale) } - // Restore state. We can't do this right away. + // Restore state. We can't do this right away + // and randomly chose 500ms as the appropriate + // wait time (lol) setTimeout(async () => { - if (!bootFresh) { - await windows95.restoreState() + if (!window.appState.bootFresh) { + windows95.restoreState() } - cursorCaptured = true + window.appState.cursorCaptured = true window.emulator.lock_mouse() window.emulator.run() }, 500) } -function start (id) { - BUTTONS.remove() +function start () { document.body.className = '' + + toggleButtons(false) setupClickListener() - main(id) -} - -function setupButtons () { - // Start - $$('.btn-start').forEach((btn) => { - btn.addEventListener('click', () => start(btn.id)) - }) - - // Reset - $('#reset').addEventListener('click', () => { - if (window.emulator.stop) { - window.emulator.stop() - } - - windows95.resetState() - - if (window.emulator.run) { - window.emulator.run() - } - - $('#reset').disabled = true - }) - - $('#discard-state').addEventListener('click', () => { - bootFresh = true - - start('win95') - }) - - // Floppy - $('#floppy').addEventListener('click', () => { - $('#file-input').click() - }) - - // Floppy (Hidden Input) - $('#file-input').addEventListener('change', (event) => { - floppyFile = event.target.files && event.target.files.length > 0 - ? event.target.files[0] - : null - - if (floppyFile) { - $('#floppy-path').innerHTML = `Inserted Floppy Disk: ${floppyFile.path}` - } - }) -} - -function setupEscListener () { - document.onkeydown = function (evt) { - evt = evt || window.event - if (evt.keyCode === 27) { - if (cursorCaptured) { - cursorCaptured = false - document.exitPointerLock() - } else { - cursorCaptured = true - window.emulator.lock_mouse() - } - } - } -} - -function setupCloseListener () { - let isQuitting = false - - const handleClose = async () => { - await windows95.saveState() - isQuitting = true - windows95.quit() - } - - window.onbeforeunload = (event) => { - if (isQuitting) return - - handleClose() - event.preventDefault() - event.returnValue = false - } -} - -function setupClickListener () { - document.addEventListener('click', () => { - if (!cursorCaptured) { - cursorCaptured = true - window.emulator.lock_mouse() - } - }) + main() } setupEscListener() setupCloseListener() -setupButtons() +setupButtons(start) diff --git a/src/state.js b/src/state.js index 2821c45..e03db7d 100644 --- a/src/state.js +++ b/src/state.js @@ -2,9 +2,15 @@ const fs = require('fs-extra') const path = require('path') const { remote } = require('electron') -const DEFAULT_PATH = path.join(__dirname, 'renderer/images/default-state.bin') +const DEFAULT_PATH = path.join(__dirname, 'images/default-state.bin') const STATE_PATH = path.join(remote.app.getPath('userData'), 'state.bin') +/** + * Returns the current machine's state - either what + * we have saved or alternatively the default state. + * + * @returns {ArrayBuffer} + */ function getState () { const statePath = fs.existsSync(STATE_PATH) ? STATE_PATH @@ -13,12 +19,58 @@ function getState () { return fs.readFileSync(statePath).buffer } -function resetState () { - fs.removeSync(STATE_PATH) +/** + * Resets a saved state by simply deleting it. + * + * @returns {Promise} + */ +async function resetState () { + if (fs.existsSync(STATE_PATH)) { + return fs.remove(STATE_PATH) + } +} + +/** + * Saves the current VM's state. + * + * @returns {Promise} + */ +async function saveState () { + return new Promise((resolve) => { + if (!window.emulator || !window.emulator.save_state) { + return resolve() + } + + window.emulator.save_state(async (error, newState) => { + if (error) { + console.log(error) + return + } + + await fs.outputFile(STATE_PATH, Buffer.from(newState)) + + console.log(`Saved state to ${STATE_PATH}`) + + resolve() + }) + }) +} + +/** + * Restores the VM's state. + */ +function restoreState () { + try { + window.emulator.restore_state(getState()) + } catch (error) { + console.log(`Could not read state file. Maybe none exists?`, error) + } } module.exports = { STATE_PATH, + saveState, + restoreState, resetState, getState }