Compare commits

..

No commits in common. "main" and "v0.0.5-alpha" have entirely different histories.

165 changed files with 4393 additions and 9985 deletions

View file

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Side**
- [ ] Lua side
- [ ] C# side
- [ ] Other
- [ ] I don't know
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- 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.

View file

@ -1,26 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Side**
- [ ] Lua side
- [ ] C# side
- [ ] Other
- [ ] I don't know
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,76 +0,0 @@
# 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}}"

View file

@ -5,59 +5,42 @@ name: .NET
on:
push:
branches: ["main"]
branches: [ "main" ]
pull_request:
branches: ["main"]
branches: [ "main" ]
jobs:
build-windows:
runs-on: windows-latest
build:
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:
- 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: Upload Linux x64 artifact
uses: actions/upload-artifact@v3.1.2
with:
name: Linux-x64
path: publish/linux-x64/**
if-no-files-found: error
- 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

69
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,69 @@
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 }}

View file

@ -1,97 +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-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 }}

View file

@ -1,128 +0,0 @@
# 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.

View file

@ -0,0 +1,36 @@
{
"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"
]
}
}
}

View file

@ -1,179 +0,0 @@
[*.{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

View file

@ -1,23 +0,0 @@
// 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) { }
}

11
Capy64/API/IPlugin.cs Normal file
View file

@ -0,0 +1,11 @@
using KeraLua;
using Microsoft.Extensions.DependencyInjection;
namespace Capy64.API;
public interface IPlugin
{
void ConfigureServices(IServiceCollection services) { }
void LuaInit(Lua L) { }
}

View file

@ -1,7 +0,0 @@
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

View file

@ -1,46 +0,0 @@
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

View file

@ -1,28 +0,0 @@
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, " ")

View file

@ -1,4 +0,0 @@
local scheduler = require("scheduler")
scheduler.spawn(function()
shell.run(arg.string)
end)

View file

@ -1,9 +0,0 @@
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()

View file

@ -1,4 +0,0 @@
local argparser = require("argparser")
local args, options = argparser.parse(...)
print(table.concat(args, " "))

View file

@ -1,40 +0,0 @@
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

View file

@ -1,98 +0,0 @@
-- 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

View file

@ -1,73 +0,0 @@
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)

View file

@ -1,209 +0,0 @@
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

View file

@ -1,11 +0,0 @@
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))

View file

@ -1,78 +0,0 @@
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

View file

@ -1,88 +0,0 @@
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

View file

@ -1,98 +0,0 @@
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

View file

@ -1,20 +0,0 @@
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])

View file

@ -1,16 +0,0 @@
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)

View file

@ -1,10 +0,0 @@
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, " "))

View file

@ -1 +0,0 @@
print(shell.getDir())

View file

@ -1,16 +0,0 @@
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)

View file

@ -1,175 +0,0 @@
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

View file

@ -1,33 +0,0 @@
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,
})

View file

@ -1,2 +0,0 @@
local machine = require("machine")
print(string.format("%s @ %s - %s", os.version(), machine.version(), _VERSION))

View file

@ -1,3 +0,0 @@
package.path = "/lib/?.lua;/lib/?/init.lua;/sys/lib/?.lua;/sys/lib/?/init.lua;" .. package.path
_G.io = require("io")

View file

@ -1,15 +0,0 @@
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

View file

@ -1,28 +0,0 @@
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

View file

@ -1,77 +0,0 @@
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

View file

@ -1,47 +0,0 @@
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()

View file

@ -1 +0,0 @@
require("machine").shutdown()

View file

@ -1,91 +0,0 @@
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

View file

@ -1,52 +0,0 @@
-- 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

View file

@ -1,616 +0,0 @@
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

View file

@ -1,388 +0,0 @@
--
-- 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

View file

@ -1,179 +0,0 @@
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

View file

@ -1,3 +0,0 @@
local x = math.random()
return x

View file

@ -1,61 +0,0 @@
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

View file

@ -1,182 +0,0 @@
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

View file

@ -1,112 +0,0 @@
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

View file

@ -1,87 +0,0 @@
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

View file

@ -1,2 +0,0 @@
Welcome to CapyOS!
Run "programs" to get a list of available programs.

View file

@ -1,12 +0,0 @@
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

View file

@ -1,3 +0,0 @@
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View file

@ -1,5 +0,0 @@
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

View file

@ -5,19 +5,13 @@ local term = require("term")
local function slowPrint(text, delay)
for i = 1, #text do
local ch = text:sub(i, i)
io.write(ch)
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)
slowPrint("Hello, World!", 50)

View file

@ -0,0 +1,25 @@
local args = { ... }
local fs = require("fs")
local term = require("term")
local colors = require("colors")
local dir = shell.getDir()
if args[1] then
dir = shell.resolve(args[1])
end
if not fs.isDir(dir) then
error("No such directory: " .. dir, 0)
return false
end
local files = fs.list(dir)
for k,v in ipairs(files) do
if fs.isDir(fs.combine(dir, v)) then
term.setForeground(colors.lightBlue)
print(v .. "/")
else
term.setForeground(colors.white)
print(v)
end
end

View file

@ -0,0 +1,78 @@
local term = require("term")
local io = require("io")
local colors = require("colors")
local colours = colors
local tArgs = { ... }
if #tArgs > 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 = {
["exit"] = setmetatable({}, {
__tostring = function() return "Call exit() to exit." end,
__call = function() bRunning = false end,
}),
["_echo"] = function(...)
return ...
end,
}
setmetatable(tEnv, { __index = _ENV })
for k, v in pairs(package.loaded) do
tEnv[k] = v
end
term.setForeground(colours.yellow)
print(_VERSION .. " interactive prompt")
print("Call exit() to exit.")
term.setForeground(colours.white)
while bRunning do
term.setForeground( colours.yellow )
write("> ")
term.setForeground( colours.white )
local s = io.read(nil, tCommandHistory)
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
table.insert(tCommandHistory, s)
end
local nForcePrint = 0
local func, e = load(s, "=lua", "t", tEnv)
local func2 = load("return _echo(" .. s .. ");", "=lua", "t", tEnv)
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(tostring(value))
n = n + 1
end
else
print(tResults[2])
end
else
print(e)
end
end

View file

@ -1,5 +1,5 @@
local fs = require("fs")
local args = { ... }
local args = {...}
if #args == 0 then
print("Usage: mkdir <directory>")
@ -11,4 +11,4 @@ if fs.exists(dir) then
error("Path already exists", 0)
end
fs.makeDir(dir)
fs.makeDir(dir)

View file

@ -0,0 +1 @@
print(shell.getDir())

View file

@ -0,0 +1,7 @@
local timer = require("timer")
print("Goodbye!")
timer.sleep(1000)
os.shutdown(true)

View file

@ -0,0 +1,10 @@
local fs = require("fs")
local args = {...}
if #args == 0 then
print("Usage: rm <file>")
return
end
local file = shell.resolve(args[1])
fs.delete(file, true)

View file

@ -0,0 +1,139 @@
local term = require("term")
local colors = require("colors")
local io = require("io")
local fs = require("fs")
local exit = false
local shell = {}
shell.path = "./?;./?.lua;/bin/?.lua"
shell.homePath = "/home"
local currentDir = shell.homePath
local function buildEnvironment()
return setmetatable({
shell = shell,
}, { __index = _G })
end
local function printError(...)
local cfg = {term.getForeground()}
local cbg = {term.getBackground()}
term.setForeground(colors.red)
term.setBackground(colors.black)
print(...)
term.setForeground(table.unpack(cfg))
term.setBackground(table.unpack(cbg))
end
local function tokenise(...)
local sLine = table.concat({ ... }, " ")
local tWords = {}
local bQuoted = false
for match in string.gmatch(sLine .. "\"", "(.-)\"") do
if bQuoted then
table.insert(tWords, match)
else
for m in string.gmatch(match, "[^ \t]+") do
table.insert(tWords, m)
end
end
bQuoted = not bQuoted
end
return tWords
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
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 = seg:gsub("%?", path)
if fs.exists(resolved) and not fs.isDir(resolved) then
return resolved
end
end
end
function shell.run(...)
local args = tokenise(...)
local command = args[1]
local path = shell.resolveProgram(command)
if not path then
printError("Command not found: " .. command)
return false
end
local env = buildEnvironment()
local func, err = loadfile(path, "t", env)
if not func then
printError(err)
return false
end
local ok, err = pcall(func, table.unpack(args, 2))
if not ok then
printError(err)
return false
end
return true
end
function shell.exit()
exit = true
end
local history = {}
local lastExecSuccess = true
while not exit do
term.setForeground(colors.white)
write(":")
term.setForeground(colors.lightBlue)
local currentDir = shell.getDir()
if currentDir == shell.homePath then
write("~")
else
write(currentDir)
end
if lastExecSuccess then
term.setForeground(colors.yellow)
else
term.setForeground(colors.red)
end
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
lastExecSuccess = shell.run(line)
end
end

View file

@ -0,0 +1,7 @@
local timer = require("timer")
print("Goodbye!")
timer.sleep(1000)
os.shutdown(false)

View file

@ -1,7 +1,7 @@
local http = require("http")
local fs = require("fs")
local args = { ... }
local args = {...}
if not http then
error("HTTP is not enabled", 0)
@ -12,25 +12,21 @@ if #args == 0 then
return false
end
local outputName = args[2] or fs.getName(args[1])
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,
})
local response, err = http.get(args[1])
if not response then
error(err, 0)
end
local file <close> = fs.open(outputPath, "wb")
file:write(response.content:read("a"))
local file = fs.open(outputPath, "w")
file:write(response:readAll())
file:close()
response.content:close()
response:close()
print("Downloaded to " .. outputPath)
print("File written to " .. outputPath)

View file

@ -1,275 +0,0 @@
-- 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()

View file

@ -0,0 +1 @@
os.print = print

View file

@ -0,0 +1 @@
package.path = "/lib/?.lua;/lib/?/init.lua;" .. package.path

View file

@ -0,0 +1,79 @@
local term = require("term")
local expect = require("expect").expect
function write(sText)
expect(1, sText, "string", "number")
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 print(...)
local nLinesPrinted = 0
local nLimit = select("#", ...)
for n = 1, nLimit do
local s = tostring(select(n, ...))
if n < nLimit then
s = s .. "\t"
end
nLinesPrinted = nLinesPrinted + write(s)
end
nLinesPrinted = nLinesPrinted + write("\n")
return nLinesPrinted
end

View file

@ -5,9 +5,10 @@ local range = require("expect").range
function timer.sleep(n)
expect(1, n, "number")
range(1, 1)
local timerId = timer.start(n)
repeat
local _, par = event.pull("timer")
until par == timerId
end
end

View file

@ -0,0 +1,92 @@
local http = require("http")
local event = require("event")
local expect = require("expect").expect
local WebSocketHandle = {}
function WebSocketHandle:close()
self:closeAsync()
local _, id
repeat
_, id = event.pull("websocket_close")
until id == self.requestId
end
function WebSocketHandle:receive()
local _, id, par
repeat
_, id, par = event.pull("websocket_message")
until id == self.requestId
return par
end
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 requestId = http.requestAsync(url, body, headers, options)
local ev, id, par
repeat
ev, id, par = event.pull("http_response", "http_failure")
until id == requestId
if ev == "http_failure" then
return nil, par
end
return par
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 function buildWebsocketHandle(requestId, handle)
handle.requestId = requestId
local metatable = getmetatable(handle) or {}
metatable.__index = WebSocketHandle
setmetatable(handle, metatable)
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 requestId = http.websocketAsync(url, headers)
local ev, id, par
repeat
ev, id, par = event.pull("websocket_connect", "websocket_failure")
until id == requestId
if ev == "http_failure" then
return nil, par
end
return buildWebsocketHandle(requestId, par)
end

View file

@ -0,0 +1,29 @@
local term = require("term")
local colors = require("colors")
term.setSize(51, 19)
term.setForeground(0x59c9ff)
term.setBackground(colors.black)
term.clear()
term.setPos(1, 1)
print(os.version())
local func, err = loadfile("/bin/shell.lua")
if func then
while true do
local ok, err = pcall(func)
if not ok then
print(err)
end
end
else
print(err)
end
print("Press any key to continue...")
coroutine.yield("key_down")
os.shutdown(false)

View file

@ -1,51 +0,0 @@
-- 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)

View file

@ -0,0 +1,6 @@
local fs = require("fs")
local files = fs.list("/boot")
for k, v in ipairs(files) do
dofile("/boot/" .. v)
end

View file

@ -1,5 +1,3 @@
local expect = require("expect")
local palette = {
{
"white",
@ -67,28 +65,29 @@ local palette = {
}
}
--[[local colors = {
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;
return colors;

View file

@ -0,0 +1,123 @@
--[[- The @{cc.expect} library provides helper functions for verifying that
function arguments are well-formed and of the correct type.
@module cc.expect
@since 1.84.0
@changed 1.96.0 The module can now be called directly as a function, which wraps around `expect.expect`.
@usage Define a basic function and check it has the correct arguments.
local expect = require "cc.expect"
local expect, field = expect.expect, expect.field
local function add_person(name, info)
expect(1, name, "string")
expect(2, info, "table", "nil")
if info then
print("Got age=", field(info, "age", "number"))
print("Got gender=", field(info, "gender", "string", "nil"))
end
end
add_person("Anastazja") -- `info' is optional
add_person("Kion", { age = 23 }) -- `gender' is optional
add_person("Caoimhin", { age = 23, gender = true }) -- error!
]]
local native_select, native_type = select, type
local function get_type_names(...)
local types = table.pack(...)
for i = types.n, 1, -1 do
if types[i] == "nil" then table.remove(types, i) end
end
if #types <= 1 then
return tostring(...)
else
return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
end
end
--- Expect an argument to have a specific type.
--
-- @tparam number index The 1-based argument index.
-- @param value The argument's value.
-- @tparam string ... The allowed types of the argument.
-- @return The given `value`.
-- @throws If the value is not one of the allowed types.
local function expect(index, value, ...)
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
-- If we can determine the function name with a high level of confidence, try to include it.
local name
local ok, info = pcall(debug.getinfo, 3, "nS")
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
local type_names = get_type_names(...)
if name then
error(("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3)
else
error(("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3)
end
end
--- Expect an field to have a specific type.
--
-- @tparam table tbl The table to index.
-- @tparam string index The field name to check.
-- @tparam string ... The allowed types of the argument.
-- @return The contents of the given field.
-- @throws If the field is not one of the allowed types.
local function field(tbl, index, ...)
expect(1, tbl, "table")
expect(2, index, "string")
local value = tbl[index]
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
if value == nil then
error(("field '%s' missing from table"):format(index), 3)
else
error(("bad field '%s' (expected %s, got %s)"):format(index, get_type_names(...), t), 3)
end
end
local function is_nan(num)
return num ~= num
end
--- Expect a number to be within a specific range.
--
-- @tparam number num The value to check.
-- @tparam number min The minimum value, if nil then `-math.huge` is used.
-- @tparam number max The maximum value, if nil then `math.huge` is used.
-- @return The given `value`.
-- @throws If the value is outside of the allowed range.
-- @since 1.96.0
local function range(num, min, max)
expect(1, num, "number")
min = expect(2, min, "number", "nil") or -math.huge
max = expect(3, max, "number", "nil") or math.huge
if min > max then
error("min must be less than or equal to max)", 2)
end
if is_nan(num) or num < min or num > max then
error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3)
end
return num
end
return setmetatable({
expect = expect,
field = field,
range = range,
}, { __call = function(_, ...) return expect(...) end })

View file

@ -0,0 +1,283 @@
local expect = require("expect").expect
local event = require("event")
local term = require("term")
local io = {}
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.getForeground()
oldBg = term.getBackground()
term.setForeground(colors.white)
term.setBackground(colors.gray)
end
if sReplace then
term.write(string.rep(sReplace, #sCompletion))
else
term.write(sCompletion)
end
if not _bClear then
term.setForeground(oldText)
term.setBackground(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 param1 == "enter" then
-- Enter/Numpad Enter
if nCompletion then
clear()
uncomplete()
redraw()
end
break
elseif param1 == "left" then
-- Left
if nPos > 0 then
clear()
nPos = nPos - 1
recomplete()
redraw()
end
elseif param1 == "right" then
-- Right
if nPos < #sLine then
-- Move right
clear()
nPos = nPos + 1
recomplete()
redraw()
else
-- Accept autocomplete
acceptCompletion()
end
elseif param1 == "up" or param1 == "down" then
-- Up or down
if nCompletion then
-- Cycle completions
clear()
if param == "up" then
nCompletion = nCompletion - 1
if nCompletion < 1 then
nCompletion = #tCompletions
end
elseif param == "down" then
nCompletion = nCompletion + 1
if nCompletion > #tCompletions then
nCompletion = 1
end
end
redraw()
elseif _tHistory then
-- Cycle history
clear()
if param1 == "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 param1 == "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 param1 == "home" then
-- Home
if nPos > 0 then
clear()
nPos = 0
recomplete()
redraw()
end
elseif param1 == "delete" then
-- Delete
if nPos < #sLine then
clear()
sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
recomplete()
redraw()
end
elseif param1 == "end" then
-- End
if nPos < #sLine then
clear()
nPos = #sLine
recomplete()
redraw()
end
elseif param1 == "tab" then
-- Tab (accept autocomplete)
acceptCompletion()
end
elseif sEvent == "mouse_down" or sEvent == "mouse_move" 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
return io

View file

@ -1,7 +1,7 @@
local expect = require "expect"
local UTFString = {}
local UTFString_mt = { __index = UTFString, __name = "UTFString" }
local UTFString_mt = {__index = UTFString, __name = "UTFString"}
local function toUTF8(s)
-- convert from ANSI if the string is invalid in UTF-8
@ -18,10 +18,10 @@ local function toUTF8(s)
end
function UTFString:new(s)
return setmetatable({ str = toUTF8(s) }, UTFString_mt)
return setmetatable({str = toUTF8(s)}, UTFString_mt)
end
setmetatable(UTFString, { __call = UTFString.new })
setmetatable(UTFString, {__call = UTFString.new})
local utf8_case_conv_utl, utf8_case_conv_ltu = nil, {} -- defined at bottom of file
@ -40,12 +40,10 @@ function UTFString:find(pattern, init, plain)
expect(1, pattern, "string", "table")
expect(2, init, "number", "nil")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local s, e = string.find(self.str, pattern, init and utf8.offset(self.str, init), plain)
if not s then return nil end
return utf8.len(string.sub(self.str, 1, s - 1)) + 1, utf8.len(string.sub(self.str, 1, e - 1)) + 1
@ -63,12 +61,10 @@ end
function UTFString:gmatch(pattern)
expect(1, pattern, "string", "table")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local iter = string.gmatch(self.str, pattern)
return function()
local matches = table.pack(iter())
@ -86,14 +82,12 @@ function UTFString:gsub(pattern, repl, n)
expect(2, repl, "string", "table", "function")
expect(3, n, "number", "nil")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
else pattern = toUTF8(pattern)end
if type(repl) == "table" and getmetatable(repl) == UTFString_mt then repl = repl.str
elseif type(repl) == "string" then repl = toUTF8(repl) end
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local s, total = string.gsub(self.str, pattern, repl, n)
return UTFString(s), total
end
@ -117,12 +111,10 @@ function UTFString:match(pattern, init)
expect(1, pattern, "string", "table")
expect(2, init, "number", "nil")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local matches = table.pack(string.match(self.str, pattern, init and utf8.offset(self.str, init)))
for i = 1, matches.n do
local tt = type(matches[i])
@ -151,7 +143,7 @@ function UTFString:rep(n, sep)
end
function UTFString:reverse()
local codes = { n = 0 }
local codes = {n = 0}
for _, c in utf8.codes(self.str) do
codes.n = codes.n + 1
codes[codes.n] = c
@ -199,10 +191,8 @@ function UTFString_mt.__concat(a, b)
if type(a) == "string" then return UTFString(a .. b.str)
elseif type(b) == "string" then return UTFString(a.str .. b)
else
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to concatenate " ..
type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to concatenate UTFString and "
.. type(b), 2) end
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to concatenate " .. type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to concatenate UTFString and " .. type(b), 2) end
return UTFString(a.str .. b.str)
end
end
@ -218,10 +208,8 @@ function UTFString_mt.__lt(a, b)
if type(a) == "string" then return a < b.str
elseif type(b) == "string" then return a.str < b
else
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " ..
type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " ..
type(b), 2) end
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " .. type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " .. type(b), 2) end
return a.str < b.str
end
end
@ -230,10 +218,8 @@ function UTFString_mt.__le(a, b)
if type(a) == "string" then return a <= b.str
elseif type(b) == "string" then return a.str <= b
else
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " ..
type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " ..
type(b), 2) end
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " .. type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " .. type(b), 2) end
return a.str <= b.str
end
end
@ -259,7 +245,7 @@ utf8_case_conv_utl = {
[0x0047] = 0x0067,
[0x0048] = 0x0068,
[0x0049] = 0x0069,
[0x004A] = 0x006A,
[0x004B] = 0x006B,
[0x004C] = 0x006C,
@ -308,7 +294,7 @@ utf8_case_conv_utl = {
[0x00DC] = 0x00FC,
[0x00DD] = 0x00FD,
[0x00DE] = 0x00FE,
[0x00DF] = { 0x0073, 0x0073 },
[0x00DF] = {0x0073, 0x0073},
[0x0100] = 0x0101,
[0x0102] = 0x0103,
[0x0104] = 0x0105,
@ -333,8 +319,8 @@ utf8_case_conv_utl = {
[0x012A] = 0x012B,
[0x012C] = 0x012D,
[0x012E] = 0x012F,
[0x0130] = { 0x0069, 0x0307 },
[0x0130] = {0x0069, 0x0307},
[0x0132] = 0x0133,
[0x0134] = 0x0135,
[0x0136] = 0x0137,
@ -346,7 +332,7 @@ utf8_case_conv_utl = {
[0x0143] = 0x0144,
[0x0145] = 0x0146,
[0x0147] = 0x0148,
[0x0149] = { 0x02BC, 0x006E },
[0x0149] = {0x02BC, 0x006E},
[0x014A] = 0x014B,
[0x014C] = 0x014D,
[0x014E] = 0x014F,
@ -434,7 +420,7 @@ utf8_case_conv_utl = {
[0x01EA] = 0x01EB,
[0x01EC] = 0x01ED,
[0x01EE] = 0x01EF,
[0x01F0] = { 0x006A, 0x030C },
[0x01F0] = {0x006A, 0x030C},
[0x01F1] = 0x01F3,
[0x01F2] = 0x01F3,
[0x01F4] = 0x01F5,
@ -495,7 +481,7 @@ utf8_case_conv_utl = {
[0x038C] = 0x03CC,
[0x038E] = 0x03CD,
[0x038F] = 0x03CE,
[0x0390] = { 0x03B9, 0x0308, 0x0301 },
[0x0390] = {0x03B9, 0x0308, 0x0301},
[0x0391] = 0x03B1,
[0x0392] = 0x03B2,
[0x0393] = 0x03B3,
@ -522,7 +508,7 @@ utf8_case_conv_utl = {
[0x03A9] = 0x03C9,
[0x03AA] = 0x03CA,
[0x03AB] = 0x03CB,
[0x03B0] = { 0x03C5, 0x0308, 0x0301 },
[0x03B0] = {0x03C5, 0x0308, 0x0301},
[0x03C2] = 0x03C3,
[0x03CF] = 0x03D7,
[0x03D0] = 0x03B2,
@ -737,7 +723,7 @@ utf8_case_conv_utl = {
[0x0554] = 0x0584,
[0x0555] = 0x0585,
[0x0556] = 0x0586,
[0x0587] = { 0x0565, 0x0582 },
[0x0587] = {0x0565, 0x0582},
[0x10A0] = 0x2D00,
[0x10A1] = 0x2D01,
[0x10A2] = 0x2D02,
@ -914,13 +900,13 @@ utf8_case_conv_utl = {
[0x1E90] = 0x1E91,
[0x1E92] = 0x1E93,
[0x1E94] = 0x1E95,
[0x1E96] = { 0x0068, 0x0331 },
[0x1E97] = { 0x0074, 0x0308 },
[0x1E98] = { 0x0077, 0x030A },
[0x1E99] = { 0x0079, 0x030A },
[0x1E9A] = { 0x0061, 0x02BE },
[0x1E96] = {0x0068, 0x0331},
[0x1E97] = {0x0074, 0x0308},
[0x1E98] = {0x0077, 0x030A},
[0x1E99] = {0x0079, 0x030A},
[0x1E9A] = {0x0061, 0x02BE},
[0x1E9B] = 0x1E61,
[0x1E9E] = { 0x0073, 0x0073 },
[0x1E9E] = {0x0073, 0x0073},
--1E9E; S;0x00DF, ; # LATIN CAPITAL LETTER SHARP S
[0x1EA0] = 0x1EA1,
[0x1EA2] = 0x1EA3,
@ -1006,10 +992,10 @@ utf8_case_conv_utl = {
[0x1F4B] = 0x1F43,
[0x1F4C] = 0x1F44,
[0x1F4D] = 0x1F45,
[0x1F50] = { 0x03C5, 0x0313 },
[0x1F52] = { 0x03C5, 0x0313, 0x0300 },
[0x1F54] = { 0x03C5, 0x0313, 0x0301 },
[0x1F56] = { 0x03C5, 0x0313, 0x0342 },
[0x1F50] = {0x03C5, 0x0313},
[0x1F52] = {0x03C5, 0x0313, 0x0300},
[0x1F54] = {0x03C5, 0x0313, 0x0301},
[0x1F56] = {0x03C5, 0x0313, 0x0342},
[0x1F59] = 0x1F51,
[0x1F5B] = 0x1F53,
[0x1F5D] = 0x1F55,
@ -1022,129 +1008,129 @@ utf8_case_conv_utl = {
[0x1F6D] = 0x1F65,
[0x1F6E] = 0x1F66,
[0x1F6F] = 0x1F67,
[0x1F80] = { 0x1F00, 0x03B9 },
[0x1F81] = { 0x1F01, 0x03B9 },
[0x1F82] = { 0x1F02, 0x03B9 },
[0x1F83] = { 0x1F03, 0x03B9 },
[0x1F84] = { 0x1F04, 0x03B9 },
[0x1F85] = { 0x1F05, 0x03B9 },
[0x1F86] = { 0x1F06, 0x03B9 },
[0x1F87] = { 0x1F07, 0x03B9 },
[0x1F88] = { 0x1F00, 0x03B9 },
[0x1F80] = {0x1F00, 0x03B9},
[0x1F81] = {0x1F01, 0x03B9},
[0x1F82] = {0x1F02, 0x03B9},
[0x1F83] = {0x1F03, 0x03B9},
[0x1F84] = {0x1F04, 0x03B9},
[0x1F85] = {0x1F05, 0x03B9},
[0x1F86] = {0x1F06, 0x03B9},
[0x1F87] = {0x1F07, 0x03B9},
[0x1F88] = {0x1F00, 0x03B9},
--1F88; S;0x1F80, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
[0x1F89] = { 0x1F01, 0x03B9 },
[0x1F89] = {0x1F01, 0x03B9},
--1F89; S;0x1F81, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
[0x1F8A] = { 0x1F02, 0x03B9 },
[0x1F8A] = {0x1F02, 0x03B9},
--1F8A; S;0x1F82, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
[0x1F8B] = { 0x1F03, 0x03B9 },
[0x1F8B] = {0x1F03, 0x03B9},
--1F8B; S;0x1F83, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
[0x1F8C] = { 0x1F04, 0x03B9 },
[0x1F8C] = {0x1F04, 0x03B9},
--1F8C; S;0x1F84, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
[0x1F8D] = { 0x1F05, 0x03B9 },
[0x1F8D] = {0x1F05, 0x03B9},
--1F8D; S;0x1F85, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
[0x1F8E] = { 0x1F06, 0x03B9 },
[0x1F8E] = {0x1F06, 0x03B9},
--1F8E; S;0x1F86, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
[0x1F8F] = { 0x1F07, 0x03B9 },
[0x1F8F] = {0x1F07, 0x03B9},
--1F8F; S;0x1F87, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
[0x1F90] = { 0x1F20, 0x03B9 },
[0x1F91] = { 0x1F21, 0x03B9 },
[0x1F92] = { 0x1F22, 0x03B9 },
[0x1F93] = { 0x1F23, 0x03B9 },
[0x1F94] = { 0x1F24, 0x03B9 },
[0x1F95] = { 0x1F25, 0x03B9 },
[0x1F96] = { 0x1F26, 0x03B9 },
[0x1F97] = { 0x1F27, 0x03B9 },
[0x1F98] = { 0x1F20, 0x03B9 },
[0x1F90] = {0x1F20, 0x03B9},
[0x1F91] = {0x1F21, 0x03B9},
[0x1F92] = {0x1F22, 0x03B9},
[0x1F93] = {0x1F23, 0x03B9},
[0x1F94] = {0x1F24, 0x03B9},
[0x1F95] = {0x1F25, 0x03B9},
[0x1F96] = {0x1F26, 0x03B9},
[0x1F97] = {0x1F27, 0x03B9},
[0x1F98] = {0x1F20, 0x03B9},
--1F98; S;0x1F90, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
[0x1F99] = { 0x1F21, 0x03B9 },
[0x1F99] = {0x1F21, 0x03B9},
--1F99; S;0x1F91, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
[0x1F9A] = { 0x1F22, 0x03B9 },
[0x1F9A] = {0x1F22, 0x03B9},
--1F9A; S;0x1F92, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
[0x1F9B] = { 0x1F23, 0x03B9 },
[0x1F9B] = {0x1F23, 0x03B9},
--1F9B; S;0x1F93, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
[0x1F9C] = { 0x1F24, 0x03B9 },
[0x1F9C] = {0x1F24, 0x03B9},
--1F9C; S;0x1F94, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
[0x1F9D] = { 0x1F25, 0x03B9 },
[0x1F9D] = {0x1F25, 0x03B9},
--1F9D; S;0x1F95, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
[0x1F9E] = { 0x1F26, 0x03B9 },
[0x1F9E] = {0x1F26, 0x03B9},
--1F9E; S;0x1F96, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
[0x1F9F] = { 0x1F27, 0x03B9 },
[0x1F9F] = {0x1F27, 0x03B9},
--1F9F; S;0x1F97, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
[0x1FA0] = { 0x1F60, 0x03B9 },
[0x1FA1] = { 0x1F61, 0x03B9 },
[0x1FA2] = { 0x1F62, 0x03B9 },
[0x1FA3] = { 0x1F63, 0x03B9 },
[0x1FA4] = { 0x1F64, 0x03B9 },
[0x1FA5] = { 0x1F65, 0x03B9 },
[0x1FA6] = { 0x1F66, 0x03B9 },
[0x1FA7] = { 0x1F67, 0x03B9 },
[0x1FA8] = { 0x1F60, 0x03B9 },
[0x1FA0] = {0x1F60, 0x03B9},
[0x1FA1] = {0x1F61, 0x03B9},
[0x1FA2] = {0x1F62, 0x03B9},
[0x1FA3] = {0x1F63, 0x03B9},
[0x1FA4] = {0x1F64, 0x03B9},
[0x1FA5] = {0x1F65, 0x03B9},
[0x1FA6] = {0x1F66, 0x03B9},
[0x1FA7] = {0x1F67, 0x03B9},
[0x1FA8] = {0x1F60, 0x03B9},
--1FA8; S;0x1FA0, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
[0x1FA9] = { 0x1F61, 0x03B9 },
[0x1FA9] = {0x1F61, 0x03B9},
--1FA9; S;0x1FA1, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
[0x1FAA] = { 0x1F62, 0x03B9 },
[0x1FAA] = {0x1F62, 0x03B9},
--1FAA; S;0x1FA2, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
[0x1FAB] = { 0x1F63, 0x03B9 },
[0x1FAB] = {0x1F63, 0x03B9},
--1FAB; S;0x1FA3, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
[0x1FAC] = { 0x1F64, 0x03B9 },
[0x1FAC] = {0x1F64, 0x03B9},
--1FAC; S;0x1FA4, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
[0x1FAD] = { 0x1F65, 0x03B9 },
[0x1FAD] = {0x1F65, 0x03B9},
--1FAD; S;0x1FA5, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
[0x1FAE] = { 0x1F66, 0x03B9 },
[0x1FAE] = {0x1F66, 0x03B9},
--1FAE; S;0x1FA6, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
[0x1FAF] = { 0x1F67, 0x03B9 },
[0x1FAF] = {0x1F67, 0x03B9},
--1FAF; S;0x1FA7, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
[0x1FB2] = { 0x1F70, 0x03B9 },
[0x1FB3] = { 0x03B1, 0x03B9 },
[0x1FB4] = { 0x03AC, 0x03B9 },
[0x1FB6] = { 0x03B1, 0x0342 },
[0x1FB7] = { 0x03B1, 0x0342, 0x03B9 },
[0x1FB2] = {0x1F70, 0x03B9},
[0x1FB3] = {0x03B1, 0x03B9},
[0x1FB4] = {0x03AC, 0x03B9},
[0x1FB6] = {0x03B1, 0x0342},
[0x1FB7] = {0x03B1, 0x0342, 0x03B9},
[0x1FB8] = 0x1FB0,
[0x1FB9] = 0x1FB1,
[0x1FBA] = 0x1F70,
[0x1FBB] = 0x1F71,
[0x1FBC] = { 0x03B1, 0x03B9 },
[0x1FBC] = {0x03B1, 0x03B9},
--1FBC; S;0x1FB3, ; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
[0x1FBE] = 0x03B9,
[0x1FC2] = { 0x1F74, 0x03B9 },
[0x1FC3] = { 0x03B7, 0x03B9 },
[0x1FC4] = { 0x03AE, 0x03B9 },
[0x1FC6] = { 0x03B7, 0x0342 },
[0x1FC7] = { 0x03B7, 0x0342, 0x03B9 },
[0x1FC2] = {0x1F74, 0x03B9},
[0x1FC3] = {0x03B7, 0x03B9},
[0x1FC4] = {0x03AE, 0x03B9},
[0x1FC6] = {0x03B7, 0x0342},
[0x1FC7] = {0x03B7, 0x0342, 0x03B9},
[0x1FC8] = 0x1F72,
[0x1FC9] = 0x1F73,
[0x1FCA] = 0x1F74,
[0x1FCB] = 0x1F75,
[0x1FCC] = { 0x03B7, 0x03B9 },
[0x1FCC] = {0x03B7, 0x03B9},
--1FCC; S;0x1FC3, ; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
[0x1FD2] = { 0x03B9, 0x0308, 0x0300 },
[0x1FD3] = { 0x03B9, 0x0308, 0x0301 },
[0x1FD6] = { 0x03B9, 0x0342 },
[0x1FD7] = { 0x03B9, 0x0308, 0x0342 },
[0x1FD2] = {0x03B9, 0x0308, 0x0300},
[0x1FD3] = {0x03B9, 0x0308, 0x0301},
[0x1FD6] = {0x03B9, 0x0342},
[0x1FD7] = {0x03B9, 0x0308, 0x0342},
[0x1FD8] = 0x1FD0,
[0x1FD9] = 0x1FD1,
[0x1FDA] = 0x1F76,
[0x1FDB] = 0x1F77,
[0x1FE2] = { 0x03C5, 0x0308, 0x0300 },
[0x1FE3] = { 0x03C5, 0x0308, 0x0301 },
[0x1FE4] = { 0x03C1, 0x0313 },
[0x1FE6] = { 0x03C5, 0x0342 },
[0x1FE7] = { 0x03C5, 0x0308, 0x0342 },
[0x1FE2] = {0x03C5, 0x0308, 0x0300},
[0x1FE3] = {0x03C5, 0x0308, 0x0301},
[0x1FE4] = {0x03C1, 0x0313},
[0x1FE6] = {0x03C5, 0x0342},
[0x1FE7] = {0x03C5, 0x0308, 0x0342},
[0x1FE8] = 0x1FE0,
[0x1FE9] = 0x1FE1,
[0x1FEA] = 0x1F7A,
[0x1FEB] = 0x1F7B,
[0x1FEC] = 0x1FE5,
[0x1FF2] = { 0x1F7C, 0x03B9 },
[0x1FF3] = { 0x03C9, 0x03B9 },
[0x1FF4] = { 0x03CE, 0x03B9 },
[0x1FF6] = { 0x03C9, 0x0342 },
[0x1FF7] = { 0x03C9, 0x0342, 0x03B9 },
[0x1FF2] = {0x1F7C, 0x03B9},
[0x1FF3] = {0x03C9, 0x03B9},
[0x1FF4] = {0x03CE, 0x03B9},
[0x1FF6] = {0x03C9, 0x0342},
[0x1FF7] = {0x03C9, 0x0342, 0x03B9},
[0x1FF8] = 0x1F78,
[0x1FF9] = 0x1F79,
[0x1FFA] = 0x1F7C,
[0x1FFB] = 0x1F7D,
[0x1FFC] = { 0x03C9, 0x03B9 },
[0x1FFC] = {0x03C9, 0x03B9},
--1FFC; S;0x1FF3, ; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
[0x2126] = 0x03C9,
[0x212A] = 0x006B,
@ -1507,18 +1493,18 @@ utf8_case_conv_utl = {
[0xABBD] = 0x13ED,
[0xABBE] = 0x13EE,
[0xABBF] = 0x13EF,
[0xFB00] = { 0x0066, 0x0066 },
[0xFB01] = { 0x0066, 0x0069 },
[0xFB02] = { 0x0066, 0x006C },
[0xFB03] = { 0x0066, 0x0066, 0x0069 },
[0xFB04] = { 0x0066, 0x0066, 0x006C },
[0xFB05] = { 0x0073, 0x0074 },
[0xFB06] = { 0x0073, 0x0074 },
[0xFB13] = { 0x0574, 0x0576 },
[0xFB14] = { 0x0574, 0x0565 },
[0xFB15] = { 0x0574, 0x056B },
[0xFB16] = { 0x057E, 0x0576 },
[0xFB17] = { 0x0574, 0x056D },
[0xFB00] = {0x0066, 0x0066},
[0xFB01] = {0x0066, 0x0069},
[0xFB02] = {0x0066, 0x006C},
[0xFB03] = {0x0066, 0x0066, 0x0069},
[0xFB04] = {0x0066, 0x0066, 0x006C},
[0xFB05] = {0x0073, 0x0074},
[0xFB06] = {0x0073, 0x0074},
[0xFB13] = {0x0574, 0x0576},
[0xFB14] = {0x0574, 0x0565},
[0xFB15] = {0x0574, 0x056B},
[0xFB16] = {0x057E, 0x0576},
[0xFB17] = {0x0574, 0x056D},
[0xFF21] = 0xFF41,
[0xFF22] = 0xFF42,
[0xFF23] = 0xFF43,
@ -1771,6 +1757,6 @@ utf8_case_conv_utl = {
[0x1E920] = 0x1E942,
[0x1E921] = 0x1E943,
}
for k, v in pairs(utf8_case_conv_utl) do utf8_case_conv_ltu[v] = k end
for k,v in pairs(utf8_case_conv_utl) do utf8_case_conv_ltu[v] = k end
return UTFString
return UTFString

126
Capy64/Assets/bios.lua Normal file
View file

@ -0,0 +1,126 @@
local term = require("term")
local timer = require("timer")
local bootSleep = 2000
local bg = 0x0
local fg = 0xffffff
term.setForeground(fg)
term.setBackground(bg)
term.clear()
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 w, h = term.getSize()
term.setBlink(false)
local function setupScreen()
local options = {
{
"Open data folder",
openDataFolder,
},
{
"Install default OS",
installOS,
},
{
"Exit setup",
exit,
},
{
"Shutdown",
os.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] == "esc" then
exit()
end
if selection > #options then
selection = 1
elseif selection < 1 then
selection = #options
end
end
end
local function bootScreen()
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
bootScreen()
term.clear()
exit()
while true do
coroutine.yield()
end

View file

@ -1,9 +1,4 @@
{
"EngineMode": 0,
"SafeMode": false,
"Window": {
"Scale": 2
},
"HTTP": {
"Enable": true,
"Blacklist": [],
@ -11,11 +6,5 @@
"Enable": true,
"MaxActiveConnections": 5
}
},
"Integrations": {
"Discord": {
"Enable": true,
"ApplicationId": "1068654606357377074"
}
}
}

185
Capy64/BIOS/Bios.cs Normal file
View file

@ -0,0 +1,185 @@
using Capy64.API;
using Capy64.Core;
using Capy64.Eventing;
using Capy64.Eventing.Events;
using Capy64.LuaRuntime;
using Capy64.LuaRuntime.Libraries;
using KeraLua;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace Capy64.BIOS;
public class Bios : IPlugin
{
private static IGame _game;
private readonly EventEmitter _eventEmitter;
private RuntimeInputEvents _runtimeInputEvents;
private readonly Drawing _drawing;
private static bool CloseRuntime = false;
private static bool OpenBios = false;
public Bios(IGame game)
{
_game = game;
_eventEmitter = game.EventEmitter;
_drawing = game.Drawing;
_game.EventEmitter.OnInit += OnInit;
_eventEmitter.OnTick += OnTick;
}
private void OnInit(object sender, EventArgs e)
{
RunBIOS();
}
private void OnTick(object sender, TickEvent e)
{
if (CloseRuntime)
{
_runtimeInputEvents.Unregister();
_game.LuaRuntime.Close();
CloseRuntime = false;
if (OpenBios)
{
OpenBios = false;
RunBIOS();
}
else
{
StartLuaOS();
}
}
Resume();
}
private void RunBIOS()
{
_game.LuaRuntime = new();
InitLuaPlugins();
_game.LuaRuntime.Thread.PushCFunction(L_OpenDataFolder);
_game.LuaRuntime.Thread.SetGlobal("openDataFolder");
_game.LuaRuntime.Thread.PushCFunction(L_InstallOS);
_game.LuaRuntime.Thread.SetGlobal("installOS");
_game.LuaRuntime.Thread.PushCFunction(L_Exit);
_game.LuaRuntime.Thread.SetGlobal("exit");
var status = _game.LuaRuntime.Thread.LoadFile("Assets/bios.lua");
if (status != LuaStatus.OK)
{
throw new LuaException(_game.LuaRuntime.Thread.ToString(-1));
}
_runtimeInputEvents = new RuntimeInputEvents(_eventEmitter, _game.LuaRuntime);
_runtimeInputEvents.Register();
}
private void StartLuaOS()
{
InstallOS();
try
{
_game.LuaRuntime = new Runtime();
InitLuaPlugins();
_game.LuaRuntime.Patch();
_game.LuaRuntime.Init();
_runtimeInputEvents = new(_eventEmitter, _game.LuaRuntime);
_runtimeInputEvents.Register();
}
catch (LuaException ex)
{
var panic = new PanicScreen(_game.Drawing);
_drawing.Begin();
panic.Render("Cannot load operating system!", ex.Message);
_drawing.End();
}
}
public void Resume()
{
try
{
var yielded = _game.LuaRuntime.Resume();
if (!yielded)
{
_game.Exit();
}
}
catch (LuaException e)
{
Console.WriteLine(e);
var panic = new PanicScreen(_game.Drawing);
panic.Render(e.Message);
_runtimeInputEvents.Unregister();
}
}
private static void InitLuaPlugins()
{
var allPlugins = new List<IPlugin>(_game.NativePlugins);
allPlugins.AddRange(_game.Plugins);
foreach (var plugin in allPlugins)
{
plugin.LuaInit(_game.LuaRuntime.Thread);
}
}
public static void InstallOS(bool force = false)
{
var installedFilePath = Path.Combine(Capy64.AppDataPath, ".installed");
if (!File.Exists(installedFilePath) || force)
{
FileSystem.CopyDirectory("Assets/Lua", FileSystem.DataPath, true, true);
File.Create(installedFilePath).Dispose();
}
}
public static void Shutdown()
{
_game.Exit();
}
public static void Reboot()
{
CloseRuntime = true;
OpenBios = true;
}
private static int L_OpenDataFolder(IntPtr state)
{
var path = FileSystem.DataPath;
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
Process.Start("explorer.exe", path);
break;
case PlatformID.Unix:
Process.Start("xdg-open", path);
break;
}
return 0;
}
private static int L_InstallOS(IntPtr state)
{
InstallOS(true);
return 0;
}
private static int L_Exit(IntPtr state)
{
CloseRuntime = true;
return 0;
}
}

View file

@ -0,0 +1,56 @@
using Capy64.Core;
using Capy64.LuaRuntime.Libraries;
using Microsoft.Xna.Framework;
namespace Capy64.BIOS;
public class PanicScreen
{
public Color ForegroundColor = Color.White;
public Color BackgroundColor = new Color(0, 51, 187);
private Drawing _drawing;
public PanicScreen(Drawing drawing)
{
_drawing = drawing;
}
public void Render(string error, string details = null)
{
Term.ForegroundColor = ForegroundColor;
Term.BackgroundColor = BackgroundColor;
Term.SetCursorBlink(false);
Term.SetSize(57, 23);
Term.Clear();
var title = " Capy64 ";
var halfX = (Term.Width / 2) + 1;
Term.SetCursorPosition(halfX - (title.Length / 2), 2);
Term.ForegroundColor = BackgroundColor;
Term.BackgroundColor = ForegroundColor;
Term.Write(title);
Term.ForegroundColor = ForegroundColor;
Term.BackgroundColor = BackgroundColor;
Term.SetCursorPosition(1, 4);
Print(error + '\n');
if (details is not null)
{
Print(details);
}
}
private void Print(string txt)
{
foreach (var ch in txt)
{
Term.Write(ch.ToString());
if (Term.CursorPosition.X >= Term.Width || ch == '\n')
{
Term.SetCursorPosition(1, (int)Term.CursorPosition.Y + 1);
}
}
Term.SetCursorPosition(1, (int)Term.CursorPosition.Y + 1);
}
}

View file

@ -0,0 +1,177 @@
using Capy64.Core;
using Capy64.Eventing;
using Capy64.Eventing.Events;
using Capy64.LuaRuntime;
using Microsoft.Xna.Framework.Input;
using System.Linq;
using static Capy64.Eventing.InputManager;
namespace Capy64.BIOS;
internal class RuntimeInputEvents
{
private EventEmitter _eventEmitter;
private Runtime _runtime;
private const int rebootDelay = 30;
private int heldReboot = 0;
public RuntimeInputEvents(EventEmitter eventEmitter, Runtime runtime)
{
_eventEmitter = eventEmitter;
_runtime = runtime;
}
public void Register()
{
_eventEmitter.OnMouseUp += OnMouseUp;
_eventEmitter.OnMouseDown += OnMouseDown;
_eventEmitter.OnMouseMove += OnMouseMove;
_eventEmitter.OnMouseWheel += OnMouseWheel;
_eventEmitter.OnKeyUp += OnKeyUp;
_eventEmitter.OnKeyDown += OnKeyDown;
_eventEmitter.OnChar += OnChar;
_eventEmitter.OnTick += OnTick;
}
public void Unregister()
{
_eventEmitter.OnMouseUp -= OnMouseUp;
_eventEmitter.OnMouseDown -= OnMouseDown;
_eventEmitter.OnMouseMove -= OnMouseMove;
_eventEmitter.OnMouseWheel -= OnMouseWheel;
_eventEmitter.OnKeyUp -= OnKeyUp;
_eventEmitter.OnKeyDown -= OnKeyDown;
_eventEmitter.OnChar -= OnChar;
_eventEmitter.OnTick -= OnTick;
}
private static Keys[] rebootKeys = new[]
{
Keys.Insert,
Keys.LeftAlt, Keys.RightAlt,
Keys.LeftControl, Keys.RightControl,
};
private void OnTick(object sender, TickEvent e)
{
var keyState = Keyboard.GetState();
var pressedKeys = keyState.GetPressedKeys();
if ((pressedKeys.Contains(Keys.LeftControl) || pressedKeys.Contains(Keys.RightControl))
&& (pressedKeys.Contains(Keys.LeftAlt) || pressedKeys.Contains(Keys.RightAlt))
&& pressedKeys.Contains(Keys.Insert))
{
heldReboot++;
}
else
{
heldReboot = 0;
}
if (heldReboot >= rebootDelay)
{
heldReboot = 0;
Bios.Reboot();
}
}
private void OnMouseUp(object sender, MouseButtonEvent e)
{
_runtime.PushEvent("mouse_up", new object[]
{
(int)e.Button,
e.Position.X,
e.Position.Y,
});
}
private void OnMouseDown(object sender, MouseButtonEvent e)
{
_runtime.PushEvent("mouse_down", new object[]
{
(int)e.Button,
e.Position.X,
e.Position.Y,
});
}
private void OnMouseMove(object sender, MouseMoveEvent e)
{
_runtime.PushEvent("mouse_move", new object[]
{
e.PressedButtons,
e.Position.X,
e.Position.Y,
});
}
private void OnMouseWheel(object sender, MouseWheelEvent e)
{
_runtime.PushEvent("mouse_scroll", new object[]
{
e.Position.X,
e.Position.Y,
e.VerticalValue,
e.HorizontalValue,
});
}
private void OnKeyUp(object sender, KeyEvent e)
{
_runtime.PushEvent("key_up", new object[]
{
e.KeyCode,
e.KeyName,
(int)e.Mods
});
}
private void OnKeyDown(object sender, KeyEvent e)
{
_runtime.PushEvent("key_down", new object[]
{
e.KeyCode,
e.KeyName,
(int)e.Mods,
e.IsHeld,
});
if ((e.Mods & Modifiers.Ctrl) != Modifiers.None && !e.IsHeld)
{
if ((e.Mods & Modifiers.Alt) != Modifiers.None)
{
if (e.Key == Keys.C)
{
_runtime.PushEvent(new LuaEvent()
{
Name = "interrupt",
Parameters = { },
BypassFilter = true,
});
}
}
else if (e.Key == Keys.V)
{
if (SDL.HasClipboardText())
{
var text = SDL.GetClipboardText();
_runtime.PushEvent(new LuaEvent()
{
Name = "paste",
Parameters = new[] {
text,
},
});
}
}
}
}
private void OnChar(object sender, CharEvent e)
{
_runtime.PushEvent("char", new object[]
{
e.Character,
});
}
}

View file

@ -1,103 +1,37 @@
// 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.API;
using Capy64.Core;
using Capy64.Eventing;
using Capy64.Extensions;
using Capy64.Integrations;
using Capy64.LuaRuntime;
using Capy64.PluginManager;
using Capy64.Runtime;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
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;
namespace Capy64;
public enum EngineMode
public class Capy64 : Game, IGame
{
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 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 const string Version = "0.0.5-alpha";
public static string AppDataPath = Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData,
Environment.SpecialFolderOption.Create),
"Capy64");
public Game 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 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 Runtime LuaRuntime { get; set; }
public EventEmitter EventEmitter { get; private set; }
public Borders Borders = new()
{
Top = 0,
@ -106,22 +40,17 @@ public class Capy64 : Game
Right = 0,
};
public SpriteBatch SpriteBatch;
private readonly InputManager _inputManager;
private RenderTarget2D renderTarget;
private readonly GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
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();
@ -130,38 +59,16 @@ public class Capy64 : Game
Drawing = new();
}
public void SetEngineMode(EngineMode mode)
public void ConfigureServices(IServiceProvider 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);
_serviceProvider = serviceProvider;
}
public void UpdateSize(bool resize = true)
public void UpdateSize()
{
if (resize)
{
_graphics.PreferredBackBufferWidth = (int)(Width * Scale) + Borders.Left + Borders.Right;
_graphics.PreferredBackBufferHeight = (int)(Height * Scale) + Borders.Top + Borders.Bottom;
_graphics.ApplyChanges();
}
_graphics.PreferredBackBufferWidth = (int)(Width * Scale) + Borders.Left + Borders.Right;
_graphics.PreferredBackBufferHeight = (int)(Height * Scale) + Borders.Top + Borders.Right;
_graphics.ApplyChanges();
renderTarget = new RenderTarget2D(
GraphicsDevice,
@ -174,164 +81,94 @@ public class Capy64 : Game
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;
Console.WriteLine(bounds);
if (Window.IsMaximized())
{
}
Width = (int)(bounds.Width / Scale);
Height = (int)(bounds.Height / Scale);
if (Window.IsMaximized())
{
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
{
ResetBorder();
UpdateSize();
}
}
private void ResetBorder()
{
var size = (int)(Scale * DefaultParameters.BorderMultiplier);
Borders = new Borders
{
Top = size,
Bottom = size,
Left = size,
Right = size
};
UpdateSize();
}
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.AllowUserResizing = false;
Window.ClientSizeChanged += OnWindowSizeChange;
InactiveSleepTime = new TimeSpan(0);
SetEngineMode(Configuration.GetValue<EngineMode>("EngineMode", DefaultParameters.EngineMode));
Audio = new Audio();
NativePlugins = GetNativePlugins();
var safeMode = Configuration.GetValue("SafeMode", false);
if (!safeMode)
Plugins = PluginLoader.LoadAllPlugins(Path.Combine(AppDataPath, "plugins"));
Plugins = PluginLoader.LoadAllPlugins("plugins", _serviceProvider);
EventEmitter.RaiseInit();
base.Initialize();
}
private List<IComponent> GetNativePlugins()
private List<IPlugin> GetNativePlugins()
{
var iType = typeof(IComponent);
var iType = typeof(IPlugin);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => iType.IsAssignableFrom(p) && !p.IsInterface);
var plugins = new List<IComponent>();
var plugins = new List<IPlugin>();
foreach (var type in types)
{
var instance = (IComponent)Activator.CreateInstance(type, this);
var instance = (IPlugin)ActivatorUtilities.CreateInstance(_serviceProvider, type)!;
plugins.Add(instance);
}
return plugins;
}
protected override void LoadContent()
{
SpriteBatch = new SpriteBatch(GraphicsDevice);
_spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
Drawing.Begin();
// Register user input
_inputManager.Update(IsActive);
EventEmitter.RaiseTick(new()
{
GameTime = gameTime,
TotalTicks = _totalTicks,
IsActiveTick = (int)_totalTicks % everyTick == 0,
TotalTicks = _totalTicks
});
// resume here
Drawing.End();
_totalTicks++;
// Register user input
_inputManager.Update(IsActive);
base.Update(gameTime);
_totalTicks++;
}
protected override void Draw(GameTime gameTime)
{
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
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()
{
GameTime = gameTime,
TotalTicks = _totalTicks,
});
SpriteBatch.End();
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
//GraphicsDevice.Clear(Color.Blue);
_spriteBatch.Draw(renderTarget, new(Borders.Left, Borders.Top), null, Color.White, 0f, Vector2.Zero, Scale, SpriteEffects.None, 0);
_spriteBatch.End();
base.Draw(gameTime);
}

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation>
@ -10,60 +10,43 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Icon.ico</ApplicationIcon>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\default.json" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\default.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Assets\font.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Lua\**">
<None Update="Assets\Lua/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<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" />
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" />
</ItemGroup>
<ItemGroup>
<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="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>
<EditorConfigFiles Remove="C:\Users\Alex\source\repos\Capy64\Capy64\Capy64\.editorconfig" />
<None Update="Assets\bios.lua">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<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>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project>

View file

@ -0,0 +1,15 @@
#----------------------------- Global Properties ----------------------------#
/outputDir:bin/$(Platform)
/intermediateDir:obj/$(Platform)
/platform:DesktopGL
/config:
/profile:Reach
/compress:False
#-------------------------------- References --------------------------------#
#---------------------------------- Content ---------------------------------#

View file

@ -1,211 +1,73 @@
// 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.Audio;
using System;
namespace Capy64.Core;
public class Audio : IDisposable
public class Audio
{
public enum Waveform
{
Sine,
Square,
Triangle,
Sawtooth,
Noise
}
public const int SampleRate = 48000;
public const int SamplesPerBuffer = 3000;
const int bytesPerSample = 2;
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();
private readonly DynamicSoundEffectInstance _instance;
private double _time = 0.0;
public Audio()
{
for (int i = 0; i < ChannelsCount; i++)
_instance = new(SampleRate, AudioChannels.Mono);
}
public void test()
{
var workingBuffer = new float[SamplesPerBuffer];
FillWorkingBuffer(workingBuffer);
var buffer = DivideBuffer(workingBuffer);
_instance.SubmitBuffer(buffer);
}
private byte[] DivideBuffer(float[] from)
{
var outBuffer = new byte[SamplesPerBuffer * bytesPerSample];
for (int i = 0; i < from.Length; i++)
{
Channels[i] = new DynamicSoundEffectInstance(SampleRate, AudioChannel);
freeChannels[i] = true;
Channels[i].BufferNeeded += Audio_BufferNeeded;
}
var floatSample = MathHelper.Clamp(from[i], -1.0f, 1.0f);
var shortSample = (short)(floatSample * short.MaxValue);
HQChannel.BufferNeeded += HQChannel_BufferNeeded;
}
int index = (i * bytesPerSample) + bytesPerSample;
private static void EnqueueAudioNeedEvent(int i, int pending)
{
Capy64.Instance.LuaRuntime.QueueEvent("audio_need", LK =>
{
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;
outBuffer[index] = (byte)(shortSample >> 8);
outBuffer[index + 1] = (byte)shortSample;
}
else
{
buffer[i] = (byte)sample;
buffer[i + 1] = (byte)(sample >> 8);
outBuffer[index] = (byte)shortSample;
outBuffer[index + 1] = (byte)(shortSample >> 8);
}
x += step;
}
return buffer;
return outBuffer;
}
public static double GetSinePoint(double frequency, double x)
public static double SineWave(double time, double frequency)
{
return Math.Sin(x * 2 * Math.PI * frequency);
return Math.Sin(time * 2 * Math.PI * frequency);
}
private static double GetSquarePoint(double frequency, double x)
private void FillWorkingBuffer(float[] buffer)
{
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++)
for (int i = 0; i < SamplesPerBuffer; i++)
{
v += Math.Pow(-1, k) / Math.Pow((2 * k) - 1, 2)
* Math.Sin(frequency * 2 * Math.PI * ((2 * k) - 1) * x);
// Here is where you sample your wave function
buffer[i] = (float)SineWave(_time, 440); // Left Channel
// Advance time passed since beginning
// Since the amount of samples in a second equals the chosen SampleRate
// Then each sample should advance the time by 1 / SampleRate
_time += 1.0 / SampleRate;
}
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);
for (int i = 0; i < ChannelsCount; i++)
{
Channels[i]?.Dispose();
}
}
}

View file

@ -1,19 +1,4 @@
// 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 FontStashSharp;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended;
@ -31,7 +16,6 @@ public class Drawing : IDisposable
private Texture2D _whitePixel;
private RenderTarget2D _canvas;
private bool _isDrawing;
private readonly HashSet<Texture2D> _disposeTextures = new();
public RenderTarget2D Canvas
{
get => _canvas;
@ -44,17 +28,21 @@ public class Drawing : IDisposable
_spriteBatch = new SpriteBatch(_canvas.GraphicsDevice);
_graphicsDevice = _canvas.GraphicsDevice;
Grid = new Color[_canvas.Width * _canvas.Height];
_canvas.GetData(Grid);
_whitePixel = new Texture2D(_spriteBatch.GraphicsDevice, 1, 1, mipmap: false, SurfaceFormat.Color);
_whitePixel.SetData(new Color[1] { Color.White });
if (isDrawing)
Begin();
}
}
public Color[] Grid;
public Drawing()
{
_fontSystem = new FontSystem();
_fontSystem.AddFont(File.ReadAllBytes(Path.Combine(Capy64.AssetsPath, "font.ttf")));
_fontSystem.AddFont(File.ReadAllBytes(@"Assets/font.ttf"));
}
public void Begin()
@ -75,12 +63,8 @@ public class Drawing : IDisposable
_spriteBatch.End();
_graphicsDevice.SetRenderTarget(null);
foreach (var t in _disposeTextures)
t.Dispose();
_disposeTextures.Clear();
_isDrawing = false;
_canvas.GetData(Grid);
}
public void DrawString(Vector2 pos, string text, Color color, int size = 13)
@ -97,28 +81,27 @@ public class Drawing : IDisposable
public void Plot(Point point, Color color)
{
//var grid = new Color[_canvas.Width * _canvas.Height];
_canvas.GetData(Grid);
if (point.X < 0 || point.Y < 0) return;
if (point.X >= _canvas.Width || point.Y >= _canvas.Height) return;
Grid[point.X + (point.Y * _canvas.Width)] = color;
var grid = new Color[_canvas.Width * _canvas.Height];
_canvas.GetData(grid);
grid[point.X + (point.Y * _canvas.Width)] = color;
_canvas.SetData(grid);
_canvas.SetData(Grid);
}
public void Plot(IEnumerable<Point> points, Color color)
{
var grid = new Color[_canvas.Width * _canvas.Height];
_canvas.GetData(grid);
//var grid = new Color[_canvas.Width * _canvas.Height];
_canvas.GetData(Grid);
foreach (var point in points)
{
if (point.X < 0 || point.Y < 0) continue;
if (point.X >= _canvas.Width || point.Y >= _canvas.Height) continue;
grid[point.X + (point.Y * _canvas.Width)] = color;
Grid[point.X + (point.Y * _canvas.Width)] = color;
}
_canvas.SetData(grid);
_canvas.SetData(Grid);
}
public Color GetPixel(Point point)
@ -126,8 +109,7 @@ public class Drawing : IDisposable
if (point.X < 0 || point.Y < 0) return Color.Black;
if (point.X >= _canvas.Width || point.Y >= _canvas.Height) return Color.Black;
var grid = new Color[_canvas.Width * _canvas.Height];
return grid[point.X + (point.Y * _canvas.Width)];
return Grid[point.X + (point.Y * _canvas.Width)];
}
public void UnsafePlot(Point point, Color color)
@ -154,10 +136,9 @@ public class Drawing : IDisposable
_spriteBatch.DrawPoint(point, color, size);
}
public void DrawCircle(Vector2 pos, int radius, Color color, int thickness = 1, int sides = -1)
public void DrawCircle(Vector2 pos, int radius, Color color, int thickness = 1)
{
sides = sides < 0 ? radius * 4 : sides;
_spriteBatch.DrawCircle(pos, radius, sides, color, thickness);
_spriteBatch.DrawCircle(pos, radius, radius * 4, color, thickness);
}
public void DrawLine(Vector2 start, Vector2 end, Color color, float thickness = 1)
@ -184,7 +165,6 @@ public class Drawing : IDisposable
public void Clear(Color? color = default)
{
Color finalColor = color ?? Color.Black;
Capy64.Instance.BorderColor = finalColor;
_graphicsDevice.Clear(finalColor);
}
@ -201,28 +181,8 @@ public class Drawing : IDisposable
_spriteBatch.Draw(_whitePixel, position3, null, color, rotation, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
}
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);
_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);
}
public void Dispose()
{
GC.SuppressFinalize(this);
_spriteBatch.Dispose();
_whitePixel.Dispose();
}

View file

@ -1,19 +1,9 @@
// 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 Capy64.Extensions.Bindings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Capy64.Core;
@ -24,11 +14,6 @@ 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;

View file

@ -1,19 +1,4 @@
// 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.Events;
using Microsoft.Xna.Framework.Input;
using System;
@ -33,17 +18,10 @@ 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;
public event EventHandler OnClose;
public event EventHandler OnScreenSizeChange;
public event EventHandler<OverlayEvent> OnOverlay;
public void RaiseMouseMove(MouseMoveEvent ev)
@ -105,30 +83,6 @@ 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)
@ -145,14 +99,6 @@ public class EventEmitter
}
}
public void RaiseClose()
{
if (OnClose is not null)
{
OnClose(this, EventArgs.Empty);
}
}
public void RaiseScreenSizeChange()
{
if (OnScreenSizeChange is not null)
@ -160,12 +106,4 @@ public class EventEmitter
OnScreenSizeChange(this, EventArgs.Empty);
}
}
public void RaiseOverlay(OverlayEvent ev)
{
if (OnOverlay is not null)
{
OnOverlay(this, ev);
}
}
}

View file

@ -1,19 +1,4 @@
// 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;
namespace Capy64.Eventing.Events;

View file

@ -1,25 +0,0 @@
// 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; }
}

View file

@ -1,25 +0,0 @@
// 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; }
}

View file

@ -1,24 +0,0 @@
// 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; }
}

View file

@ -1,19 +1,4 @@
// 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 Microsoft.Xna.Framework.Input;
using System;
using static Capy64.Eventing.InputManager;

View file

@ -1,19 +1,4 @@
// 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;
using Microsoft.Xna.Framework.Input;
using System;
using static Capy64.Eventing.InputManager;

View file

@ -1,19 +1,4 @@
// 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;
using System;
namespace Capy64.Eventing.Events;

View file

@ -1,19 +1,4 @@
// 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;
using System;
namespace Capy64.Eventing.Events;

Some files were not shown because too many files have changed in this diff Show more