Skip to content

Commit

Permalink
[Aviraj] Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
avirajkhare00 committed Feb 9, 2025
0 parents commit 05f16dd
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 0 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Build and Release WebAssembly

on:
push:
tags:
- 'v*' # Trigger on version tags
workflow_dispatch: # Allow manual trigger

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Emscripten
uses: mymindstorm/setup-emsdk@v12
with:
version: latest
actions-cache-folder: 'emsdk-cache'

- name: Verify Emscripten Installation
run: emcc --version

- name: Create Build Directory
run: mkdir build

- name: Configure CMake
run: |
cd build
emcmake cmake ..
- name: Build
run: |
cd build
emmake make
- name: Prepare Release Files
run: |
mkdir release
cp build/terminal.wasm release/
cp build/terminal.js release/
cp index.html release/
cp terminal.js release/
- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: |
release/terminal.wasm
release/terminal.js
release/index.html
release/terminal.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.13)
project(wasm_terminal)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

# Ensure we're using Emscripten
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "This project must be compiled with Emscripten")
endif()

add_executable(terminal terminal.c)

# Set Emscripten link flags
set_target_properties(terminal PROPERTIES
LINK_FLAGS "-s WASM=1 -s EXPORTED_RUNTIME_METHODS=['stringToNewUTF8','UTF8ToString'] -s EXPORTED_FUNCTIONS=['_malloc','_free','_init_terminal','_put_char','_get_line','_get_cursor_x','_get_cursor_y','_write_text'] -s NO_EXIT_RUNTIME=1"
)
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# WebAssembly Terminal

A simple terminal emulator implemented in C and compiled to WebAssembly, rendered in an HTML canvas.

## Prerequisites

- Emscripten SDK (emsdk)
- CMake (version 3.13 or higher)
- A modern web browser with WebAssembly support

## Building the Project

1. First, ensure you have activated the Emscripten environment:
```bash
source /path/to/emsdk/emsdk_env.sh
```

2. Create a build directory and navigate to it:
```bash
mkdir build
cd build
```

3. Configure the project with CMake:
```bash
emcmake cmake ..
```

4. Build the project:
```bash
emmake make
```

5. The build process will generate:
- `terminal.wasm`: The WebAssembly binary
- `terminal.js`: The JavaScript glue code

6. Copy these files to your project root directory.

## Running the Terminal

You can serve the files using any HTTP server. For example, using Python:

```bash
python3 -m http.server 8000
```

Then open your browser and navigate to `http://localhost:8000`

## Features

- Basic terminal emulation
- Text input and display
- Cursor movement
- Scrolling support
- Classic green-on-black terminal styling

## Implementation Details

The terminal is implemented with the following components:

- `terminal.c`: Core terminal logic in C
- `terminal.js`: WebAssembly integration and canvas rendering
- `index.html`: HTML canvas container and styling
33 changes: 33 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>WebAssembly Terminal</title>
<style>
body {
margin: 0;
padding: 20px;
background-color: #1e1e1e;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: monospace;
}
#terminal-container {
background-color: #000;
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
#terminal {
border: none;
}
</style>
</head>
<body>
<div id="terminal-container">
<canvas id="terminal" width="800" height="480"></canvas>
</div>
<script src="terminal.js"></script>
</body>
</html>
82 changes: 82 additions & 0 deletions terminal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <emscripten.h>
#include <stdlib.h>
#include <string.h>

#define TERM_WIDTH 80
#define TERM_HEIGHT 24
#define CHAR_WIDTH 10
#define CHAR_HEIGHT 20

// Terminal state
static char terminal_buffer[TERM_HEIGHT][TERM_WIDTH];
static int cursor_x = 0;
static int cursor_y = 0;

EMSCRIPTEN_KEEPALIVE
void init_terminal() {
// Initialize terminal buffer with spaces
for (int y = 0; y < TERM_HEIGHT; y++) {
for (int x = 0; x < TERM_WIDTH; x++) {
terminal_buffer[y][x] = ' ';
}
}
}

