mirror of
https://github.com/felixrieseberg/windows95.git
synced 2026-05-14 18:31:59 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bdbff6a4b | ||
|
|
e062548c81 | ||
|
|
609668c581 | ||
|
|
bec9577409 | ||
|
|
dd9ff5a319 | ||
|
|
f5125219fd | ||
|
|
b4fd81b364 | ||
|
|
e62a8cbed6 | ||
|
|
2038ce9c31 | ||
|
|
6c12063353 | ||
|
|
91783e7d26 | ||
|
|
9f8040bb22 | ||
|
|
c261079b67 | ||
|
|
6f337ac986 | ||
|
|
3189f3a8a2 | ||
|
|
5c1af3ae86 | ||
|
|
1ae2e5d546 | ||
|
|
7f3b23c5bf | ||
|
|
aa62c10700 | ||
|
|
86674b6090 | ||
|
|
90182076e6 | ||
|
|
10d2e37e9d | ||
|
|
e03e6148db | ||
|
|
59ee32806e | ||
|
|
9b32bf58d9 | ||
|
|
5bceb2e448 | ||
|
|
6918c425d6 | ||
|
|
8c3f608621 | ||
|
|
8b3117689e | ||
|
|
334a52b238 | ||
|
|
c11ae3caea | ||
|
|
1fbfca70b2 | ||
|
|
9c1ba25119 | ||
|
|
69d8b0d2be | ||
|
|
4f34053a84 | ||
|
|
5be27d7bab | ||
|
|
5ffbf4a106 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
node_modules
|
||||
out
|
||||
src/renderer/images
|
||||
src/images
|
||||
.DS_Store
|
||||
|
||||
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"]
|
||||
18
HELP.md
Normal file
18
HELP.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Help & Commonly Asked Questions
|
||||
|
||||
## 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 Windows 10, 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 Linux, search the Internet for instructions on how to mount an `img` disk
|
||||
image on your distribution.
|
||||
|
||||
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.
|
||||
|
||||
38
README.md
38
README.md
@@ -1,11 +1,47 @@
|
||||
# Windows95
|
||||
# windows95
|
||||
|
||||
This is Windows 95, running in an Electron app. Yes, it's the full thing. I'm sorry.
|
||||
|
||||
## 💿⏬ [Download it here](https://github.com/felixrieseberg/windows95/releases).
|
||||
|
||||

