46 Commits

Author SHA1 Message Date
Felix Rieseberg
a28aef8cf0 build: Oh, and don't install tree 2019-05-17 15:37:29 +09:00
Felix Rieseberg
d2b8d9dd35 build: Don't even tree 2019-05-17 15:31:48 +09:00
Felix Rieseberg
3802734ef0 build: Install tree on Linux 2019-05-17 15:31:13 +09:00
Felix Rieseberg
1f478676f1 build: Download images on Travis 2019-05-17 15:25:30 +09:00
Felix Rieseberg
d19bbeee8f build: Make standalone Windows builds 2019-05-17 15:25:21 +09:00
Felix Rieseberg
1e130b6140 build: Install x64 Node 2019-05-17 14:22:37 +09:00
Felix Rieseberg
e1c5992ff9 build: Add GitHub publisher 2019-05-17 13:56:02 +09:00
Felix Rieseberg
e879760678 build: Cut the flatpack 2019-05-17 13:43:39 +09:00
Felix Rieseberg
2a11633171 chore: Update version number to 2.1.0 2019-05-17 13:33:11 +09:00
Felix Rieseberg
b68d54ae62 build: Fix travis build 2019-05-17 13:32:28 +09:00
Felix Rieseberg
9600630340 build: Build on AppVeyor, too 2019-05-17 13:28:37 +09:00
Felix Rieseberg
bae1909793 build: What if we just don't asar? 2019-05-17 13:18:14 +09:00
Felix Rieseberg
ee9e138034 build: Enable verbose debugging 2019-05-17 13:01:56 +09:00
Felix Rieseberg
5558671688 build: Add a filename 2019-05-17 12:53:42 +09:00
Felix Rieseberg
9a46ed5080 build: Step by step, I guess 2019-05-17 12:43:54 +09:00
Felix Rieseberg
2c160d0f7f build: Add an AppVeyor file 2019-05-17 12:33:17 +09:00
Felix Rieseberg
aafab62707 chore: Update all dependencies 2019-05-17 12:30:03 +09:00
Felix Rieseberg
78126a57cb docs: Fix a typo 2019-02-22 11:41:46 +00:00
Felix Rieseberg
f5256ec7a2 docs: Add gitpod instructions 2019-02-22 11:40:56 +00:00
Felix Rieseberg
6c1687c9a5 Merge pull request #124 from jankeromnes/master
Add an online live demo
2019-02-22 11:39:07 +00:00
Jan Keromnes
2c041115d0 chore: Add online live demo 2019-02-13 15:31:23 +00:00
Felix Rieseberg
987dc57309 Merge pull request #117 from kant/patch-1
Formatting proposals
2019-02-12 00:05:42 -08:00
Darío Hereñú
614b18969d Formatting proposals 2019-02-08 13:58:24 -03:00
Felix Rieseberg
264ef7d069 Update README.md 2019-02-04 10:45:32 -08:00
Felix Rieseberg
e85cf4f1b2 docs: Add FrontPage credentials 2019-02-03 17:14:33 -08:00
Felix Rieseberg
e987da5460 docs: Update links 2019-02-03 17:13:39 -08:00
Felix Rieseberg
a542639bc3 docs: Update the readme with some answers 2019-02-03 16:49:19 -08:00
Felix Rieseberg
5d1928beb2 docs: Add instructions on how to mount the disk image 2019-02-03 16:45:58 -08:00
Felix Rieseberg
f1b657a53b docs: Remove link to CovalenceConf 2019-02-03 16:38:54 -08:00
Felix Rieseberg
6aa39e66ec 2.0.0 2019-02-03 15:40:31 -08:00
Felix Rieseberg
ed42ea8e0e fix: Ensure smooth migration 2019-02-03 15:38:06 -08:00
Felix Rieseberg
0779f18071 fix: Handle missing state 2019-02-03 15:24:29 -08:00
Felix Rieseberg
a9c4e38386 feat: Allow resetting the machien 2019-02-03 15:23:20 -08:00
Felix Rieseberg
62b0909cb4 fix: Correctly capture the mouse 2019-02-03 14:08:09 -08:00
Felix Rieseberg
873cb75241 fix: the various things I just broke 2019-02-03 14:02:30 -08:00
Felix Rieseberg
6467acb0c8 fix: Cleanup 2019-02-03 13:49:09 -08:00
Felix Rieseberg
ed1bd0a1e0 chore: Custom menu 2019-02-03 13:44:57 -08:00
Felix Rieseberg
ac84f4164e fix: Don't clear the cache on each start 2019-02-03 12:36:06 -08:00
Felix Rieseberg
77569d4ce6 infra: Update dependencies 2019-02-03 12:33:11 -08:00
Felix Rieseberg
68b7c181ad Merge pull request #109 from samuell/patch-1
Update README.md: Fix broken download links
2018-12-30 09:25:24 +01:00
Felix Rieseberg
293491477b Merge pull request #111 from malept/forge-arch-squirrel
chore: automatically add arch to squirrel installer filename
2018-12-30 09:24:56 +01:00
Felix Rieseberg
7eb750752b infra: Update to Electron 4.0 2018-12-30 09:22:32 +01:00
Mark Lee
f1488cedc2 Make it look more like a function 2018-12-26 22:41:56 -08:00
Mark Lee
9f366063eb chore: automatically add arch to squirrel installer filename 2018-12-26 22:40:08 -08:00
Samuel Lampa
55135f052e Update README.md: Fix download links
Fixes download links for Windows and the .deb Linux installer
2018-12-21 15:08:18 +01:00
Felix Rieseberg
95fd8e4925 chore: Update the readme with new download links 2018-12-20 11:25:33 -08:00
25 changed files with 2232 additions and 1591 deletions