EMSCRIPTEN_KEEPALIVE
void put_char(char c) {
if (c == '\n') {
cursor_x = 0;
cursor_y++;
if (cursor_y >= TERM_HEIGHT) {
// Scroll up
for (int y = 0; y < TERM_HEIGHT - 1; y++) {
memcpy(terminal_buffer[y], terminal_buffer[y + 1], TERM_WIDTH);
}
// Clear last line
memset(terminal_buffer[TERM_HEIGHT - 1], ' ', TERM_WIDTH);
cursor_y = TERM_HEIGHT - 1;
}
} else {
if (cursor_x >= TERM_WIDTH) {
cursor_x = 0;
cursor_y++;
if (cursor_y >= TERM_HEIGHT) {
// Scroll up
for (int y = 0; y < TERM_HEIGHT - 1; y++) {
memcpy(terminal_buffer[y], terminal_buffer[y + 1], TERM_WIDTH);
}
// Clear last line
memset(terminal_buffer[TERM_HEIGHT - 1], ' ', TERM_WIDTH);
cursor_y = TERM_HEIGHT - 1;
}
}
terminal_buffer[cursor_y][cursor_x] = c;
cursor_x++;
}
}

EMSCRIPTEN_KEEPALIVE
char* get_line(int y) {
if (y >= 0 && y < TERM_HEIGHT) {
return terminal_buffer[y];
}
return NULL;
}

EMSCRIPTEN_KEEPALIVE
int get_cursor_x() {
return cursor_x;
}

EMSCRIPTEN_KEEPALIVE
int get_cursor_y() {
return cursor_y;
}

EMSCRIPTEN_KEEPALIVE
void write_text(const char* text) {
int len = strlen(text);
for (int i = 0; i < len; i++) {
put_char(text[i]);
}
}
83 changes: 83 additions & 0 deletions terminal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
let Module = {
onRuntimeInitialized: function() {
initTerminal();
}
};

const CHAR_WIDTH = 10;
const CHAR_HEIGHT = 20;
const TERM_WIDTH = 80;
const TERM_HEIGHT = 24;

let canvas, ctx;

function initTerminal() {
canvas = document.getElementById('terminal');
ctx = canvas.getContext('2d');

// Set up canvas for terminal rendering
ctx.font = `${CHAR_HEIGHT}px monospace`;
ctx.textBaseline = 'top';
ctx.fillStyle = '#00ff00'; // Classic green terminal text

// Initialize the terminal buffer in WebAssembly
Module._init_terminal();

// Write some initial text
writeToTerminal("WebAssembly Terminal v1.0\n> ");

// Set up keyboard input
document.addEventListener('keypress', handleKeyPress);

// Start the render loop
requestAnimationFrame(render);
}

function writeToTerminal(text) {
const textPtr = Module.stringToNewUTF8(text);
Module._write_text(textPtr);
Module._free(textPtr);
}

function handleKeyPress(event) {
const char = String.fromCharCode(event.charCode);
writeToTerminal(char);
if (event.key === 'Enter') {
writeToTerminal('\n> ');
}
}

function render() {
// Clear the canvas
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#00ff00';

// Render each line
for (let y = 0; y < TERM_HEIGHT; y++) {
const linePtr = Module._get_line(y);
if (linePtr) {
const line = Module.UTF8ToString(linePtr, TERM_WIDTH);
for (let x = 0; x < TERM_WIDTH; x++) {
ctx.fillText(
line[x],
x * CHAR_WIDTH,
y * CHAR_HEIGHT
);
}
}
}

// Draw cursor
const cursorX = Module._get_cursor_x();
const cursorY = Module._get_cursor_y();
ctx.fillStyle = '#00ff00';
ctx.fillRect(
cursorX * CHAR_WIDTH,
cursorY * CHAR_HEIGHT,
CHAR_WIDTH,
2
);

requestAnimationFrame(render);
}

0 comments on commit 05f16dd

Please sign in to comment.