-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
84be7e6
commit c910d40
Showing
6 changed files
with
4,826 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.