Skip to content

Commit

Permalink
Added Example
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacarpet committed Nov 17, 2016
1 parent 84be7e6 commit c910d40
Show file tree
Hide file tree
Showing 6 changed files with 4,826 additions and 0 deletions.
198 changes: 198 additions & 0 deletions example/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package main

/*
* Go-WinPTY example.
* Provides a web based cmd or powershell prompt using xterm.js
*
* Usage:
* GOOS=windows GOARCH=amd64 go build -o web-pty.exe server.go
* Place winpty.dll and winpty-agent.exe in the same directory as web-pty.exe, then run.
* .\web-pty.exe -cmd "powershell" -addr 127.0.0.1:9000 -static "static_folder_name"
* .\web-pty.exe -cmd "cmd"
*
* Adapted from https://github.com/codenvy/che-websocket-terminal
* Which was written by Al Tobey [email protected]
* By Samuel Melrose [email protected]
*/

import (
"io"
"os"
"log"
"flag"
"bufio"
"bytes"
"net/http"
"unicode/utf8"
"encoding/json"
"github.com/gorilla/websocket"
"github.com/iamacarpet/go-winpty"
)

var addrFlag, cmdFlag, staticFlag string

var upgrader = websocket.Upgrader{
ReadBufferSize: 1,
WriteBufferSize: 1,
CheckOrigin: func(r *http.Request) bool {
return true
},
}

type Message struct{
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}

type wsPty struct {
Pty *winpty.WinPTY
ws *websocket.Conn
}

func (wp *wsPty) Start() {
var err error
// If you want to use a location other than the same folder for the DLL and exe
// specify the path as the first param, e.g. winpty.Open(`C:\MYAPP\support`, cmdFlag)
wp.Pty, err = winpty.Open("", cmdFlag)
if err != nil {
log.Fatalf("Failed to start command: %s\n", err)
}
//Set the size of the pty
wp.Pty.SetSize(200, 60)
}

func (wp *wsPty) Stop() {
wp.Pty.Close()

wp.ws.Close()
}

func ptyHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatalf("Websocket upgrade failed: %s\n", err)
}
defer conn.Close()

wp := wsPty{ ws: conn }

wp.Start()

go wp.writePump()
wp.readPump()
}

func (wp *wsPty) readPump() {
defer wp.Stop()

for {
mt, payload, err := wp.ws.ReadMessage()
if err != nil {
if err != io.EOF {
log.Printf("conn.ReadMessage failed: %s\n", err)
return
}
}
var msg Message;
switch mt {
case websocket.BinaryMessage:
log.Printf("Ignoring binary message: %q\n", payload)
case websocket.TextMessage:
err := json.Unmarshal(payload, &msg);
if err != nil{
log.Printf("Invalid message %s\n", err);
continue
}
switch msg.Type{
case "resize" :
var size []float64;
err := json.Unmarshal(msg.Data, &size)
if err != nil{
log.Printf("Invalid resize message: %s\n", err);
} else{
wp.Pty.SetSize(uint32(size[0]), uint32(size[1]));
}

case "data" :
var dat string;
err := json.Unmarshal(msg.Data, &dat);
if err != nil{
log.Printf("Invalid data message %s\n", err);
} else{
wp.Pty.StdIn.Write([]byte(dat));
}

default:
log.Printf("Invalid message type %d\n", mt)
return
}

default:
log.Printf("Invalid message type %d\n", mt)
return
}
}
}

func (wp *wsPty) writePump() {
defer wp.Stop()

buf := make([]byte, 8192)
reader := bufio.NewReader(wp.Pty.StdOut)
var buffer bytes.Buffer
for {
n, err := reader.Read(buf)
if err != nil{
log.Printf("Failed to read from pty master: %s", err)
return
}
//read byte array as Unicode code points (rune in go)
bufferBytes := buffer.Bytes()
runeReader := bufio.NewReader(bytes.NewReader(append(bufferBytes[:],buf[:n]...)))
buffer.Reset()
i := 0;
for i< n{
char, charLen, e := runeReader.ReadRune()
if e != nil{
log.Printf("Failed to read from pty master: %s", err)
return
}
if char == utf8.RuneError {
runeReader.UnreadRune()
break
}
i += charLen;
buffer.WriteRune(char)
}
err = wp.ws.WriteMessage(websocket.TextMessage, buffer.Bytes())
if err != nil {
log.Printf("Failed to send UTF8 char: %s", err)
return
}
buffer.Reset();
if i < n{
buffer.Write(buf[i:n])
}
}
}