45
.appveyor.yml Normal file
View File

@@ -0,0 +1,45 @@
environment:
matrix:
- nodejs_version: "10"
init:
- git config --global core.symlinks true
install:
# Setup the code signing certificate
- ps: >-
if (Test-Path Env:\WINDOWS_CERTIFICATE_P12) {
$workingDirectory = Convert-Path (Resolve-Path -path ".")
$filename = "$workingDirectory\cert.p12"
$bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_P12)
[IO.File]::WriteAllBytes($filename, $bytes)
}
- ps: Install-Product node $env:nodejs_version x64
- node --version
- npm ci
- ps: cd ./src/
- ps: mkdir images
- ps: cd images
- ps: Start-FileDownload 'https://1drv.ws/u/s!AkfaAw_EaahOkulh8rA41x2phgfYXQ' -FileName images.zip -Timeout 600000
- ps: 7z x images.zip -y -aoa
- ps: Remove-Item images.zip
- ps: Remove-Item __MACOSX -Recurse -ErrorAction Ignore
- ps: cd ../..
- ps: Tree ./src /F
cache:
- '%APPDATA%\npm-cache -> appveyor.yml'
test_script:
- node --version
- npm --version
- npm run lint
artifacts:
- path: 'out\make\squirrel.windows\**\*.exe'
build_script:
- if %APPVEYOR_REPO_TAG% EQU false npm run make
- if %APPVEYOR_REPO_TAG% EQU true npm run publish
- if %APPVEYOR_REPO_TAG% EQU true npm run publish -- --arch=ia32
- ps: Tree ./out/make /F

64
.travis.yml Normal file
View File

@@ -0,0 +1,64 @@
language: node_js
node_js: "12"
os:
- linux
- osx
dist: trusty
osx_image: xcode8.3
sudo: false
cache:
directories:
- node_modules
- $HOME/.cache/electron
addons:
apt:
packages:
- fakeroot
- rpm
branches:
only:
- master
- /^v\d+\.\d+\.\d+/
install:
- npm install
- mkdir -p ./src/images
- cd ./src/images
- wget -O images.zip https://1drv.ws/u/s!AkfaAw_EaahOkulh8rA41x2phgfYXQ
- unzip -o images.zip
- rm images.zip
- rm -r __MACOSX
- cd ../..
- ls src
- ls src/images
- |
if [[ "$TRAVIS_OS_NAME" == "osx" && "$TRAVIS_SECURE_ENV_VARS" == "true" ]]; then
export CERTIFICATE_P12=cert.p12;
echo $MACOS_CERT_P12 | base64 --decode > $CERTIFICATE_P12;
export KEYCHAIN=build.keychain;
# Create the keychain with a password
security create-keychain -p travis $KEYCHAIN;
# Make the custom keychain default, so xcodebuild will use it for signing
security default-keychain -s $KEYCHAIN;
# Unlock the keychain
security unlock-keychain -p travis $KEYCHAIN;
# Add certificates to keychain and allow codesign to access them
# Apple Worldwide Developer Relations Certification Authority
security import ./tools/certs/apple.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
# Developer Authentication Certification Authority
security import ./tools/certs/dac.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
# Developer ID Felix
security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MACOS_CERT_PASSWORD -T /usr/bin/codesign 2>&1 >/dev/null;
rm $CERTIFICATE_P12;
security set-key-partition-list -S apple-tool:,apple: -s -k travis $KEYCHAIN
# Echo the identity
security find-identity -v -p codesigning
fi
script:
- npm run lint
- if test -z "$TRAVIS_TAG"; then npm run make; fi
after_success: if test -n "$TRAVIS_TAG"; then npm run publish; fi