|
||||
|
||||
## Does it work?
|
||||
Yes! Quite well, actually.
|
||||
|
||||
## Should this have been a native app?
|
||||
Absolutely.
|
||||
|
||||
## Does it run Doom (or my other favorite game)?
|
||||
You'll likely be better off with an actual virtualization app, but the short answer is yes. [Thanks to
|
||||
@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 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` folder, creating this layout:
|
||||
|
||||
```
|
||||
./src/images/windows95.img
|
||||
```
|
||||
|
||||
Once you've done so, run `npm install` and `npm start` to run your local build.
|
||||
|
||||
## Other Questions
|
||||
|
||||
* [Running in Docker](./docs/docker-instructions.md)
|
||||
|
||||
## License
|
||||
|
||||
This project is provided for educational purposes only. It is not affiliated with and has
|
||||
|
||||
25
docs/docker-instructions.md
Normal file
25
docs/docker-instructions.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Running Windows 95 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. Obtain the ip of the host machine running the Xming server
|
||||
3. Edit X0.hosts (Located in the install directory of Xming) by adding the ip of the host machine obtained in step 2
|
||||
4. Run the command below and replace the `<XmingServerHostIp>` placeholder with the ip from step 2
|
||||
|
||||
docker run -it -e DISPLAY=<XmingServerHostIp> --name windows95 toolboc/windows95
|
||||
@@ -3,7 +3,9 @@ const package = require('./package.json');
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
asar: true,
|
||||
asar: {
|
||||
unpack: '**/images/*.img'
|
||||
},
|
||||
icon: path.resolve(__dirname, 'assets', 'icon'),
|
||||
appBundleId: 'com.felixrieseberg.windows95',
|
||||
appCategoryType: 'public.app-category.developer-tools',
|
||||
@@ -20,7 +22,7 @@ module.exports = {
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
platforms: ['win32'],
|
||||
config: {
|
||||
name: 'windows98',
|
||||
name: 'windows95',
|
||||
authors: 'Felix Rieseberg',
|
||||
exe: 'windows95.exe',
|
||||
noMsi: true,
|
||||
@@ -42,6 +44,10 @@ module.exports = {
|
||||
{
|
||||
name: '@electron-forge/maker-rpm',
|
||||
platforms: ['linux']
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-flatpak',
|
||||
platforms: ['linux']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
7
issue_template.md
Normal file
7
issue_template.md
Normal file
@@ -0,0 +1,7 @@
|
||||
⚠️ Thank you for reporting an issue!
|
||||
|
||||
Before we go any further, understand that I probably won't be able to fullfil feature requests.
|
||||
Feel free to report what feature you'd love to see, just don't get angry when I don't have
|
||||
time to implement it 🙇♂️
|
||||
|
||||
I will however _gladly_ help you make a pull request if you're willing to play with Javascript!
|
||||
247
package-lock.json
generated
247
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windows95",
|
||||
"version": "1.0.0",
|
||||
"version": "1.3.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -291,6 +291,32 @@
|
||||
"electron-installer-debian": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"@electron-forge/maker-flatpak": {
|
||||
"version": "6.0.0-beta.22",
|
||||
"resolved": "https://registry.npmjs.org/@electron-forge/maker-flatpak/-/maker-flatpak-6.0.0-beta.22.tgz",
|
||||
"integrity": "sha512-TsYSEz2ZlCUrEfpH7oJYoczpYryYSr8px7v5J3kbzEq3MLG7UDYxgsyrRHB5MKq1hLjkFc3g0GOAPuXUO+ShGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron-forge/maker-base": "6.0.0-beta.22",
|
||||
"@electron-forge/shared-types": "6.0.0-beta.22",
|
||||
"electron-installer-flatpak": "^0.8.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"pify": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
|
||||
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@electron-forge/maker-rpm": {
|
||||
"version": "6.0.0-beta.22",
|
||||
"resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-6.0.0-beta.22.tgz",
|
||||
@@ -1424,6 +1450,122 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-installer-flatpak": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-flatpak/-/electron-installer-flatpak-0.8.0.tgz",
|
||||
"integrity": "sha1-jt0Xjg4E7C2g+kDnd7Z8nMt4d18=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"asar": "^0.12.0",
|
||||
"async": "^2.0.0",
|
||||
"debug": "^2.2.0",
|
||||
"flatpak-bundler": "^0.1.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"lodash": "^4.13.0",
|
||||
"temp": "^0.8.3",
|
||||
"yargs": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"asar": {
|
||||
"version": "0.12.4",
|
||||
"resolved": "https://registry.npmjs.org/asar/-/asar-0.12.4.tgz",
|
||||
"integrity": "sha1-LdPxFoguq4wPI7dUeSqCp9n84XE=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"commander": "^2.9.0",
|
||||
"cuint": "^0.2.1",
|
||||
"glob": "^6.0.4",
|
||||
"minimatch": "^3.0.3",
|
||||
"mkdirp": "^0.5.0",
|
||||
"mksnapshot": "^0.3.0",
|
||||
"tmp": "0.0.28"
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
|
||||
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^2.1.0",
|
||||
"klaw": "^1.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"rimraf": "^2.2.8"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
|
||||
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
|
||||
"integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"camelcase": "^3.0.0",
|
||||
"cliui": "^3.2.0",
|
||||
"decamelize": "^1.1.1",
|
||||
"get-caller-file": "^1.0.1",
|
||||
"os-locale": "^1.4.0",
|
||||
"read-pkg-up": "^1.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^1.0.1",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^1.0.2",
|
||||
"which-module": "^1.0.0",
|
||||
"y18n": "^3.2.1",
|
||||
"yargs-parser": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz",
|
||||
"integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"camelcase": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-installer-redhat": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-0.5.0.tgz",
|
||||
@@ -1531,6 +1673,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",
|
||||
@@ -1792,6 +1939,23 @@
|
||||
"is-symbol": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
|
||||
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -2259,6 +2423,56 @@
|
||||
"write": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"flatpak-bundler": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/flatpak-bundler/-/flatpak-bundler-0.1.3.tgz",
|
||||
"integrity": "sha1-5F36DEp0hcNw4JFeRyiSl5hQOUY=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"debug": "^2.2.0",
|
||||
"es6-promisify": "^5.0.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"lodash": "^4.16.2",
|
||||
"tmp": "0.0.29"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
|
||||
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^2.1.0",
|
||||
"klaw": "^1.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"rimraf": "^2.2.8"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
|
||||
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz",
|
||||
"integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flora-colossus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.0.tgz",
|
||||
@@ -2545,6 +2759,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 +3123,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 +4877,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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windows95",
|
||||
"productName": "windows95",
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0",
|
||||
"description": "Windows 95, in an app. I'm sorry.",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
@@ -19,6 +19,7 @@
|
||||
},
|
||||
"standard": {
|
||||
"globals": [
|
||||
"appState",
|
||||
"V86Starter",
|
||||
"windows95"
|
||||
],
|
||||
@@ -29,11 +30,13 @@
|
||||
"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",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.22",
|
||||
"@electron-forge/maker-flatpak": "^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",
|
||||
|
||||
29
src/es6.js
Normal file
29
src/es6.js
Normal file
@@ -0,0 +1,29 @@
|
||||
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://', ''))
|
||||
.replace('.js/', '.js')
|
||||
.replace('.js\\', '.js')
|
||||
|
||||
const fileContent = await fs.readFile(filePath)
|
||||
|
||||
cb({ mimeType: 'text/javascript', data: fileContent }) // eslint-disable-line
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupProtocol
|
||||
}
|
||||
14
src/index.js
14
src/index.js
@@ -3,11 +3,19 @@ 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()
|
||||
}
|
||||
|
||||
if (app.isPackaged) {
|
||||
require('update-electron-app')({
|
||||
repo: 'felixrieseberg/windows95',
|
||||
updateInterval: '1 hour'
|
||||
})
|
||||
}
|
||||
|
||||
let mainWindow
|
||||
|
||||
const createWindow = () => {
|
||||
@@ -16,13 +24,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 +38,10 @@ const createWindow = () => {
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
await setupProtocol()
|
||||
await createMenu()
|
||||
await clearCaches()
|
||||
|
||||
createWindow()
|
||||
})
|
||||
|
||||
|
||||
82
src/menu.js
82
src/menu.js
@@ -1,8 +1,88 @@
|
||||
const { app, shell, Menu } = require('electron')
|
||||
const { app, shell, Menu, BrowserWindow } = require('electron')
|
||||
const defaultMenu = require('electron-default-menu')
|
||||
|
||||
const LINKS = {
|
||||
homepage: 'https://www.felixrieseberg.com',
|
||||
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()
|
||||
|
||||
if (windows[0]) {
|
||||
windows[0].webContents.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)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
.filter((item) => {
|
||||
return item.label !== 'Edit'
|
||||
})
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
const { remote } = require('electron')
|
||||
const fs = require('fs-extra')
|
||||
const { remote, shell, ipcRenderer } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
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()
|
||||
}
|
||||
showDiskImage () {
|
||||
const imagePath = path.join(__dirname, 'images/windows95.img')
|
||||
.replace('app.asar', 'app.asar.unpacked')
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
shell.showItemInFolder(imagePath)
|
||||
},
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
8
src/renderer/app-state.js
Normal file
8
src/renderer/app-state.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export function setupState () {
|
||||
window.appState = {
|
||||
cursorCaptured: false,
|
||||
floppyFile: null,
|
||||
bootFresh: false,
|
||||
infoInterval: null
|
||||
}
|
||||
}
|
||||
44
src/renderer/buttons.js
Normal file
44
src/renderer/buttons.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const $ = document.querySelector.bind(document)
|
||||
|
||||
export function setupButtons (start) {
|
||||
// Start
|
||||
$('.btn-start').addEventListener('click', () => start())
|
||||
|
||||
// Disk Image
|
||||
$('#show-disk-image').addEventListener('click', () => windows95.showDiskImage())
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@
|
||||
</head>
|
||||
|
||||
<body class="paused">
|
||||
<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>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<div id="start-buttons">
|
||||
<!-- <div class="btn" id="win98">Windows 98</div> -->
|
||||
@@ -26,14 +31,15 @@
|
||||
<div class="btn" id="reset">Reset Machine & Delete State</div>
|
||||
<div class="btn" id="floppy">Insert Floppy Disk</div>
|
||||
<div class="btn" id="discard-state">Discard State & Boot From Scratch</div>
|
||||
<div class="btn" id="show-disk-image">Show Disk Image</div>
|
||||
<input id="file-input" type='file'>
|
||||
</div>
|
||||
<div id="information">
|
||||
<p id="floppy-path"></p>
|
||||
<p>You can insert a floppy disk image with the ".img" format.</p>
|
||||
<p>
|
||||
Boot the machine from scratch if you've inserted a new floppy disk to
|
||||
make sure that Windows can load it.
|
||||
Boot the machine from scratch if you've inserted a new floppy disk
|
||||
or if you've changed the disk image.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,8 +47,8 @@
|
||||
<div style="white-space: pre; font: 14px monospace; line-height: 14px"></div>
|
||||
<canvas style="display: none"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
require('./renderer.js')
|
||||
<script type="module">
|
||||
import("es6://renderer.js")
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
36
src/renderer/info.js
Normal file
36
src/renderer/info.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const $ = document.querySelector.bind(document)
|
||||
|
||||
export function setupInfo () {
|
||||
const diskStatus = $('#disk-status')
|
||||
const cpuStatus = $('#cpu-status')
|
||||
let lastCounter = 0
|
||||
let lastTick = 0
|
||||
|
||||
window.emulator.add_listener('ide-read-start', () => {
|
||||
diskStatus.innerHTML = 'Read'
|
||||
})
|
||||
|
||||
window.emulator.add_listener('ide-read-end', () => {
|
||||
diskStatus.innerHTML = 'Idle'
|
||||
})
|
||||
|
||||
window.emulator.add_listener('ide-write-end', () => {
|
||||
diskStatus.innerHTML = 'Idle'
|
||||
})
|
||||
|
||||
window.emulator.add_listener('screen-set-size-graphical', (...args) => {
|
||||
console.log(...args)
|
||||
})
|
||||
|
||||
setInterval(() => {
|
||||
const now = Date.now()
|
||||
const instructionCounter = window.emulator.get_instruction_counter()
|
||||
const ips = instructionCounter - lastCounter
|
||||
const deltaTime = now - lastTick
|
||||
|
||||
lastTick = now
|
||||
lastCounter = instructionCounter
|
||||
|
||||
cpuStatus.innerHTML = Math.round(ips / deltaTime)
|
||||
}, 500)
|
||||
}
|
||||
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.
|
||||
41
src/renderer/listeners.js
Normal file
41
src/renderer/listeners.js
Normal file
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,24 +1,16 @@
|
||||
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'
|
||||
import { setupInfo } from 'es6://info.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,110 +19,48 @@ async function main (id) {
|
||||
vga_bios: {
|
||||
url: './bios/vgabios.bin'
|
||||
},
|
||||
hda: {
|
||||
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')) {
|
||||
const scale = window.devicePixelRatio
|
||||
|
||||
// Restore state. We can't do this right away.
|
||||
window.emulator.screen_adapter.set_scale(scale, scale)
|
||||
}
|
||||
|
||||
// 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
|
||||
setupInfo()
|
||||
|
||||
window.appState.cursorCaptured = true
|
||||
window.emulator.lock_mouse()
|
||||
window.emulator.run()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function start (id) {
|
||||
BUTTONS.remove()
|
||||
function start () {
|
||||
document.body.className = ''
|
||||
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
|
||||
}
|
||||
toggleButtons(false)
|
||||
setupClickListener()
|
||||
main()
|
||||
}
|
||||
|
||||
setupEscListener()
|
||||
setupCloseListener()
|
||||
setupButtons()
|
||||
setupButtons(start)
|
||||
|
||||
@@ -16,6 +16,28 @@ body.paused {
|
||||
font-family: Courier;
|
||||
}
|
||||
|
||||
#buttons {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#status {
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
left: calc(50vw - 110px);
|
||||
background: white;
|
||||
text-align: center;
|
||||
font-family: Courier;
|
||||
font-size: 10px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom-left-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
overflow: hidden;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
max-height: 18px;
|
||||
}
|
||||
|
||||
#floppy-path {
|
||||
background: beige;
|
||||
padding: 5px;
|
||||
|
||||
58
src/state.js
58
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<void>}
|
||||
*/
|
||||
async function resetState () {
|
||||
if (fs.existsSync(STATE_PATH)) {
|
||||
return fs.remove(STATE_PATH)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current VM's state.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user