func init() {
cwd, _ := os.Getwd()
flag.StringVar(&addrFlag, "addr", ":9000", "IP:PORT or :PORT address to listen on")
flag.StringVar(&cmdFlag, "cmd", "cmd", "command to execute on slave side of the pty")
flag.StringVar(&staticFlag, "static", cwd, "path to static content")
}

func main() {
flag.Parse()

http.HandleFunc("/pty", ptyHandler)

// serve html & javascript
http.Handle("/", http.FileServer(http.Dir(staticFlag)))

err := http.ListenAndServe(addrFlag, nil)
if err != nil {
log.Fatalf("net.http could not listen on address '%s': %s\n", addrFlag, err)
}
}
86 changes: 86 additions & 0 deletions example/static/fit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Fit terminal columns and rows to the dimensions of its
* DOM element.
*
* Approach:
* - Rows: Truncate the division of the terminal parent element height
* by the terminal row height
*
* - Columns: Truncate the division of the terminal parent element width by
* the terminal character width (apply display: inline at the
* terminal row and truncate its width with the current number
* of columns)
*/
(function (fit) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fit(require('../../dist/xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../dist/xterm'], fit);
} else {
/*
* Plain browser environment
*/
fit(window.Terminal);
}
})(function (Xterm) {
/**
* This module provides methods for fitting a terminal's size to a parent container.
*
* @module xterm/addons/fit/fit
*/
var exports = {};

exports.proposeGeometry = function (term) {
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
parentElementWidth = parseInt(parentElementStyle.getPropertyValue('width')),
elementStyle = window.getComputedStyle(term.element),
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
availableHeight = parentElementHeight - elementPaddingVer,
availableWidth = parentElementWidth - elementPaddingHor,
container = term.rowContainer,
subjectRow = term.rowContainer.firstElementChild,
contentBuffer = subjectRow.innerHTML,
characterHeight,
rows,
characterWidth,
cols,
geometry;

subjectRow.style.display = 'inline';
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
characterWidth = subjectRow.getBoundingClientRect().width;
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
characterHeight = parseInt(subjectRow.offsetHeight);
subjectRow.innerHTML = contentBuffer;

rows = parseInt(availableHeight / characterHeight);
cols = parseInt(availableWidth / characterWidth) - 1;

geometry = {cols: cols, rows: rows};
return geometry;
};

exports.fit = function (term) {
var geometry = exports.proposeGeometry(term);

term.resize(geometry.cols, geometry.rows);
};

Xterm.prototype.proposeGeometry = function () {
return exports.proposeGeometry(this);
};

Xterm.prototype.fit = function () {
return exports.fit(this);
};

return exports;
});
74 changes: 74 additions & 0 deletions example/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<html>
<head>
<link rel="stylesheet" href="./xterm.css" />
</head>
<body>
<div id="bash" style="width: 100%; height: 100%; background-color: gray;"></div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

<script src="./xterm.js" type="text/javascript"></script>
<script src="./fit.js" type="text/javascript"></script>

<script>
$(document).ready(function () {
var socketUrl = window.location.host + "/pty";
if (window.location.protocol === "https:") {
socketUrl = "wss://" + socketUrl;
} else {
socketUrl = "ws://" + socketUrl;
}
var sock = new WebSocket(socketUrl);

sock.onerror = function (e) {
console.log("socket error", e);
};

var timer;
sock.onopen = function (e) {
$(window).resize(function () {
if(timer != undefined){
clearTimeout(timer);
}
timer = setTimeout(setSize, 500);
});

var setSize = function () {
var initialGeometry = term.proposeGeometry(),
cols = initialGeometry.cols,
rows = initialGeometry.rows;
term.resize(cols, rows);
sock.send(JSON.stringify({ type : "resize", "data" : [cols,rows] }));
};

var term = new Terminal({
cols: 200,
rows: 60,
useStyle: true,
screenKeys: true
});

term.open(document.getElementById("bash"));
term.fit();

term.on('title', function (title) {
document.title = title;
});
setSize();

term.on('resize', function (data) {
sock.send(JSON.stringify({ type : "resize", "data" : [data.cols,data.rows] }));
});

term.on('data', function (data) {
sock.send(JSON.stringify({ type : "data", "data" : data }));
});

sock.onmessage = function (msg) {
term.write(msg.data);
};
};
});
</script>
</body>
</html>
Loading

0 comments on commit c910d40

Please sign in to comment.