mirror of
https://github.com/felixrieseberg/windows95.git
synced 2026-05-14 18:31:59 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55e88cd5b5 | ||
|
|
e0ad866256 | ||
|
|
cf3acd4182 | ||
|
|
a28aef8cf0 | ||
|
|
d2b8d9dd35 | ||
|
|
3802734ef0 | ||
|
|
1f478676f1 | ||
|
|
d19bbeee8f | ||
|
|
1e130b6140 | ||
|
|
e1c5992ff9 | ||
|
|
e879760678 | ||
|
|
2a11633171 | ||
|
|
b68d54ae62 | ||
|
|
9600630340 | ||
|
|
bae1909793 | ||
|
|
ee9e138034 | ||
|
|
5558671688 | ||
|
|
9a46ed5080 | ||
|
|
2c160d0f7f | ||
|
|
aafab62707 | ||
|
|
78126a57cb | ||
|
|
f5256ec7a2 | ||
|
|
6c1687c9a5 | ||
|
|
2c041115d0 | ||
|
|
987dc57309 | ||
|
|
614b18969d | ||
|
|
264ef7d069 | ||
|
|
e85cf4f1b2 | ||
|
|
e987da5460 | ||
|
|
a542639bc3 | ||
|
|
5d1928beb2 | ||
|
|
f1b657a53b | ||
|
|
6aa39e66ec | ||
|
|
ed42ea8e0e | ||
|
|
0779f18071 | ||
|
|
a9c4e38386 | ||
|
|
62b0909cb4 | ||
|
|
873cb75241 | ||
|
|
6467acb0c8 | ||
|
|
ed1bd0a1e0 | ||
|
|
ac84f4164e | ||
|
|
77569d4ce6 | ||
|
|
68b7c181ad | ||
|
|
293491477b | ||
|
|
7eb750752b | ||
|
|
f1488cedc2 | ||
|
|
9f366063eb | ||
|
|
55135f052e | ||
|
|
95fd8e4925 | ||
|
|
b794954da4 | ||
|
|
18a73c45a0 | ||
|
|
b83914060f | ||
|
|
93955564d9 | ||
|
|
d31920aaf4 | ||
|
|
cdfe47d92b | ||
|
|
b8259784e7 | ||
|
|
b70b9fabd5 | ||
|
|
f2c1fc4142 | ||
|
|
aeba364a7a | ||
|
|
a34ce54b56 | ||
|
|
e1477bfc05 | ||
|
|
71d6f16318 | ||
|
|
4bdbff6a4b | ||
|
|
e062548c81 | ||
|
|
609668c581 | ||
|
|
bec9577409 | ||
|
|
dd9ff5a319 | ||
|
|
f5125219fd | ||
|
|
b4fd81b364 | ||
|
|
e62a8cbed6 | ||
|
|
2038ce9c31 | ||
|
|
6c12063353 | ||
|
|
91783e7d26 | ||
|
|
9f8040bb22 | ||
|
|
10d2e37e9d |
45
.appveyor.yml
Normal file
45
.appveyor.yml
Normal 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
64
.travis.yml
Normal 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 ./assets/certs/apple.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
|
||||
# Developer Authentication Certification Authority
|
||||
security import ./assets/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
|
||||
59
CREDITS.md
Normal file
59
CREDITS.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# windows95 Credits
|
||||
|
||||
This app was made possible by three major engineering efforts:
|
||||
|
||||
* [v86 by Fabian Hemmer](https://github.com/copy/v86)
|
||||
* [Electron by the Electron Maintainers](https://electronjs.org)
|
||||
* Windows 95 by Microsoft
|
||||
|
||||
# v86 License and Copyright Notice
|
||||
|
||||
Copyright (c) 2012-2018, Fabian Hemmer
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
|
||||
# Electron License and Copyright Notice
|
||||
|
||||
Copyright (c) 2013-2018 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
45
Dockerfile
Normal file
45
Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
||||
# DESCRIPTION: Run Windows 95 in a container
|
||||
# AUTHOR: Paul DeCarlo <toolboc@gmail.com>
|
||||
#
|
||||
# Made possible through prior art by:
|
||||
# copy (v86 - x86 virtualization in JavaScript)
|
||||
# felixrieseberg (Windows95 running in electron)
|
||||
# Microsoft (Windows 95)
|
||||
#
|
||||
# ***Docker Run Command***
|
||||
#
|
||||
# docker run -it \
|
||||
# -v /tmp/.X11-unix:/tmp/.X11-unix \ # mount the X11 socket
|
||||
# -e DISPLAY=unix$DISPLAY \ # pass the display
|
||||
# --device /dev/snd \ # sound
|
||||
# --name windows95 \
|
||||
# toolboc/windows95
|
||||
#
|
||||
# ***TroubleShooting***
|
||||
# If you receive Gtk-WARNING **: cannot open display: unix:0
|
||||
# Run:
|
||||
# xhost +
|
||||
#
|
||||
|
||||
FROM node:10.9-stretch
|
||||
|
||||
LABEL maintainer "Paul DeCarlo <toolboc@gmail.com>"
|
||||
|
||||
RUN apt update && apt install -y \
|
||||
libgtk-3-0 \
|
||||
libcanberra-gtk3-module \
|
||||
libx11-xcb-dev \
|
||||
libgconf2-dev \
|
||||
libnss3 \
|
||||
libasound2 \
|
||||
libxtst-dev \
|
||||
libxss1 \
|
||||
git \
|
||||
--no-install-recommends && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install
|
||||
|
||||
ENTRYPOINT [ "npm", "start"]
|
||||
35
HELP.md
Normal file
35
HELP.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Help & Commonly Asked Questions
|
||||
|
||||
## MS-DOS seems to brick the screen
|
||||
Hit `Alt + Enter` to make the command screen "full screen" (as far as Windows 95 is
|
||||
concerned). This should restore the display from the garbled mess you see and allow
|
||||
you to access the command prompt. Press Alt-Enter again to leave full screen and go
|
||||
back to a window mode. (Thanks to @DisplacedGamer for that wisdom)
|
||||
|
||||
## Windows 95 is stuck in a bad state
|
||||
|
||||
Restart the application and click on the "Reset machine & delete state" button.
|
||||
You can find it in the lower left of the screen. Then, hit the "Start Windows 95"
|
||||
button to start your virtual machine again.
|
||||
|
||||
## I want to install additional apps or games
|
||||
|
||||
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 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. 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
|
||||
33
LICENSE.md
33
LICENSE.md
@@ -4,4 +4,35 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
____
|
||||
|
||||
v86 Source Code
|
||||
|
||||
Copyright (c) 2012-2018, Fabian Hemmer
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
|
||||
29
README.md
29
README.md
@@ -1,13 +1,17 @@
|
||||
# windows95
|
||||
|
||||
This is Windows 95, running in an Electron app. Yes, it's the full thing. I'm sorry.
|
||||
This is Windows 95, running in an [Electron](https://electronjs.org/) app. Yes, it's the full thing. I'm sorry.
|
||||
|
||||
## 💿⏬ [Download it here](https://github.com/felixrieseberg/windows95/releases).
|
||||
## Downloads
|
||||
| | Windows | macOS | Linux |
|
||||
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Standalone Download | 📦[Standalone, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-2.1.0-win32-standalone-ia32.zip) <br /> 📦[Standalone, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-2.1.0-win32-standalone-x64.zip) | 📦[Standalone](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-macos-2.1.0.zip) | |
|
||||
| Installer | 💽[Setup, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-2.1.0-setup-win32-x64.exe) <br /> 💽[Setup, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-2.1.0-setup-win32-ia32.exe) | | 💽[deb, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-linux-2.1.0_amd64.deb) <br /> 💽[rpm, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.1.0/windows95-linux-2.1.0.x86_64.rpm) |
|
||||
|
||||

|
||||
|
||||
## Does it work?
|
||||
Yes! Quite well, actually.
|
||||
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.
|
||||
@@ -17,27 +21,32 @@ 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.
|
||||
|
||||
## Credits
|
||||
|
||||
99.999% of the work was done over at [v86](https://github.com/copy/v86/) by Copy.
|
||||
|
||||
## Contributing
|
||||
|
||||
Before you can run this from source, you'll need the disk and state images. They're not part of the repo,
|
||||
but [you can download them here](https://mega.nz/#!euxygQBT!i03vtE4kYTgrZ1rjZa1gT2F8hvhcwIAgGBsY4ECjs0w).
|
||||
Before you can run this from source, you'll need the disk image. It's not part of the
|
||||
repository, but you can grab it using the `Show Disk Image` button from the packaged
|
||||
release, which does include the disk image.
|
||||
|
||||
Unpack the `images` folder into the `src/renderer` folder, creating this layout:
|
||||
Unpack the `images` folder into the `src` folder, creating this layout:
|
||||
|
||||
```
|
||||
./src/images/default-state.bin
|
||||
./src/images/windows95.img
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
This project is provided for educational purposes only. It is not affiliated with and has
|
||||
|
||||
BIN
assets/certs/apple.cer
Normal file
BIN
assets/certs/apple.cer
Normal file
Binary file not shown.
BIN
assets/certs/dac.cer
Normal file
BIN
assets/certs/dac.cer
Normal file
Binary file not shown.
39
docs/docker-instructions.md
Normal file
39
docs/docker-instructions.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Running windows95 in Docker
|
||||
|
||||
## Display using a volume mount of the host X11 Unix Socket (Linux Only):
|
||||
|
||||
**Requirements:**
|
||||
* Linux OS with a running X-Server Display
|
||||
* [Docker](http://docker.io)
|
||||
|
||||
docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY --device /dev/snd --name windows95 toolboc/windows95
|
||||
|
||||
|
||||
Note: You may need to run `xhost +` on your system to allow connections to the X server running on the host.
|
||||
|
||||
## Display using Xming X11 Server over tcp Socket (Windows and beyond):
|
||||
|
||||
**Requirements:**
|
||||
* [Xming](https://sourceforge.net/projects/xming/)
|
||||
* [Docker](http://docker.io)
|
||||
|
||||
1. Start the Xming X11 Server
|
||||
2. Run the command below:
|
||||
|
||||
docker run -e DISPLAY=host.docker.internal:0 --name windows95 toolboc/windows95
|
||||
|
||||
## Display using the host XQuartz Server (MacOS Only):
|
||||
**Requirements:**
|
||||
* [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
|
||||
```
|
||||
4
docs/docker-kubernetes-gitpod.md
Normal file
4
docs/docker-kubernetes-gitpod.md
Normal 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?
|
||||
|
||||
[](https://gitpod.io/#https://github.com/felixrieseberg/windows95)
|
||||
@@ -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',
|
||||
@@ -45,5 +45,18 @@ module.exports = {
|
||||
name: '@electron-forge/maker-rpm',
|
||||
platforms: ['linux']
|
||||
}
|
||||
],
|
||||
publishers: [
|
||||
{
|
||||
name: '@electron-forge/publisher-github',
|
||||
config: {
|
||||
repository: {
|
||||
owner: 'felixrieseberg',
|
||||
name: 'windows95'
|
||||
},
|
||||
draft: true,
|
||||
prerelease: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
3018
package-lock.json
generated
3018
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windows95",
|
||||
"productName": "windows95",
|
||||
"version": "1.2.0",
|
||||
"version": "2.1.1",
|
||||
"description": "Windows 95, in an app. I'm sorry.",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
@@ -28,18 +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.22",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.22",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.22",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.22",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.22",
|
||||
"electron": "3.0.0-beta.6",
|
||||
"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
24
src/constants.js
Normal 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
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
197
src/menu.js
197
src/menu.js
@@ -1,5 +1,14 @@
|
||||
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.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'
|
||||
}
|
||||
|
||||
function send (cmd) {
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
@@ -10,42 +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'
|
||||
})
|
||||
}
|
||||
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 = {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
@@ -13,7 +13,7 @@ export function setupButtons (start) {
|
||||
$('#discard-state').addEventListener('click', () => {
|
||||
window.appState.bootFresh = true
|
||||
|
||||
start('win95')
|
||||
start()
|
||||
})
|
||||
|
||||
// Floppy
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
44
src/renderer/ipc.js
Normal 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()
|
||||
})
|
||||
}
|
||||
26
src/renderer/lib/LICENSE.md
Normal file
26
src/renderer/lib/LICENSE.md
Normal file
@@ -0,0 +1,26 @@
|
||||
Copyright (c) 2012-2018, Fabian Hemmer
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
37
src/state.js
37
src/state.js
@@ -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,
|
||||
|
||||
26
src/utils/disk-image-size.js
Normal file
26
src/utils/disk-image-size.js
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user