17
HELP.md
View File

@@ -14,11 +14,22 @@ button to start your virtual machine again.
## I want to install additional apps or games
If you are running Windows 10, macOS, or Linux, you can probably "mount" the
If you are running macOS, or Linux, you can probably "mount" the
virtual hard drive used by `windows95` to add files. Hit the "Show Disk Image"
button in the lower right of the app, which will take you to the disk image.
On both Windows 10 and macOS, double-click the disk image to open it.
On macOS, double-click the disk image to open it.
On Windows 10, Windows will _think_ that it can open up the image, but will
actually fail to do so. Use a tool [like OSFMount][osfmount] to mount your
disk image.
On Linux, search the Internet for instructions on how to mount an `img` disk
image on your distribution.
image on your distribution. It's likely that you'll be able to run `mount`
with the image as input.
[osfmount]: https://www.osforensics.com/tools/mount-disk-images.html
## What's the FrontPage Username and Password?
Username: windows95
Password: password

View File

@@ -2,18 +2,16 @@
This is Windows 95, running in an [Electron](https://electronjs.org/) app. Yes, it's the full thing. I'm sorry.
Interested in Electron? Join as at [CovalenceConf](http://covalenceconf.com) in San Francisco!
## Downloads
| | Windows | macOS | Linux |
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Standalone Download | 📦[Standalone, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-win32-1.3.0-standalone-ia32.zip) <br /> 📦[Standalone, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-win32-1.3.0-standalone-x64.zip) | 📦[Standalone](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-macos-1.3.0.zip) | |
| Installer | 💽[Setup, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-win32-1.3.0-setup-x64.exe) <br /> 💽[Setup, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-win32-1.3.0-setup-ia32.exe) | | 💽[deb, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-linux_1.3.0_amd64.deb) <br /> 💽[rpm, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v1.3.0/windows95-linux-1.3.0.x86_64.rpm) |
| Standalone Download | 📦[Standalone, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-2.0.0-win32-standalone-ia32.zip) <br /> 📦[Standalone, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-2.0.0-win32-standalone-x64.zip) | 📦[Standalone](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-macos-2.0.0.zip) | |
| Installer | 💽[Setup, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-2.0.0-setup-win32-x64.exe) <br /> 💽[Setup, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-2.0.0-setup-win32-ia32.exe) | | 💽[deb, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-linux-2.0.0_amd64.deb) <br /> 💽[rpm, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.0.0/windows95-linux-2.0.0.x86_64.rpm) |
![Screenshot](https://user-images.githubusercontent.com/1426799/44532591-4ceb3680-a6a8-11e8-8c2c-bc29f3bfdef7.png)
## Does it work?
Yes! Quite well, actually - on macOS, Windows, and Linux.
Yes! Quite well, actually - on macOS, Windows, and Linux. Bear in mind that this is written entirely in JavaScript, so please adjust your expectations.
## Should this have been a native app?
Absolutely.
@@ -23,9 +21,6 @@ You'll likely be better off with an actual virtualization app, but the short ans
@DisplacedGamers](https://youtu.be/xDXqmdFxofM) I can recommend that you switch to a resolution of
640x480 @ 256 colors before starting DOS games - just like in the good ol' days.
## How's the code?
This only works well by accident and was mostly a joke. The code quality is accordingly. Thus it should not be used for anything other than personal amusement.
## Credits
99.999% of the work was done over at [v86](https://github.com/copy/v86/) by Copy.
@@ -46,7 +41,11 @@ Once you've done so, run `npm install` and `npm start` to run your local build.
## Other Questions
* [MS-DOS seems to brick the screen](./HELP.md#ms-dos-seems-to-brick-the-screen)
* [Windows 95 is stuck in a bad state](./HELP.md#windows-95-is-stuck-in-a-bad-state)
* [I want to install additional apps or games](./HELP.md#i-want-to-install-additional-apps-or-games)
* [Running in Docker](./docs/docker-instructions.md)
* [Running in an online VM with Kubernetes and Gitpod](./docs/docker-kubernetes-gitpod.md)
## License

View File

@@ -27,7 +27,13 @@ Note: You may need to run `xhost +` on your system to allow connections to the X
* [XQuartz](https://www.xquartz.org/)
* [Docker](http://docker.io)
1. Start XQuartz ,go to "Preferences -> Security " ,and check the box "allow connections from network clients"
2. restart XQuartz
3. In the terminal ,run "xhost +"
4. run "docker run -it -e DISPLAY=host.docker.internal:1 toolboc/windows95"
1. Start XQuartz, go to `Preferences` -> `Security`, and check the box `Allow connections from network clients`
2. Restart XQuartz
3. In the terminal, run
```
xhost +
```
4. run
```
docker run -it -e DISPLAY=host.docker.internal:1 toolboc/windows95
```

View File

@@ -0,0 +1,4 @@
## Running an online version of windows95
You can also run windows95 in Electron, in a virtual X server, in a JavaScript VNC client, in a Kubernetes workspace. What could go wrong?
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/felixrieseberg/windows95)

View File

@@ -3,9 +3,7 @@ const package = require('./package.json');
module.exports = {
packagerConfig: {
asar: {
unpack: '**/images/*.img'
},
asar: false,
icon: path.resolve(__dirname, 'assets', 'icon'),
appBundleId: 'com.felixrieseberg.windows95',
appCategoryType: 'public.app-category.developer-tools',
@@ -21,21 +19,23 @@ module.exports = {
{
name: '@electron-forge/maker-squirrel',
platforms: ['win32'],
config: {
name: 'windows95',
authors: 'Felix Rieseberg',
exe: 'windows95.exe',
noMsi: true,
remoteReleases: '',
setupExe: `windows95-${package.version}-setup-${process.arch}.exe`,
setupIcon: path.resolve(__dirname, 'assets', 'icon.ico'),
certificateFile: process.env.WINDOWS_CERTIFICATE_FILE,
certificatePassword: process.env.WINDOWS_CERTIFICATE_PASSWORD
config: (arch) => {
return {
name: 'windows95',
authors: 'Felix Rieseberg',
exe: 'windows95.exe',
noMsi: true,
remoteReleases: '',
setupExe: `windows95-${package.version}-setup-${arch}.exe`,
setupIcon: path.resolve(__dirname, 'assets', 'icon.ico'),
certificateFile: process.env.WINDOWS_CERTIFICATE_FILE,
certificatePassword: process.env.WINDOWS_CERTIFICATE_PASSWORD
}
}
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin']
platforms: ['darwin', 'win32']
},
{
name: '@electron-forge/maker-deb',
@@ -44,10 +44,19 @@ module.exports = {
{
name: '@electron-forge/maker-rpm',
platforms: ['linux']
},
}
],
publishers: [
{
name: '@electron-forge/maker-flatpak',
platforms: ['linux']
name: '@electron-forge/publisher-github',
config: {
repository: {
owner: 'felixrieseberg',
name: 'windows95'
},
draft: true,
prerelease: true
}
}
]
};

3014
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "windows95",
"productName": "windows95",
"version": "1.4.0",
"version": "2.1.0",
"description": "Windows 95, in an app. I'm sorry.",
"main": "src/index.js",
"scripts": {
@@ -28,19 +28,20 @@
]
},
"dependencies": {
"electron-default-menu": "^1.0.1",
"electron-squirrel-startup": "^1.0.0",
"fs-extra": "^7.0.0",
"fs-extra": "^8.0.1",
"update-electron-app": "^1.3.0"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.30",
"@electron-forge/maker-deb": "^6.0.0-beta.30",
"@electron-forge/maker-flatpak": "^6.0.0-beta.30",
"@electron-forge/maker-rpm": "^6.0.0-beta.30",
"@electron-forge/maker-squirrel": "^6.0.0-beta.30",
"@electron-forge/maker-zip": "^6.0.0-beta.30",
"electron": "3.0.13",
"standard": "^11.0.1"
"@electron-forge/cli": "^6.0.0-beta.34",
"@electron-forge/maker-deb": "^6.0.0-beta.34",
"@electron-forge/maker-flatpak": "^6.0.0-beta.34",
"@electron-forge/maker-rpm": "^6.0.0-beta.34",
"@electron-forge/maker-squirrel": "^6.0.0-beta.34",
"@electron-forge/maker-zip": "^6.0.0-beta.34",
"@electron-forge/publisher-github": "^6.0.0-beta.34",
"electron": "5.0.1",
"node-abi": "^2.8.0",
"standard": "^12.0.1"
}
}

24
src/constants.js Normal file
View File

@@ -0,0 +1,24 @@
const { remote, app } = require('electron')
const path = require('path')
const _app = app || remote.app
const CONSTANTS = {
IMAGE_PATH: path.join(__dirname, 'images/windows95.img'),
IMAGE_DEFAULT_SIZE: 1073741824, // 1GB
DEFAULT_STATE_PATH: path.join(__dirname, 'images/default-state.bin'),
STATE_PATH: path.join(_app.getPath('userData'), 'state-v2.bin')
}
const IPC_COMMANDS = {
TOGGLE_INFO: 'TOGGLE_INFO',
MACHINE_RESTART: 'MACHINE_RESTART',
MACHINE_RESET: 'MACHINE_RESET',
MACHINE_CTRL_ALT_DEL: 'MACHINE_CTRL_ALT_DEL',
SHOW_DISK_IMAGE: 'SHOW_DISK_IMAGE'
}
module.exports = {
CONSTANTS,
IPC_COMMANDS
}

View File

@@ -4,7 +4,14 @@ const path = require('path')
const ES6_PATH = path.join(__dirname, 'renderer')
protocol.registerStandardSchemes(['es6'])
protocol.registerSchemesAsPrivileged([
{
scheme: 'es6',
privileges: {
standard: true
}
}
])
async function setupProtocol () {
protocol.registerBufferProtocol('es6', async (req, cb) => {

View File

@@ -1,7 +1,6 @@
const { app, BrowserWindow } = require('electron')
const path = require('path')
const { clearCaches } = require('./cache')
const { createMenu } = require('./menu')
const { setupProtocol } = require('./es6')
@@ -21,8 +20,8 @@ let mainWindow
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
width: 1024,
height: 768,
useContentSize: true,
webPreferences: {
nodeIntegration: false,
@@ -40,7 +39,6 @@ const createWindow = () => {
app.on('ready', async () => {
await setupProtocol()
await createMenu()
await clearCaches()
createWindow()
})

View File

@@ -1,8 +1,10 @@
const { app, shell, Menu, BrowserWindow } = require('electron')
const defaultMenu = require('electron-default-menu')
const { clearCaches } = require('./cache')
const { IPC_COMMANDS } = require('./constants')
const LINKS = {
homepage: 'https://www.felixrieseberg.com',
homepage: 'https://www.twitter.com/felixrieseberg',
repo: 'https://github.com/felixrieseberg/windows95',
credits: 'https://github.com/felixrieseberg/windows95/blob/master/CREDITS.md',
help: 'https://github.com/felixrieseberg/windows95/blob/master/HELP.md'
@@ -17,74 +19,162 @@ function send (cmd) {
}
async function createMenu () {
const menu = defaultMenu(app, shell)
.map((item) => {
if (item.label === 'View') {
item.submenu = item.submenu.filter((subItem) => {
return subItem.label !== 'Reload'
})
}
if (item.label === 'Help') {
item.submenu = [
{
label: 'Author',
click() {
shell.openExternal(LINKS.homepage)
},
},
{
label: 'Learn More',
click() {
shell.openExternal(LINKS.repo)
},
},
{
type: 'separator'
},
{
label: 'Help',
click() {
shell.openExternal(LINKS.help)
}
},
{
label: 'Credits',
click() {
shell.openExternal(LINKS.credits)
}
const template = [
{
label: 'View',
submenu: [
{
label: 'Toggle Full Screen',
accelerator: (function () {
if (process.platform === 'darwin') { return 'Ctrl+Command+F' } else { return 'F11' }
})(),
click: function (_item, focusedWindow) {
if (focusedWindow) { focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) }
}
]
}
},
{
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') { return 'Alt+Command+I' } else { return 'Ctrl+Shift+I' }
})(),
click: function (_item, focusedWindow) {
if (focusedWindow) { focusedWindow.toggleDevTools() }
}
},
{
type: 'separator'
},
{
label: 'Toggle Emulator Info',
click: () => send(IPC_COMMANDS.TOGGLE_INFO)
}
]
},
{
label: 'Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
}
]
},
{
label: 'Machine',
submenu: [
{
label: 'Send Ctrl+Alt+Del',
click: () => send(IPC_COMMANDS.MACHINE_CTRL_ALT_DEL)
},
{
label: 'Restart',
click: () => send(IPC_COMMANDS.MACHINE_RESTART)
},
{
label: 'Reset',
click: () => send(IPC_COMMANDS.MACHINE_RESET)
},
{
type: 'separator'
},
{
label: 'Go to Disk Image',
click: () => send(IPC_COMMANDS.SHOW_DISK_IMAGE)
}
]
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Author',
click: () => shell.openExternal(LINKS.homepage)
},
{
label: 'windows95 on GitHub',
click: () => shell.openExternal(LINKS.repo)
},
{
label: 'Help',
click: () => shell.openExternal(LINKS.help)
},
{
type: 'separator'
},
{
label: 'Troubleshooting',
submenu: [
{
label: 'Clear Cache and Restart',
async click () {
await clearCaches()
return item
})
.filter((item) => {
return item.label !== 'Edit'
app.relaunch()
app.quit()
}
}
]
}
]
}
]
if (process.platform === 'darwin') {
template.unshift({
label: 'windows95',
submenu: [
{
label: 'About windows95',
role: 'about'
},
{
type: 'separator'
},
{
label: 'Services',
role: 'services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide windows95',
accelerator: 'Command+H',
role: 'hide'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
role: 'hideothers'
},
{
label: 'Show All',
role: 'unhide'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
click () {
app.quit()
}
}
]
})
}
menu.splice(1, 0, {
label: 'Machine',
submenu: [
{
label: 'Send Ctrl+Alt+Del',
click: () => send('ctrlaltdel')
},
{
label: 'Restart',
click: () => send('restart')
},
{
type: 'separator'
},
{
label: 'Go to Disk Image',
click: () => send('disk-image')
}
]
})
Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
}
module.exports = {

View File

@@ -1,45 +1,42 @@
const { remote, shell, ipcRenderer } = require('electron')
const path = require('path')
const EventEmitter = require('events')
const { STATE_PATH, resetState, restoreState, saveState } = require('./state')
const { resetState, restoreState, saveState } = require('./state')
const { getDiskImageSize } = require('./utils/disk-image-size')
const { IPC_COMMANDS, CONSTANTS } = require('./constants')
window.windows95 = {
STATE_PATH,
restoreState,
resetState,
saveState,
class Windows95 extends EventEmitter {
constructor () {
super()
// Constants
this.CONSTANTS = CONSTANTS
this.IPC_COMMANDS = IPC_COMMANDS
// Methods
this.getDiskImageSize = getDiskImageSize
this.restoreState = restoreState
this.resetState = resetState
this.saveState = saveState
Object.keys(IPC_COMMANDS).forEach((command) => {
ipcRenderer.on(command, (...args) => {
this.emit(command, args)
})
})
}
showDiskImage () {
const imagePath = path.join(__dirname, 'images/windows95.img')
.replace('app.asar', 'app.asar.unpacked')
shell.showItemInFolder(imagePath)
},
}
quit: () => remote.app.quit()
quit () {
remote.app.quit()
}
}
ipcRenderer.on('ctrlaltdel', () => {
if (!window.emulator || !window.emulator.is_running) return
window.emulator.keyboard_send_scancodes([
0x1D, // ctrl
0x38, // alt
0x53, // delete
// break codes
0x1D | 0x80,
0x38 | 0x80,
0x53 | 0x80
])
})
ipcRenderer.on('restart', () => {
if (!window.emulator || !window.emulator.is_running) return
window.emulator.restart()
})
ipcRenderer.on('disk-image', () => {
windows95.showDiskImage()
})
window.windows95 = new Windows95()

View File

@@ -1,8 +1,9 @@
export function setupState () {
window.appState = {
isResetting: false,
isQuitting: false,
cursorCaptured: false,
floppyFile: null,
bootFresh: false,
infoInterval: null
bootFresh: false
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -13,7 +13,7 @@ export function setupButtons (start) {
$('#discard-state').addEventListener('click', () => {
window.appState.bootFresh = true
start('win95')
start()
})
// Floppy

View File

@@ -14,7 +14,7 @@
<div id="status">
Disk: <span id="disk-status">Idle</span>
| CPU Speed: <span id="cpu-status">0</span>
| <a onclick="document.querySelector('#status').style.display='none'">Hide</a>
| <a href="#" id="toggle-status">Hide</a>
</div>
<div id="buttons">
<div id="start-buttons">

View File

@@ -1,28 +1,63 @@
const $ = document.querySelector.bind(document)
const status = $('#status')
const diskStatus = $('#disk-status')
const cpuStatus = $('#cpu-status')
const toggleStatus = $('#toggle-status')
export function setupInfo () {
const diskStatus = $('#disk-status')
const cpuStatus = $('#cpu-status')
let lastCounter = 0
let lastTick = 0
let lastCounter = 0
let lastTick = 0
let infoInterval = null
window.emulator.add_listener('ide-read-start', () => {
diskStatus.innerHTML = 'Read'
})
const onIDEReadStart = () => {
diskStatus.innerHTML = 'Read'
}
window.emulator.add_listener('ide-read-end', () => {
diskStatus.innerHTML = 'Idle'
})
const onIDEReadWriteEnd = () => {
diskStatus.innerHTML = 'Idle'
}
window.emulator.add_listener('ide-write-end', () => {
diskStatus.innerHTML = 'Idle'
})
toggleStatus.onclick = toggleInfo
window.emulator.add_listener('screen-set-size-graphical', (...args) => {
console.log(...args)
})
/**
* Toggle the information display
*/
export function toggleInfo () {
if (status.style.display !== 'none') {
disableInfo()
} else {
enableInfo()
}
}
setInterval(() => {
/**
* Start information gathering, but only if the panel is visible
*/
export function startInfoMaybe () {
if (status.style.display !== 'none') {
enableInfo()
}
}
/**
* Enable the gathering of information (and hide the little information tab)
*/
export function enableInfo () {
// Show the info thingy
status.style.display = 'block'
// We can only do the rest with an emulator
if (!window.emulator.add_listener) {
return
}
// Set listeners
window.emulator.add_listener('ide-read-start', onIDEReadStart)
window.emulator.add_listener('ide-read-end', onIDEReadWriteEnd)
window.emulator.add_listener('ide-write-end', onIDEReadWriteEnd)
window.emulator.add_listener('screen-set-size-graphical', console.log)
// Set an interval
infoInterval = setInterval(() => {
const now = Date.now()
const instructionCounter = window.emulator.get_instruction_counter()
const ips = instructionCounter - lastCounter
@@ -34,3 +69,26 @@ export function setupInfo () {
cpuStatus.innerHTML = Math.round(ips / deltaTime)
}, 500)
}
/**
* Disable the gathering of information (and hide the little information tab)
*/
export function disableInfo () {
// Hide the info thingy
status.style.display = 'none'
// Clear the interval
clearInterval(infoInterval)
infoInterval = null
// We can only do the rest with an emulator
if (!window.emulator.remove_listener) {
return
}
// Unset the listeners
window.emulator.remove_listener('ide-read-start', onIDEReadStart)
window.emulator.remove_listener('ide-read-end', onIDEReadWriteEnd)
window.emulator.remove_listener('ide-write-end', onIDEReadWriteEnd)
window.emulator.remove_listener('screen-set-size-graphical', console.log)
}

44
src/renderer/ipc.js Normal file
View File

@@ -0,0 +1,44 @@
import { toggleInfo } from 'es6://info.js'
export function setupIpcListeners (start) {
const { windows95 } = window
windows95.addListener(windows95.IPC_COMMANDS.TOGGLE_INFO, () => {
toggleInfo()
})
windows95.addListener(windows95.IPC_COMMANDS.MACHINE_RESTART, () => {
console.log(`Restarting machine`)
if (!window.emulator || !window.emulator.is_running) return
window.emulator.restart()
})
windows95.addListener(windows95.IPC_COMMANDS.MACHINE_RESET, () => {
console.log(`Resetting machine`)
window.appState.isResetting = true
document.location.hash = `#AUTO_START`
document.location.reload()
})
windows95.addListener(windows95.IPC_COMMANDS.MACHINE_CTRL_ALT_DEL, () => {
if (!window.emulator || !window.emulator.is_running) return
window.emulator.keyboard_send_scancodes([
0x1D, // ctrl
0x38, // alt
0x53, // delete
// break codes
0x1D | 0x80,
0x38 | 0x80,
0x53 | 0x80
])
})
windows95.addListener(windows95.IPC_COMMANDS.SHOW_DISK_IMAGE, () => {
windows95.showDiskImage()
})
}

View File

@@ -9,6 +9,7 @@ export function setupCloseListener () {
window.onbeforeunload = (event) => {
if (window.appState.isQuitting) return
if (window.appState.isResetting) return
handleClose()
event.preventDefault()
@@ -22,6 +23,7 @@ export function setupEscListener () {
if (evt.keyCode === 27) {
if (window.appState.cursorCaptured) {
window.appState.cursorCaptured = false
window.emulator.mouse_set_status(false)
document.exitPointerLock()
} else {
window.appState.cursorCaptured = true
@@ -31,11 +33,15 @@ export function setupEscListener () {
}
}
export function setupClickListener () {
document.addEventListener('click', () => {
if (!window.appState.cursorCaptured) {
window.appState.cursorCaptured = true
window.emulator.lock_mouse()
}
})
function onDocumentClick () {
if (!window.appState.cursorCaptured) {
window.appState.cursorCaptured = true
window.emulator.mouse_set_status(true)
window.emulator.lock_mouse()
}
}
export function setupClickListener () {
document.removeEventListener('click', onDocumentClick)
document.addEventListener('click', onDocumentClick)
}

View File

@@ -1,7 +1,9 @@
/* We're using modern esm imports here */
import { setupState } from 'es6://app-state.js'
import { setupClickListener, setupEscListener, setupCloseListener } from 'es6://listeners.js'
import { toggleButtons, setupButtons } from 'es6://buttons.js'
import { setupInfo } from 'es6://info.js'
import { startInfoMaybe } from 'es6://info.js'
import { setupIpcListeners } from 'es6://ipc.js'
setupState()
@@ -9,9 +11,10 @@ setupState()
* The main method executing the VM.
*/
async function main () {
// New v86 instance
window.emulator = new V86Starter({
memory_size: 64 * 1024 * 1024,
const imageSize = await window.windows95.getDiskImageSize()
const options = {
memory_size: 128 * 1024 * 1024,
video_memory_size: 32 * 1024 * 1024,
screen_container: document.getElementById('emulator'),
bios: {
url: './bios/seabios.bin'
@@ -22,21 +25,19 @@ async function main () {
hda: {
url: '../images/windows95.img',
async: true,
size: 242049024
size: imageSize
},
fda: {
buffer: window.appState.floppyFile || undefined
},
boot_order: 0x132
})
// High DPI support
if (navigator.userAgent.includes('Windows')) {
const scale = window.devicePixelRatio
window.emulator.screen_adapter.set_scale(scale, scale)
}
console.log(`Starting emulator with options`, options)
// New v86 instance
window.emulator = new V86Starter(options)
// Restore state. We can't do this right away
// and randomly chose 500ms as the appropriate
// wait time (lol)
@@ -45,7 +46,7 @@ async function main () {
windows95.restoreState()
}
setupInfo()
startInfoMaybe()
window.appState.cursorCaptured = true
window.emulator.lock_mouse()
@@ -61,6 +62,11 @@ function start () {
main()
}
setupIpcListeners(start)
setupEscListener()
setupCloseListener()
setupButtons(start)
if (document.location.hash.includes('AUTO_START')) {
start()
}

View File

@@ -1,9 +1,6 @@
const fs = require('fs-extra')
const path = require('path')
const { remote } = require('electron')
const DEFAULT_PATH = path.join(__dirname, 'images/default-state.bin')
const STATE_PATH = path.join(remote.app.getPath('userData'), 'state.bin')
const { CONSTANTS } = require('./constants')
/**
* Returns the current machine's state - either what
@@ -12,11 +9,13 @@ const STATE_PATH = path.join(remote.app.getPath('userData'), 'state.bin')
* @returns {ArrayBuffer}
*/
function getState () {
const statePath = fs.existsSync(STATE_PATH)
? STATE_PATH
: DEFAULT_PATH
const statePath = fs.existsSync(CONSTANTS.STATE_PATH)
? CONSTANTS.STATE_PATH
: CONSTANTS.DEFAULT_STATE_PATH
return fs.readFileSync(statePath).buffer
if (fs.existsSync(statePath)) {
return fs.readFileSync(statePath).buffer
}
}
/**
@@ -25,8 +24,8 @@ function getState () {
* @returns {Promise<void>}
*/
async function resetState () {
if (fs.existsSync(STATE_PATH)) {
return fs.remove(STATE_PATH)
if (fs.existsSync(CONSTANTS.STATE_PATH)) {
return fs.remove(CONSTANTS.STATE_PATH)
}
}
@@ -43,13 +42,13 @@ async function saveState () {
window.emulator.save_state(async (error, newState) => {
if (error) {
console.log(error)
console.warn(`State: Could not save state`, error)
return
}
await fs.outputFile(STATE_PATH, Buffer.from(newState))
await fs.outputFile(CONSTANTS.STATE_PATH, Buffer.from(newState))
console.log(`Saved state to ${STATE_PATH}`)
console.log(`State: Saved state to ${CONSTANTS.STATE_PATH}`)
resolve()
})
@@ -60,15 +59,21 @@ async function saveState () {
* Restores the VM's state.
*/
function restoreState () {
const state = getState()
// Nothing to do with if we don't have a state
if (!state) {
console.log(`State: No state present, not restoring.`)
}
try {
window.emulator.restore_state(getState())
window.emulator.restore_state(state)
} catch (error) {
console.log(`Could not read state file. Maybe none exists?`, error)
console.log(`State: Could not read state file. Maybe none exists?`, error)
}
}
module.exports = {
STATE_PATH,
saveState,
restoreState,
resetState,

View File

@@ -0,0 +1,26 @@
const fs = require('fs-extra')
const { CONSTANTS } = require('../constants')
/**
* Get the size of the disk image
*
* @returns {number}
*/
async function getDiskImageSize () {
try {
const stats = await fs.stat(CONSTANTS.IMAGE_PATH)
if (stats) {
return stats.size
}
} catch (error) {
console.warn(`Could not determine image size`, error)
}
return CONSTANTS.IMAGE_DEFAULT_SIZE
}
module.exports = {
getDiskImageSize
}