mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-12-14 18:15:44 +00:00
Compare commits
138 commits
v0.0.8-alp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2445175248 | |||
| c19fb78b1f | |||
| 5d0b6e864a | |||
| b2c3158d72 | |||
| 1ba7185939 | |||
| e0afdf177c | |||
| 993b7f43f3 | |||
| 74c9e25ad6 | |||
| f5d6bbfaae | |||
| 2bf4b17577 | |||
| 990b30b7bb | |||
| 3326d0e73d | |||
| f7d4b728f5 | |||
| bf24c6e989 | |||
|
|
2b8eb54a8b | ||
|
|
b85adb960b | ||
| d8d6e76729 | |||
| 3fa0a73ac6 | |||
| fe9446e2e7 | |||
| 2f7c4fb032 | |||
| 55c4f70533 | |||
| 4880fea8e3 | |||
|
|
59bbf10e78 | ||
|
|
b44f943456 | ||
|
|
efe510d0da | ||
| 589042ca38 | |||
|
|
86c433e966 | ||
|
|
ad8a0dcb84 | ||
|
|
74b799a0b4 | ||
| e152203afd | |||
| 97161f0c85 | |||
| 7c799426a9 | |||
|
|
fa39689e3a | ||
|
|
b09ab4bdff | ||
|
|
756b8b35dd | ||
| e520383da3 | |||
| f5887c63c0 | |||
| 544025290b | |||
| 4b21daa095 | |||
| 1d28f07a7e | |||
| e07f79c31a | |||
| e926bd6d6b | |||
| f124520a2f | |||
| 451abe6b86 | |||
| 8121920ad7 | |||
| f782dbddb2 | |||
| 57195bcf0b | |||
| 63838e97c7 | |||
| 44437e84f1 | |||
| 4a9d2bad34 | |||
| 9fadf6d064 | |||
| 9abb828dbd | |||
| 7b07ec4f09 | |||
| a84aedbe38 | |||
|
|
b2e7969793 | ||
|
|
6d4161e251 | ||
|
|
b7a956bfd6 | ||
|
|
f9a88f39bb | ||
|
|
7628d4a29e | ||
|
|
d56a0d1a2a | ||
| c5fa0d44ca | |||
| 16223c9986 | |||
| 9aadc35329 | |||
| d137c46ed9 | |||
| 0e4d3c135a | |||
| 341ed38897 | |||
| 67d3ea6154 | |||
| 07da11fe40 | |||
| 9ed2cc1687 | |||
| eb58bdda14 | |||
| e6c70d43a0 | |||
| 085e2c2947 | |||
| 311b705fa9 | |||
| 96e1fb9223 | |||
| 1cf706f633 | |||
| d9f947c406 | |||
| 240efdc493 | |||
| 7090bc842c | |||
|
|
444652da12 | ||
|
|
4c78f8afff | ||
| fd8b38932b | |||
| 1c6419ff10 | |||
| 2a84c8cb35 | |||
| 191a2954c6 | |||
| f9b0a38fd2 | |||
| aee3434b44 | |||
| dd1834ad2b | |||
| 12272f13ab | |||
| e662e77fa5 | |||
| 8b32d524de | |||
|
|
70ff5ba20b | ||
|
|
adaf63c7fc | ||
|
|
ed66911489 | ||
|
|
65cb15f6c8 | ||
|
|
3f9f036b49 | ||
|
|
001ce2f626 | ||
|
|
ac9081cf89 | ||
|
|
dee550b28d | ||
|
|
03e0992328 | ||
|
|
1dbbb23ff6 | ||
|
|
aa63b19929 | ||
|
|
38478b3359 | ||
|
|
e606dfee95 | ||
|
|
40295112fe | ||
|
|
b86d330532 | ||
|
|
0e5a4895fa | ||
|
|
19d65651bd | ||
|
|
0a365f8b72 | ||
|
|
03f82a6796 | ||
|
|
fffbb76a5e | ||
| 6fd4f6ec39 | |||
|
|
9cc8377503 | ||
|
|
8703733b91 | ||
|
|
32d5299a3f | ||
|
|
9907232af7 | ||
|
|
9dd01041d0 | ||
|
|
dba8f8c600 | ||
| ee44873e07 | |||
|
|
2f701a6338 | ||
| 5045fa757c | |||
|
|
f7591e0d35 | ||
|
|
6cce546749 | ||
|
|
4017296426 | ||
|
|
dc51203d3e | ||
|
|
8cdf2148ed | ||
|
|
d744432a50 | ||
|
|
e37ed56a43 | ||
|
|
e3616e4ac6 | ||
|
|
5e95e74545 | ||
|
|
8e08c80d91 | ||
|
|
e24192fb26 | ||
|
|
d3bbedb30e | ||
|
|
6d9dbda7b5 | ||
|
|
34cf7a6bc4 | ||
|
|
1ca007b8d5 | ||
|
|
33037a40fc | ||
|
|
1b2ad30759 | ||
|
|
b477e74141 |
133 changed files with 10159 additions and 2127 deletions
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -30,9 +30,9 @@ A clear and concise description of what you expected to happen.
|
|||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
- OS: [e.g. Linux, Windows, OSX]
|
||||
- OS Version: [e.g. 22]
|
||||
- Capy64 Version: [e.g. 0.0.9-alpha]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
|
|
|||
76
.github/workflows/codeql.yml
vendored
Normal file
76
.github/workflows/codeql.yml
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '25 14 * * 2'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'csharp' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
35
.github/workflows/dotnet.yml
vendored
35
.github/workflows/dotnet.yml
vendored
|
|
@ -10,8 +10,33 @@ on:
|
|||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
|
||||
- name: Publish for Windows x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release --no-self-contained -a x64 --os win -p:PublishSingleFile=true -o publish/win-x64
|
||||
|
||||
- name: Upload Windows x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: Windows-x64
|
||||
path: publish/win-x64/**
|
||||
if-no-files-found: error
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
@ -29,8 +54,6 @@ jobs:
|
|||
|
||||
- name: Publish for Linux x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release --no-self-contained -a x64 --os linux -p:PublishSingleFile=true -o publish/linux-x64
|
||||
- name: Publish for Windows x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release --no-self-contained -a x64 --os win -p:PublishSingleFile=true -o publish/win-x64
|
||||
|
||||
- name: Upload Linux x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
|
|
@ -38,9 +61,3 @@ jobs:
|
|||
name: Linux-x64
|
||||
path: publish/linux-x64/**
|
||||
if-no-files-found: error
|
||||
- name: Upload Windows x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: Windows-x64
|
||||
path: publish/win-x64/**
|
||||
if-no-files-found: error
|
||||
|
|
|
|||
69
.github/workflows/main.yml
vendored
69
.github/workflows/main.yml
vendored
|
|
@ -1,69 +0,0 @@
|
|||
name: Publish release draft
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag
|
||||
required: true
|
||||
prerelease:
|
||||
description: Prerelease
|
||||
type: boolean
|
||||
generatenotes:
|
||||
description: Generate release notes
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
|
||||
- name: Publish for Linux x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release --no-self-contained -a x64 --os linux -p:PublishSingleFile=true -o publish/linux-x64
|
||||
- name: Publish for Windows x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release --no-self-contained -a x64 --os win -p:PublishSingleFile=true -o publish/win-x64
|
||||
|
||||
- name: Upload Linux x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: Linux-x64
|
||||
path: publish/linux-x64/**
|
||||
if-no-files-found: error
|
||||
- name: Upload Windows x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: Windows-x64
|
||||
path: publish/win-x64/**
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Zip Linux x64
|
||||
run: 7z a capy64-linux-x64.zip publish/linux-x64/**
|
||||
- name: Zip Windows x64
|
||||
run: 7z a capy64-windows-x64.zip publish/win-x64/**
|
||||
|
||||
- name: Create Release
|
||||
uses: ncipollo/release-action@v1.12.0
|
||||
with:
|
||||
commit: ${{ github.ref_name }}
|
||||
tag: ${{ inputs.tag }}
|
||||
allowUpdates: true
|
||||
artifactErrorsFailBuild: true
|
||||
artifacts: "capy64-linux-x64.zip,capy64-windows-x64.zip"
|
||||
draft: true
|
||||
generateReleaseNotes: ${{ inputs.generatenotes }}
|
||||
makeLatest: true
|
||||
prerelease: ${{ inputs.prerelease }}
|
||||
97
.github/workflows/publish.yml
vendored
Normal file
97
.github/workflows/publish.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
name: Publish release draft
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag
|
||||
required: true
|
||||
prerelease:
|
||||
description: Prerelease
|
||||
type: boolean
|
||||
generatenotes:
|
||||
description: Generate release notes
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
release-windows:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
|
||||
- name: Publish for Windows x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release -a x64 --os win -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:TieredCompilation=false -o capy64-windows-x64 --self-contained
|
||||
|
||||
- name: Upload Windows x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: Windows-x64
|
||||
path: capy64-windows-x64/**
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Zip Windows x64
|
||||
run: 7z a capy64-windows-x64.zip capy64-windows-x64/**
|
||||
|
||||
release-linux:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
|
||||
- name: Publish for Linux x64
|
||||
run: dotnet publish Capy64/Capy64.csproj -c Release -a x64 --os linux -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:TieredCompilation=false -o capy64-linux-x64 --self-contained
|
||||
|
||||
- name: Upload Linux x64 artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: Linux-x64
|
||||
path: capy64-linux-x64/**
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Zip Linux x64
|
||||
run: 7z a capy64-linux-x64.zip capy64-linux-x64/**
|
||||
|
||||
publish:
|
||||
needs: [release-windows, release-linux]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Create Release
|
||||
uses: ncipollo/release-action@v1.12.0
|
||||
with:
|
||||
commit: ${{ github.ref_name }}
|
||||
tag: ${{ inputs.tag }}
|
||||
allowUpdates: true
|
||||
artifactErrorsFailBuild: true
|
||||
artifacts: "capy64-linux-x64.zip,capy64-windows-x64.zip"
|
||||
draft: true
|
||||
generateReleaseNotes: ${{ inputs.generatenotes }}
|
||||
makeLatest: true
|
||||
prerelease: ${{ inputs.prerelease }}
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
capy64@alexdevs.me.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
179
Capy64/.editorconfig
Normal file
179
Capy64/.editorconfig
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
[*.{cs,vb,lua}]
|
||||
#### Top of file license comment
|
||||
file_header_template = This file is part of Capy64 - https://github.com/Ale32bit/Capy64\nCopyright 2023 Alessandro "AlexDevs" Proto\n\nLicensed under the Apache License, Version 2.0 (the "License").\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.
|
||||
|
||||
[*.cs]
|
||||
#### Stili di denominazione ####
|
||||
|
||||
# Regole di denominazione
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Specifiche dei simboli
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Stili di denominazione
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:silent
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
csharp_style_var_when_type_is_apparent = false:silent
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
|
||||
[*.vb]
|
||||
#### Stili di denominazione ####
|
||||
|
||||
# Regole di denominazione
|
||||
|
||||
dotnet_naming_rule.interface_should_be_inizia_con_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_inizia_con_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_inizia_con_i.style = inizia_con_i
|
||||
|
||||
dotnet_naming_rule.tipi_should_be_notazione_pascal.severity = suggestion
|
||||
dotnet_naming_rule.tipi_should_be_notazione_pascal.symbols = tipi
|
||||
dotnet_naming_rule.tipi_should_be_notazione_pascal.style = notazione_pascal
|
||||
|
||||
dotnet_naming_rule.membri_non_di_campo_should_be_notazione_pascal.severity = suggestion
|
||||
dotnet_naming_rule.membri_non_di_campo_should_be_notazione_pascal.symbols = membri_non_di_campo
|
||||
dotnet_naming_rule.membri_non_di_campo_should_be_notazione_pascal.style = notazione_pascal
|
||||
|
||||
# Specifiche dei simboli
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.tipi.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.tipi.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.tipi.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.membri_non_di_campo.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.membri_non_di_campo.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.membri_non_di_campo.required_modifiers =
|
||||
|
||||
# Stili di denominazione
|
||||
|
||||
dotnet_naming_style.inizia_con_i.required_prefix = I
|
||||
dotnet_naming_style.inizia_con_i.required_suffix =
|
||||
dotnet_naming_style.inizia_con_i.word_separator =
|
||||
dotnet_naming_style.inizia_con_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.notazione_pascal.required_prefix =
|
||||
dotnet_naming_style.notazione_pascal.required_suffix =
|
||||
dotnet_naming_style.notazione_pascal.word_separator =
|
||||
dotnet_naming_style.notazione_pascal.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.notazione_pascal.required_prefix =
|
||||
dotnet_naming_style.notazione_pascal.required_suffix =
|
||||
dotnet_naming_style.notazione_pascal.word_separator =
|
||||
dotnet_naming_style.notazione_pascal.capitalization = pascal_case
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = false:silent
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||
dotnet_code_quality_unused_parameters = all:suggestion
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_event = false:silent
|
||||
23
Capy64/API/IComponent.cs
Normal file
23
Capy64/API/IComponent.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using KeraLua;
|
||||
|
||||
namespace Capy64.API;
|
||||
|
||||
public interface IComponent
|
||||
{
|
||||
void LuaInit(Lua L) { }
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using KeraLua;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Capy64.API;
|
||||
|
||||
public interface IPlugin
|
||||
{
|
||||
void ConfigureServices(IServiceCollection services) { }
|
||||
void LuaInit(Lua L) { }
|
||||
|
||||
}
|
||||
7
Capy64/Assets/Lua/CapyOS/home/.shrc
Normal file
7
Capy64/Assets/Lua/CapyOS/home/.shrc
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
alias ll "ls -al"
|
||||
alias la "ls -a"
|
||||
alias rmdir "rm -r"
|
||||
alias reboot "shutdown -r"
|
||||
|
||||
# Comment or remove the line below to disable the MOTD
|
||||
motd
|
||||
46
Capy64/Assets/Lua/CapyOS/init.lua
Normal file
46
Capy64/Assets/Lua/CapyOS/init.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
local version = "0.1.1"
|
||||
local systemDirectory = "/sys"
|
||||
|
||||
print("Starting CapyOS")
|
||||
|
||||
local term = require("term")
|
||||
local fs = require("fs")
|
||||
local machine = require("machine")
|
||||
|
||||
local nPrint = print
|
||||
local function showError(err)
|
||||
nPrint(err)
|
||||
local x, y = term.getPos()
|
||||
term.setForeground(0xff0000)
|
||||
term.setPos(1, y)
|
||||
term.write(err)
|
||||
|
||||
term.setPos(1, y + 1)
|
||||
term.setForeground(0xffffff)
|
||||
term.write("Press any key to continue")
|
||||
|
||||
coroutine.yield("key_down")
|
||||
end
|
||||
|
||||
function os.version()
|
||||
return "CapyOS " .. version
|
||||
end
|
||||
|
||||
term.setPos(1, 1)
|
||||
term.write(machine.version())
|
||||
term.setPos(1, 3)
|
||||
|
||||
local files = fs.list(fs.combine(systemDirectory, "boot/autorun"))
|
||||
for i = 1, #files do
|
||||
local func, err = loadfile(fs.combine(systemDirectory, "boot/autorun", files[i]))
|
||||
if not func then
|
||||
showError(err)
|
||||
break
|
||||
end
|
||||
|
||||
local ok, err = pcall(func)
|
||||
if not ok then
|
||||
showError(debug.traceback(err))
|
||||
break
|
||||
end
|
||||
end
|
||||
28
Capy64/Assets/Lua/CapyOS/sys/bin/alias.lua
Normal file
28
Capy64/Assets/Lua/CapyOS/sys/bin/alias.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
|
||||
if options.l or options.list then
|
||||
for alias, value in pairs(shell.aliases) do
|
||||
print(string.format("%s = \"%s\"", alias, value))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local alias = args[1]
|
||||
|
||||
if not alias or options.h or options.help then
|
||||
print("Usage: alias [option...] <alias> [command]")
|
||||
print("Options:")
|
||||
print(" -l --list: List aliases")
|
||||
return false
|
||||
end
|
||||
|
||||
local command = table.pack(select(2, ...))
|
||||
if #command == 0 then
|
||||
shell.aliases[alias] = nil
|
||||
return
|
||||
end
|
||||
|
||||
shell.aliases[alias] = table.concat(command, " ")
|
||||
4
Capy64/Assets/Lua/CapyOS/sys/bin/bg.lua
Normal file
4
Capy64/Assets/Lua/CapyOS/sys/bin/bg.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
local scheduler = require("scheduler")
|
||||
scheduler.spawn(function()
|
||||
shell.run(arg.string)
|
||||
end)
|
||||
9
Capy64/Assets/Lua/CapyOS/sys/bin/cat.lua
Normal file
9
Capy64/Assets/Lua/CapyOS/sys/bin/cat.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local fs = require("fs")
|
||||
|
||||
local args = {...}
|
||||
|
||||
local path = shell.resolve(args[1])
|
||||
|
||||
local f<close> = fs.open(path, "r")
|
||||
print(f:read("a"))
|
||||
f:close()
|
||||
17
Capy64/Assets/Lua/CapyOS/sys/bin/cd.lua
Normal file
17
Capy64/Assets/Lua/CapyOS/sys/bin/cd.lua
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
local args = {...}
|
||||
local fs = require("fs")
|
||||
|
||||
local dir = args[1]
|
||||
|
||||
if not dir then
|
||||
dir = shell.homePath
|
||||
end
|
||||
|
||||
dir = shell.resolve(dir)
|
||||
|
||||
if not fs.isDir(dir) then
|
||||
error("No such directory: " .. dir, 0)
|
||||
return false
|
||||
end
|
||||
|
||||
shell.setDir(dir)
|
||||
5
Capy64/Assets/Lua/CapyOS/sys/bin/clear.lua
Normal file
5
Capy64/Assets/Lua/CapyOS/sys/bin/clear.lua
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
term.setBackground(colors.black)
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
4
Capy64/Assets/Lua/CapyOS/sys/bin/echo.lua
Normal file
4
Capy64/Assets/Lua/CapyOS/sys/bin/echo.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
print(table.concat(args, " "))
|
||||
1
Capy64/Assets/Lua/CapyOS/sys/bin/exit.lua
Normal file
1
Capy64/Assets/Lua/CapyOS/sys/bin/exit.lua
Normal file
|
|
@ -0,0 +1 @@
|
|||
shell.exit()
|
||||
40
Capy64/Assets/Lua/CapyOS/sys/bin/fun/donuts.lua
Normal file
40
Capy64/Assets/Lua/CapyOS/sys/bin/fun/donuts.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local gpu = require("gpu")
|
||||
local event = require("event")
|
||||
local donuts = {}
|
||||
local limit = 100
|
||||
|
||||
local w, h = gpu.getSize()
|
||||
local function insert()
|
||||
local donut = {
|
||||
x = math.random(-20, w + 20),
|
||||
y = math.random(-20, h + 20),
|
||||
d = math.random() * math.pi*2,
|
||||
dir = math.random(0, 1),
|
||||
c = math.random(0xffffff),
|
||||
life = math.random(100, 1000),
|
||||
}
|
||||
table.insert(donuts, donut)
|
||||
end
|
||||
|
||||
while true do
|
||||
if #donuts < limit then
|
||||
insert()
|
||||
end
|
||||
gpu.clear(0)
|
||||
for k, donut in ipairs(donuts) do
|
||||
if donut.life <= 0 then
|
||||
table.remove(donuts, k)
|
||||
end
|
||||
local doReverse = math.random(0, 1000) > 950
|
||||
donut.x = donut.x + math.cos(donut.d) * 4
|
||||
donut.y = donut.y + math.sin(donut.d) * 4
|
||||
donut.d = donut.d + (donut.dir == 1 and 0.05 or -0.05)
|
||||
gpu.drawCircle(donut.x, donut.y, 20, donut.c, 10)
|
||||
if doReverse then
|
||||
donut.dir = donut.dir == 1 and 0 or 1
|
||||
end
|
||||
donut.life = donut.life - 1
|
||||
end
|
||||
event.push("donuts")
|
||||
event.pull("donuts")
|
||||
end
|
||||
98
Capy64/Assets/Lua/CapyOS/sys/bin/fun/mandelbrot.lua
Normal file
98
Capy64/Assets/Lua/CapyOS/sys/bin/fun/mandelbrot.lua
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
-- Mandelbrot in Capy64
|
||||
|
||||
local gpu = require("gpu")
|
||||
local timer = require("timer")
|
||||
local event = require("event")
|
||||
local term = require("term")
|
||||
|
||||
-- lower = closer = slower
|
||||
local scale = 4
|
||||
|
||||
-- higher = more detailed = slower
|
||||
local iterations = 100
|
||||
|
||||
local pscale = scale
|
||||
local w, h = gpu.getSize()
|
||||
local px = pscale / w
|
||||
local colorUnit = math.floor(0xffffff / iterations)
|
||||
-- todo: make it interactive
|
||||
local dx, dy = 0, 0
|
||||
local cx, cy = math.floor(w / 2), math.floor(h / 2)
|
||||
|
||||
-- z = z^2 + c
|
||||
local function mandelbrot(zr, zi, cr, ci)
|
||||
return zr ^ 2 - zi ^ 2 + cr,
|
||||
2 * zr * zi + ci
|
||||
end
|
||||
|
||||
local function iter(cr, ci)
|
||||
local zr, zi = 0, 0
|
||||
for i = 1, iterations do
|
||||
zr, zi = mandelbrot(zr, zi, cr, ci)
|
||||
if math.abs(zr) >= (pscale >= 2 and pscale or 2) or math.abs(zi) >= (pscale >= 2 and pscale or 2) then
|
||||
return false, colorUnit, i
|
||||
end
|
||||
end
|
||||
|
||||
return true, 0, iterations
|
||||
end
|
||||
|
||||
local function draw()
|
||||
local size = w * h
|
||||
local canvas = { string.unpack(("B"):rep(size), ("\0"):rep(size)) }
|
||||
canvas[#canvas] = nil
|
||||
|
||||
|
||||
for y = 0, h - 1 do
|
||||
for x = 0, w - 1 do
|
||||
local _, _, i = iter((x - cx + dx * pscale) * px, (y - cy + dy * pscale) * px)
|
||||
canvas[y * w + x] = colorUnit * (iterations - i)
|
||||
end
|
||||
end
|
||||
|
||||
local buffer <close> = gpu.bufferFrom(canvas, w, h)
|
||||
gpu.setBuffer(buffer)
|
||||
end
|
||||
|
||||
-- no idea why it's needed
|
||||
timer.sleep(0)
|
||||
|
||||
draw()
|
||||
|
||||
local tw, th = term.getSize()
|
||||
|
||||
while true do
|
||||
term.setPos(1, th)
|
||||
term.setBackground(0)
|
||||
term.setForeground(0xffffff)
|
||||
term.write("X: " .. dx .. "; Y: " .. dy .. "; S: " .. pscale .. "; " .. px .. "!")
|
||||
local ev = { event.pull("key_down") }
|
||||
if ev[1] == "key_down" then
|
||||
local key = ev[3]
|
||||
if key == "up" then
|
||||
dy = dy - 10 / pscale
|
||||
elseif key == "down" then
|
||||
dy = dy + 10 / pscale
|
||||
elseif key == "right" then
|
||||
dx = dx + 10 / pscale
|
||||
elseif key == "left" then
|
||||
dx = dx - 10 / pscale
|
||||
elseif key == "enter" then
|
||||
draw()
|
||||
elseif key == "page_down" then
|
||||
pscale = pscale * 1.25
|
||||
dx = dx * pscale
|
||||
dy = dy * pscale
|
||||
elseif key == "page_up" then
|
||||
pscale = pscale / 1.25
|
||||
dx = dx / pscale
|
||||
dy = dy / pscale
|
||||
elseif key == "r" then
|
||||
pscale = scale
|
||||
dx = 0
|
||||
dy = 0
|
||||
end
|
||||
end
|
||||
|
||||
px = pscale / w
|
||||
end
|
||||
73
Capy64/Assets/Lua/CapyOS/sys/bin/fun/melt.lua
Normal file
73
Capy64/Assets/Lua/CapyOS/sys/bin/fun/melt.lua
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
local gpu = require("gpu")
|
||||
local timer = require("timer")
|
||||
local event = require("event")
|
||||
local colors = require("colors")
|
||||
local parallel = require("parallel")
|
||||
|
||||
local melts = 2 ^ 12
|
||||
|
||||
local function contains(arr, val)
|
||||
for k, v in ipairs(arr) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function melt()
|
||||
local w, h = gpu.getSize()
|
||||
local x, y = 0, 0
|
||||
|
||||
while true do
|
||||
local buffer <close> = gpu.getBuffer()
|
||||
for i = 1, melts do
|
||||
local nx = math.random(x, w)
|
||||
local ny = math.random(y, h)
|
||||
|
||||
local c = buffer[ny * w + nx]
|
||||
buffer[(ny + 1) * w + nx] = c
|
||||
end
|
||||
gpu.setBuffer(buffer)
|
||||
|
||||
timer.delay(0):await()
|
||||
end
|
||||
end
|
||||
|
||||
local function draw()
|
||||
local ox, oy
|
||||
while true do
|
||||
local ev, b, x, y = event.pull("mouse_move", "mouse_down")
|
||||
|
||||
if ev == "mouse_down" then
|
||||
if b == 1 then
|
||||
ox = x
|
||||
oy = y
|
||||
end
|
||||
elseif ev == "mouse_move" then
|
||||
if contains(b, 1) then
|
||||
gpu.plot(x, y, colors.red)
|
||||
gpu.drawLine(x, y, ox, oy, colors.red)
|
||||
ox, oy = x, y
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function random()
|
||||
local w, h = gpu.getSize()
|
||||
while true do
|
||||
for i = 1, 24 do
|
||||
gpu.drawString(
|
||||
math.random(-7, w),
|
||||
math.random(-13, h),
|
||||
math.random(0, 0xffffff),
|
||||
string.char(math.random(32, 127))
|
||||
)
|
||||
end
|
||||
|
||||
timer.delay(0.1):await()
|
||||
end
|
||||
end
|
||||
|
||||
parallel.waitForAny(draw, melt, random)
|
||||
209
Capy64/Assets/Lua/CapyOS/sys/bin/fun/paint.lua
Normal file
209
Capy64/Assets/Lua/CapyOS/sys/bin/fun/paint.lua
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
local event = require("event")
|
||||
local gpu = require("gpu")
|
||||
local colors = require("colors")
|
||||
local term = require("term")
|
||||
local timer = require("timer")
|
||||
|
||||
local w, h = gpu.getSize()
|
||||
local tw, th = term.getSize()
|
||||
|
||||
local selectedColor = 1
|
||||
local thickness = 4
|
||||
|
||||
local canvasW, canvasH = term.toRealPos(tw - 1, th + 1)
|
||||
canvasW = canvasW - 3
|
||||
local size = canvasW * canvasH
|
||||
local canvas = {string.unpack(("B"):rep(size), ("\0"):rep(size))}
|
||||
canvas[#canvas] = nil
|
||||
|
||||
local function drawCircle(buffer, x, y, radius, color)
|
||||
radius = math.max(0, radius)
|
||||
if radius == 0 then
|
||||
buffer[x + buffer.width * y] = color
|
||||
return
|
||||
end
|
||||
local width = buffer.width
|
||||
local height = buffer.height
|
||||
|
||||
local index = function(x, y)
|
||||
return y * width + x
|
||||
end
|
||||
|
||||
local isValid = function(x, y)
|
||||
return x >= 0 and x < width and y >= 0 and y < height
|
||||
end
|
||||
|
||||
local setPixel = function(x, y, color)
|
||||
if isValid(x, y) then
|
||||
buffer[index(x, y)] = color
|
||||
end
|
||||
end
|
||||
|
||||
local drawFilledCirclePoints = function(cx, cy, x, y)
|
||||
for dx = -x, x do
|
||||
for dy = -y, y do
|
||||
setPixel(cx + dx, cy + dy, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local drawCircleBresenham = function(cx, cy, radius)
|
||||
local x = 0
|
||||
local y = radius
|
||||
local d = 3 - 2 * radius
|
||||
drawFilledCirclePoints(cx, cy, x, y)
|
||||
while y >= x do
|
||||
x = x + 1
|
||||
if d > 0 then
|
||||
y = y - 1
|
||||
d = d + 4 * (x - y) + 10
|
||||
else
|
||||
d = d + 4 * x + 6
|
||||
end
|
||||
drawFilledCirclePoints(cx, cy, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
drawCircleBresenham(x, y, radius)
|
||||
end
|
||||
|
||||
local function drawLine(buffer, x0, y0, x1, y1, color, thickness)
|
||||
local width = canvasW
|
||||
local height = canvasH
|
||||
|
||||
local index = function(x, y)
|
||||
return y * width + x
|
||||
end
|
||||
|
||||
local isValid = function(x, y)
|
||||
return x >= 0 and x < width and y >= 0 and y < height
|
||||
end
|
||||
|
||||
local setPixel = function(x, y)
|
||||
if isValid(x, y) then
|
||||
buffer[index(x, y)] = color
|
||||
end
|
||||
end
|
||||
|
||||
local drawLineBresenham = function()
|
||||
local i = 0
|
||||
local dx = math.abs(x1 - x0)
|
||||
local dy = math.abs(y1 - y0)
|
||||
local sx = x0 < x1 and 1 or -1
|
||||
local sy = y0 < y1 and 1 or -1
|
||||
local err = dx - dy
|
||||
|
||||
local majorAxis = dx > dy
|
||||
|
||||
while x0 ~= x1 or y0 ~= y1 do
|
||||
for i = 0, thickness - 1 do
|
||||
if majorAxis then
|
||||
setPixel(x0, y0 + i)
|
||||
else
|
||||
setPixel(x0 + i, y0)
|
||||
end
|
||||
end
|
||||
|
||||
local err2 = 2 * err
|
||||
if err2 > -dy then
|
||||
err = err - dy
|
||||
x0 = x0 + sx
|
||||
end
|
||||
if err2 < dx then
|
||||
err = err + dx
|
||||
y0 = y0 + sy
|
||||
end
|
||||
|
||||
if i % 1024 == 0 then
|
||||
--event.push("paint")
|
||||
--event.pull("paint")
|
||||
--timer.sleep(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
drawLineBresenham()
|
||||
end
|
||||
|
||||
local function drawUI()
|
||||
term.setBackground(0)
|
||||
term.clear()
|
||||
for y = 1, 16 do
|
||||
term.setPos(tw - 1, y)
|
||||
term.setBackground(0)
|
||||
term.setForeground(colors[y])
|
||||
if selectedColor == y then
|
||||
term.setBackground(colors[y])
|
||||
term.write(" ")
|
||||
else
|
||||
term.write("##")
|
||||
end
|
||||
end
|
||||
term.setPos(tw - 1, 17)
|
||||
|
||||
if selectedColor == 0 then
|
||||
term.setBackground(colors.white)
|
||||
term.setForeground(0)
|
||||
else
|
||||
term.setBackground(0)
|
||||
term.setForeground(colors.white)
|
||||
end
|
||||
term.write("XX")
|
||||
|
||||
term.setPos(tw - 1, 18)
|
||||
term.setBackground(colors.black)
|
||||
term.setForeground(colors.white)
|
||||
term.write(thickness)
|
||||
|
||||
gpu.drawLine(canvasW + 1, 0, canvasW, canvasH, colors.gray, 2)
|
||||
|
||||
local b<close> = gpu.bufferFrom(canvas, canvasW, canvasH)
|
||||
gpu.drawBuffer(b, 0, 0, {
|
||||
source = {
|
||||
0, 0, canvasW, canvasH
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
local function contains(arr, val)
|
||||
for i, v in ipairs(arr) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local oldX, oldY
|
||||
while true do
|
||||
drawUI()
|
||||
|
||||
local ev, b, x, y = event.pull("mouse_down", "mouse_up", "mouse_move", "mouse_scroll")
|
||||
local tx, ty = term.fromRealPos(x, y)
|
||||
if ev == "mouse_up" then
|
||||
if x >= canvasW then
|
||||
if ty <= 16 then
|
||||
selectedColor = ty
|
||||
elseif ty == 17 then
|
||||
selectedColor = 0
|
||||
end
|
||||
end
|
||||
oldX, oldY = nil, nil
|
||||
elseif ev == "mouse_down" or (ev == "mouse_move" and contains(b, 1)) then
|
||||
if x < canvasW and y < canvasH then
|
||||
--canvas[x + y * canvasW] = colors[selectedColor] or 0
|
||||
--drawCircle(canvas, x, y, thickness - 2, colors[selectedColor])
|
||||
|
||||
drawLine(canvas, x, y, oldX or x, oldY or y, colors[selectedColor] or 0, thickness)
|
||||
--gpu.drawLine(x, y, oldX or x, oldY or y, colors[selectedColor] or 0)
|
||||
--canvas = gpu.getBuffer()
|
||||
|
||||
oldX, oldY = x, y
|
||||
end
|
||||
elseif ev == "mouse_scroll" then
|
||||
local x, y, b = b, x, y
|
||||
local tx, ty = term.fromRealPos(x, y)
|
||||
if x >= canvasW and ty == 18 then
|
||||
thickness = math.min(99, math.max(0, thickness - b))
|
||||
end
|
||||
end
|
||||
end
|
||||
23
Capy64/Assets/Lua/CapyOS/sys/bin/hello.lua
Normal file
23
Capy64/Assets/Lua/CapyOS/sys/bin/hello.lua
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
local timer = require("timer")
|
||||
local colors = require("colors")
|
||||
local term = require("term")
|
||||
|
||||
local function slowPrint(text, delay)
|
||||
for i = 1, #text do
|
||||
local ch = text:sub(i, i)
|
||||
io.write(ch)
|
||||
timer.sleep(delay)
|
||||
end
|
||||
print()
|
||||
end
|
||||
|
||||
local args = {...}
|
||||
local text = "Hello, World!"
|
||||
if #args > 0 then
|
||||
text = table.concat(args, " ")
|
||||
end
|
||||
|
||||
local color = colors[math.random(1, #colors)]
|
||||
|
||||
term.setForeground(color)
|
||||
slowPrint(text, 0.05)
|
||||
11
Capy64/Assets/Lua/CapyOS/sys/bin/help.lua
Normal file
11
Capy64/Assets/Lua/CapyOS/sys/bin/help.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local fs = require("fs")
|
||||
local helpPath = "/sys/share/help"
|
||||
|
||||
local topicName = arg[1] or "index"
|
||||
|
||||
if not fs.exists(fs.combine(helpPath, topicName)) then
|
||||
print(string.format("Topic \"%s\" not found.", topicName))
|
||||
return false
|
||||
end
|
||||
|
||||
shell.run("/sys/bin/less.lua", fs.combine(helpPath, topicName))
|
||||
78
Capy64/Assets/Lua/CapyOS/sys/bin/less.lua
Normal file
78
Capy64/Assets/Lua/CapyOS/sys/bin/less.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
local term = require("term")
|
||||
local keys = require("keys")
|
||||
local event = require("event")
|
||||
local fs = require("fs")
|
||||
local timer = require("timer")
|
||||
local colors = require("colors")
|
||||
|
||||
local filename = shell.resolve(arg[1])
|
||||
|
||||
local f<close> = fs.open(filename, "r")
|
||||
local lines = {}
|
||||
local lineMax = 0
|
||||
for line in f:lines() do
|
||||
table.insert(lines, line)
|
||||
lineMax = math.max(lineMax, #line)
|
||||
end
|
||||
f:close()
|
||||
|
||||
|
||||
local width, height = term.getSize()
|
||||
height = height - 1
|
||||
local posx, posy = 0, 0
|
||||
|
||||
local function redraw()
|
||||
term.clear()
|
||||
term.setForeground(colors.white)
|
||||
for i = 1, height do
|
||||
if i + posy > #lines then
|
||||
break
|
||||
end
|
||||
term.setPos(-posx + 1, i)
|
||||
term.write(lines[i + posy])
|
||||
end
|
||||
|
||||
term.setForeground(colors.yellow)
|
||||
term.setPos(1, height + 1)
|
||||
term.write("Use arrow keys to move or press Q to exit.")
|
||||
end
|
||||
|
||||
while true do
|
||||
redraw()
|
||||
|
||||
local _, key = event.pull("key_down")
|
||||
|
||||
if key == keys.enter or key == keys.down then
|
||||
posy = posy + 1
|
||||
elseif key == keys.up then
|
||||
posy = posy - 1
|
||||
elseif key == keys.right then
|
||||
posx = posx + 1
|
||||
elseif key == keys.left then
|
||||
posx = posx - 1
|
||||
elseif key == keys.q or key == keys.escape then
|
||||
-- Clear event queue
|
||||
timer.sleep(0)
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
break
|
||||
end
|
||||
|
||||
|
||||
|
||||
if posy > #lines - height then
|
||||
posy = #lines - height
|
||||
end
|
||||
|
||||
if posy < 0 then
|
||||
posy = 0
|
||||
end
|
||||
|
||||
if posx + width > lineMax + 1 then
|
||||
posx = lineMax - width + 1
|
||||
end
|
||||
|
||||
if posx < 0 then
|
||||
posx = 0
|
||||
end
|
||||
end
|
||||
88
Capy64/Assets/Lua/CapyOS/sys/bin/ls.lua
Normal file
88
Capy64/Assets/Lua/CapyOS/sys/bin/ls.lua
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
local fs = require("fs")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local argparser = require("argparser")
|
||||
|
||||
local theme = {
|
||||
directory = colors.lightBlue,
|
||||
file = colors.white,
|
||||
lua = colors.yellow,
|
||||
}
|
||||
|
||||
local function humanizeBytes(n)
|
||||
local prefixes = {
|
||||
[0] = "",
|
||||
"k",
|
||||
"M",
|
||||
"G",
|
||||
"T",
|
||||
}
|
||||
local block = 1024
|
||||
local prefixIndex = 0
|
||||
|
||||
while n >= block do
|
||||
n = n / 1024
|
||||
prefixIndex = prefixIndex + 1
|
||||
end
|
||||
|
||||
return string.format("%.0f%s", n, prefixes[prefixIndex])
|
||||
end
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if options.h or options.help then
|
||||
print("Usage: ls [option...] [path]")
|
||||
print("List files (current directory by default)")
|
||||
print("Options:")
|
||||
print(" -a: Include hidden files")
|
||||
print(" -l: Use long listing format")
|
||||
return
|
||||
end
|
||||
local path = shell.getDir()
|
||||
|
||||
if args[1] then
|
||||
path = shell.resolve(args[1])
|
||||
end
|
||||
|
||||
if not fs.isDir(path) then
|
||||
error("No such directory: " .. path, 0)
|
||||
return false
|
||||
end
|
||||
|
||||
local entries = fs.list(path)
|
||||
|
||||
if options.l then
|
||||
print(string.format("total %d", #entries))
|
||||
end
|
||||
local printed = 0
|
||||
for i, entry in ipairs(entries) do
|
||||
if entry:sub(1, 1) ~= "." or options.a then
|
||||
printed = printed + 1
|
||||
local attributes = fs.attributes(fs.combine(path, entry))
|
||||
local size = humanizeBytes(attributes.size)
|
||||
local date = os.date("%x %H:%m", attributes.modified // 1000)
|
||||
|
||||
local entryType
|
||||
if attributes.isDirectory then
|
||||
entryType = "directory"
|
||||
else
|
||||
entryType = "file"
|
||||
if string.match(entry, "%.lua$") then
|
||||
entryType = "lua"
|
||||
end
|
||||
end
|
||||
|
||||
if options.l then
|
||||
term.setForeground(colors.white)
|
||||
term.write(string.format("%s %5s %s ", attributes.isDirectory and "d" or "-", size, date))
|
||||
end
|
||||
term.setForeground(theme[entryType])
|
||||
io.write(entry)
|
||||
|
||||
io.write(options.l and "\n" or "\t")
|
||||
end
|
||||
end
|
||||
|
||||
if not options.l and printed > 0 then
|
||||
print()
|
||||
end
|
||||
98
Capy64/Assets/Lua/CapyOS/sys/bin/lua.lua
Normal file
98
Capy64/Assets/Lua/CapyOS/sys/bin/lua.lua
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
local term = require("term")
|
||||
local io = require("io")
|
||||
local colors = require("colors")
|
||||
local argparser = require("argparser")
|
||||
local tableutils = require("tableutils")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
local function evaluate(str, env, chunkname)
|
||||
chunkname = chunkname or "=lua"
|
||||
local nForcePrint = 0
|
||||
local func, e = load(str, chunkname, "t", env)
|
||||
local func2 = load("return " .. str, chunkname, "t", env)
|
||||
if not func then
|
||||
if func2 then
|
||||
func = func2
|
||||
e = nil
|
||||
nForcePrint = 1
|
||||
end
|
||||
else
|
||||
if func2 then
|
||||
func = func2
|
||||
end
|
||||
end
|
||||
|
||||
if func then
|
||||
local tResults = table.pack(pcall(func))
|
||||
if tResults[1] then
|
||||
local n = 1
|
||||
while n < tResults.n or n <= nForcePrint do
|
||||
local value = tResults[n + 1]
|
||||
print(tableutils.pretty(value))
|
||||
n = n + 1
|
||||
end
|
||||
else
|
||||
io.stderr.print(tResults[2])
|
||||
return false
|
||||
end
|
||||
else
|
||||
io.stderr.print(e)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function createEnvironment()
|
||||
return setmetatable({}, { __index = _ENV })
|
||||
end
|
||||
|
||||
local function loadPackages(env)
|
||||
for k, v in pairs(package.loaded) do
|
||||
env[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if options.e then
|
||||
local env = createEnvironment()
|
||||
loadPackages(env)
|
||||
return evaluate(table.concat(args, " "), env)
|
||||
end
|
||||
|
||||
if #args > 0 then
|
||||
print("This is an interactive Lua prompt.")
|
||||
print("To run a lua program, just type its name.")
|
||||
return
|
||||
end
|
||||
|
||||
--local pretty = require "cc.pretty"
|
||||
|
||||
local bRunning = true
|
||||
local tCommandHistory = {}
|
||||
|
||||
|
||||
local tEnv = createEnvironment()
|
||||
tEnv.exit = setmetatable({}, {
|
||||
__tostring = function() return "Call exit() to exit." end,
|
||||
__call = function() bRunning = false end,
|
||||
})
|
||||
loadPackages(tEnv)
|
||||
|
||||
term.setForeground(colors.yellow)
|
||||
print(_VERSION .. " interactive prompt")
|
||||
print("Call exit() to exit.")
|
||||
term.setForeground(colors.white)
|
||||
|
||||
while bRunning do
|
||||
term.setForeground(colors.yellow)
|
||||
io.write("> ")
|
||||
term.setForeground(colors.white)
|
||||
|
||||
local s = io.read(nil, tCommandHistory)
|
||||
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
||||
table.insert(tCommandHistory, s)
|
||||
end
|
||||
|
||||
evaluate(s, tEnv)
|
||||
|
||||
end
|
||||
14
Capy64/Assets/Lua/CapyOS/sys/bin/mkdir.lua
Normal file
14
Capy64/Assets/Lua/CapyOS/sys/bin/mkdir.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
local fs = require("fs")
|
||||
local args = { ... }
|
||||
|
||||
if #args == 0 then
|
||||
print("Usage: mkdir <directory>")
|
||||
return
|
||||
end
|
||||
|
||||
local dir = shell.resolve(args[1])
|
||||
if fs.exists(dir) then
|
||||
error("Path already exists", 0)
|
||||
end
|
||||
|
||||
fs.makeDir(dir)
|
||||
20
Capy64/Assets/Lua/CapyOS/sys/bin/motd.lua
Normal file
20
Capy64/Assets/Lua/CapyOS/sys/bin/motd.lua
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
local fs = require("fs")
|
||||
|
||||
local date = os.date("*t")
|
||||
|
||||
if date.month == 4 and date.day == 28 then
|
||||
print("Ed Balls")
|
||||
return
|
||||
end
|
||||
|
||||
local motdList = {}
|
||||
|
||||
local f<close> = fs.open("/sys/share/motd.txt", "r")
|
||||
for line in f:lines() do
|
||||
table.insert(motdList, line)
|
||||
end
|
||||
f:close()
|
||||
|
||||
local motdIndex = math.random(1, #motdList)
|
||||
|
||||
print(motdList[motdIndex])
|
||||
16
Capy64/Assets/Lua/CapyOS/sys/bin/mv.lua
Normal file
16
Capy64/Assets/Lua/CapyOS/sys/bin/mv.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local fs = require("fs")
|
||||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if not args[1] or not args[2] or options.h or options.help then
|
||||
print("Usage: mv [option...] <source> <target>")
|
||||
print("Options:")
|
||||
print(" -h --help: Display help")
|
||||
return
|
||||
end
|
||||
|
||||
local source = shell.resolve(args[1])
|
||||
local destination = shell.resolve(args[2])
|
||||
|
||||
fs.move(source, destination)
|
||||
10
Capy64/Assets/Lua/CapyOS/sys/bin/programs.lua
Normal file
10
Capy64/Assets/Lua/CapyOS/sys/bin/programs.lua
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
local fs = require("fs")
|
||||
local programs = fs.list("/sys/bin", function(name, attr)
|
||||
return not attr.isDirectory
|
||||
end)
|
||||
|
||||
for i, v in ipairs(programs) do
|
||||
programs[i] = string.gsub(v, "%.lua$", "")
|
||||
end
|
||||
|
||||
print(table.concat(programs, " "))
|
||||
1
Capy64/Assets/Lua/CapyOS/sys/bin/pwd.lua
Normal file
1
Capy64/Assets/Lua/CapyOS/sys/bin/pwd.lua
Normal file
|
|
@ -0,0 +1 @@
|
|||
print(shell.getDir())
|
||||
16
Capy64/Assets/Lua/CapyOS/sys/bin/rm.lua
Normal file
16
Capy64/Assets/Lua/CapyOS/sys/bin/rm.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local fs = require("fs")
|
||||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if not args[1] or options.h or options.help then
|
||||
print("Usage: rm [option...] <path>")
|
||||
print("Options:")
|
||||
print(" -r --recursive: Delete non-empty directories")
|
||||
print(" -h --help: Display help")
|
||||
return
|
||||
end
|
||||
|
||||
local file = shell.resolve(args[1])
|
||||
|
||||
fs.delete(file, options.recursive or options.r)
|
||||
175
Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua
Normal file
175
Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local fs = require("fs")
|
||||
local machine = require("machine")
|
||||
local argparser = require("argparser")
|
||||
local scheduler = require("scheduler")
|
||||
local createPackageEnvironment = require("shell.package")
|
||||
|
||||
local useScheduler = false
|
||||
local exit = false
|
||||
local parentShell = shell
|
||||
local isStartupShell = parentShell == nil
|
||||
local shell = {}
|
||||
|
||||
shell.path = parentShell and parentShell.path or "./?;./?.lua;/bin/?.lua;/sys/bin/?.lua"
|
||||
shell.homePath = parentShell and parentShell.home or "/home"
|
||||
shell.aliases = parentShell and parentShell.aliases or {}
|
||||
|
||||
local currentDir = parentShell and parentShell.getDir() or shell.homePath
|
||||
|
||||
local function buildEnvironment(command, filepath, args, argf)
|
||||
local arg = { table.unpack(args, 2) }
|
||||
arg[0] = command
|
||||
arg.string = argf
|
||||
|
||||
local envPackage = createPackageEnvironment(filepath)
|
||||
envPackage.loaded.scheduler = scheduler
|
||||
|
||||
return setmetatable({
|
||||
shell = shell,
|
||||
arg = arg,
|
||||
scheduler = scheduler,
|
||||
}, { __index = envPackage.loaded._G })
|
||||
end
|
||||
|
||||
function shell.getDir()
|
||||
return currentDir
|
||||
end
|
||||
|
||||
function shell.setDir(path)
|
||||
currentDir = path
|
||||
end
|
||||
|
||||
function shell.resolve(path)
|
||||
if path:sub(1, 1) == "/" then
|
||||
return fs.combine("", path)
|
||||
end
|
||||
|
||||
if path:sub(1, 1) == "~" then
|
||||
return fs.combine(shell.homePath, path)
|
||||
end
|
||||
|
||||
return fs.combine(currentDir, path)
|
||||
end
|
||||
|
||||
function shell.resolveProgram(path)
|
||||
if path:sub(1, 1) == "/" then
|
||||
return shell.resolve(path)
|
||||
end
|
||||
|
||||
for seg in shell.path:gmatch("[^;]+") do
|
||||
local resolved = shell.resolve(seg:gsub("%?", path))
|
||||
if fs.exists(resolved) and not fs.isDir(resolved) then
|
||||
return resolved
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function shell.run(...)
|
||||
local args = argparser.tokenize(...)
|
||||
local argf = table.concat({...}, " ")
|
||||
local command = args[1]
|
||||
|
||||
argf = argf:sub(#command + 2)
|
||||
|
||||
local path = shell.resolveProgram(command)
|
||||
|
||||
if not path then
|
||||
if shell.aliases[command] then
|
||||
return shell.run(shell.aliases[command], select(2, table.unpack(args)))
|
||||
else
|
||||
io.stderr.print("Command not found: " .. command)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local env = buildEnvironment(command, path, args, argf)
|
||||
|
||||
local func, err = loadfile(path, "t", env)
|
||||
|
||||
if not func then
|
||||
io.stderr.print(err)
|
||||
return false
|
||||
end
|
||||
|
||||
local ok, err
|
||||
local function run()
|
||||
ok, err = pcall(func, table.unpack(args, 2))
|
||||
end
|
||||
|
||||
if useScheduler then
|
||||
local programTask, yielded = scheduler.spawn(run)
|
||||
|
||||
if yielded then
|
||||
coroutine.yield("scheduler_task_end")
|
||||
end
|
||||
else
|
||||
run()
|
||||
end
|
||||
|
||||
if not ok then
|
||||
io.stderr.print(err)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function shell.exit()
|
||||
exit = true
|
||||
end
|
||||
|
||||
if not fs.exists(shell.homePath) then
|
||||
fs.makeDir(shell.homePath)
|
||||
end
|
||||
|
||||
term.setForeground(colors.white)
|
||||
term.setBackground(colors.black)
|
||||
|
||||
if isStartupShell then
|
||||
if fs.exists(fs.combine(shell.homePath, ".shrc")) then
|
||||
local f <close> = fs.open(fs.combine(shell.homePath, ".shrc"), "r")
|
||||
for line in f:lines() do
|
||||
if line:match("%S") and not line:match("^%s-#") then
|
||||
shell.run(line)
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
local history = {}
|
||||
local lastExecSuccess = true
|
||||
while not exit do
|
||||
machine.setRPC(os.version(), "On shell")
|
||||
|
||||
term.setBackground(colors.black)
|
||||
term.setForeground(colors.white)
|
||||
io.write(":")
|
||||
term.setForeground(colors.lightBlue)
|
||||
if currentDir == shell.homePath then
|
||||
io.write("~")
|
||||
else
|
||||
io.write(currentDir)
|
||||
end
|
||||
|
||||
if lastExecSuccess then
|
||||
term.setForeground(colors.yellow)
|
||||
else
|
||||
term.setForeground(colors.red)
|
||||
end
|
||||
io.write("$ ")
|
||||
|
||||
term.setForeground(colors.white)
|
||||
local line = io.read(nil, history)
|
||||
|
||||
if line:match("%S") and history[#history] ~= line then
|
||||
table.insert(history, line)
|
||||
end
|
||||
|
||||
if line:match("%S") then
|
||||
machine.setRPC(os.version(), "Running: " .. line)
|
||||
lastExecSuccess = shell.run(line)
|
||||
end
|
||||
end
|
||||
33
Capy64/Assets/Lua/CapyOS/sys/bin/shutdown.lua
Normal file
33
Capy64/Assets/Lua/CapyOS/sys/bin/shutdown.lua
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
local machine = require("machine")
|
||||
local scheduler = require("scheduler")
|
||||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if options.h or options.help then
|
||||
print("Usage: shutdown [option...]")
|
||||
print("Shutdown or restart Capy64.")
|
||||
print("Options:")
|
||||
print(" -s --shutdown: Shutdown and exit Capy64. (default)")
|
||||
print(" -r --reboot: Restart Capy64.")
|
||||
print(" -t --time: Time to wait in seconds. (\"now\" is 0 seconds, default)")
|
||||
return
|
||||
end
|
||||
|
||||
local time = 0
|
||||
if options.t or options.time then
|
||||
time = options.t or options.time
|
||||
end
|
||||
if time == "now" then
|
||||
time = 0
|
||||
else
|
||||
time = tonumber(time)
|
||||
if not time then
|
||||
error("Invalid time option: " .. (options.t or options.time), 0)
|
||||
end
|
||||
end
|
||||
|
||||
scheduler.ipc(1, "power", {
|
||||
reboot = options.r or options.reboot,
|
||||
time = time,
|
||||
})
|
||||
2
Capy64/Assets/Lua/CapyOS/sys/bin/version.lua
Normal file
2
Capy64/Assets/Lua/CapyOS/sys/bin/version.lua
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
local machine = require("machine")
|
||||
print(string.format("%s @ %s - %s", os.version(), machine.version(), _VERSION))
|
||||
36
Capy64/Assets/Lua/CapyOS/sys/bin/wget.lua
Normal file
36
Capy64/Assets/Lua/CapyOS/sys/bin/wget.lua
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
local http = require("http")
|
||||
local fs = require("fs")
|
||||
|
||||
local args = { ... }
|
||||
|
||||
if not http then
|
||||
error("HTTP is not enabled", 0)
|
||||
end
|
||||
|
||||
if #args == 0 then
|
||||
print("Usage: wget <url> [outputPath]")
|
||||
return false
|
||||
end
|
||||
|
||||
local outputName = args[2] or fs.getName(args[1])
|
||||
local outputPath = shell.resolve(outputName)
|
||||
|
||||
if not http.checkURL(args[1]) then
|
||||
error("Invalid URL", 0)
|
||||
end
|
||||
|
||||
print("Connecting...")
|
||||
|
||||
local response, err = http.get(args[1], nil, {
|
||||
binary = true,
|
||||
})
|
||||
if not response then
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
local file <close> = fs.open(outputPath, "wb")
|
||||
file:write(response.content:read("a"))
|
||||
file:close()
|
||||
response.content:close()
|
||||
|
||||
print("Downloaded to " .. outputPath)
|
||||
3
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/00_package.lua
Normal file
3
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/00_package.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package.path = "/lib/?.lua;/lib/?/init.lua;/sys/lib/?.lua;/sys/lib/?/init.lua;" .. package.path
|
||||
|
||||
_G.io = require("io")
|
||||
15
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/01_stdio.lua
Normal file
15
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/01_stdio.lua
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
function _G.print(...)
|
||||
local args = { ... }
|
||||
local size = #args
|
||||
local lines = 0
|
||||
for n, v in ipairs(args) do
|
||||
local s = tostring(v)
|
||||
if n < size then
|
||||
s = s .. "\t"
|
||||
end
|
||||
lines = lines + io.write(s)
|
||||
end
|
||||
lines = lines + io.write("\n")
|
||||
|
||||
return lines
|
||||
end
|
||||
28
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_fs.lua
Normal file
28
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_fs.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local fs = require("fs")
|
||||
local expect = require("expect").expect
|
||||
|
||||
local fsList = fs.list
|
||||
|
||||
function fs.list(path, filter)
|
||||
expect(1, path, "string")
|
||||
expect(2, filter, "nil", "function")
|
||||
|
||||
if not fs.isDir(path) then
|
||||
error("directory not found", 2)
|
||||
end
|
||||
|
||||
local list = fsList(path)
|
||||
if not filter then
|
||||
return list
|
||||
end
|
||||
|
||||
local filteredList = {}
|
||||
for i = 1, #list do
|
||||
local attributes = fs.attributes(fs.combine(path, list[i]))
|
||||
if filter(list[i], attributes) then
|
||||
table.insert(filteredList, list[i])
|
||||
end
|
||||
end
|
||||
|
||||
return filteredList
|
||||
end
|
||||
77
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_http.lua
Normal file
77
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_http.lua
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
local http = require("http")
|
||||
local event = require("event")
|
||||
local expect = require("expect").expect
|
||||
|
||||
|
||||
function http.request(url, body, headers, options)
|
||||
expect(1, url, "string")
|
||||
expect(2, body, "string", "nil")
|
||||
expect(3, headers, "table", "nil")
|
||||
expect(4, options, "table", "nil")
|
||||
|
||||
if not http.checkURL(url) then
|
||||
return nil, "Invalid URL"
|
||||
end
|
||||
|
||||
local task<close> = http.requestAsync(url, body, headers, options)
|
||||
return task:await()
|
||||
end
|
||||
|
||||
function http.get(url, headers, options)
|
||||
expect(1, url, "string")
|
||||
expect(2, headers, "table", "nil")
|
||||
expect(3, options, "table", "nil")
|
||||
|
||||
return http.request(url, nil, headers, options)
|
||||
end
|
||||
|
||||
function http.post(url, body, headers, options)
|
||||
expect(1, url, "string")
|
||||
expect(2, body, "string", "nil")
|
||||
expect(3, headers, "table", "nil")
|
||||
expect(4, options, "table", "nil")
|
||||
|
||||
return http.request(url, body, headers, options)
|
||||
end
|
||||
|
||||
local WebSocketHandle
|
||||
local function buildWebsocketHandle(handle)
|
||||
if not handle then
|
||||
return nil
|
||||
end
|
||||
if not WebSocketHandle then
|
||||
WebSocketHandle = getmetatable(handle) or { __index = {} }
|
||||
function WebSocketHandle.__index:close()
|
||||
self:closeAsync()
|
||||
local _, id
|
||||
repeat
|
||||
_, id = event.pull("websocket_close")
|
||||
until id == self:getRequestID()
|
||||
end
|
||||
|
||||
function WebSocketHandle.__index:receive()
|
||||
local _, id, par
|
||||
repeat
|
||||
_, id, par = event.pull("websocket_message")
|
||||
until id == self:getRequestID()
|
||||
|
||||
return par
|
||||
end
|
||||
end
|
||||
|
||||
return handle
|
||||
end
|
||||
|
||||
function http.websocket(url, headers)
|
||||
expect(1, url, "string")
|
||||
expect(2, headers, "table", "nil")
|
||||
|
||||
if not http.checkURL(url) then
|
||||
return nil, "Invalid URL"
|
||||
end
|
||||
|
||||
local task<close> = http.websocketAsync(url, headers)
|
||||
local client, err = task:await()
|
||||
|
||||
return buildWebsocketHandle(client), err
|
||||
end
|
||||
13
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_timer.lua
Normal file
13
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_timer.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
local timer = require("timer")
|
||||
local event = require("event")
|
||||
local expect = require("expect").expect
|
||||
local range = require("expect").range
|
||||
|
||||
function timer.sleep(n)
|
||||
expect(1, n, "number")
|
||||
|
||||
local timerId = timer.start(n)
|
||||
repeat
|
||||
local _, par = event.pull("timer")
|
||||
until par == timerId
|
||||
end
|
||||
47
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/50_os_manager.lua
Normal file
47
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/50_os_manager.lua
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
local machine = require("machine")
|
||||
local scheduler = require("scheduler")
|
||||
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
term.setForeground(0x59c9ff)
|
||||
term.setBackground(colors.black)
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
|
||||
term.write(os.version())
|
||||
term.setPos(1, 2)
|
||||
|
||||
local function spawnShell()
|
||||
return scheduler.spawn(loadfile("/sys/bin/shell.lua"))
|
||||
end
|
||||
|
||||
local function main()
|
||||
local shellTask = spawnShell()
|
||||
while true do
|
||||
local ev = {coroutine.yield()}
|
||||
if ev[1] == "ipc_message" then
|
||||
local sender = ev[2]
|
||||
local call = ev[3]
|
||||
if call == "power" then
|
||||
local options = ev[4]
|
||||
--todo: handle time and cancels
|
||||
if options.reboot then
|
||||
machine.reboot()
|
||||
else
|
||||
machine.shutdown()
|
||||
end
|
||||
end
|
||||
elseif ev[1] == "scheduler_task_end" then
|
||||
if ev[2].pid == shellTask.pid then
|
||||
if not ev[3] then
|
||||
io.stderr.print(ev[4])
|
||||
end
|
||||
shellTask = spawnShell()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scheduler.spawn(main)
|
||||
|
||||
scheduler.init()
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("machine").shutdown()
|
||||
91
Capy64/Assets/Lua/CapyOS/sys/lib/argparser.lua
Normal file
91
Capy64/Assets/Lua/CapyOS/sys/lib/argparser.lua
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
local argparser = {}
|
||||
|
||||
function argparser.tokenize(...)
|
||||
local input = table.concat(table.pack(...), " ")
|
||||
local tokens = {}
|
||||
|
||||
-- surely there must be a better way
|
||||
local quoted = false
|
||||
local escaped = false
|
||||
local current = ""
|
||||
for i = 1, #input do
|
||||
local char = input:sub(i, i)
|
||||
if escaped then
|
||||
escaped = false
|
||||
current = current .. char
|
||||
else
|
||||
if char == "\\" then
|
||||
escaped = true
|
||||
elseif char == "\"" then
|
||||
if quoted then
|
||||
-- close quote
|
||||
table.insert(tokens, current)
|
||||
current = ""
|
||||
end
|
||||
quoted = not quoted
|
||||
elseif char == " " and not quoted then
|
||||
if #current > 0 then
|
||||
table.insert(tokens, current)
|
||||
end
|
||||
current = ""
|
||||
else
|
||||
current = current .. char
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if current ~= "" then
|
||||
table.insert(tokens, current)
|
||||
end
|
||||
|
||||
return tokens
|
||||
end
|
||||
|
||||
function argparser.parse(...)
|
||||
local tokens = { ... }
|
||||
local args = {}
|
||||
local options = {}
|
||||
local ignoreOptions = false
|
||||
|
||||
for i = 1, #tokens do
|
||||
local token = tokens[i]
|
||||
if not ignoreOptions then
|
||||
if token == "--" then
|
||||
ignoreOptions = true
|
||||
elseif token:sub(1, 2) == "--" then
|
||||
local opt, value = token:match("%-%-(.+)=(.+)")
|
||||
if not opt then
|
||||
opt = token:sub(3)
|
||||
if opt:sub(-1) == "=" then
|
||||
-- next token is value
|
||||
value = tokens[i + 1]
|
||||
opt = opt:sub(1, -2)
|
||||
options[opt] = value
|
||||
i = i + 1
|
||||
else
|
||||
options[opt] = true
|
||||
end
|
||||
else
|
||||
options[opt] = value
|
||||
end
|
||||
elseif token:sub(1, 1) == "-" then
|
||||
local opts = token:sub(2)
|
||||
for j = 1, #opts do
|
||||
options[opts:sub(j, j)] = true
|
||||
end
|
||||
else
|
||||
if #token > 0 then
|
||||
table.insert(args, token)
|
||||
end
|
||||
end
|
||||
else
|
||||
if #token > 0 then
|
||||
table.insert(args, token)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return args, options
|
||||
end
|
||||
|
||||
return argparser
|
||||
94
Capy64/Assets/Lua/CapyOS/sys/lib/colors.lua
Normal file
94
Capy64/Assets/Lua/CapyOS/sys/lib/colors.lua
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
local expect = require("expect")
|
||||
|
||||
local palette = {
|
||||
{
|
||||
"white",
|
||||
0xf0f0f0
|
||||
},
|
||||
{
|
||||
"orange",
|
||||
0xf2b233
|
||||
},
|
||||
{
|
||||
"magenta",
|
||||
0xe57fd8
|
||||
},
|
||||
{
|
||||
"lightBlue",
|
||||
0x99b2f2
|
||||
},
|
||||
{
|
||||
"yellow",
|
||||
0xdede6c
|
||||
},
|
||||
{
|
||||
"lime",
|
||||
0x7fcc19
|
||||
},
|
||||
{
|
||||
"pink",
|
||||
0xf2b2cc
|
||||
},
|
||||
{
|
||||
"gray",
|
||||
0x4c4c4c
|
||||
},
|
||||
{
|
||||
"lightGray",
|
||||
0x999999
|
||||
},
|
||||
{
|
||||
"cyan",
|
||||
0x4c99b2
|
||||
},
|
||||
{
|
||||
"purple",
|
||||
0xb266e5
|
||||
},
|
||||
{
|
||||
"blue",
|
||||
0x3366cc
|
||||
},
|
||||
{
|
||||
"brown",
|
||||
0x7f664c
|
||||
},
|
||||
{
|
||||
"green",
|
||||
0x57a64e
|
||||
},
|
||||
{
|
||||
"red",
|
||||
0xcc4c4c
|
||||
},
|
||||
{
|
||||
"black",
|
||||
0x111111
|
||||
}
|
||||
}
|
||||
|
||||
local colors = {}
|
||||
for k, v in ipairs(palette) do
|
||||
colors[v[1]] = v[2]
|
||||
colors[k] = v[2]
|
||||
end
|
||||
|
||||
function colors.packRGB(r, g, b)
|
||||
expect(1, r, "number")
|
||||
expect(2, g, "number")
|
||||
expect(3, b, "number")
|
||||
|
||||
return (r << 16) +
|
||||
(g << 8) +
|
||||
b
|
||||
end
|
||||
|
||||
function colors.unpackRGB(rgb)
|
||||
expect(1, rgb, "number")
|
||||
|
||||
return (rgb >> 16) & 0xff,
|
||||
(rgb >> 8) & 0xff,
|
||||
rgb & 0xff
|
||||
end
|
||||
|
||||
return colors;
|
||||
52
Capy64/Assets/Lua/CapyOS/sys/lib/expect.lua
Normal file
52
Capy64/Assets/Lua/CapyOS/sys/lib/expect.lua
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
-- Credits: https://github.com/Ocawesome101/recrafted
|
||||
|
||||
-- cc.expect
|
||||
|
||||
local _expect = {}
|
||||
|
||||
local function checkType(index, valueType, value, ...)
|
||||
local expected = table.pack(...)
|
||||
local isType = false
|
||||
|
||||
for i = 1, expected.n, 1 do
|
||||
if type(value) == expected[i] then
|
||||
isType = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not isType then
|
||||
error(string.format("bad %s %s (%s expected, got %s)", valueType,
|
||||
index, table.concat(expected, " or "), type(value)), 3)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
function _expect.expect(index, value, ...)
|
||||
return checkType(("#%d"):format(index), "argument", value, ...)
|
||||
end
|
||||
|
||||
function _expect.field(tbl, index, ...)
|
||||
_expect.expect(1, tbl, "table")
|
||||
_expect.expect(2, index, "string")
|
||||
return checkType(("%q"):format(index), "field", tbl[index], ...)
|
||||
end
|
||||
|
||||
function _expect.range(num, min, max)
|
||||
_expect.expect(1, num, "number")
|
||||
_expect.expect(2, min, "number", "nil")
|
||||
_expect.expect(3, max, "number", "nil")
|
||||
min = min or -math.huge
|
||||
max = max or math.huge
|
||||
if num < min or num > max then
|
||||
error(("number outside of range (expected %d to be within %d and %d")
|
||||
:format(num, min, max), 2)
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(_expect, { __call = function(_, ...)
|
||||
return _expect.expect(...)
|
||||
end })
|
||||
|
||||
return _expect
|
||||
616
Capy64/Assets/Lua/CapyOS/sys/lib/io.lua
Normal file
616
Capy64/Assets/Lua/CapyOS/sys/lib/io.lua
Normal file
|
|
@ -0,0 +1,616 @@
|
|||
local expect = require("expect").expect
|
||||
local event = require("event")
|
||||
local term = require("term")
|
||||
local keys = require("keys")
|
||||
local machine = require("machine")
|
||||
|
||||
local io = {}
|
||||
|
||||
function io.write(sText)
|
||||
sText = tostring(sText)
|
||||
|
||||
local w, h = term.getSize()
|
||||
local x, y = term.getPos()
|
||||
|
||||
local nLinesPrinted = 0
|
||||
local function newLine()
|
||||
if y + 1 <= h then
|
||||
term.setPos(1, y + 1)
|
||||
else
|
||||
term.setPos(1, h)
|
||||
term.scroll(1)
|
||||
end
|
||||
x, y = term.getPos()
|
||||
nLinesPrinted = nLinesPrinted + 1
|
||||
end
|
||||
|
||||
-- Print the line with proper word wrapping
|
||||
sText = tostring(sText)
|
||||
while #sText > 0 do
|
||||
local whitespace = string.match(sText, "^[ \t]+")
|
||||
if whitespace then
|
||||
-- Print whitespace
|
||||
term.write(whitespace)
|
||||
x, y = term.getPos()
|
||||
sText = string.sub(sText, #whitespace + 1)
|
||||
end
|
||||
|
||||
local newline = string.match(sText, "^\n")
|
||||
if newline then
|
||||
-- Print newlines
|
||||
newLine()
|
||||
sText = string.sub(sText, 2)
|
||||
end
|
||||
|
||||
local text = string.match(sText, "^[^ \t\n]+")
|
||||
if text then
|
||||
sText = string.sub(sText, #text + 1)
|
||||
if #text > w then
|
||||
-- Print a multiline word
|
||||
while #text > 0 do
|
||||
if x > w then
|
||||
newLine()
|
||||
end
|
||||
term.write(text)
|
||||
text = string.sub(text, w - x + 2)
|
||||
x, y = term.getPos()
|
||||
end
|
||||
else
|
||||
-- Print a word normally
|
||||
if x + #text - 1 > w then
|
||||
newLine()
|
||||
end
|
||||
term.write(text)
|
||||
x, y = term.getPos()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nLinesPrinted
|
||||
end
|
||||
|
||||
function io.read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
|
||||
expect(1, _sReplaceChar, "string", "nil")
|
||||
expect(2, _tHistory, "table", "nil")
|
||||
expect(3, _fnComplete, "function", "nil")
|
||||
expect(4, _sDefault, "string", "nil")
|
||||
|
||||
term.setBlink(true)
|
||||
|
||||
local sLine
|
||||
if type(_sDefault) == "string" then
|
||||
sLine = _sDefault
|
||||
else
|
||||
sLine = ""
|
||||
end
|
||||
local nHistoryPos
|
||||
local nPos, nScroll = #sLine, 0
|
||||
if _sReplaceChar then
|
||||
_sReplaceChar = string.sub(_sReplaceChar, 1, 1)
|
||||
end
|
||||
|
||||
local tCompletions
|
||||
local nCompletion
|
||||
local function recomplete()
|
||||
if _fnComplete and nPos == #sLine then
|
||||
tCompletions = _fnComplete(sLine)
|
||||
if tCompletions and #tCompletions > 0 then
|
||||
nCompletion = 1
|
||||
else
|
||||
nCompletion = nil
|
||||
end
|
||||
else
|
||||
tCompletions = nil
|
||||
nCompletion = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function uncomplete()
|
||||
tCompletions = nil
|
||||
nCompletion = nil
|
||||
end
|
||||
|
||||
local w = term.getSize()
|
||||
local sx = term.getPos()
|
||||
|
||||
local function redraw(_bClear)
|
||||
local cursor_pos = nPos - nScroll
|
||||
if sx + cursor_pos >= w then
|
||||
-- We've moved beyond the RHS, ensure we're on the edge.
|
||||
nScroll = sx + nPos - w
|
||||
elseif cursor_pos < 0 then
|
||||
-- We've moved beyond the LHS, ensure we're on the edge.
|
||||
nScroll = nPos
|
||||
end
|
||||
|
||||
local _, cy = term.getPos()
|
||||
term.setPos(sx, cy)
|
||||
local sReplace = _bClear and " " or _sReplaceChar
|
||||
if sReplace then
|
||||
term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0)))
|
||||
else
|
||||
term.write(string.sub(sLine, nScroll + 1))
|
||||
end
|
||||
|
||||
if nCompletion then
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
local oldText, oldBg
|
||||
if not _bClear then
|
||||
oldText = term.getTextColor()
|
||||
oldBg = term.getBackgroundColor()
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.gray)
|
||||
end
|
||||
if sReplace then
|
||||
term.write(string.rep(sReplace, #sCompletion))
|
||||
else
|
||||
term.write(sCompletion)
|
||||
end
|
||||
if not _bClear then
|
||||
term.setTextColor(oldText)
|
||||
term.setBackgroundColor(oldBg)
|
||||
end
|
||||
end
|
||||
|
||||
term.setPos(sx + nPos - nScroll, cy)
|
||||
end
|
||||
|
||||
local function clear()
|
||||
redraw(true)
|
||||
end
|
||||
|
||||
recomplete()
|
||||
redraw()
|
||||
|
||||
local function acceptCompletion()
|
||||
if nCompletion then
|
||||
-- Clear
|
||||
clear()
|
||||
|
||||
-- Find the common prefix of all the other suggestions which start with the same letter as the current one
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
sLine = sLine .. sCompletion
|
||||
nPos = #sLine
|
||||
|
||||
-- Redraw
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local sEvent, param, param1, param2 = event.pull()
|
||||
if sEvent == "char" then
|
||||
-- Typed key
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos + 1
|
||||
recomplete()
|
||||
redraw()
|
||||
|
||||
elseif sEvent == "paste" then
|
||||
-- Pasted text
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos + #param
|
||||
recomplete()
|
||||
redraw()
|
||||
|
||||
elseif sEvent == "key_down" then
|
||||
if param == keys.enter or param == keys.numPadEnter then
|
||||
-- Enter/Numpad Enter
|
||||
if nCompletion then
|
||||
clear()
|
||||
uncomplete()
|
||||
redraw()
|
||||
end
|
||||
break
|
||||
|
||||
elseif param == keys.left then
|
||||
-- Left
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
nPos = nPos - 1
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.right then
|
||||
-- Right
|
||||
if nPos < #sLine then
|
||||
-- Move right
|
||||
clear()
|
||||
nPos = nPos + 1
|
||||
recomplete()
|
||||
redraw()
|
||||
else
|
||||
-- Accept autocomplete
|
||||
acceptCompletion()
|
||||
end
|
||||
|
||||
elseif param == keys.up or param == keys.down then
|
||||
-- Up or down
|
||||
if nCompletion then
|
||||
-- Cycle completions
|
||||
clear()
|
||||
if param == keys.up then
|
||||
nCompletion = nCompletion - 1
|
||||
if nCompletion < 1 then
|
||||
nCompletion = #tCompletions
|
||||
end
|
||||
elseif param == keys.down then
|
||||
nCompletion = nCompletion + 1
|
||||
if nCompletion > #tCompletions then
|
||||
nCompletion = 1
|
||||
end
|
||||
end
|
||||
redraw()
|
||||
|
||||
elseif _tHistory then
|
||||
-- Cycle history
|
||||
clear()
|
||||
if param == keys.up then
|
||||
-- Up
|
||||
if nHistoryPos == nil then
|
||||
if #_tHistory > 0 then
|
||||
nHistoryPos = #_tHistory
|
||||
end
|
||||
elseif nHistoryPos > 1 then
|
||||
nHistoryPos = nHistoryPos - 1
|
||||
end
|
||||
else
|
||||
-- Down
|
||||
if nHistoryPos == #_tHistory then
|
||||
nHistoryPos = nil
|
||||
elseif nHistoryPos ~= nil then
|
||||
nHistoryPos = nHistoryPos + 1
|
||||
end
|
||||
end
|
||||
if nHistoryPos then
|
||||
sLine = _tHistory[nHistoryPos]
|
||||
nPos, nScroll = #sLine, 0
|
||||
else
|
||||
sLine = ""
|
||||
nPos, nScroll = 0, 0
|
||||
end
|
||||
uncomplete()
|
||||
redraw()
|
||||
|
||||
end
|
||||
|
||||
elseif param == keys.back then
|
||||
-- Backspace
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos - 1
|
||||
if nScroll > 0 then nScroll = nScroll - 1 end
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.home then
|
||||
-- Home
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
nPos = 0
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.delete then
|
||||
-- Delete
|
||||
if nPos < #sLine then
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys["end"] then
|
||||
-- End
|
||||
if nPos < #sLine then
|
||||
clear()
|
||||
nPos = #sLine
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.tab then
|
||||
-- Tab (accept autocomplete)
|
||||
acceptCompletion()
|
||||
|
||||
end
|
||||
|
||||
elseif sEvent == "mouse_down" or sEvent == "mouse_drag" and param == 1 then
|
||||
local _, cy = term.getPos()
|
||||
if param1 >= sx and param1 <= w and param2 == cy then
|
||||
-- Ensure we don't scroll beyond the current line
|
||||
nPos = math.min(math.max(nScroll + param1 - sx, 0), #sLine)
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif sEvent == "term_resize" then
|
||||
-- Terminal resized
|
||||
w = term.getSize()
|
||||
redraw()
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
local _, cy = term.getPos()
|
||||
term.setBlink(false)
|
||||
term.setPos(w + 1, cy)
|
||||
print()
|
||||
|
||||
return sLine
|
||||
end
|
||||
|
||||
|
||||
--[[function io.write(text)
|
||||
text = tostring(text)
|
||||
|
||||
local lines = 0
|
||||
local w, h = term.getSize()
|
||||
|
||||
local function inc_cy(cy)
|
||||
lines = lines + 1
|
||||
|
||||
if cy > h - 1 then
|
||||
term.scroll(1)
|
||||
return cy
|
||||
else
|
||||
return cy + 1
|
||||
end
|
||||
end
|
||||
|
||||
while #text > 0 do
|
||||
local nl = text:find("\n") or #text
|
||||
local chunk = text:sub(1, nl)
|
||||
text = text:sub(#chunk + 1)
|
||||
|
||||
local has_nl = chunk:sub(-1) == "\n"
|
||||
if has_nl then chunk = chunk:sub(1, -2) end
|
||||
|
||||
local cx, cy = term.getPos()
|
||||
while #chunk > 0 do
|
||||
if cx > w then
|
||||
term.setPos(1, inc_cy(cy))
|
||||
cx, cy = term.getPos()
|
||||
end
|
||||
|
||||
local to_write = chunk:sub(1, w - cx + 1)
|
||||
term.write(to_write)
|
||||
|
||||
chunk = chunk:sub(#to_write + 1)
|
||||
cx, cy = term.getPos()
|
||||
end
|
||||
|
||||
if has_nl then
|
||||
term.setPos(1, inc_cy(cy))
|
||||
end
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
local empty = {}
|
||||
function io.read(replace, history, complete, default)
|
||||
expect(1, replace, "string", "nil")
|
||||
expect(2, history, "table", "nil")
|
||||
expect(3, complete, "function", "nil")
|
||||
expect(4, default, "string", "nil")
|
||||
|
||||
if replace then replace = replace:sub(1, 1) end
|
||||
local hist = history or {}
|
||||
history = {}
|
||||
for i = 1, #hist, 1 do
|
||||
history[i] = hist[i]
|
||||
end
|
||||
|
||||
local buffer = default or ""
|
||||
local prev_buf = buffer
|
||||
history[#history + 1] = buffer
|
||||
|
||||
local hist_pos = #history
|
||||
local cursor_pos = 0
|
||||
|
||||
local stx, sty = term.getPos()
|
||||
local w, h = term.getSize()
|
||||
|
||||
local dirty = false
|
||||
local completions = {}
|
||||
local comp_id = 0
|
||||
|
||||
local function clearCompletion()
|
||||
if completions[comp_id] then
|
||||
io.write((" "):rep(#completions[comp_id]))
|
||||
end
|
||||
end
|
||||
|
||||
local function full_redraw(force)
|
||||
if force or dirty then
|
||||
if complete and buffer ~= prev_buf then
|
||||
completions = complete(buffer) or empty
|
||||
comp_id = math.min(1, #completions)
|
||||
end
|
||||
prev_buf = buffer
|
||||
|
||||
term.setPos(stx, sty)
|
||||
local text = buffer
|
||||
if replace then text = replace:rep(#text) end
|
||||
local ln = io.write(text)
|
||||
|
||||
if completions[comp_id] then
|
||||
local oldfg = term.getForeground()
|
||||
local oldbg = term.getBackground()
|
||||
term.setForeground(colors.white)
|
||||
term.setBackground(colors.gray)
|
||||
ln = ln + write(completions[comp_id])
|
||||
term.setForeground(oldfg)
|
||||
term.setBackground(oldbg)
|
||||
else
|
||||
ln = ln + io.write(" ")
|
||||
end
|
||||
|
||||
if sty + ln > h then
|
||||
sty = sty - (sty + ln - h)
|
||||
end
|
||||
end
|
||||
|
||||
-- set cursor to the appropriate spot
|
||||
local cx, cy = stx, sty
|
||||
cx = cx + #buffer - cursor_pos -- + #(completions[comp_id] or "")
|
||||
while cx > w do
|
||||
cx = cx - w
|
||||
cy = cy + 1
|
||||
end
|
||||
term.setPos(cx, cy)
|
||||
end
|
||||
|
||||
term.setBlink(true)
|
||||
|
||||
while true do
|
||||
full_redraw()
|
||||
-- get input
|
||||
local evt, par1, par2, mods = event.pull()
|
||||
|
||||
if evt == "char" then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if cursor_pos == 0 then
|
||||
buffer = buffer .. par1
|
||||
elseif cursor_pos == #buffer then
|
||||
buffer = par1 .. buffer
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. par1 .. buffer:sub(-cursor_pos)
|
||||
end
|
||||
elseif evt == "key_down" then
|
||||
if par1 == keys.back and #buffer > 0 then
|
||||
dirty = true
|
||||
if cursor_pos == 0 then
|
||||
buffer = buffer:sub(1, -2)
|
||||
clearCompletion()
|
||||
elseif cursor_pos < #buffer then
|
||||
buffer = buffer:sub(0, -cursor_pos - 2) .. buffer:sub(-cursor_pos)
|
||||
end
|
||||
elseif par1 == keys.delete and cursor_pos > 0 then
|
||||
dirty = true
|
||||
|
||||
if cursor_pos == #buffer then
|
||||
buffer = buffer:sub(2)
|
||||
elseif cursor_pos == 1 then
|
||||
buffer = buffer:sub(1, -2)
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. buffer:sub(-cursor_pos + 1)
|
||||
end
|
||||
cursor_pos = cursor_pos - 1
|
||||
elseif par1 == keys.up then
|
||||
if #completions > 1 then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if comp_id > 1 then
|
||||
comp_id = comp_id - 1
|
||||
else
|
||||
comp_id = #completions
|
||||
end
|
||||
elseif hist_pos > 1 then
|
||||
cursor_pos = 0
|
||||
|
||||
history[hist_pos] = buffer
|
||||
hist_pos = hist_pos - 1
|
||||
|
||||
buffer = (" "):rep(#buffer)
|
||||
full_redraw(true)
|
||||
|
||||
buffer = history[hist_pos]
|
||||
dirty = true
|
||||
end
|
||||
elseif par1 == keys.down then
|
||||
if #completions > 1 then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if comp_id < #completions then
|
||||
comp_id = comp_id + 1
|
||||
else
|
||||
comp_id = 1
|
||||
end
|
||||
elseif hist_pos < #history then
|
||||
cursor_pos = 0
|
||||
|
||||
history[hist_pos] = buffer
|
||||
hist_pos = hist_pos + 1
|
||||
|
||||
buffer = (" "):rep(#buffer)
|
||||
full_redraw(true)
|
||||
|
||||
buffer = history[hist_pos]
|
||||
dirty = true
|
||||
end
|
||||
elseif par1 == keys.left then
|
||||
if cursor_pos < #buffer then
|
||||
clearCompletion()
|
||||
cursor_pos = cursor_pos + 1
|
||||
end
|
||||
elseif par1 == keys.right then
|
||||
if cursor_pos > 0 then
|
||||
cursor_pos = cursor_pos - 1
|
||||
elseif comp_id > 0 then
|
||||
dirty = true
|
||||
buffer = buffer .. completions[comp_id]
|
||||
end
|
||||
elseif par1 == keys.tab then
|
||||
if comp_id > 0 then
|
||||
dirty = true
|
||||
buffer = buffer .. completions[comp_id]
|
||||
end
|
||||
elseif par1 == keys.home then
|
||||
cursor_pos = #buffer
|
||||
elseif par1 == keys["end"] then
|
||||
cursor_pos = 0
|
||||
elseif par1 == keys.enter then
|
||||
clearCompletion()
|
||||
print()
|
||||
break
|
||||
elseif mods & keys.mods.ctrl ~= 0 then
|
||||
if par1 == keys.v then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
local text = machine.getClipboard()
|
||||
if text then
|
||||
if cursor_pos == 0 then
|
||||
buffer = buffer .. text
|
||||
elseif cursor_pos == #buffer then
|
||||
buffer = text .. buffer
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. text ..
|
||||
buffer:sub(-cursor_pos + (#text - 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
term.setBlink(false)
|
||||
|
||||
return buffer
|
||||
end
|
||||
]]
|
||||
|
||||
io.stderr = {}
|
||||
|
||||
function io.stderr.write(text)
|
||||
local fg = term.getForeground()
|
||||
term.setForeground(0xff0000)
|
||||
io.write(text)
|
||||
term.setForeground(fg)
|
||||
end
|
||||
|
||||
function io.stderr.print(...)
|
||||
local fg = term.getForeground()
|
||||
term.setForeground(0xff0000)
|
||||
print(...)
|
||||
term.setForeground(fg)
|
||||
end
|
||||
|
||||
return io
|
||||
388
Capy64/Assets/Lua/CapyOS/sys/lib/json.lua
Normal file
388
Capy64/Assets/Lua/CapyOS/sys/lib/json.lua
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
179
Capy64/Assets/Lua/CapyOS/sys/lib/keys.lua
Normal file
179
Capy64/Assets/Lua/CapyOS/sys/lib/keys.lua
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
local keys = {
|
||||
none = 0,
|
||||
back = 8,
|
||||
tab = 9,
|
||||
enter = 13,
|
||||
pause = 19,
|
||||
caps_lock = 20,
|
||||
kana = 21,
|
||||
kanji = 25,
|
||||
escape = 27,
|
||||
ime_convert = 28,
|
||||
ime_no_convert = 29,
|
||||
space = 32,
|
||||
page_up = 33,
|
||||
page_down = 34,
|
||||
["end"] = 35,
|
||||
home = 36,
|
||||
left = 37,
|
||||
up = 38,
|
||||
right = 39,
|
||||
down = 40,
|
||||
select = 41,
|
||||
print = 42,
|
||||
execute = 43,
|
||||
print_screen = 44,
|
||||
insert = 45,
|
||||
delete = 46,
|
||||
help = 47,
|
||||
zero = 48,
|
||||
one = 49,
|
||||
two = 50,
|
||||
three = 51,
|
||||
four = 52,
|
||||
five = 53,
|
||||
six = 54,
|
||||
seven = 55,
|
||||
eight = 56,
|
||||
nine = 57,
|
||||
a = 65,
|
||||
b = 66,
|
||||
c = 67,
|
||||
d = 68,
|
||||
e = 69,
|
||||
f = 70,
|
||||
g = 71,
|
||||
h = 72,
|
||||
i = 73,
|
||||
j = 74,
|
||||
k = 75,
|
||||
l = 76,
|
||||
m = 77,
|
||||
n = 78,
|
||||
o = 79,
|
||||
p = 80,
|
||||
q = 81,
|
||||
r = 82,
|
||||
s = 83,
|
||||
t = 84,
|
||||
u = 85,
|
||||
v = 86,
|
||||
w = 87,
|
||||
x = 88,
|
||||
y = 89,
|
||||
z = 90,
|
||||
left_windows = 91,
|
||||
right_windows = 92,
|
||||
apps = 93,
|
||||
sleep = 95,
|
||||
num_pad0 = 96,
|
||||
num_pad1 = 97,
|
||||
num_pad2 = 98,
|
||||
num_pad3 = 99,
|
||||
num_pad4 = 100,
|
||||
num_pad5 = 101,
|
||||
num_pad6 = 102,
|
||||
num_pad7 = 103,
|
||||
num_pad8 = 104,
|
||||
num_pad9 = 105,
|
||||
multiply = 106,
|
||||
add = 107,
|
||||
separator = 108,
|
||||
subtract = 109,
|
||||
decimal = 110,
|
||||
divide = 111,
|
||||
f1 = 112,
|
||||
f2 = 113,
|
||||
f3 = 114,
|
||||
f4 = 115,
|
||||
f5 = 116,
|
||||
f6 = 117,
|
||||
f7 = 118,
|
||||
f8 = 119,
|
||||
f9 = 120,
|
||||
f10 = 121,
|
||||
f11 = 122,
|
||||
f12 = 123,
|
||||
f13 = 124,
|
||||
f14 = 125,
|
||||
f15 = 126,
|
||||
f16 = 127,
|
||||
f17 = 128,
|
||||
f18 = 129,
|
||||
f19 = 130,
|
||||
f20 = 131,
|
||||
f21 = 132,
|
||||
f22 = 133,
|
||||
f23 = 134,
|
||||
f24 = 135,
|
||||
num_lock = 144,
|
||||
scroll = 145,
|
||||
left_shift = 160,
|
||||
right_shift = 161,
|
||||
left_control = 162,
|
||||
right_control = 163,
|
||||
left_alt = 164,
|
||||
right_alt = 165,
|
||||
browser_back = 166,
|
||||
browser_forward = 167,
|
||||
browser_refresh = 168,
|
||||
browser_stop = 169,
|
||||
browser_search = 170,
|
||||
browser_favorites = 171,
|
||||
browser_home = 172,
|
||||
volume_mute = 173,
|
||||
volume_down = 174,
|
||||
volume_up = 175,
|
||||
media_next_track = 176,
|
||||
media_previous_track = 177,
|
||||
media_stop = 178,
|
||||
media_play_pause = 179,
|
||||
launch_mail = 180,
|
||||
select_media = 181,
|
||||
launch_application1 = 182,
|
||||
launch_application2 = 183,
|
||||
semicolon = 186,
|
||||
plus = 187,
|
||||
comma = 188,
|
||||
minus = 189,
|
||||
period = 190,
|
||||
question = 191,
|
||||
tilde = 192,
|
||||
chat_pad_green = 202,
|
||||
chat_pad_orange = 203,
|
||||
open_brackets = 219,
|
||||
pipe = 220,
|
||||
close_brackets = 221,
|
||||
quotes = 222,
|
||||
oem8 = 223,
|
||||
backslash = 226,
|
||||
process_key = 229,
|
||||
copy = 242,
|
||||
auto = 243,
|
||||
enl_w = 244,
|
||||
attn = 246,
|
||||
crsel = 247,
|
||||
exsel = 248,
|
||||
erase_eof = 249,
|
||||
play = 250,
|
||||
zoom = 251,
|
||||
pa1 = 253,
|
||||
clear = 254,
|
||||
}
|
||||
|
||||
keys.mods = {
|
||||
none = 0,
|
||||
left_shift = 1,
|
||||
right_shift = 2,
|
||||
left_alt = 4,
|
||||
right_alt = 8,
|
||||
left_control = 16,
|
||||
right_control = 32,
|
||||
}
|
||||
|
||||
keys.mods.shift = keys.mods.left_shift | keys.mods.right_shift
|
||||
keys.mods.alt = keys.mods.left_alt | keys.mods.right_alt
|
||||
keys.mods.control = keys.mods.left_control | keys.mods.right_control
|
||||
keys.mods.ctrl = keys.mods.control
|
||||
|
||||
return keys
|
||||
3
Capy64/Assets/Lua/CapyOS/sys/lib/lib.lua
Normal file
3
Capy64/Assets/Lua/CapyOS/sys/lib/lib.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
local x = math.random()
|
||||
|
||||
return x
|
||||
61
Capy64/Assets/Lua/CapyOS/sys/lib/parallel.lua
Normal file
61
Capy64/Assets/Lua/CapyOS/sys/lib/parallel.lua
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
local expect = require("expect")
|
||||
|
||||
local parallel = {}
|
||||
|
||||
local function contains(array, value)
|
||||
for k, v in pairs(array) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function run(threads, exitOnAny)
|
||||
local alive = #threads
|
||||
local filters = {}
|
||||
local ev = {}
|
||||
while true do
|
||||
for i, thread in pairs(threads) do
|
||||
if not filters[i] or #filters[i] == 0 or contains(filters[i], ev[1]) or ev[1] == "interrupt" then
|
||||
local pars = table.pack(coroutine.resume(thread, table.unpack(ev)))
|
||||
if pars[1] then
|
||||
filters[i] = table.pack(table.unpack(pars, 2))
|
||||
else
|
||||
error(pars[2], 0)
|
||||
end
|
||||
end
|
||||
|
||||
if coroutine.status(thread) == "dead" then
|
||||
alive = alive - 1
|
||||
if exitOnAny or alive <= 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ev = table.pack(coroutine.yield())
|
||||
end
|
||||
end
|
||||
|
||||
function parallel.waitForAll(...)
|
||||
local threads = {}
|
||||
for k, v in ipairs({ ... }) do
|
||||
expect(k, v, "function")
|
||||
table.insert(threads, coroutine.create(v))
|
||||
end
|
||||
|
||||
return run(threads, false)
|
||||
end
|
||||
|
||||
function parallel.waitForAny(...)
|
||||
local threads = {}
|
||||
for k, v in ipairs({ ... }) do
|
||||
expect(k, v, "function")
|
||||
table.insert(threads, coroutine.create(v))
|
||||
end
|
||||
|
||||
return run(threads, true)
|
||||
end
|
||||
|
||||
return parallel
|
||||
182
Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua
Normal file
182
Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
local expect = require("expect").expect
|
||||
local tableutils = require("tableutils")
|
||||
local event = require("event")
|
||||
|
||||
local scheduler = {}
|
||||
|
||||
local function contains(array, value)
|
||||
for k, v in pairs(array) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local tasks = {}
|
||||
local processes = 0
|
||||
|
||||
local Task = {}
|
||||
local TaskMeta = {
|
||||
__index = Task,
|
||||
__name = "OS_TASK",
|
||||
__tostring = function(self)
|
||||
return string.format("OS_TASK[%s]: %d", self.source or "", self.pid or 0)
|
||||
end,
|
||||
}
|
||||
local function newTask()
|
||||
local task = {}
|
||||
return setmetatable(task, TaskMeta)
|
||||
end
|
||||
|
||||
function Task:queue(eventName, ...)
|
||||
expect(1, eventName, "string")
|
||||
event.push("scheduler", self.pid, eventName, ...)
|
||||
end
|
||||
|
||||
local function findParent()
|
||||
local i = 3
|
||||
|
||||
while true do
|
||||
local info = debug.getinfo(i)
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
|
||||
for pid, task in pairs(tasks) do
|
||||
if task.uuid == tostring(info.func) then
|
||||
return task
|
||||
end
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function cascadeKill(pid, err)
|
||||
local task = tasks[pid]
|
||||
if not task then
|
||||
return
|
||||
end
|
||||
for i, cpid in ipairs(task.children) do
|
||||
cascadeKill(cpid, err)
|
||||
end
|
||||
if task.parent then
|
||||
local parent = tasks[task.parent]
|
||||
if parent then
|
||||
local index = tableutils.find(parent.children, task.pid)
|
||||
table.remove(parent.children, index)
|
||||
parent:queue("scheduler_task_end", task, err == nil, err)
|
||||
end
|
||||
else
|
||||
if err then
|
||||
error(err, 0)
|
||||
end
|
||||
end
|
||||
if task then
|
||||
task.killed = true
|
||||
coroutine.close(task.thread)
|
||||
tasks[pid] = nil
|
||||
processes = processes - 1
|
||||
end
|
||||
end
|
||||
|
||||
local function resumeTask(task, yieldPars)
|
||||
local pars = table.pack(coroutine.resume(task.thread, table.unpack(yieldPars)))
|
||||
if pars[1] then
|
||||
task.filters = table.pack(table.unpack(pars, 2))
|
||||
return coroutine.status(task.thread) ~= "dead"
|
||||
else
|
||||
cascadeKill(task.pid, pars[2])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function scheduler.spawn(func, options)
|
||||
expect(1, func, "function")
|
||||
expect(2, options, "nil", "table")
|
||||
|
||||
options = options or {}
|
||||
options.args = options.args or {}
|
||||
|
||||
local source = debug.getinfo(2)
|
||||
|
||||
local task = newTask()
|
||||
local pid = #tasks + 1
|
||||
task.pid = pid
|
||||
task.options = options
|
||||
task.source = source.source
|
||||
task.uuid = tostring(func)
|
||||
task.thread = coroutine.create(func)
|
||||
local parent = findParent()
|
||||
if parent then
|
||||
task.parent = parent.pid
|
||||
table.insert(parent.children, pid)
|
||||
end
|
||||
task.filters = {}
|
||||
task.children = {}
|
||||
task.eventQueue = {}
|
||||
task.skip = true
|
||||
|
||||
tasks[pid] = task
|
||||
|
||||
processes = processes + 1
|
||||
|
||||
return task, resumeTask(task, task.options.args)
|
||||
end
|
||||
|
||||
function scheduler.kill(pid)
|
||||
expect(1, pid, "number")
|
||||
cascadeKill(pid)
|
||||
end
|
||||
|
||||
function scheduler.ipc(pid, ...)
|
||||
expect(1, pid, "number")
|
||||
if not tasks[pid] then
|
||||
error("process by pid " .. pid .. " does not exist.", 2)
|
||||
end
|
||||
|
||||
local sender = findParent()
|
||||
tasks[pid]:queue("ipc_message", sender, ...)
|
||||
end
|
||||
|
||||
local running = false
|
||||
function scheduler.init()
|
||||
if running then
|
||||
error("scheduler already running", 2)
|
||||
end
|
||||
running = true
|
||||
|
||||
local ev = { n = 0 }
|
||||
while processes > 0 do
|
||||
for pid, task in pairs(tasks) do
|
||||
local yieldPars = ev
|
||||
if ev[1] == "scheduler" and ev[2] == pid then
|
||||
yieldPars = table.pack(table.unpack(ev, 3))
|
||||
end
|
||||
if yieldPars[1] ~= "scheduler" and not task.filters or #task.filters == 0 or contains(task.filters, yieldPars[1]) or yieldPars[1] == "interrupt" then
|
||||
if task.skip then
|
||||
task.skip = false
|
||||
else
|
||||
resumeTask(task, yieldPars)
|
||||
end
|
||||
end
|
||||
|
||||
if coroutine.status(task.thread) == "dead" then
|
||||
cascadeKill(pid)
|
||||
end
|
||||
end
|
||||
|
||||
if processes <= 0 then
|
||||
break
|
||||
end
|
||||
|
||||
ev = table.pack(coroutine.yield())
|
||||
end
|
||||
|
||||
running = false
|
||||
end
|
||||
|
||||
return scheduler
|
||||
112
Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua
Normal file
112
Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
local expect = require("expect").expect
|
||||
local fs = require("fs")
|
||||
local nativePackage = package
|
||||
|
||||
local function copyTable(source, target)
|
||||
target = target or {}
|
||||
|
||||
for k, v in pairs(source) do
|
||||
target[k] = v
|
||||
end
|
||||
|
||||
return target
|
||||
end
|
||||
|
||||
local hostPackages = copyTable(nativePackage._host)
|
||||
|
||||
local function createPreloadSearcher(envPackage)
|
||||
return function(name)
|
||||
if not envPackage.preload[name] then
|
||||
return string.format("no field package.preload['%s']", name)
|
||||
end
|
||||
return envPackage.preload[name], ":preload:"
|
||||
end
|
||||
end
|
||||
|
||||
local function createLoaderSearcher(envPackage)
|
||||
return function(name)
|
||||
local path, err = envPackage.searchpath(name, envPackage.path)
|
||||
|
||||
if not path then
|
||||
return err
|
||||
end
|
||||
|
||||
local func, err = loadfile(path)
|
||||
if not func then
|
||||
return string.format("error loading module '%s' from file '%s':\t%s", name, path, err)
|
||||
end
|
||||
|
||||
return func, path
|
||||
end
|
||||
end
|
||||
|
||||
local function createEnvironment(filePath)
|
||||
local envPackage = {
|
||||
cpath = nativePackage.cpath,
|
||||
searchpath = nativePackage.searchpath,
|
||||
config = nativePackage.config,
|
||||
searchers = {},
|
||||
loaded = {},
|
||||
preload = {},
|
||||
}
|
||||
|
||||
local dirName = fs.getDir(filePath)
|
||||
--envPackage.path = string.format("%s/?.lua;%s/?/init.lua;", dirName, dirName) .. nativePackage.path
|
||||
envPackage.path = nativePackage.path
|
||||
|
||||
envPackage.searchers[1] = createPreloadSearcher(envPackage)
|
||||
envPackage.searchers[2] = createLoaderSearcher(envPackage)
|
||||
|
||||
local function envRequire(modname)
|
||||
expect(1, modname, "string", "number")
|
||||
modname = tostring(modname)
|
||||
|
||||
if envPackage.loaded[modname] then
|
||||
return envPackage.loaded[modname]
|
||||
end
|
||||
|
||||
local errorOutput = ""
|
||||
local libFunction, libPath
|
||||
for i = 1, #envPackage.searchers do
|
||||
local par, path = envPackage.searchers[i](modname)
|
||||
if type(par) == "function" then
|
||||
libFunction, libPath = par, path
|
||||
break
|
||||
else
|
||||
errorOutput = errorOutput .. "\n\t" .. par
|
||||
end
|
||||
end
|
||||
|
||||
if not libFunction then
|
||||
error(string.format("module '%s' not found:%s", modname, errorOutput), 2)
|
||||
end
|
||||
|
||||
local ok, par = pcall(libFunction)
|
||||
if not ok then
|
||||
error(par, 0)
|
||||
end
|
||||
|
||||
if par == nil then
|
||||
envPackage.loaded[modname] = true
|
||||
return true
|
||||
end
|
||||
|
||||
envPackage.loaded[modname] = par
|
||||
|
||||
return par, libPath
|
||||
end
|
||||
|
||||
copyTable(hostPackages, envPackage.loaded)
|
||||
envPackage.loaded.package = envPackage
|
||||
|
||||
local env_G = copyTable(envPackage.loaded._G or _G)
|
||||
envPackage.loaded._G = env_G
|
||||
env_G._G = env_G
|
||||
|
||||
envPackage.loaded._G.package = envPackage
|
||||
envPackage.loaded._G.require = envRequire
|
||||
|
||||
return envPackage, envRequire
|
||||
end
|
||||
|
||||
return createEnvironment
|
||||
87
Capy64/Assets/Lua/CapyOS/sys/lib/tableutils.lua
Normal file
87
Capy64/Assets/Lua/CapyOS/sys/lib/tableutils.lua
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
local expect = require("expect").expect
|
||||
local tableutils = {}
|
||||
|
||||
local function serialize(data, circular)
|
||||
expect(1, data, "string", "table", "number", "boolean", "nil")
|
||||
if type(data) == "table" then
|
||||
if not circular then
|
||||
circular = {}
|
||||
end
|
||||
local output = "{"
|
||||
for k, v in pairs(data) do
|
||||
if type(v) == "table" then
|
||||
local name = tostring(v)
|
||||
if circular[name] then
|
||||
error("circular reference in table", 2)
|
||||
end
|
||||
circular[name] = true
|
||||
end
|
||||
output = output .. string.format("[%q] = %s,", k, serialize(v, circular))
|
||||
end
|
||||
output = output .. "}"
|
||||
return output
|
||||
else
|
||||
return string.format("%q", data)
|
||||
end
|
||||
end
|
||||
|
||||
function tableutils.serialize(data)
|
||||
expect(1, data, "string", "table", "number", "boolean", "nil")
|
||||
return serialize(data)
|
||||
end
|
||||
|
||||
function tableutils.deserialize(data)
|
||||
local func, err = load("return " .. data, "=tableutils", "t", {})
|
||||
if not func then
|
||||
error(err, 2)
|
||||
end
|
||||
return func()
|
||||
end
|
||||
|
||||
local function prettyvalue(value)
|
||||
if type(value) == "table" or type(value) == "function" or type(value) == "thread" or type(value) == "userdata" or type(value) == "number" then
|
||||
return tostring(value)
|
||||
else
|
||||
return string.format("%q", value)
|
||||
end
|
||||
end
|
||||
|
||||
function tableutils.pretty(data)
|
||||
if type(data) == "table" then
|
||||
local output = "{"
|
||||
|
||||
local index = 0
|
||||
for k, v in pairs(data) do
|
||||
local value = prettyvalue(v)
|
||||
|
||||
if type(k) == "number" and k - 1 == index then
|
||||
index = index + 1
|
||||
output = output .. string.format("\n %s,", value)
|
||||
elseif type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
||||
output = output .. string.format("\n %s = %s,", k, value)
|
||||
else
|
||||
output = output .. string.format("\n [%s] = %s,", prettyvalue(k), value)
|
||||
end
|
||||
end
|
||||
if output == "{" then
|
||||
return "{}"
|
||||
end
|
||||
output = output .. "\n}"
|
||||
|
||||
return output
|
||||
else
|
||||
return prettyvalue(data)
|
||||
end
|
||||
end
|
||||
|
||||
function tableutils.find(tbl, element)
|
||||
for i = 1, #tbl do
|
||||
if tbl[i] == element then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
return tableutils
|
||||
1776
Capy64/Assets/Lua/CapyOS/sys/lib/utfstring.lua
Normal file
1776
Capy64/Assets/Lua/CapyOS/sys/lib/utfstring.lua
Normal file
File diff suppressed because it is too large
Load diff
2
Capy64/Assets/Lua/CapyOS/sys/share/help/index
Normal file
2
Capy64/Assets/Lua/CapyOS/sys/share/help/index
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Welcome to CapyOS!
|
||||
Run "programs" to get a list of available programs.
|
||||
12
Capy64/Assets/Lua/CapyOS/sys/share/help/license
Normal file
12
Capy64/Assets/Lua/CapyOS/sys/share/help/license
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
CapyOS and Capy64 are licensed under the Apache 2.0 Public License.
|
||||
https://capy64.alexdevs.me/
|
||||
https://github.com/Ale32bit/Capy64
|
||||
|
||||
Some CapyOS components include code from the CC Recrafted project.
|
||||
https://github.com/Ocawesome101/recrafted
|
||||
|
||||
Some CapyOS components may include code from CC: Tweaked.
|
||||
https://github.com/CC-Tweaked/CC-Tweaked
|
||||
|
||||
json.lua by rxi is licensed under the MIT License.
|
||||
https://github.com/rxi/json.lua
|
||||
3
Capy64/Assets/Lua/CapyOS/sys/share/motd.txt
Normal file
3
Capy64/Assets/Lua/CapyOS/sys/share/motd.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You can get started with Lua by following this manual: https://www.lua.org/manual/
|
||||
Learn more about Capy64 by following the documentation: https://capy64.alexdevs.me/
|
||||
Found a bug or would like to suggest a feature? https://github.com/Ale32bit/Capy64/issues
|
||||
BIN
Capy64/Assets/Lua/CapyOS/sys/vendor.bmp
Normal file
BIN
Capy64/Assets/Lua/CapyOS/sys/vendor.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
5
Capy64/Assets/Lua/README.txt
Normal file
5
Capy64/Assets/Lua/README.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
If you are looking for your data directory look in:
|
||||
|
||||
Windows: %APPDATA%\Capy64\data
|
||||
Mac: $HOME/.local/share/Capy64/data
|
||||
Linux: $HOME/.local/share/Capy64/data
|
||||
275
Capy64/Assets/Lua/bios.lua
Normal file
275
Capy64/Assets/Lua/bios.lua
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
-- This file is part of Capy64 - https://github.com/Capy64/Capy64
|
||||
-- Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License").
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
local term = require("term")
|
||||
local timer = require("timer")
|
||||
local gpu = require("gpu")
|
||||
local fs = require("fs")
|
||||
local machine = require("machine")
|
||||
local audio = require("audio")
|
||||
local event = require("event")
|
||||
|
||||
local bootSleep = 2
|
||||
local bg = 0x0
|
||||
local fg = 0xffffff
|
||||
local setupbg = 0x0608a6
|
||||
local setupfg = 0xffffff
|
||||
local accent = 0xffea00
|
||||
|
||||
term.setForeground(fg)
|
||||
term.setBackground(bg)
|
||||
term.clear()
|
||||
|
||||
if term.isResizable() then
|
||||
term.setSize(53, 20)
|
||||
end
|
||||
|
||||
local w, h = term.getSize()
|
||||
|
||||
local function sleep(n)
|
||||
local timerId = timer.start(n)
|
||||
repeat
|
||||
local ev, par = coroutine.yield("timer")
|
||||
until par == timerId
|
||||
end
|
||||
|
||||
local function writeCenter(text)
|
||||
local w, h = term.getSize()
|
||||
local _, y = term.getPos()
|
||||
term.setPos(
|
||||
(1 + w / 2) - (#text / 2),
|
||||
y
|
||||
)
|
||||
term.write(text)
|
||||
end
|
||||
|
||||
local function drawVendorImage()
|
||||
if not fs.exists("/sys/vendor.bmp") then
|
||||
return
|
||||
end
|
||||
|
||||
local w, h = gpu.getSize()
|
||||
local ok, err = pcall(function()
|
||||
local task<close> = gpu.loadImageAsync("/sys/vendor.bmp")
|
||||
local buffer<close> = task:await()
|
||||
|
||||
local x, y =
|
||||
math.ceil((w / 2) - (buffer.width / 2)),
|
||||
math.ceil((h / 2) - (buffer.height / 2))
|
||||
|
||||
gpu.drawBuffer(buffer, x, y)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
print("Warning: Could not draw vendor.bmp", err)
|
||||
end
|
||||
end
|
||||
|
||||
local nPrint = print
|
||||
local function print( text )
|
||||
local x, y = term.getPos()
|
||||
term.write(tostring(text))
|
||||
if y == h then
|
||||
term.scroll(1)
|
||||
term.setPos(1, y)
|
||||
else
|
||||
term.setPos(1, y + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function printError( text )
|
||||
term.setForeground(0xff0000)
|
||||
print(text)
|
||||
term.setForeground(0xffffff)
|
||||
end
|
||||
|
||||
local function promptKey()
|
||||
print("Press any key to continue")
|
||||
event.pull("key_down")
|
||||
end
|
||||
|
||||
local function alert(...)
|
||||
local args = {...}
|
||||
table.insert(args, "[ OK ]")
|
||||
local lines = {}
|
||||
local width = 0
|
||||
local padding = 1
|
||||
for k, v in ipairs(args) do
|
||||
lines[k] = tostring(v)
|
||||
width = math.max(width, #tostring(v))
|
||||
end
|
||||
|
||||
lines[#lines] = nil
|
||||
|
||||
local okPad = string.rep(" ", (width - #args[#args]) // 2)
|
||||
local okLine = string.format("%s%s", okPad, args[#args])
|
||||
|
||||
local w, h = term.getSize()
|
||||
local cx, cy = w//2, h//2
|
||||
local dx, dy = cx - (width + padding * 2) // 2, cy - #lines//2 - 1
|
||||
|
||||
local pad = string.rep(" ", padding)
|
||||
local emptyLine = string.format("\u{258C}%s%s%s\u{2590}", pad, string.rep(" ", width), pad)
|
||||
|
||||
term.setPos(dx, dy)
|
||||
print("\u{259B}" .. string.rep("\u{2580}", width + padding * 2) .. "\u{259C}")
|
||||
for k, v in ipairs(lines) do
|
||||
term.setPos(dx, dy + k)
|
||||
local space = string.rep(" ", width - #v)
|
||||
print(string.format("\u{258C}%s%s%s%s\u{2590}", pad, v, space, pad))
|
||||
end
|
||||
term.setPos(dx, dy + #lines + 1)
|
||||
print(emptyLine)
|
||||
term.setPos(dx, dy + #lines + 2)
|
||||
local space = string.rep(" ", width - #okLine)
|
||||
term.write(string.format("\u{258C}%s", pad))
|
||||
term.setForeground(accent)
|
||||
term.write(okLine)
|
||||
term.setForeground(setupfg)
|
||||
print(string.format("%s%s\u{2590}", space, pad))
|
||||
term.setPos(dx, dy + #lines + 3)
|
||||
print("\u{2599}" .. string.rep("\u{2584}", width + padding * 2) .. "\u{259F}")
|
||||
|
||||
local _, key, keyname
|
||||
repeat
|
||||
_, key, keyname = event.pull("key_down")
|
||||
until keyname == "enter" or keyname == "escape"
|
||||
end
|
||||
|
||||
local function installDefaultOS()
|
||||
if fs.exists("/sys") then
|
||||
fs.delete("/sys", true)
|
||||
end
|
||||
installOS()
|
||||
alert("Default OS installed!")
|
||||
end
|
||||
|
||||
term.setBlink(false)
|
||||
|
||||
local function setupScreen()
|
||||
local options = {
|
||||
{
|
||||
"Open data folder",
|
||||
openDataFolder,
|
||||
},
|
||||
{
|
||||
"Install default OS",
|
||||
installDefaultOS,
|
||||
},
|
||||
{},
|
||||
{
|
||||
"Exit setup",
|
||||
exit,
|
||||
},
|
||||
{
|
||||
"Shutdown",
|
||||
machine.shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
local selection = 1
|
||||
local function redraw(noDrawSel)
|
||||
local w, h = term.getSize()
|
||||
term.setForeground(setupfg)
|
||||
term.setBackground(setupbg)
|
||||
term.clear()
|
||||
term.setPos(1,2)
|
||||
writeCenter("Capy64 Setup")
|
||||
|
||||
term.setPos(1,3)
|
||||
|
||||
term.setForeground(accent)
|
||||
|
||||
for k, v in ipairs(options) do
|
||||
local _, y = term.getPos()
|
||||
term.setPos(1, y + 1)
|
||||
term.clearLine()
|
||||
|
||||
if #v > 0 then
|
||||
if selection == k and not noDrawSel then
|
||||
writeCenter("[ " .. v[1] .. " ]")
|
||||
else
|
||||
writeCenter(v[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
term.setForeground(setupfg)
|
||||
|
||||
term.setPos(1, h - 2)
|
||||
term.write("\u{2190}\u{2191}\u{2192}\u{2193}: Move")
|
||||
term.setPos(1, h - 1)
|
||||
term.write("Escape: Exit")
|
||||
term.setPos(1, h)
|
||||
term.write("Enter: Select")
|
||||
end
|
||||
|
||||
while true do
|
||||
redraw()
|
||||
local ev = { coroutine.yield("key_down") }
|
||||
if ev[3] == "up" then
|
||||
selection = selection - 1
|
||||
if options[selection] and #options[selection] == 0 then
|
||||
selection = selection - 1
|
||||
end
|
||||
elseif ev[3] == "down" then
|
||||
selection = selection + 1
|
||||
if options[selection] and #options[selection] == 0 then
|
||||
selection = selection + 1
|
||||
end
|
||||
elseif ev[3] == "enter" then
|
||||
redraw(true)
|
||||
options[selection][2]()
|
||||
elseif ev[3] == "escape" then
|
||||
exit()
|
||||
end
|
||||
|
||||
if selection > #options then
|
||||
selection = 1
|
||||
elseif selection < 1 then
|
||||
selection = #options
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function bootScreen()
|
||||
drawVendorImage()
|
||||
|
||||
term.setPos(1,2)
|
||||
writeCenter("Capy64")
|
||||
term.setPos(1,4)
|
||||
writeCenter("(c) 2023 AlexDevs")
|
||||
|
||||
term.setPos(1, h - 1)
|
||||
writeCenter("Press F2 to open setup")
|
||||
|
||||
local timerId = timer.start(bootSleep)
|
||||
while true do
|
||||
local ev = {coroutine.yield("timer", "key_down")}
|
||||
if ev[1] == "timer" and ev[2] == timerId then
|
||||
exit()
|
||||
elseif ev[1] == "key_down" and ev[3] == "f2" then
|
||||
setupScreen()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
audio.beep(1000, 0.2, 0.2, "square")
|
||||
|
||||
bootScreen()
|
||||
|
||||
term.clear()
|
||||
51
Capy64/Assets/Lua/firmware.lua
Normal file
51
Capy64/Assets/Lua/firmware.lua
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-- This file is part of Capy64 - https://github.com/Capy64/Capy64
|
||||
-- Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License").
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
local event = require("event")
|
||||
local coroutine = coroutine
|
||||
|
||||
-- Declare event functions
|
||||
function event.pull(...)
|
||||
local ev = table.pack(coroutine.yield(...))
|
||||
if ev[1] == "interrupt" then
|
||||
error("Interrupted", 2)
|
||||
end
|
||||
return table.unpack(ev)
|
||||
end
|
||||
|
||||
function event.pullRaw(...)
|
||||
return coroutine.yield(...)
|
||||
end
|
||||
|
||||
-- Set task awaiter
|
||||
|
||||
local function awaiter(task)
|
||||
local status = task:getStatus()
|
||||
local uuid = task:getID()
|
||||
if status == "running" then
|
||||
local _, taskId, result, err
|
||||
repeat
|
||||
_, taskId, result, err = event.pull("task_finish")
|
||||
until taskId == uuid
|
||||
return result, err
|
||||
elseif status == "succeeded" then
|
||||
return task:getResult(), nil
|
||||
elseif status == "failed" then
|
||||
return nil, task:getError()
|
||||
end
|
||||
end
|
||||
|
||||
-- Second argument freezes the awaiter function, so it cannot be modified
|
||||
event.setAwaiter(awaiter, true)
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
local term = require("term")
|
||||
local timer = require("timer")
|
||||
local gpu = require("gpu")
|
||||
local fs = require("fs")
|
||||
local machine = require("machine")
|
||||
local audio = require("audio")
|
||||
local http = require("http")
|
||||
local event = require("event")
|
||||
|
||||
|
||||
local INDEX_URL = "https://raw.github.com/Capy64/CapyOS/deploy/index.json"
|
||||
local JSON_URL = "https://raw.github.com/Capy64/CapyOS/main/lib/json.lua"
|
||||
|
||||
local bootSleep = 2000
|
||||
local bg = 0x0
|
||||
local fg = 0xffffff
|
||||
|
||||
term.setForeground(fg)
|
||||
term.setBackground(bg)
|
||||
term.clear()
|
||||
|
||||
term.setSize(51, 19)
|
||||
gpu.setScale(2)
|
||||
|
||||
local w, h = term.getSize()
|
||||
|
||||
local function sleep(n)
|
||||
local timerId = timer.start(n)
|
||||
repeat
|
||||
local ev, par = coroutine.yield("timer")
|
||||
until par == timerId
|
||||
end
|
||||
|
||||
local function writeCenter(text)
|
||||
local w, h = term.getSize()
|
||||
local _, y = term.getPos()
|
||||
term.setPos(
|
||||
(1 + w / 2) - (#text / 2),
|
||||
y
|
||||
)
|
||||
term.write(text)
|
||||
end
|
||||
|
||||
local function drawVendorImage()
|
||||
if not fs.exists("/boot/vendor.bmp") then
|
||||
return
|
||||
end
|
||||
|
||||
local w, h = gpu.getSize()
|
||||
local ok, err = pcall(function()
|
||||
local buffer<close>, width, height = gpu.loadImage("/boot/vendor.bmp")
|
||||
|
||||
local x, y =
|
||||
math.ceil((w / 2) - (width / 2)),
|
||||
math.ceil((h / 2) - (height / 2))
|
||||
|
||||
gpu.drawBuffer(buffer, x, y, width, height)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
print("Warning: Could not draw vendor.bmp", err)
|
||||
end
|
||||
end
|
||||
|
||||
local nPrint = print
|
||||
local function print( text )
|
||||
local x, y = term.getPos()
|
||||
term.write(tostring(text))
|
||||
if y == h then
|
||||
term.scroll(1)
|
||||
term.setPos(1, y)
|
||||
else
|
||||
term.setPos(1, y + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function printError( text )
|
||||
term.setForeground(0xff0000)
|
||||
print(text)
|
||||
term.setForeground(0xffffff)
|
||||
end
|
||||
|
||||
local function hget(url, headers)
|
||||
local requestId = http.requestAsync(url, nil, headers, {binary = true})
|
||||
|
||||
local ev, rId, par, info
|
||||
repeat
|
||||
ev, rId, par, info = event.pull("http_response", "http_failure")
|
||||
until rId == requestId
|
||||
|
||||
if ev == "http_failure" then
|
||||
return nil, par
|
||||
end
|
||||
local content = par:read("a")
|
||||
par:close()
|
||||
return content, info
|
||||
end
|
||||
|
||||
local function promptKey()
|
||||
print("Press any key to continue")
|
||||
event.pull("key_down")
|
||||
end
|
||||
|
||||
local function installOS()
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
|
||||
print("Installing CapyOS...")
|
||||
|
||||
local jsonLib, par = hget(JSON_URL)
|
||||
if not jsonLib then
|
||||
printError(par)
|
||||
promptKey()
|
||||
return
|
||||
end
|
||||
|
||||
local json = load(jsonLib)()
|
||||
local indexData, par = hget(INDEX_URL)
|
||||
if not indexData then
|
||||
printError(par)
|
||||
promptKey()
|
||||
return
|
||||
end
|
||||
local index = json.decode(indexData)
|
||||
|
||||
for i, v in ipairs(index) do
|
||||
local dirname = fs.getDir(v.path)
|
||||
if not fs.exists(dirname) then
|
||||
fs.makeDir(dirname)
|
||||
end
|
||||
print("Downloading " .. v.path)
|
||||
local fileContent = hget(v.raw_url)
|
||||
local f = fs.open(v.path, "w")
|
||||
f:write(fileContent)
|
||||
f:close()
|
||||
print("Written to " .. v.path)
|
||||
end
|
||||
|
||||
flagInstalled()
|
||||
|
||||
print("CapyOS installed!")
|
||||
promptKey()
|
||||
end
|
||||
|
||||
term.setBlink(false)
|
||||
|
||||
local function setupScreen()
|
||||
local options = {
|
||||
{
|
||||
"Open data folder",
|
||||
openDataFolder,
|
||||
},
|
||||
{
|
||||
"Install default OS",
|
||||
installOS,
|
||||
},
|
||||
{
|
||||
"Exit setup",
|
||||
exit,
|
||||
},
|
||||
{
|
||||
"Shutdown",
|
||||
machine.shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
local selection = 1
|
||||
local function redraw()
|
||||
term.setForeground(fg)
|
||||
term.setBackground(bg)
|
||||
term.clear()
|
||||
term.setPos(1,2)
|
||||
writeCenter("Capy64 Setup")
|
||||
|
||||
term.setPos(1,3)
|
||||
|
||||
for k, v in ipairs(options) do
|
||||
local _, y = term.getPos()
|
||||
term.setPos(1, y + 1)
|
||||
term.clearLine()
|
||||
|
||||
if selection == k then
|
||||
writeCenter("[ " .. v[1] .. " ]")
|
||||
else
|
||||
writeCenter(v[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
redraw()
|
||||
local ev = { coroutine.yield("key_down") }
|
||||
if ev[3] == "up" then
|
||||
selection = selection - 1
|
||||
elseif ev[3] == "down" then
|
||||
selection = selection + 1
|
||||
elseif ev[3] == "enter" then
|
||||
options[selection][2]()
|
||||
elseif ev[3] == "escape" then
|
||||
exit()
|
||||
end
|
||||
|
||||
if selection > #options then
|
||||
selection = 1
|
||||
elseif selection < 1 then
|
||||
selection = #options
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function bootScreen()
|
||||
drawVendorImage()
|
||||
|
||||
term.setPos(1,2)
|
||||
writeCenter("Capy64")
|
||||
term.setPos(1,4)
|
||||
writeCenter("Powered by Capybaras")
|
||||
|
||||
term.setPos(1, h - 1)
|
||||
writeCenter("Press F2 to open setup")
|
||||
|
||||
local timerId = timer.start(bootSleep)
|
||||
while true do
|
||||
local ev = {coroutine.yield("timer", "key_down")}
|
||||
if ev[1] == "timer" and ev[2] == timerId then
|
||||
exit()
|
||||
elseif ev[1] == "key_down" and ev[3] == "f2" then
|
||||
setupScreen()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
audio.beep(1000, 0.4, 0.2)
|
||||
|
||||
if shouldInstallOS() then
|
||||
installOS()
|
||||
end
|
||||
|
||||
bootScreen()
|
||||
|
||||
term.clear()
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"EngineMode": 0,
|
||||
"SafeMode": false,
|
||||
"Window": {
|
||||
"Scale": 2
|
||||
},
|
||||
"HTTP": {
|
||||
"Enable": true,
|
||||
"Blacklist": [],
|
||||
|
|
|
|||
201
Capy64/Capy64.cs
201
Capy64/Capy64.cs
|
|
@ -1,41 +1,102 @@
|
|||
using Capy64.API;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.API;
|
||||
using Capy64.Core;
|
||||
using Capy64.Eventing;
|
||||
using Capy64.Extensions;
|
||||
using Capy64.Runtime;
|
||||
using Capy64.Integrations;
|
||||
using Capy64.PluginManager;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Capy64.Runtime;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MonoGame.Extended;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Capy64.Utils;
|
||||
using Capy64.Integrations;
|
||||
|
||||
namespace Capy64;
|
||||
|
||||
public class Capy64 : Game, IGame
|
||||
public enum EngineMode
|
||||
{
|
||||
public const string Version = "0.0.8-alpha";
|
||||
public static string AppDataPath = Path.Combine(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData,
|
||||
Environment.SpecialFolderOption.Create),
|
||||
"Capy64");
|
||||
Classic,
|
||||
Free
|
||||
}
|
||||
|
||||
public class Capy64 : Game
|
||||
{
|
||||
public const string Version = "1.1.2-beta";
|
||||
|
||||
public static class DefaultParameters
|
||||
{
|
||||
public const int Width = 318;
|
||||
public const int Height = 240;
|
||||
public const float Scale = 2f;
|
||||
public const float BorderMultiplier = 1.5f;
|
||||
public static readonly EngineMode EngineMode = EngineMode.Classic;
|
||||
|
||||
public const int ClassicTickrate = 30;
|
||||
public const int FreeTickrate = 60;
|
||||
}
|
||||
|
||||
public static readonly string AssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
public static readonly string AssetsPath = Path.Combine(AssemblyPath, "Assets");
|
||||
|
||||
public static string AppDataPath
|
||||
{
|
||||
get
|
||||
{
|
||||
string baseDir =
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData,
|
||||
Environment.SpecialFolderOption.Create) :
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData,
|
||||
Environment.SpecialFolderOption.Create) :
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData,
|
||||
Environment.SpecialFolderOption.Create) :
|
||||
"./";
|
||||
|
||||
return Path.Combine(baseDir, "Capy64");
|
||||
}
|
||||
}
|
||||
|
||||
public static Capy64 Instance { get; private set; }
|
||||
public Capy64 Game => this;
|
||||
public IList<IPlugin> NativePlugins { get; private set; }
|
||||
public IList<IPlugin> Plugins { get; private set; }
|
||||
public int Width { get; set; } = 400;
|
||||
public int Height { get; set; } = 300;
|
||||
public float Scale { get; set; } = 2f;
|
||||
public EngineMode EngineMode { get; private set; } = EngineMode.Classic;
|
||||
public IList<IComponent> NativePlugins { get; private set; }
|
||||
public IList<IComponent> Plugins { get; private set; }
|
||||
public int Width { get; set; } = DefaultParameters.Width;
|
||||
public int Height { get; set; } = DefaultParameters.Height;
|
||||
public float Scale { get; set; } = DefaultParameters.Scale;
|
||||
public Drawing Drawing { get; private set; }
|
||||
public Audio Audio { get; private set; }
|
||||
public LuaState LuaRuntime { get; set; }
|
||||
public Eventing.EventEmitter EventEmitter { get; private set; }
|
||||
public DiscordIntegration Discord { get; set; }
|
||||
public int TickRate => tickrate;
|
||||
public IConfiguration Configuration { get; private set; }
|
||||
|
||||
public Color BorderColor { get; set; } = Color.Black;
|
||||
|
||||
public Borders Borders = new()
|
||||
{
|
||||
|
|
@ -44,21 +105,23 @@ public class Capy64 : Game, IGame
|
|||
Left = 0,
|
||||
Right = 0,
|
||||
};
|
||||
|
||||
public SpriteBatch SpriteBatch;
|
||||
|
||||
|
||||
private readonly InputManager _inputManager;
|
||||
private RenderTarget2D renderTarget;
|
||||
private readonly GraphicsDeviceManager _graphics;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ulong _totalTicks = 0;
|
||||
private int tickrate = 0;
|
||||
private int everyTick => 60 / tickrate;
|
||||
|
||||
public Capy64()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
_graphics = new GraphicsDeviceManager(this);
|
||||
Content.RootDirectory = "Content";
|
||||
//Content.RootDirectory = "Content";
|
||||
IsMouseVisible = true;
|
||||
|
||||
EventEmitter = new();
|
||||
|
|
@ -67,9 +130,28 @@ public class Capy64 : Game, IGame
|
|||
Drawing = new();
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceProvider serviceProvider)
|
||||
public void SetEngineMode(EngineMode mode)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
switch (mode)
|
||||
{
|
||||
case EngineMode.Classic:
|
||||
tickrate = DefaultParameters.ClassicTickrate;
|
||||
Width = DefaultParameters.Width;
|
||||
Height = DefaultParameters.Height;
|
||||
Window.AllowUserResizing = false;
|
||||
ResetBorder();
|
||||
|
||||
break;
|
||||
|
||||
case EngineMode.Free:
|
||||
tickrate = DefaultParameters.FreeTickrate;
|
||||
Window.AllowUserResizing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
EngineMode = mode;
|
||||
|
||||
UpdateSize(true);
|
||||
}
|
||||
|
||||
public void UpdateSize(bool resize = true)
|
||||
|
|
@ -77,7 +159,7 @@ public class Capy64 : Game, IGame
|
|||
if (resize)
|
||||
{
|
||||
_graphics.PreferredBackBufferWidth = (int)(Width * Scale) + Borders.Left + Borders.Right;
|
||||
_graphics.PreferredBackBufferHeight = (int)(Height * Scale) + Borders.Top + Borders.Right;
|
||||
_graphics.PreferredBackBufferHeight = (int)(Height * Scale) + Borders.Top + Borders.Bottom;
|
||||
_graphics.ApplyChanges();
|
||||
}
|
||||
|
||||
|
|
@ -92,13 +174,18 @@ public class Capy64 : Game, IGame
|
|||
Drawing.Canvas = renderTarget;
|
||||
|
||||
_inputManager.Texture = renderTarget;
|
||||
_inputManager.WindowScale = Scale;
|
||||
|
||||
EventEmitter.RaiseScreenSizeChange();
|
||||
}
|
||||
|
||||
private void OnWindowSizeChange(object sender, EventArgs e)
|
||||
{
|
||||
if (EngineMode == EngineMode.Classic)
|
||||
{
|
||||
UpdateSize(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = Window.ClientBounds;
|
||||
|
||||
Width = (int)(bounds.Width / Scale);
|
||||
|
|
@ -106,56 +193,96 @@ public class Capy64 : Game, IGame
|
|||
|
||||
if (Window.IsMaximized())
|
||||
{
|
||||
var vertical = bounds.Height - Height * Scale;
|
||||
var horizontal = bounds.Width - Width * Scale;
|
||||
var vertical = bounds.Height - (Height * Scale);
|
||||
var horizontal = bounds.Width - (Width * Scale);
|
||||
|
||||
Borders.Top = (int)Math.Floor(vertical / 2d);
|
||||
Borders.Bottom = (int)Math.Ceiling(vertical / 2d);
|
||||
|
||||
Borders.Left = (int)Math.Floor(horizontal / 2d);
|
||||
Borders.Right = (int)Math.Ceiling(horizontal / 2d);
|
||||
|
||||
UpdateSize(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Borders = new Borders();
|
||||
ResetBorder();
|
||||
UpdateSize();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSize(false);
|
||||
private void ResetBorder()
|
||||
{
|
||||
var size = (int)(Scale * DefaultParameters.BorderMultiplier);
|
||||
Borders = new Borders
|
||||
{
|
||||
Top = size,
|
||||
Bottom = size,
|
||||
Left = size,
|
||||
Right = size
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
var configBuilder = new ConfigurationBuilder();
|
||||
|
||||
var settingsPath = Path.Combine(AppDataPath, "settings.json");
|
||||
if (!Directory.Exists(AppDataPath))
|
||||
{
|
||||
Directory.CreateDirectory(AppDataPath);
|
||||
}
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
File.Copy(Path.Combine(AssetsPath, "default.json"), settingsPath);
|
||||
}
|
||||
|
||||
configBuilder.AddJsonFile(Path.Combine(AssetsPath, "default.json"), false);
|
||||
configBuilder.AddJsonFile(settingsPath, false);
|
||||
|
||||
Configuration = configBuilder.Build();
|
||||
|
||||
Window.Title = "Capy64 " + Version;
|
||||
|
||||
Scale = Configuration.GetValue("Window:Scale", DefaultParameters.Scale);
|
||||
|
||||
ResetBorder();
|
||||
UpdateSize();
|
||||
|
||||
Window.AllowUserResizing = true;
|
||||
Window.ClientSizeChanged += OnWindowSizeChange;
|
||||
|
||||
InactiveSleepTime = new TimeSpan(0);
|
||||
|
||||
SetEngineMode(Configuration.GetValue<EngineMode>("EngineMode", DefaultParameters.EngineMode));
|
||||
|
||||
Audio = new Audio();
|
||||
|
||||
NativePlugins = GetNativePlugins();
|
||||
Plugins = PluginLoader.LoadAllPlugins("plugins", _serviceProvider);
|
||||
var safeMode = Configuration.GetValue("SafeMode", false);
|
||||
if (!safeMode)
|
||||
Plugins = PluginLoader.LoadAllPlugins(Path.Combine(AppDataPath, "plugins"));
|
||||
|
||||
EventEmitter.RaiseInit();
|
||||
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
private List<IPlugin> GetNativePlugins()
|
||||
private List<IComponent> GetNativePlugins()
|
||||
{
|
||||
var iType = typeof(IPlugin);
|
||||
var iType = typeof(IComponent);
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => iType.IsAssignableFrom(p) && !p.IsInterface);
|
||||
|
||||
var plugins = new List<IPlugin>();
|
||||
var plugins = new List<IComponent>();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var instance = (IPlugin)ActivatorUtilities.CreateInstance(_serviceProvider, type)!;
|
||||
var instance = (IComponent)Activator.CreateInstance(type, this);
|
||||
plugins.Add(instance);
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +302,8 @@ public class Capy64 : Game, IGame
|
|||
EventEmitter.RaiseTick(new()
|
||||
{
|
||||
GameTime = gameTime,
|
||||
TotalTicks = _totalTicks
|
||||
TotalTicks = _totalTicks,
|
||||
IsActiveTick = (int)_totalTicks % everyTick == 0,
|
||||
});
|
||||
|
||||
Drawing.End();
|
||||
|
|
@ -188,7 +316,14 @@ public class Capy64 : Game, IGame
|
|||
protected override void Draw(GameTime gameTime)
|
||||
{
|
||||
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
|
||||
SpriteBatch.Draw(renderTarget, new(Borders.Left, Borders.Top), null, Color.White, 0f, Vector2.Zero, Scale, SpriteEffects.None, 0);
|
||||
GraphicsDevice.Clear(BorderColor);
|
||||
|
||||
SpriteBatch.DrawRectangle(renderTarget.Bounds.Location.ToVector2() + new Vector2(Borders.Left, Borders.Top),
|
||||
new Size2(renderTarget.Bounds.Width * Scale, renderTarget.Bounds.Height * Scale), Color.Black,
|
||||
Math.Min(renderTarget.Bounds.Width, renderTarget.Bounds.Height), 0);
|
||||
|
||||
SpriteBatch.Draw(renderTarget, new(Borders.Left, Borders.Top), null, Color.White, 0f, Vector2.Zero, Scale,
|
||||
SpriteEffects.None, 0);
|
||||
|
||||
EventEmitter.RaiseOverlay(new()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RollForward>Major</RollForward>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
|
|
@ -26,9 +26,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Assets\Lua/**">
|
||||
<Content Include="Assets\Lua\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE">
|
||||
|
|
@ -37,22 +37,33 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8" />
|
||||
<PackageReference Include="KeraLua" Version="1.3.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.3.6" />
|
||||
<PackageReference Include="KeraLua" Version="1.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="MonoGame.Extended.Graphics" Version="3.8.0" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Assets\bios.lua">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<EditorConfigFiles Remove="C:\Users\Alex\source\repos\Capy64\Capy64\Capy64\.editorconfig" />
|
||||
</ItemGroup>
|
||||
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
|
||||
<Message Text="Restoring dotnet tools" Importance="High" />
|
||||
<Exec Command="dotnet tool restore" />
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\Lua\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
|
||||
<DefineConstants>_WINDOWS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' ">
|
||||
<DefineConstants>_LINUX</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' ">
|
||||
<DefineConstants>_OSX</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
#----------------------------- Global Properties ----------------------------#
|
||||
|
||||
/outputDir:bin/$(Platform)
|
||||
/intermediateDir:obj/$(Platform)
|
||||
/platform:DesktopGL
|
||||
/config:
|
||||
/profile:Reach
|
||||
/compress:False
|
||||
|
||||
#-------------------------------- References --------------------------------#
|
||||
|
||||
|
||||
#---------------------------------- Content ---------------------------------#
|
||||
|
||||
|
|
@ -1,67 +1,211 @@
|
|||
using Microsoft.Xna.Framework.Audio;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework.Audio;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capy64.Core;
|
||||
|
||||
public class Audio : IDisposable
|
||||
{
|
||||
public const int SampleRate = 48000;
|
||||
public const AudioChannels Channels = AudioChannels.Mono;
|
||||
public readonly DynamicSoundEffectInstance Sound;
|
||||
public enum Waveform
|
||||
{
|
||||
Sine,
|
||||
Square,
|
||||
Triangle,
|
||||
Sawtooth,
|
||||
Noise
|
||||
}
|
||||
|
||||
public const int SampleRate = 24000;
|
||||
public const int HQSampleRate = 48000;
|
||||
public const AudioChannels AudioChannel = AudioChannels.Mono;
|
||||
public const int ChannelsCount = 8;
|
||||
public readonly DynamicSoundEffectInstance[] Channels = new DynamicSoundEffectInstance[ChannelsCount];
|
||||
private readonly bool[] freeChannels = new bool[ChannelsCount];
|
||||
|
||||
public readonly DynamicSoundEffectInstance HQChannel = new(HQSampleRate, AudioChannel);
|
||||
|
||||
private static readonly Random rng = new();
|
||||
public Audio()
|
||||
{
|
||||
Sound = new DynamicSoundEffectInstance(SampleRate, Channels);
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
Channels[i] = new DynamicSoundEffectInstance(SampleRate, AudioChannel);
|
||||
freeChannels[i] = true;
|
||||
Channels[i].BufferNeeded += Audio_BufferNeeded;
|
||||
}
|
||||
|
||||
public TimeSpan Submit(byte[] buffer)
|
||||
{
|
||||
Sound.SubmitBuffer(buffer);
|
||||
return Sound.GetSampleDuration(buffer.Length);
|
||||
HQChannel.BufferNeeded += HQChannel_BufferNeeded;
|
||||
}
|
||||
|
||||
public static byte[] GenerateSineWave(double frequency, double time, double volume = 1d)
|
||||
private static void EnqueueAudioNeedEvent(int i, int pending)
|
||||
{
|
||||
var amplitude = 128 * volume;
|
||||
var timeStep = 1d / SampleRate;
|
||||
|
||||
var buffer = new byte[(int)(SampleRate * time)];
|
||||
var ctime = 0d;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
Capy64.Instance.LuaRuntime.QueueEvent("audio_need", LK =>
|
||||
{
|
||||
double angle = (Math.PI * frequency) * ctime;
|
||||
double factor = 0.5 * (Math.Sin(angle) + 1.0);
|
||||
buffer[i] = (byte)(amplitude * factor);
|
||||
ctime += timeStep;
|
||||
LK.PushInteger(i);
|
||||
LK.PushInteger(pending);
|
||||
return 2;
|
||||
});
|
||||
}
|
||||
|
||||
private void HQChannel_BufferNeeded(object sender, EventArgs e)
|
||||
{
|
||||
var pending = HQChannel.PendingBufferCount;
|
||||
EnqueueAudioNeedEvent(-1, pending);
|
||||
}
|
||||
|
||||
private void Audio_BufferNeeded(object sender, EventArgs e)
|
||||
{
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
if (Channels[i] == sender)
|
||||
{
|
||||
freeChannels[i] = true;
|
||||
var pending = Channels[i].PendingBufferCount;
|
||||
EnqueueAudioNeedEvent(i, pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetChannelId(int inp)
|
||||
{
|
||||
if (inp >= 0)
|
||||
return inp;
|
||||
|
||||
if (inp == -1)
|
||||
return -1;
|
||||
|
||||
if (inp == -2)
|
||||
{
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
if (freeChannels[i])
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -3;
|
||||
}
|
||||
|
||||
public bool TryGetChannel(int id, out DynamicSoundEffectInstance channel, out int resolvedId)
|
||||
{
|
||||
resolvedId = GetChannelId(id);
|
||||
|
||||
if (resolvedId >= 0)
|
||||
channel = Channels[resolvedId];
|
||||
else if (resolvedId == -1)
|
||||
channel = HQChannel;
|
||||
else
|
||||
channel = null;
|
||||
|
||||
return channel != null;
|
||||
}
|
||||
|
||||
public TimeSpan Submit(int id, byte[] buffer)
|
||||
{
|
||||
if (!TryGetChannel(id, out var channel, out var rId))
|
||||
return TimeSpan.Zero;
|
||||
|
||||
channel.SubmitBuffer(buffer);
|
||||
freeChannels[rId] = false;
|
||||
return channel.GetSampleDuration(buffer.Length);
|
||||
}
|
||||
|
||||
public TimeSpan SubmitHQ(byte[] buffer)
|
||||
{
|
||||
|
||||
HQChannel.SubmitBuffer(buffer);
|
||||
return HQChannel.GetSampleDuration(buffer.Length);
|
||||
}
|
||||
|
||||
public static byte[] GenerateWave(DynamicSoundEffectInstance channel, Waveform form, double frequency, TimeSpan time, float volume = 1f)
|
||||
{
|
||||
var size = channel.GetSampleSizeInBytes(time);
|
||||
var buffer = new byte[size];
|
||||
|
||||
var step = 1d / SampleRate;
|
||||
double x = 0;
|
||||
for (int i = 0; i < size; i += 2)
|
||||
{
|
||||
var value = form switch
|
||||
{
|
||||
Waveform.Sine => GetSinePoint(frequency, x),
|
||||
Waveform.Square => GetSquarePoint(frequency, x),
|
||||
Waveform.Triangle => GetTrianglePoint(frequency, x),
|
||||
Waveform.Sawtooth => GetSawtoothPoint(frequency, x),
|
||||
Waveform.Noise => (rng.NextDouble() * 2) - 1,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
value = Math.Clamp(value, -1, 1);
|
||||
var sample = (short)((value >= 0.0f ? value * short.MaxValue : value * short.MinValue * -1) * volume);
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
buffer[i] = (byte)(sample >> 8);
|
||||
buffer[i + 1] = (byte)sample;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[i] = (byte)sample;
|
||||
buffer[i + 1] = (byte)(sample >> 8);
|
||||
}
|
||||
x += step;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static byte[] GenerateSquareWave(double frequency, double time, double volume = 1d)
|
||||
public static double GetSinePoint(double frequency, double x)
|
||||
{
|
||||
var amplitude = 128 * volume;
|
||||
var timeStep = 1d / SampleRate;
|
||||
|
||||
var buffer = new byte[(int)(SampleRate * time)];
|
||||
var ctime = 0d;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
double angle = (Math.PI * frequency) * ctime;
|
||||
double factor = Math.Sin(angle);
|
||||
buffer[i] = (byte)(factor >= 0 ? amplitude : 0);
|
||||
ctime += timeStep;
|
||||
return Math.Sin(x * 2 * Math.PI * frequency);
|
||||
}
|
||||
return buffer;
|
||||
|
||||
private static double GetSquarePoint(double frequency, double x)
|
||||
{
|
||||
double v = GetSinePoint(frequency, x);
|
||||
return v > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
private static double GetTrianglePoint(double frequency, double x)
|
||||
{
|
||||
double v = 0;
|
||||
for (int k = 1; k <= 25; k++)
|
||||
{
|
||||
v += Math.Pow(-1, k) / Math.Pow((2 * k) - 1, 2)
|
||||
* Math.Sin(frequency * 2 * Math.PI * ((2 * k) - 1) * x);
|
||||
}
|
||||
return -(8 / Math.Pow(Math.PI, 2)) * v;
|
||||
}
|
||||
|
||||
private static double GetSawtoothPoint(double frequency, double x)
|
||||
{
|
||||
double v = 0;
|
||||
for (int k = 1; k <= 50; k++)
|
||||
{
|
||||
v += Math.Pow(-1, k) / k * Math.Sin(frequency * 2 * Math.PI * k * x);
|
||||
}
|
||||
return -(2 / Math.PI) * v;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Sound.Dispose();
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
Channels[i]?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using FontStashSharp;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using FontStashSharp;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MonoGame.Extended;
|
||||
|
|
@ -16,7 +31,7 @@ public class Drawing : IDisposable
|
|||
private Texture2D _whitePixel;
|
||||
private RenderTarget2D _canvas;
|
||||
private bool _isDrawing;
|
||||
private HashSet<Texture2D> _disposeTextures = new();
|
||||
private readonly HashSet<Texture2D> _disposeTextures = new();
|
||||
public RenderTarget2D Canvas
|
||||
{
|
||||
get => _canvas;
|
||||
|
|
@ -39,7 +54,7 @@ public class Drawing : IDisposable
|
|||
public Drawing()
|
||||
{
|
||||
_fontSystem = new FontSystem();
|
||||
_fontSystem.AddFont(File.ReadAllBytes(@"Assets/font.ttf"));
|
||||
_fontSystem.AddFont(File.ReadAllBytes(Path.Combine(Capy64.AssetsPath, "font.ttf")));
|
||||
}
|
||||
|
||||
public void Begin()
|
||||
|
|
@ -139,9 +154,10 @@ public class Drawing : IDisposable
|
|||
_spriteBatch.DrawPoint(point, color, size);
|
||||
}
|
||||
|
||||
public void DrawCircle(Vector2 pos, int radius, Color color, int thickness = 1)
|
||||
public void DrawCircle(Vector2 pos, int radius, Color color, int thickness = 1, int sides = -1)
|
||||
{
|
||||
_spriteBatch.DrawCircle(pos, radius, radius * 4, color, thickness);
|
||||
sides = sides < 0 ? radius * 4 : sides;
|
||||
_spriteBatch.DrawCircle(pos, radius, sides, color, thickness);
|
||||
}
|
||||
|
||||
public void DrawLine(Vector2 start, Vector2 end, Color color, float thickness = 1)
|
||||
|
|
@ -168,6 +184,7 @@ public class Drawing : IDisposable
|
|||
public void Clear(Color? color = default)
|
||||
{
|
||||
Color finalColor = color ?? Color.Black;
|
||||
Capy64.Instance.BorderColor = finalColor;
|
||||
_graphicsDevice.Clear(finalColor);
|
||||
}
|
||||
|
||||
|
|
@ -184,17 +201,22 @@ public class Drawing : IDisposable
|
|||
_spriteBatch.Draw(_whitePixel, position3, null, color, rotation, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
||||
}
|
||||
|
||||
public void DrawTexture(Texture2D texture, Vector2 pos)
|
||||
{
|
||||
_spriteBatch.Draw(texture, pos, null, Color.White, 0f, Vector2.Zero, 1, SpriteEffects.None, 0f);
|
||||
}
|
||||
|
||||
public void DrawBuffer(uint[] buffer, Rectangle rect)
|
||||
public void DrawBuffer(uint[] buffer, Rectangle rect, Rectangle? source = null, Color? color = null, float rotation = 0f, Vector2? origin = null, Vector2? scale = null, SpriteEffects spriteEffects = 0)
|
||||
{
|
||||
var texture = new Texture2D(_graphicsDevice, rect.Width, rect.Height, false, SurfaceFormat.Color);
|
||||
texture.SetData(buffer);
|
||||
|
||||
DrawTexture(texture, new(rect.X, rect.Y));
|
||||
_spriteBatch.Draw(
|
||||
texture, // Texture
|
||||
rect.Location.ToVector2(), // Position
|
||||
source, // source
|
||||
color ?? Color.White, // Color
|
||||
rotation, // Rotation
|
||||
origin ?? Vector2.Zero, // Origin
|
||||
scale ?? Vector2.One, // Scale
|
||||
spriteEffects, // Flip effects
|
||||
0f // layer depth
|
||||
);
|
||||
_disposeTextures.Add(texture);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
using Capy64.Extensions.Bindings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.Extensions.Bindings;
|
||||
|
||||
namespace Capy64.Core;
|
||||
|
||||
|
|
@ -14,6 +24,11 @@ public class SDL
|
|||
return SDL2.UTF8_ToManaged(SDL2.Native_SDL_GetClipboardText(), true);
|
||||
}
|
||||
|
||||
public static void SetClipboardText(string contents)
|
||||
{
|
||||
SDL2.Native_SDL_SetClipboardText(contents);
|
||||
}
|
||||
|
||||
public static bool HasClipboardText()
|
||||
{
|
||||
return SDL2.SDL_HasClipboardText() == 1;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Capy64.Eventing.Events;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.Eventing.Events;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
|
||||
|
|
@ -18,6 +33,11 @@ public class EventEmitter
|
|||
public event EventHandler<KeyEvent> OnKeyUp;
|
||||
public event EventHandler<CharEvent> OnChar;
|
||||
|
||||
// GamePad events
|
||||
public event EventHandler<GamePadButtonEvent> OnGamePadButton;
|
||||
public event EventHandler<GamePadTriggerEvent> OnGamePadTrigger;
|
||||
public event EventHandler<GamePadThumbstickEvent> OnGamePadThumbstick;
|
||||
|
||||
// Functional events
|
||||
public event EventHandler<TickEvent> OnTick;
|
||||
public event EventHandler OnInit;
|
||||
|
|
@ -85,6 +105,30 @@ public class EventEmitter
|
|||
}
|
||||
}
|
||||
|
||||
public void RaiseGamePadButton(GamePadButtonEvent ev)
|
||||
{
|
||||
if (OnGamePadButton is not null)
|
||||
{
|
||||
OnGamePadButton(this, ev);
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseGamePadTrigger(GamePadTriggerEvent ev)
|
||||
{
|
||||
if (OnGamePadTrigger is not null)
|
||||
{
|
||||
OnGamePadTrigger(this, ev);
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseGamePadThumbstick(GamePadThumbstickEvent ev)
|
||||
{
|
||||
if (OnGamePadThumbstick is not null)
|
||||
{
|
||||
OnGamePadThumbstick(this, ev);
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseTick(TickEvent ev)
|
||||
{
|
||||
if (OnTick is not null)
|
||||
|
|
@ -119,7 +163,8 @@ public class EventEmitter
|
|||
|
||||
public void RaiseOverlay(OverlayEvent ev)
|
||||
{
|
||||
if(OnOverlay is not null) {
|
||||
if (OnOverlay is not null)
|
||||
{
|
||||
OnOverlay(this, ev);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using System;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
||||
|
|
|
|||
25
Capy64/Eventing/Events/GamePadButtonEvent.cs
Normal file
25
Capy64/Eventing/Events/GamePadButtonEvent.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
||||
public class GamePadButtonEvent : EventArgs
|
||||
{
|
||||
public Buttons Button { get; set; }
|
||||
public ButtonState State { get; set; }
|
||||
}
|
||||
25
Capy64/Eventing/Events/GamePadThumbstickEvent.cs
Normal file
25
Capy64/Eventing/Events/GamePadThumbstickEvent.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
||||
public class GamePadThumbstickEvent : EventArgs
|
||||
{
|
||||
public int Stick { get; set; }
|
||||
public Vector2 Value { get; set; }
|
||||
}
|
||||
24
Capy64/Eventing/Events/GamePadTriggerEvent.cs
Normal file
24
Capy64/Eventing/Events/GamePadTriggerEvent.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
||||
public class GamePadTriggerEvent : EventArgs
|
||||
{
|
||||
public int Trigger { get; set; }
|
||||
public float Value { get; set; }
|
||||
}
|
||||
|
|
@ -1,4 +1,19 @@
|
|||
using Microsoft.Xna.Framework.Input;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using static Capy64.Eventing.InputManager;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using static Capy64.Eventing.InputManager;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
|
@ -7,4 +22,5 @@ public class TickEvent : EventArgs
|
|||
{
|
||||
public GameTime GameTime { get; set; }
|
||||
public ulong TotalTicks { get; set; }
|
||||
public bool IsActiveTick { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
|
|
@ -37,7 +52,7 @@ public class InputManager
|
|||
Ctrl = LCtrl | RCtrl,
|
||||
}
|
||||
|
||||
private static Keys[] IgnoredTextInputKeys =
|
||||
private static readonly Keys[] IgnoredTextInputKeys =
|
||||
{
|
||||
Keys.Enter,
|
||||
Keys.Back,
|
||||
|
|
@ -59,7 +74,7 @@ public class InputManager
|
|||
};
|
||||
|
||||
public Texture2D Texture { get; set; }
|
||||
public float WindowScale { get; set; }
|
||||
public static float WindowScale => Capy64.Instance.Scale;
|
||||
public const int MouseScrollDelta = 120;
|
||||
|
||||
private Point mousePosition;
|
||||
|
|
@ -67,7 +82,7 @@ public class InputManager
|
|||
private int hMouseScroll;
|
||||
|
||||
private Modifiers keyboardMods = 0;
|
||||
private HashSet<Keys> pressedKeys = new();
|
||||
private readonly HashSet<Keys> pressedKeys = new();
|
||||
|
||||
private readonly Game _game;
|
||||
private readonly EventEmitter _eventEmitter;
|
||||
|
|
@ -90,6 +105,7 @@ public class InputManager
|
|||
{
|
||||
UpdateMouse(Mouse.GetState(), IsActive);
|
||||
UpdateKeyboard(Keyboard.GetState(), IsActive);
|
||||
UpdateGamePad(GamePad.GetState(PlayerIndex.One), IsActive);
|
||||
}
|
||||
|
||||
private void UpdateMouse(MouseState state, bool isActive)
|
||||
|
|
@ -97,7 +113,7 @@ public class InputManager
|
|||
if (!isActive)
|
||||
return;
|
||||
|
||||
var rawPosition = state.Position;
|
||||
var rawPosition = state.Position - new Point(Capy64.Instance.Borders.Left, Capy64.Instance.Borders.Top);
|
||||
var pos = new Point((int)(rawPosition.X / WindowScale), (int)(rawPosition.Y / WindowScale)) + new Point(1, 1);
|
||||
|
||||
if (pos.X < 1 || pos.Y < 1 || pos.X > Texture.Width || pos.Y > Texture.Height)
|
||||
|
|
@ -288,4 +304,210 @@ public class InputManager
|
|||
{
|
||||
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
|
||||
}
|
||||
|
||||
private GamePadState oldGamePadState = new();
|
||||
private void UpdateGamePad(GamePadState state, bool isActive)
|
||||
{
|
||||
if (!isActive)
|
||||
return;
|
||||
|
||||
if (!state.IsConnected)
|
||||
return;
|
||||
|
||||
var ob = oldGamePadState.Buttons;
|
||||
var b = state.Buttons;
|
||||
// ABXY
|
||||
if (ob.A != b.A)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.A,
|
||||
State = b.A,
|
||||
});
|
||||
}
|
||||
|
||||
if (ob.B != b.B)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.B,
|
||||
State = b.B,
|
||||
});
|
||||
}
|
||||
|
||||
if (ob.X != b.X)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.X,
|
||||
State = b.X,
|
||||
});
|
||||
}
|
||||
|
||||
if (ob.Y != b.Y)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.Y,
|
||||
State = b.Y,
|
||||
});
|
||||
}
|
||||
|
||||
// Back & Start
|
||||
if (ob.Back != b.Back)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.Back,
|
||||
State = b.Back,
|
||||
});
|
||||
}
|
||||
|
||||
if (ob.Start != b.Start)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.Start,
|
||||
State = b.Start,
|
||||
});
|
||||
}
|
||||
|
||||
// Shoulders
|
||||
if (ob.LeftShoulder != b.LeftShoulder)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.LeftShoulder,
|
||||
State = b.LeftShoulder,
|
||||
});
|
||||
}
|
||||
|
||||
if (ob.RightShoulder != b.RightShoulder)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.RightShoulder,
|
||||
State = b.RightShoulder,
|
||||
});
|
||||
}
|
||||
|
||||
// Sticks
|
||||
if (ob.LeftStick != b.LeftStick)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.LeftStick,
|
||||
State = b.LeftStick,
|
||||
});
|
||||
}
|
||||
|
||||
if (ob.RightStick != b.RightStick)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.RightStick,
|
||||
State = b.RightStick,
|
||||
});
|
||||
}
|
||||
|
||||
// BIG BUTTON
|
||||
if (ob.BigButton != b.BigButton)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.BigButton,
|
||||
State = b.BigButton,
|
||||
});
|
||||
}
|
||||
|
||||
// D-PAD
|
||||
var od = oldGamePadState.DPad;
|
||||
var d = state.DPad;
|
||||
|
||||
if (od.Left != d.Left)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.DPadLeft,
|
||||
State = d.Left,
|
||||
});
|
||||
}
|
||||
|
||||
if (od.Right != d.Right)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.DPadRight,
|
||||
State = d.Right,
|
||||
});
|
||||
}
|
||||
|
||||
if (od.Up != d.Up)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.DPadUp,
|
||||
State = d.Up,
|
||||
});
|
||||
}
|
||||
|
||||
if (od.Down != d.Down)
|
||||
{
|
||||
_eventEmitter.RaiseGamePadButton(new()
|
||||
{
|
||||
Button = Buttons.DPadDown,
|
||||
State = d.Down,
|
||||
});
|
||||
}
|
||||
|
||||
// triggers
|
||||
var ot = oldGamePadState.Triggers;
|
||||
var t = state.Triggers;
|
||||
|
||||
if (ot.Left != t.Left)
|
||||
{
|
||||
|
||||
_eventEmitter.RaiseGamePadTrigger(new()
|
||||
{
|
||||
Trigger = 1, // left
|
||||
Value = t.Left,
|
||||
});
|
||||
}
|
||||
|
||||
if (ot.Right != t.Right)
|
||||
{
|
||||
|
||||
_eventEmitter.RaiseGamePadTrigger(new()
|
||||
{
|
||||
Trigger = 2, // right
|
||||
Value = t.Right,
|
||||
});
|
||||
}
|
||||
|
||||
// thumbsticks
|
||||
var os = oldGamePadState.ThumbSticks;
|
||||
var s = state.ThumbSticks;
|
||||
|
||||
if (os.Left != s.Left)
|
||||
{
|
||||
|
||||
_eventEmitter.RaiseGamePadThumbstick(new()
|
||||
{
|
||||
Stick = 1, // left
|
||||
Value = s.Left,
|
||||
});
|
||||
}
|
||||
|
||||
if (os.Right != s.Right)
|
||||
{
|
||||
|
||||
_eventEmitter.RaiseGamePadThumbstick(new()
|
||||
{
|
||||
Stick = 2, // right
|
||||
Value = s.Right,
|
||||
});
|
||||
}
|
||||
|
||||
oldGamePadState = state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
Capy64/Extensions/Bindings/Common.cs
Normal file
36
Capy64/Extensions/Bindings/Common.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Capy64.Extensions.Bindings;
|
||||
|
||||
public partial class Common
|
||||
{
|
||||
public const int SW_HIDE = 0;
|
||||
public const int SW_SHOW = 5;
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
public static partial IntPtr GetConsoleWindow();
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
}
|
||||
|
|
@ -1,32 +1,59 @@
|
|||
using System;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Capy64.Extensions.Bindings;
|
||||
|
||||
public partial class SDL2
|
||||
{
|
||||
private const string SDL = "SDL2.dll";
|
||||
#if _WINDOWS
|
||||
private const string LibraryName = "SDL2.dll";
|
||||
#elif _LINUX
|
||||
private const string LibraryName = "libSDL2-2.0.so.0";
|
||||
#elif _OSX
|
||||
private const string LibraryName = "libSDL2.dylib";
|
||||
#else
|
||||
private const string LibraryName = "SDL2";
|
||||
#endif
|
||||
|
||||
[LibraryImport(SDL)]
|
||||
[LibraryImport(LibraryName)]
|
||||
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
internal static partial void SDL_MaximizeWindow(IntPtr window);
|
||||
|
||||
[LibraryImport(SDL)]
|
||||
[LibraryImport(LibraryName)]
|
||||
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
internal static partial uint SDL_GetWindowFlags(IntPtr window);
|
||||
|
||||
[LibraryImport(SDL, EntryPoint = "SDL_GetClipboardText")]
|
||||
[LibraryImport(LibraryName, EntryPoint = "SDL_GetClipboardText")]
|
||||
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
internal static partial IntPtr Native_SDL_GetClipboardText();
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <returns>0 is false; 1 is true</returns>
|
||||
[LibraryImport(SDL)]
|
||||
[LibraryImport(LibraryName)]
|
||||
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
internal static partial int SDL_HasClipboardText();
|
||||
|
||||
[LibraryImport(SDL)]
|
||||
[LibraryImport(LibraryName, EntryPoint = "SDL_SetClipboardText", StringMarshalling = StringMarshalling.Utf8)]
|
||||
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
internal static partial int Native_SDL_SetClipboardText(string contents);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
internal static partial void SDL_free(IntPtr memblock);
|
||||
|
||||
|
|
@ -78,7 +105,7 @@ public partial class SDL2
|
|||
}
|
||||
char* chars = stackalloc char[len];
|
||||
int strLen = System.Text.Encoding.UTF8.GetChars((byte*)s, len, chars, len);
|
||||
string result = new string(chars, 0, strLen);
|
||||
string result = new(chars, 0, strLen);
|
||||
#endif
|
||||
|
||||
/* Some SDL functions will malloc, we have to free! */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using Capy64.Extensions.Bindings;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.Extensions.Bindings;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
|
|
@ -54,4 +69,19 @@ public static class GameWindowExtensions
|
|||
{
|
||||
return window.GetWindowFlags().HasFlag(WindowFlags.SDL_WINDOW_MAXIMIZED);
|
||||
}
|
||||
|
||||
public static void ToggleConsole(this GameWindow _, bool show)
|
||||
{
|
||||
var console = Common.GetConsoleWindow();
|
||||
if (console != IntPtr.Zero)
|
||||
Common.ShowWindow(console, show ? Common.SW_SHOW : Common.SW_HIDE);
|
||||
}
|
||||
|
||||
public static bool IsConsoleVisible(this GameWindow _)
|
||||
{
|
||||
var console = Common.GetConsoleWindow();
|
||||
if (console != IntPtr.Zero)
|
||||
return false;
|
||||
return Common.IsWindowVisible(console);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
using Capy64.API;
|
||||
using Capy64.Core;
|
||||
using Capy64.Eventing;
|
||||
using Capy64.Integrations;
|
||||
using Capy64.Runtime;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Capy64;
|
||||
|
||||
public interface IGame
|
||||
{
|
||||
Capy64 Game { get; }
|
||||
IList<IPlugin> NativePlugins { get; }
|
||||
IList<IPlugin> Plugins { get; }
|
||||
GameWindow Window { get; }
|
||||
Drawing Drawing { get; }
|
||||
Audio Audio { get; }
|
||||
LuaState LuaRuntime { get; set; }
|
||||
Eventing.EventEmitter EventEmitter { get; }
|
||||
void ConfigureServices(IServiceProvider serviceProvider);
|
||||
|
||||
int Width { get; set; }
|
||||
int Height { get; set; }
|
||||
float Scale { get; set; }
|
||||
void UpdateSize(bool resize = true);
|
||||
|
||||
event EventHandler<EventArgs> Exiting;
|
||||
void Run();
|
||||
void Exit();
|
||||
|
||||
// Integrations
|
||||
DiscordIntegration Discord { get; }
|
||||
}
|
||||
|
|
@ -1,43 +1,59 @@
|
|||
using Capy64.API;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.API;
|
||||
using DiscordRPC;
|
||||
using DiscordRPC.Logging;
|
||||
using DiscordRPC.Message;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capy64.Integrations;
|
||||
|
||||
public class DiscordIntegration : IPlugin
|
||||
public class DiscordIntegration : IComponent
|
||||
{
|
||||
public DiscordRpcClient Client { get; private set; }
|
||||
public readonly bool Enabled;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public DiscordIntegration(IConfiguration configuration)
|
||||
public DiscordIntegration(Capy64 game)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_configuration = game.Configuration;
|
||||
|
||||
var discordConfig = _configuration.GetSection("Integrations:Discord");
|
||||
Client = new(discordConfig["ApplicationId"]);
|
||||
Enabled = discordConfig.GetValue("Enable", false);
|
||||
|
||||
Client.Logger = new ConsoleLogger() { Level = DiscordRPC.Logging.LogLevel.Warning };
|
||||
Client = new(discordConfig["ApplicationId"])
|
||||
{
|
||||
Logger = new ConsoleLogger() { Level = LogLevel.Warning }
|
||||
};
|
||||
|
||||
Client.OnReady += OnReady;
|
||||
|
||||
Capy64.Instance.Discord = this;
|
||||
|
||||
if (discordConfig.GetValue("Enable", false))
|
||||
{
|
||||
if (Enabled)
|
||||
Client.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public void SetPresence(string details, string? state = null)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
Client.SetPresence(new RichPresence()
|
||||
{
|
||||
Details = details,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using System;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
|
|
@ -6,7 +21,7 @@ namespace Capy64.PluginManager
|
|||
{
|
||||
class PluginLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private AssemblyDependencyResolver _resolver;
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
|
||||
public PluginLoadContext(string pluginPath)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
using Capy64.API;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.API;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
@ -18,21 +32,21 @@ internal class PluginLoader
|
|||
return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path)));
|
||||
}
|
||||
|
||||
public static List<IPlugin> LoadAllPlugins(string pluginsPath, IServiceProvider provider)
|
||||
public static List<IComponent> LoadAllPlugins(string pluginsPath)
|
||||
{
|
||||
if (!Directory.Exists(pluginsPath))
|
||||
Directory.CreateDirectory(pluginsPath);
|
||||
|
||||
var plugins = new List<IPlugin>();
|
||||
var plugins = new List<IComponent>();
|
||||
foreach (var fileName in Directory.GetFiles(pluginsPath).Where(q => q.EndsWith(".dll")))
|
||||
{
|
||||
var assembly = LoadPlugin(fileName);
|
||||
|
||||
foreach (Type type in assembly.GetTypes())
|
||||
{
|
||||
if (typeof(IPlugin).IsAssignableFrom(type))
|
||||
if (typeof(IComponent).IsAssignableFrom(type))
|
||||
{
|
||||
IPlugin result = ActivatorUtilities.CreateInstance(provider, type) as IPlugin;
|
||||
IComponent result = Activator.CreateInstance(type, Capy64.Instance) as IComponent;
|
||||
plugins.Add(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,18 @@
|
|||
using Capy64;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.IO;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using var game = new Capy64.Capy64();
|
||||
using IHost host = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureAppConfiguration((context, c) =>
|
||||
{
|
||||
var settingsPath = Path.Combine(Capy64.Capy64.AppDataPath, "settings.json");
|
||||
if(!Directory.Exists(Capy64.Capy64.AppDataPath))
|
||||
{
|
||||
Directory.CreateDirectory(Capy64.Capy64.AppDataPath);
|
||||
}
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
File.Copy("Assets/default.json", settingsPath);
|
||||
}
|
||||
|
||||
c.AddJsonFile("Assets/default.json", false);
|
||||
c.AddJsonFile(settingsPath, false);
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IGame>(game);
|
||||
services.AddHostedService<Worker>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync();
|
||||
game.Run();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
namespace Capy64.Runtime;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace Capy64.Runtime;
|
||||
|
||||
public class Constants
|
||||
{
|
||||
public const int MULTRET = -1;
|
||||
public const int MINSTACK = 20;
|
||||
public const int MAXLENNUM = 200;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,30 @@
|
|||
using Capy64.Core;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.Eventing.Events;
|
||||
using Capy64.Eventing;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using static Capy64.Eventing.InputManager;
|
||||
using Capy64.Runtime.Extensions;
|
||||
|
||||
namespace Capy64.Runtime;
|
||||
|
||||
internal class EventEmitter
|
||||
{
|
||||
private Eventing.EventEmitter _eventEmitter;
|
||||
private LuaState _runtime;
|
||||
private readonly Eventing.EventEmitter _eventEmitter;
|
||||
private readonly LuaState _runtime;
|
||||
private const int rebootDelay = 30;
|
||||
private int heldReboot = 0;
|
||||
|
||||
|
|
@ -35,6 +47,10 @@ internal class EventEmitter
|
|||
|
||||
_eventEmitter.OnTick += OnTick;
|
||||
_eventEmitter.OnScreenSizeChange += OnScreenSizeChange;
|
||||
|
||||
_eventEmitter.OnGamePadButton += OnGamePadButton;
|
||||
_eventEmitter.OnGamePadTrigger += OnGamePadTrigger;
|
||||
_eventEmitter.OnGamePadThumbstick += OnGamePadThumbstick;
|
||||
}
|
||||
|
||||
public void Unregister()
|
||||
|
|
@ -49,6 +65,11 @@ internal class EventEmitter
|
|||
_eventEmitter.OnChar -= OnChar;
|
||||
|
||||
_eventEmitter.OnTick -= OnTick;
|
||||
_eventEmitter.OnScreenSizeChange -= OnScreenSizeChange;
|
||||
|
||||
_eventEmitter.OnGamePadButton -= OnGamePadButton;
|
||||
_eventEmitter.OnGamePadTrigger -= OnGamePadTrigger;
|
||||
_eventEmitter.OnGamePadThumbstick -= OnGamePadThumbstick;
|
||||
}
|
||||
|
||||
private void OnTick(object sender, TickEvent e)
|
||||
|
|
@ -158,24 +179,13 @@ internal class EventEmitter
|
|||
if (e.Key == Keys.C)
|
||||
_runtime.QueueEvent("interrupt", LK => 0);
|
||||
}
|
||||
else if (e.Key == Keys.V)
|
||||
{
|
||||
if (SDL.HasClipboardText())
|
||||
{
|
||||
var text = SDL.GetClipboardText();
|
||||
_runtime.QueueEvent("paste", LK => {
|
||||
LK.PushString(text);
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChar(object sender, CharEvent e)
|
||||
{
|
||||
_runtime.QueueEvent("char", LK => {
|
||||
_runtime.QueueEvent("char", LK =>
|
||||
{
|
||||
LK.PushString(e.Character.ToString());
|
||||
|
||||
return 1;
|
||||
|
|
@ -189,4 +199,35 @@ internal class EventEmitter
|
|||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGamePadButton(object sender, GamePadButtonEvent e)
|
||||
{
|
||||
_runtime.QueueEvent("gamepad_button", LK =>
|
||||
{
|
||||
LK.PushInteger((int)e.Button);
|
||||
LK.PushBoolean(e.State == ButtonState.Pressed);
|
||||
return 2;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGamePadTrigger(object sender, GamePadTriggerEvent e)
|
||||
{
|
||||
_runtime.QueueEvent("gamepad_trigger", LK =>
|
||||
{
|
||||
LK.PushInteger(e.Trigger);
|
||||
LK.PushNumber(e.Value);
|
||||
return 2;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGamePadThumbstick(object sender, GamePadThumbstickEvent e)
|
||||
{
|
||||
_runtime.QueueEvent("gamepad_stick", LK =>
|
||||
{
|
||||
LK.PushInteger(e.Stick);
|
||||
LK.PushNumber(e.Value.X);
|
||||
LK.PushNumber(e.Value.Y);
|
||||
return 2;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using KeraLua;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using KeraLua;
|
||||
|
||||
namespace Capy64.Runtime.Extensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using System;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Capy64.Runtime.Extensions
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
using KeraLua;
|
||||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using KeraLua;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
|
|
@ -82,17 +97,4 @@ public static class Utils
|
|||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
[Obsolete("This method does not work as intended and requires more research")]
|
||||
public static void PushManagedObject<T>(this Lua L, T obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var members = type.GetMembers().Where(m => m.MemberType == MemberTypes.Method);
|
||||
L.CreateTable(0, members.Count());
|
||||
foreach (var m in members)
|
||||
{
|
||||
L.PushCFunction(L => (int)type.InvokeMember(m.Name, BindingFlags.InvokeMethod, null, obj, new object[] { L }));
|
||||
L.SetField(-2, m.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
using Capy64.API;
|
||||
using KeraLua;
|
||||
using Microsoft.Xna.Framework.Audio;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capy64.Runtime.Libraries;
|
||||
|
||||
public class Audio : IPlugin
|
||||
{
|
||||
private const int queueLimit = 8;
|
||||
|
||||
private static IGame _game;
|
||||
public Audio(IGame game)
|
||||
{
|
||||
_game = game;
|
||||
_game.EventEmitter.OnClose += OnClose;
|
||||
}
|
||||
|
||||
private static LuaRegister[] AudioLib = new LuaRegister[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
name = "play",
|
||||
function = L_Play,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "beep",
|
||||
function = L_Beep,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "resume",
|
||||
function = L_Resume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "pause",
|
||||
function = L_Pause,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "stop",
|
||||
function = L_Stop,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "getVolume",
|
||||
function = L_GetVolume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "setVolume",
|
||||
function = L_SetVolume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "status",
|
||||
function = L_Status,
|
||||
},
|
||||
new(),
|
||||
};
|
||||
|
||||
public void LuaInit(Lua L)
|
||||
{
|
||||
L.RequireF("audio", OpenLib, false);
|
||||
}
|
||||
|
||||
private static int OpenLib(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
L.NewLib(AudioLib);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static async Task DelayEmit(TimeSpan time)
|
||||
{
|
||||
var waitTime = time - TimeSpan.FromMilliseconds(1000 / 60);
|
||||
if (waitTime.TotalMilliseconds < 0)
|
||||
waitTime = time;
|
||||
await Task.Delay(waitTime);
|
||||
_game.LuaRuntime.QueueEvent("audio_end", LK =>
|
||||
{
|
||||
LK.PushInteger(_game.Audio.Sound.PendingBufferCount);
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
private static int L_Play(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
byte[] buffer;
|
||||
|
||||
if (L.IsString(1))
|
||||
{
|
||||
buffer = L.CheckBuffer(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
L.CheckType(1, LuaType.Table);
|
||||
var len = L.RawLen(1);
|
||||
buffer = new byte[len];
|
||||
for (int i = 1; i <= len; i++)
|
||||
{
|
||||
L.GetInteger(1, i);
|
||||
var value = L.CheckInteger(-1);
|
||||
buffer[i - 1] = (byte)value;
|
||||
L.Pop(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (_game.Audio.Sound.PendingBufferCount > queueLimit)
|
||||
{
|
||||
L.PushBoolean(false);
|
||||
L.PushString("queue is full");
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var ts = _game.Audio.Submit(buffer);
|
||||
DelayEmit(ts);
|
||||
|
||||
if (_game.Audio.Sound.State != SoundState.Playing)
|
||||
_game.Audio.Sound.Play();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
L.Error(ex.Message);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Beep(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var freq = L.CheckNumber(1);
|
||||
var time = L.OptNumber(2, 1);
|
||||
var volume = L.OptNumber(3, 1);
|
||||
Math.Clamp(volume, 0, 1);
|
||||
|
||||
var buffer = Core.Audio.GenerateSquareWave(freq, time, volume);
|
||||
|
||||
try
|
||||
{
|
||||
var ts = _game.Audio.Submit(buffer);
|
||||
DelayEmit(ts);
|
||||
|
||||
if (_game.Audio.Sound.State != SoundState.Playing)
|
||||
_game.Audio.Sound.Play();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
L.Error(ex.Message);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Resume(IntPtr state)
|
||||
{
|
||||
_game.Audio.Sound.Resume();
|
||||
return 0;
|
||||
}
|
||||
private static int L_Pause(IntPtr state)
|
||||
{
|
||||
_game.Audio.Sound.Pause();
|
||||
return 0;
|
||||
}
|
||||
private static int L_Stop(IntPtr state)
|
||||
{
|
||||
_game.Audio.Sound.Stop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_GetVolume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
L.PushNumber(_game.Audio.Sound.Volume);
|
||||
|
||||
return 1;
|
||||
}
|
||||
private static int L_SetVolume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var volume = (float)L.CheckNumber(1);
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
_game.Audio.Sound.Volume = volume;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Status(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var status = _game.Audio.Sound.State switch
|
||||
{
|
||||
SoundState.Playing => "playing",
|
||||
SoundState.Paused => "paused",
|
||||
SoundState.Stopped => "stopped",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
L.PushString(status);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void OnClose(object sender, EventArgs e)
|
||||
{
|
||||
_game.Audio.Sound.Stop(true);
|
||||
}
|
||||
}
|
||||
312
Capy64/Runtime/Libraries/AudioLib.cs
Normal file
312
Capy64/Runtime/Libraries/AudioLib.cs
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
||||
// Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Capy64.API;
|
||||
using KeraLua;
|
||||
using Microsoft.Xna.Framework.Audio;
|
||||
using System;
|
||||
using static Capy64.Core.Audio;
|
||||
|
||||
namespace Capy64.Runtime.Libraries;
|
||||
|
||||
public class AudioLib : IComponent
|
||||
{
|
||||
private const int queueLimit = 8;
|
||||
|
||||
private static Capy64 _game;
|
||||
public AudioLib(Capy64 game)
|
||||
{
|
||||
_game = game;
|
||||
_game.EventEmitter.OnClose += OnClose;
|
||||
}
|
||||
|
||||
private static readonly LuaRegister[] Library = new LuaRegister[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
name = "play",
|
||||
function = L_Play,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "beep",
|
||||
function = L_Beep,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "resume",
|
||||
function = L_Resume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "pause",
|
||||
function = L_Pause,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "stop",
|
||||
function = L_Stop,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "getVolume",
|
||||
function = L_GetVolume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "setVolume",
|
||||
function = L_SetVolume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "status",
|
||||
function = L_Status,
|
||||
},
|
||||
new(),
|
||||
};
|
||||
|
||||
public void LuaInit(Lua L)
|
||||
{
|
||||
L.RequireF("audio", OpenLib, false);
|
||||
}
|
||||
|
||||
private static int OpenLib(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
L.NewLib(Library);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert 8bit PCM to 16bit PCM
|
||||
private static void PCMTo16bit(Span<byte> buffer, int i, byte value)
|
||||
{
|
||||
var value16bit = value / sbyte.MaxValue * short.MaxValue;
|
||||
buffer[2 * i - 2] = (byte)(value16bit & 0xff);
|
||||
buffer[2 * i - 1] = (byte)(value16bit >> 8);
|
||||
}
|
||||
|
||||
private static int L_Play(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
byte[] buffer;
|
||||
|
||||
if (L.IsString(1))
|
||||
{
|
||||
var inputBuffer = L.CheckBuffer(1);
|
||||
buffer = new byte[inputBuffer.Length * 2];
|
||||
var span = new Span<byte>(buffer);
|
||||
for (int i = 1; i < inputBuffer.Length; i++)
|
||||
{
|
||||
PCMTo16bit(span, i, inputBuffer[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
L.CheckType(1, LuaType.Table);
|
||||
var len = L.RawLen(1);
|
||||
buffer = new byte[len * 2];
|
||||
var span = new Span<byte>(buffer);
|
||||
for (int i = 1; i <= len; i++)
|
||||
{
|
||||
L.GetInteger(1, i);
|
||||
PCMTo16bit(span, i, (byte)L.CheckNumber(-1));
|
||||
L.Pop(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (_game.Audio.HQChannel.PendingBufferCount > queueLimit)
|
||||
{
|
||||
L.PushBoolean(false);
|
||||
L.PushString("queue is full");
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var ts = _game.Audio.SubmitHQ(buffer);
|
||||
|
||||
if (_game.Audio.HQChannel.State != SoundState.Playing)
|
||||
_game.Audio.HQChannel.Play();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
L.Error(ex.Message);
|
||||
}
|
||||
|
||||
var playTime = _game.Audio.HQChannel.GetSampleDuration(buffer.Length);
|
||||
|
||||
L.PushBoolean(true);
|
||||
L.PushNumber(playTime.TotalSeconds);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
private static int L_Beep(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var freq = L.OptNumber(1, 440);
|
||||
var time = L.OptNumber(2, 1);
|
||||
var timespan = TimeSpan.FromSeconds(time);
|
||||
|
||||
var volume = (float)L.OptNumber(3, 1);
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
var form = L.CheckOption(4, "sine", new string[]
|
||||
{
|
||||
"sine",
|
||||
"square",
|
||||
"triangle",
|
||||
"sawtooth",
|
||||
"noise",
|
||||
null,
|
||||
});
|
||||
|
||||
var id = (int)L.OptInteger(5, -2);
|
||||
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.PushBoolean(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var buffer = GenerateWave(channel, (Waveform)form, freq, timespan, volume);
|
||||
|
||||
try
|
||||
{
|
||||
var ts = _game.Audio.Submit(rid, buffer);
|
||||
|
||||
if (channel.State != SoundState.Playing)
|
||||
channel.Play();
|
||||
|
||||
L.PushBoolean(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
L.Error(ex.Message);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int L_Resume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
var id = (int)L.CheckInteger(1);
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.ArgumentError(1, "channel id not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
channel.Resume();
|
||||
return 0;
|
||||
}
|
||||
private static int L_Pause(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
var id = (int)L.CheckInteger(1);
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.ArgumentError(1, "channel id not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
channel.Pause();
|
||||
return 0;
|
||||
}
|
||||
private static int L_Stop(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
var id = (int)L.CheckInteger(1);
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.ArgumentError(1, "channel id not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
channel.Stop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_GetVolume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var id = (int)L.CheckInteger(1);
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.ArgumentError(1, "channel id not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
L.PushNumber(channel.Volume);
|
||||
|
||||
return 1;
|
||||
}
|
||||
private static int L_SetVolume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var id = (int)L.CheckInteger(1);
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.ArgumentError(1, "channel id not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var volume = (float)L.CheckNumber(2);
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
channel.Volume = volume;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Status(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var id = (int)L.CheckInteger(1);
|
||||
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
|
||||
{
|
||||
L.ArgumentError(1, "channel id not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var status = channel.State switch
|
||||
{
|
||||
SoundState.Playing => "playing",
|
||||
SoundState.Paused => "paused",
|
||||
SoundState.Stopped => "stopped",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
L.PushString(status);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void OnClose(object sender, EventArgs e)
|
||||
{
|
||||
foreach (var channel in _game.Audio.Channels)
|
||||
{
|
||||
channel.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
using Capy64.API;
|
||||
using KeraLua;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capy64.Runtime.Libraries;
|
||||
|
||||
public class Event : IPlugin
|
||||
{
|
||||
private static IGame _game;
|
||||
public Event(IGame game)
|
||||
{
|
||||
_game = game;
|
||||
}
|
||||
|
||||
private static LuaRegister[] EventLib = new LuaRegister[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
name = "pull",
|
||||
function = L_Pull,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "pullRaw",
|
||||
function = L_PullRaw
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "push",
|
||||
function = L_Push,
|
||||
},
|
||||
new(),
|
||||
};
|
||||
|
||||
public void LuaInit(Lua L)
|
||||
{
|
||||
L.RequireF("event", OpenLib, false);
|
||||
}
|
||||
|
||||
private static int OpenLib(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
L.NewLib(EventLib);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int LK_Pull(IntPtr state, int status, IntPtr ctx)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
if (L.ToString(1) == "interrupt")
|
||||
{
|
||||
L.Error("interrupt");
|
||||
}
|
||||
|
||||
var nargs = L.GetTop();
|
||||
|
||||
return nargs;
|
||||
}
|
||||
|
||||
private static int L_Pull(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var nargs = L.GetTop();
|
||||
for (int i = 1; i <= nargs; i++)
|
||||
{
|
||||
L.CheckString(i);
|
||||
}
|
||||
|
||||
L.YieldK(nargs, 0, LK_Pull);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_PullRaw(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var nargs = L.GetTop();
|
||||
for (int i = 1; i <= nargs; i++)
|
||||
{
|
||||
L.CheckString(i);
|
||||
}
|
||||
|
||||
L.Yield(nargs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Push(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var eventName = L.CheckString(1);
|
||||
|
||||
var nargs = L.GetTop();
|
||||
var parsState = L.NewThread();
|
||||
L.Pop(1);
|
||||
L.XMove(parsState, nargs - 1);
|
||||
|
||||
_game.LuaRuntime.QueueEvent(eventName, LK =>
|
||||
{
|
||||
var nargs = parsState.GetTop();
|
||||
parsState.XMove(LK, nargs);
|
||||
|
||||
return nargs;
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue