├── README.md ├── client ├── server ├── avro ├── .gitignore ├── AttachToProcess.avsc ├── InputOutput.avsc ├── Resize.avsc ├── Message.avsc └── CreateProcess.avsc ├── clojure ├── doc │ └── intro.md ├── .gitignore ├── test │ └── multimux │ │ └── core_test.clj ├── README.md ├── project.clj ├── src │ ├── multimux │ │ ├── serialize.clj │ │ ├── connection.clj │ │ ├── terminal.clj │ │ └── core.clj │ └── java │ │ └── JMultimuxSplit.java └── LICENSE └── go └── src └── server ├── .gitignore ├── serialize.go ├── connection.go ├── process.go └── server.go /README.md: -------------------------------------------------------------------------------- 1 | foo 2 | -------------------------------------------------------------------------------- /client: -------------------------------------------------------------------------------- 1 | ./clojure -------------------------------------------------------------------------------- /server: -------------------------------------------------------------------------------- 1 | ./go/src/server/ -------------------------------------------------------------------------------- /avro/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /clojure/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to multimux 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /avro/AttachToProcess.avsc: -------------------------------------------------------------------------------- 1 | {"type" : "record", 2 | "name" : "AttachToProcess", 3 | "namespace": "multimux", 4 | "fields" : [ {"name": "processId", "type": "int" } ] } 5 | -------------------------------------------------------------------------------- /avro/InputOutput.avsc: -------------------------------------------------------------------------------- 1 | {"type" : "record", 2 | "name" : "InputOutput", 3 | "namespace": "multimux", 4 | "fields" : [ {"name": "processId", "type": "int" }, 5 | {"name": "bytes", "type": "bytes"} ] } 6 | -------------------------------------------------------------------------------- /clojure/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | .nrepl-port 13 | *.swp 14 | -------------------------------------------------------------------------------- /clojure/test/multimux/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns multimux.core-test 2 | (:require [clojure.test :refer :all] 3 | [multimux.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /avro/Resize.avsc: -------------------------------------------------------------------------------- 1 | {"type" : "record", 2 | "name" : "Resize", 3 | "namespace": "multimux", 4 | "fields" : [ {"name": "processId", "type": "int"}, 5 | {"name": "cols", "type": "int"}, 6 | {"name": "rows", "type": "int"}, 7 | {"name": "xpixel", "type": "int"}, 8 | {"name": "ypixel", "type": "int"} ] } 9 | -------------------------------------------------------------------------------- /go/src/server/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | *.swp 27 | -------------------------------------------------------------------------------- /avro/Message.avsc: -------------------------------------------------------------------------------- 1 | {"namespace": "multimux", 2 | "type": "record", 3 | "name": "Message", 4 | "fields": [ 5 | {"name": "messageType", 6 | "type": {"type":"enum", 7 | "name":"messageTypes", 8 | "symbols": [ "stdin", "stdout", "resize", "createProcess", "attachToProcess" ] } }, 9 | {"name": "data", 10 | "type": ["InputOutput", "Resize", "CreateProcess", "AttachToProcess" ] } ] 11 | } 12 | -------------------------------------------------------------------------------- /avro/CreateProcess.avsc: -------------------------------------------------------------------------------- 1 | {"type" : "record", 2 | "name" : "CreateProcess", 3 | "namespace": "multimux", 4 | "fields" : [ {"name": "path", "type": "string", "default": ""}, 5 | {"name": "processId", "type": "int", "default": -1}, 6 | {"name": "cols", "type": "int", "default": 0}, 7 | {"name": "rows", "type": "int", "default": 0}, 8 | {"name": "xpixel", "type": "int", "default": 0}, 9 | {"name": "ypixel", "type": "int", "default": 0} ] } 10 | -------------------------------------------------------------------------------- /clojure/README.md: -------------------------------------------------------------------------------- 1 | # multimux 2 | 3 | FIXME: description 4 | 5 | ## Installation 6 | 7 | Download from http://example.com/FIXME. 8 | 9 | ## Usage 10 | 11 | FIXME: explanation 12 | 13 | $ java -jar multimux-0.1.0-standalone.jar [args] 14 | 15 | ## Options 16 | 17 | FIXME: listing of options this app accepts. 18 | 19 | ## Examples 20 | 21 | ... 22 | 23 | ### Bugs 24 | 25 | ... 26 | 27 | ### Any Other Sections 28 | ### That You Think 29 | ### Might be Useful 30 | 31 | ## License 32 | 33 | Copyright © 2015 FIXME 34 | 35 | Distributed under the Eclipse Public License either version 1.0 or (at 36 | your option) any later version. 37 | -------------------------------------------------------------------------------- /clojure/project.clj: -------------------------------------------------------------------------------- 1 | (defproject multimux "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 8 | [org.apache.avro/avro "1.7.7"] 9 | [com.taoensso/timbre "3.4.0"] 10 | [com.jcraft/jsch "0.1.52"] 11 | [log4j "1.2.16"] 12 | [com.google.guava/guava "14.0.1"]] 13 | :resource-paths ["lib/jediterm-pty-2.0.jar" "lib/guava-14.0.1.jar"] 14 | :java-source-paths ["src/java/"] 15 | :jvm-opts ["-Dsun.java2d.pmoffscreen=false"] 16 | :main ^:skip-aot multimux.core 17 | :target-path "target/%s" 18 | :profiles {:uberjar {:aot :all}}) 19 | -------------------------------------------------------------------------------- /go/src/server/serialize.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/fede1024/goavro" 5 | "io/ioutil" 6 | "log" 7 | ) 8 | 9 | var InputOutputSchema goavro.RecordSetter 10 | var InputOutputCodec goavro.Codec 11 | var ResizeSchema goavro.RecordSetter 12 | var ResizeCodec goavro.Codec 13 | var CreateProcessSchema goavro.RecordSetter 14 | var CreateProcessCodec goavro.Codec 15 | var AttachToProcessSchema goavro.RecordSetter 16 | var AttachToProcessCodec goavro.Codec 17 | var MessageSchema goavro.RecordSetter 18 | var MessageCodec goavro.Codec 19 | 20 | func LoadAllCodecs() { 21 | st := goavro.NewSymtab() 22 | InputOutputCodec, InputOutputSchema = LoadCodec(st, "../../../avro/InputOutput.avsc") 23 | ResizeCodec, ResizeSchema = LoadCodec(st, "../../../avro/Resize.avsc") 24 | CreateProcessCodec, CreateProcessSchema = LoadCodec(st, "../../../avro/CreateProcess.avsc") 25 | AttachToProcessCodec, AttachToProcessSchema = LoadCodec(st, "../../../avro/AttachToProcess.avsc") 26 | MessageCodec, MessageSchema = LoadCodec(st, "../../../avro/Message.avsc") 27 | } 28 | 29 | func LoadCodec(st goavro.Symtab, path string) (goavro.Codec, goavro.RecordSetter) { 30 | avscJson, err := ioutil.ReadFile(path) 31 | if err != nil { 32 | log.Fatal("Can't open avro schema", err.Error()) 33 | } 34 | 35 | jsonString := string(avscJson) 36 | 37 | schema := goavro.RecordSchema(jsonString) 38 | codec, err := st.NewCodec(jsonString) 39 | if err != nil { 40 | log.Fatal("Can't decode avro schema", err.Error()) 41 | } 42 | 43 | return codec, schema 44 | } 45 | 46 | func NewOutputMessage(data []byte, procId int) *goavro.Record { 47 | record, err := goavro.NewRecord(MessageSchema) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | inputOutput, err := goavro.NewRecord(InputOutputSchema) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | inputOutput.Set("processId", int32(procId)) 57 | inputOutput.Set("bytes", data) 58 | record.Set("messageType", "stdout") 59 | record.Set("data", inputOutput) 60 | 61 | return record 62 | } 63 | 64 | func NewProcessCreationMessage(procId int) *goavro.Record { 65 | record, err := goavro.NewRecord(MessageSchema) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | 70 | createProcess, err := goavro.NewRecord(CreateProcessSchema) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | createProcess.Set("processId", int32(procId)) 76 | record.Set("messageType", "createProcess") 77 | record.Set("data", createProcess) 78 | 79 | return record 80 | } 81 | -------------------------------------------------------------------------------- /go/src/server/connection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/fede1024/goavro" 5 | "log" 6 | "net" 7 | ) 8 | 9 | // type Producer interface { 10 | // GetOutputChannel() chan *interface{} 11 | // } 12 | // 13 | // type ProducerSet interface { 14 | // GetProducers() []*Producer 15 | // AddProducer() chan *Producer 16 | // } 17 | 18 | // CONNECTION 19 | 20 | type Connection struct { 21 | socket *net.Conn 22 | recChan, sendChan chan *goavro.Record 23 | processes map[int]bool 24 | alive bool 25 | } 26 | 27 | func (conn *Connection) ReceiveWorker() { 28 | for { 29 | message, err := MessageCodec.Decode(*conn.socket) 30 | if err != nil { 31 | log.Println("Error decoding:", err) 32 | conn.Terminate() 33 | return 34 | } 35 | 36 | conn.recChan <- message.(*goavro.Record) 37 | } 38 | } 39 | 40 | func (conn *Connection) SendWorker() { 41 | for message := range conn.sendChan { 42 | err := MessageCodec.Encode(*conn.socket, message) 43 | if err != nil { 44 | log.Println("Error encoding:", err) 45 | conn.Terminate() 46 | return 47 | } 48 | } 49 | } 50 | 51 | func NewConnection(l net.Listener) (*Connection, error) { 52 | socket, err := l.Accept() 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | conn := &Connection{socket: &socket, alive: true} 58 | conn.recChan = make(chan *goavro.Record) 59 | conn.sendChan = make(chan *goavro.Record) 60 | conn.processes = make(map[int]bool) 61 | 62 | go conn.ReceiveWorker() 63 | go conn.SendWorker() 64 | 65 | return conn, nil 66 | } 67 | 68 | func (c *Connection) Terminate() { 69 | if c.alive { 70 | (*c.socket).Close() 71 | close(c.sendChan) 72 | close(c.recChan) 73 | c.alive = false 74 | } 75 | } 76 | 77 | func (c *Connection) FollowProcess(proc *Process) { 78 | c.processes[proc.id] = true 79 | } 80 | 81 | func (c *Connection) UnfollowProcess(proc *Process) { 82 | c.processes[proc.id] = false 83 | } 84 | 85 | func (c *Connection) IsFollowing(proc *Process) bool { 86 | val, found := c.processes[proc.id] 87 | return found && val 88 | } 89 | 90 | // CONNECTION REGISTRY 91 | 92 | type ConnectionRegistry struct { 93 | connections []*Connection 94 | newConnectionChan chan *Connection 95 | } 96 | 97 | func NewConnectionRegistry() *ConnectionRegistry { 98 | return &ConnectionRegistry{newConnectionChan: make(chan *Connection)} 99 | } 100 | 101 | func (cr *ConnectionRegistry) AddConnection(conn *Connection) { 102 | cr.connections = append(cr.connections, conn) 103 | select { 104 | case cr.newConnectionChan <- conn: // Notify the new connection 105 | default: // Do nothing if full 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /clojure/src/multimux/serialize.clj: -------------------------------------------------------------------------------- 1 | (ns multimux.serialize 2 | (:require [multimux.connection :as conn] 3 | [taoensso.timbre :as log] 4 | [clojure.core.async :refer [>!! !! msg-read-chan msg) 29 | (recur (.read reader nil decoder)))) 30 | (catch java.io.EOFException e 31 | (log/warn "Socket exception" e)) 32 | (catch java.net.SocketException e 33 | (log/warn "Socket exception" e)))) 34 | (async/thread 35 | (try 36 | (let [out (conn/output-stream connection) 37 | writer (GenericDatumWriter. messageSchema) 38 | encoder (.binaryEncoder (EncoderFactory/get) out nil)] 39 | (loop [msg (SocketConnection server port nil)) 20 | 21 | (defn open-socket-connection [conn] 22 | (try 23 | (assoc conn :socket (Socket. (:host conn) (:port conn))) 24 | (catch java.net.ConnectException e 25 | (log/warn "Connection failure" e) 26 | nil))) 27 | 28 | (defn close-socket-connection [conn] 29 | (when (:socket conn) 30 | (.close (:socket conn)) 31 | conn)) 32 | 33 | (defn get-socket-connection-input-stream [conn] 34 | (.getInputStream (:socket conn))) 35 | 36 | (defn get-socket-connection-output-stream [conn] 37 | (.getOutputStream (:socket conn))) 38 | 39 | (extend SocketConnection 40 | ConnectionProtocol 41 | {:open open-socket-connection 42 | :close close-socket-connection 43 | :input-stream get-socket-connection-input-stream 44 | :output-stream get-socket-connection-output-stream}) 45 | 46 | 47 | ;; Connection over ssh to a unix socket 48 | (defrecord SSHUnixConnection [^String username ^String password ^String host ^Session session 49 | ^Channel channel ^OutputStream output-stream ^InputStream input-stream 50 | ^InputStream error-stream]) 51 | 52 | (def jsch (JSch.)) 53 | (def unix-socket-path "/tmp/mm.sock") 54 | (def socat-command (str "socat STDIO UNIX-CONNECT:" unix-socket-path)) 55 | 56 | (defn create-ssh-session [username password host & {:keys [host-key user-info]}] 57 | (let [session (.getSession jsch username host) 58 | known-host-repository (.getHostKeyRepository session)] 59 | (when host-key 60 | (log/warn "Adding ssh host key for" (.getHost session)) 61 | (.add known-host-repository host-key user-info)) 62 | (.setPassword session password) 63 | (try 64 | (.connect session 20000) 65 | session 66 | (catch JSchException e 67 | (if (and (.startsWith (.getMessage e) "UnknownHostKey") (not host-key) (not user-info)) 68 | (create-ssh-session username password host :host-key (.getHostKey session) 69 | :user-info (.getUserInfo session)) 70 | (log/error e)))))) 71 | 72 | (defn ssh-exec-channel [session command] 73 | (let [channel (.openChannel session "exec") 74 | input-stream (.getInputStream channel) 75 | output-stream (.getOutputStream channel) 76 | error-stream (.getErrStream channel)] 77 | (async/thread 78 | (let [reader (BufferedReader. (InputStreamReader. error-stream))] 79 | (loop [] 80 | (when-let [line (.readLine reader)] 81 | (log/warn "SSH stderr:" line) 82 | (recur))))) 83 | (.setCommand channel command) 84 | (.connect channel) 85 | [channel input-stream output-stream error-stream])) 86 | 87 | (defn create-ssh-unix-connection [username password host] 88 | (->SSHUnixConnection username password host nil nil nil nil nil)) 89 | 90 | (defn open-ssh-unix-connection [conn] 91 | (if-let [session (create-ssh-session (:username conn) (:password conn) (:host conn))] 92 | (let [[channel input output error] (ssh-exec-channel session socat-command)] 93 | ;; TODO: add protocol message to check for connection 94 | (assoc conn :session session :channel channel :input-stream input :output-stream output :error-stream error)))) 95 | 96 | (defn close-ssh-unix-connection [conn] 97 | (.disconnect (:channel conn)) 98 | (.disconnect (:session conn))) 99 | 100 | (defn get-ssh-unix-connection-input-stream [conn] 101 | (:input-stream conn)) 102 | 103 | (defn get-ssh-unix-connection-output-stream [conn] 104 | (:output-stream conn)) 105 | 106 | (extend SSHUnixConnection 107 | ConnectionProtocol 108 | {:open open-ssh-unix-connection 109 | :close close-ssh-unix-connection 110 | :input-stream get-ssh-unix-connection-input-stream 111 | :output-stream get-ssh-unix-connection-output-stream}) 112 | -------------------------------------------------------------------------------- /go/src/server/process.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kr/pty" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "syscall" 9 | "time" 10 | "unsafe" 11 | ) 12 | 13 | func openTty(c *exec.Cmd) (ptty *os.File, err error) { 14 | ptty, tty, err := pty.Open() 15 | if err != nil { 16 | return nil, err 17 | } 18 | //defer tty.Close() 19 | c.Stdout = tty 20 | c.Stdin = tty 21 | c.Stderr = tty 22 | c.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} 23 | 24 | return ptty, err 25 | } 26 | 27 | // PROCESS 28 | 29 | type Process struct { 30 | path string 31 | id int 32 | tty *os.File 33 | command *exec.Cmd 34 | stdin, stdout chan []byte 35 | terminate chan bool 36 | alive bool 37 | } 38 | 39 | func NewProcess(path string) (*Process, error) { 40 | c := exec.Command(path) 41 | tty, err := openTty(c) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return &Process{path: path, id: -1, tty: tty, command: c, alive: true}, nil 47 | } 48 | 49 | func (proc *Process) Start() error { 50 | err := proc.command.Start() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | proc.stdin = make(chan []byte) 56 | proc.stdout = make(chan []byte) 57 | proc.terminate = make(chan bool) 58 | 59 | go proc.StdinWorker() 60 | go proc.StdoutWorker() 61 | go proc.TerminationWorker() 62 | 63 | proc.alive = true 64 | 65 | return nil 66 | } 67 | 68 | func (proc *Process) StdinWorker() { 69 | for input := range proc.stdin { 70 | nw, err := proc.tty.Write(input) 71 | if err != nil { 72 | log.Println(err) 73 | select { 74 | case proc.terminate <- true: 75 | default: 76 | } 77 | break 78 | } 79 | if len(input) != nw { 80 | panic("Fix here") 81 | } 82 | } 83 | log.Println("STDIN END") 84 | } 85 | 86 | func (proc *Process) StdoutWorker() { 87 | for { 88 | buf := make([]byte, 1024) 89 | reqLen, err := proc.tty.Read(buf) 90 | if err != nil { 91 | log.Println(err) 92 | select { 93 | case proc.terminate <- true: 94 | default: 95 | } 96 | break 97 | } 98 | 99 | proc.stdout <- buf[:reqLen] 100 | } 101 | log.Println("STDOUT END") 102 | } 103 | 104 | func (proc *Process) TerminationWorker() { 105 | done := make(chan error, 1) 106 | go func() { 107 | done <- proc.command.Wait() 108 | }() 109 | 110 | select { 111 | case <-proc.terminate: 112 | waitAndKill(proc.command) 113 | case <-done: 114 | // Process already terminated 115 | } 116 | 117 | proc.alive = false 118 | close(proc.stdin) 119 | close(proc.stdout) 120 | close(proc.terminate) 121 | proc.tty.Close() 122 | log.Println("Process teminated") 123 | } 124 | 125 | func waitAndKill(cmd *exec.Cmd) { 126 | done := make(chan error, 1) 127 | go func() { 128 | done <- cmd.Wait() 129 | }() 130 | select { 131 | case <-time.After(3 * time.Second): 132 | if err := cmd.Process.Kill(); err != nil { 133 | log.Fatal("Failed to kill: ", err) 134 | } 135 | <-done // allow goroutine to exit 136 | log.Println("Process killed") 137 | case err := <-done: 138 | if err != nil { 139 | log.Printf("process done with error = %v", err) 140 | } 141 | } 142 | } 143 | 144 | func (proc *Process) SetSize(row, columns, xpixel, ypixel int32) error { 145 | return setTtySize(proc.tty.Fd(), row, columns, xpixel, ypixel) 146 | } 147 | 148 | type winsize struct { 149 | ws_row uint16 150 | ws_col uint16 151 | ws_xpixel uint16 152 | ws_ypixel uint16 153 | } 154 | 155 | func setTtySize(fd uintptr, rows, columns, xpixel, ypixel int32) error { 156 | var ws winsize 157 | 158 | _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) 159 | if errno != 0 { 160 | return syscall.Errno(errno) 161 | } 162 | 163 | ws.ws_col = uint16(columns) 164 | ws.ws_row = uint16(rows) 165 | ws.ws_xpixel = uint16(xpixel) 166 | ws.ws_ypixel = uint16(ypixel) 167 | 168 | _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TIOCSWINSZ, uintptr(unsafe.Pointer(&ws))) 169 | if errno != 0 { 170 | return syscall.Errno(errno) 171 | } 172 | return nil 173 | } 174 | 175 | // PROCESS REGISTRY 176 | 177 | type ProcessRegistry struct { 178 | processes []*Process 179 | newProcessChan chan *Process 180 | } 181 | 182 | func NewProcessRegistry() *ProcessRegistry { 183 | prChan := make(chan *Process) 184 | return &ProcessRegistry{processes: []*Process{}, newProcessChan: prChan} 185 | } 186 | 187 | func (pr *ProcessRegistry) AddProcess(proc *Process) { 188 | proc.id = len(pr.processes) 189 | pr.processes = append(pr.processes, proc) 190 | select { 191 | case pr.newProcessChan <- proc: // Notify the new process 192 | default: // Do nothing if full 193 | } 194 | } 195 | 196 | func (pr *ProcessRegistry) GetProcess(id int) *Process { 197 | if id >= 0 && id < len(pr.processes) { 198 | return pr.processes[id] 199 | } 200 | return nil 201 | } 202 | -------------------------------------------------------------------------------- /go/src/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fede1024/goavro" 6 | "log" 7 | "net" 8 | "os" 9 | "reflect" 10 | ) 11 | 12 | var sockPath = "/tmp/mm.sock" 13 | 14 | func processInputMessage(msg *goavro.Record, pReg *ProcessRegistry, conn *Connection) { 15 | messageType, err := msg.Get("messageType") 16 | if err != nil { 17 | fmt.Println(err) 18 | return 19 | } 20 | payload, err := msg.Get("data") 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | dataRecord := payload.(*goavro.Record) 26 | if messageType == "stdin" { 27 | bytesRaw, _ := dataRecord.Get("bytes") 28 | processIdRaw, _ := dataRecord.Get("processId") 29 | processId := int(processIdRaw.(int32)) 30 | 31 | if pReg.GetProcess(processId) == nil { 32 | log.Printf("Process id %d not valid for stdin command\n", processId) 33 | return 34 | } 35 | if pReg.GetProcess(processId).alive { 36 | pReg.GetProcess(processId).stdin <- bytesRaw.([]byte) // TODO: put a select here? 37 | } 38 | } else if messageType == "resize" { 39 | processIdRaw, _ := dataRecord.Get("processId") 40 | processId := int(processIdRaw.(int32)) 41 | 42 | cols, _ := dataRecord.Get("cols") 43 | rows, _ := dataRecord.Get("rows") 44 | xpixel, _ := dataRecord.Get("xpixel") 45 | ypixel, _ := dataRecord.Get("ypixel") 46 | 47 | if pReg.GetProcess(processId) == nil { 48 | log.Printf("Process id %d not valid for resize command\n", processId) 49 | return 50 | } 51 | if pReg.GetProcess(processId).alive { 52 | pReg.GetProcess(processId).SetSize(rows.(int32), cols.(int32), xpixel.(int32), ypixel.(int32)) 53 | } 54 | } else if messageType == "createProcess" { 55 | path, err := dataRecord.Get("path") 56 | cols, _ := dataRecord.Get("cols") 57 | rows, _ := dataRecord.Get("rows") 58 | xpixel, _ := dataRecord.Get("xpixel") 59 | ypixel, _ := dataRecord.Get("ypixel") 60 | 61 | proc, err := NewProcess(path.(string)) 62 | if err != nil { 63 | log.Println("Error while creating process:", err) 64 | os.Exit(1) 65 | } 66 | proc.SetSize(rows.(int32), cols.(int32), xpixel.(int32), ypixel.(int32)) 67 | if err = proc.Start(); err != nil { 68 | log.Println("Error while starting process:", err) 69 | os.Exit(1) 70 | } 71 | pReg.AddProcess(proc) 72 | log.Printf("New process") 73 | 74 | conn.sendChan <- NewProcessCreationMessage(proc.id) // Notifies the sender that the process is up 75 | conn.FollowProcess(proc) // Register the connection as follower 76 | } else { 77 | log.Printf("Unknown message type: %s", messageType) 78 | } 79 | } 80 | 81 | func inputMessagesWorker(pReg *ProcessRegistry, cReg *ConnectionRegistry) { 82 | for { 83 | cases := make([]reflect.SelectCase, 0, len(cReg.connections)+1) 84 | caseToConnection := make([]*Connection, 0, len(cReg.connections)+1) 85 | cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(cReg.newConnectionChan)}) 86 | caseToConnection = append(caseToConnection, nil) 87 | for _, conn := range cReg.connections { 88 | if conn.alive { 89 | cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(conn.recChan)}) 90 | caseToConnection = append(caseToConnection, conn) 91 | } 92 | } 93 | chosen, value, ok := reflect.Select(cases) 94 | if ok == true { 95 | if chosen == 0 { 96 | continue // New connection added 97 | } 98 | processInputMessage(value.Interface().(*goavro.Record), pReg, caseToConnection[chosen]) 99 | } else { 100 | if chosen == 0 { 101 | log.Fatal("This should never happen") 102 | } 103 | log.Println("Connection closed") 104 | } 105 | } 106 | } 107 | 108 | func outputMessagesWorker(pReg *ProcessRegistry, cReg *ConnectionRegistry) { 109 | for { 110 | cases := make([]reflect.SelectCase, 0, len(pReg.processes)+1) 111 | caseToProcess := make([]*Process, 0, len(pReg.processes)+1) 112 | cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(pReg.newProcessChan)}) 113 | caseToProcess = append(caseToProcess, nil) 114 | for _, proc := range pReg.processes { 115 | if proc.alive { 116 | cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(proc.stdout)}) 117 | caseToProcess = append(caseToProcess, proc) 118 | } 119 | } 120 | chosen, value, ok := reflect.Select(cases) 121 | proc := caseToProcess[chosen] 122 | if ok == true { 123 | if chosen == 0 { 124 | continue // New process added 125 | } 126 | for _, conn := range cReg.connections { 127 | if conn.alive && conn.IsFollowing(proc) { 128 | conn.sendChan <- NewOutputMessage(value.Interface().([]byte), proc.id) 129 | } 130 | } 131 | } else { 132 | if chosen == 0 { 133 | log.Fatal("This should never happen") 134 | } 135 | log.Printf("Process %d is dead\n", chosen) 136 | } 137 | } 138 | } 139 | 140 | func main() { 141 | // to change the flags on the default logger 142 | log.SetFlags(log.LstdFlags | log.Lshortfile) 143 | 144 | procRegistry := NewProcessRegistry() 145 | 146 | LoadAllCodecs() 147 | 148 | // Listen for incoming connections. 149 | //listener, err := net.Listen("tcp", "localhost:3333") 150 | listener, err := net.Listen("unix", sockPath) 151 | if err != nil { 152 | fmt.Println("Error listening:", err.Error()) 153 | os.Exit(1) 154 | } 155 | // Close the listener when the application closes. 156 | defer listener.Close() 157 | 158 | connRegistry := NewConnectionRegistry() 159 | 160 | go inputMessagesWorker(procRegistry, connRegistry) 161 | go outputMessagesWorker(procRegistry, connRegistry) 162 | 163 | fmt.Println("Listening on " + sockPath) 164 | for { 165 | conn, err := NewConnection(listener) 166 | if err != nil { 167 | fmt.Println("Error accepting: ", err.Error()) 168 | os.Exit(1) 169 | } 170 | connRegistry.AddConnection(conn) 171 | 172 | log.Printf("Total processes: %d\n", len(procRegistry.processes)) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /clojure/src/multimux/terminal.clj: -------------------------------------------------------------------------------- 1 | (ns multimux.terminal 2 | (:require [clojure.core.async :refer [>!! !! (:keyboard terminal) [:initialize nil]) (when chan true)) 32 | (isConnected [this] 33 | (when chan true)) 34 | (resize [this term-size pixel-size] 35 | (>!! (:keyboard terminal) [:resize [(.width term-size) (.height term-size) 36 | (.width pixel-size) (.height pixel-size)]])) 37 | (read [this buf offset length] 38 | (let [n (.read @input-stream buf offset length)] 39 | (if (= n -1) 40 | (let [stream (message-to-stream (:screen terminal) charset)] 41 | (reset! input-stream stream) 42 | (.read stream buf offset length)) 43 | n))) 44 | (^void write [this ^bytes buf] 45 | (>!! (:keyboard terminal) [:input buf])) 46 | (^void write [this ^String buf] 47 | (>!! (:keyboard terminal) [:input (.getBytes buf charset)])) 48 | (getName [this] "channelTty") 49 | (close [this] nil) ; TODO: fix 50 | (waitFor [this] 1)))) ; TODO: protocol wait? 51 | 52 | (defrecord Terminal [^JediTermWidget widget ^ManyToManyChannel keyboard ^ManyToManyChannel screen 53 | ^int process-id]) 54 | 55 | (defn settings-provider [] 56 | (proxy [DefaultSettingsProvider] [] 57 | (maxRefreshRate [] 50) 58 | (scrollToBottomOnTyping [] true) 59 | (getBufferMaxLinesCount [] 1000) 60 | (useAntialiasing [] true) 61 | (getTerminalFontSize [] 22) 62 | (getDefaultStyle [] 63 | (TextStyle. (TerminalColor. 255 255 255) (TerminalColor. 6 26 39))) 64 | (getTerminalFont [] 65 | (.deriveFont (Font/decode "DejaVu Sans Mono for Powerline") 22.0)))) 66 | 67 | (defn get-scrollbar [term-widget] 68 | (first (filter #(= (type %) javax.swing.JScrollBar) 69 | (.getComponents term-widget)))) 70 | 71 | (defn create-term [columns rows key-listener] 72 | (let [term-widget (JediTermWidget. columns rows (settings-provider)) 73 | screen (chan 100) 74 | keyboard (chan 100) 75 | terminal (->Terminal term-widget keyboard screen -1) 76 | connector (tty-terminal-connector terminal (Charset/forName "UTF-8")) 77 | listener (proxy [TerminalPanel$TerminalKeyHandler] [(.getTerminalPanel term-widget)] 78 | (keyPressed [event] 79 | (when (not (key-listener term-widget event)) 80 | (proxy-super keyPressed event))))] 81 | (.setTtyConnector term-widget connector) 82 | (.setModeEnabled (.getTerminal term-widget) (TerminalMode/CursorBlinking) false) 83 | (.setModeEnabled (.getTerminal term-widget) (TerminalMode/AutoWrap) true) 84 | (.setVisible (get-scrollbar term-widget) false) 85 | ;(.start term-widget) 86 | ;; substitutes JediTermWidget.start() to use setKeyListener 87 | (async/thread 88 | (.setName (Thread/currentThread) (str "Connector-" (.getName connector))) 89 | (when (.init connector nil) 90 | (.setKeyListener (.getTerminalPanel term-widget) listener) 91 | (.start (.getTerminalStarter term-widget)))) 92 | terminal)) 93 | 94 | (defn get-size [term-widget] 95 | (let [panel (.getTerminalPanel (:widget term-widget))] 96 | [(.getColumnCount panel) (.getRowCount panel) (.getPixelWidth panel) (.getPixelHeight panel)])) 97 | 98 | (defn get-cursor-position [term-widget] 99 | (let [term (.getTerminal term-widget)] 100 | [(.getCursorX term) (.getCursorY term)])) 101 | 102 | (defn get-font-size [] 103 | (let [settings (settings-provider) 104 | font (.getTerminalFont settings) 105 | linespace (.getLineSpace settings) 106 | graphics (.createGraphics (BufferedImage. 1 1 BufferedImage/TYPE_INT_RGB)) 107 | metrics (.getFontMetrics graphics font)] 108 | [(.charWidth metrics \W) (+ (.getHeight metrics) (* linespace 2))])) 109 | 110 | (defn split-panel [direction panel old-term-widget new-term-widget] 111 | (let [orientation (if (= direction :vertical) 112 | JSplitPane/HORIZONTAL_SPLIT 113 | JSplitPane/VERTICAL_SPLIT) 114 | [w h] (get-font-size) 115 | split (JMultimuxSplit. orientation old-term-widget new-term-widget w h)] 116 | (.remove panel old-term-widget) 117 | (.add panel split) 118 | (.setResizeWeight split 0.5) 119 | (.revalidate panel))) 120 | 121 | (defn split-splitpane [direction splitpane old-term-widget new-term-widget] 122 | (let [orientation (if (= direction :vertical) 123 | JSplitPane/HORIZONTAL_SPLIT 124 | JSplitPane/VERTICAL_SPLIT) 125 | [w h] (get-font-size) 126 | new-split (JMultimuxSplit. orientation old-term-widget new-term-widget w h)] 127 | (.remove splitpane old-term-widget) 128 | (if (= (.getOrientation splitpane) JSplitPane/HORIZONTAL_SPLIT) 129 | (if (.getLeftComponent splitpane) 130 | (.setRightComponent splitpane new-split) 131 | (.setLeftComponent splitpane new-split)) 132 | (if (.getTopComponent splitpane) 133 | (.setBottomComponent splitpane new-split) 134 | (.setTopComponent splitpane new-split))) 135 | (.setResizeWeight new-split 0.5))) 136 | 137 | ; (defn split [direction new-term] 138 | ; {:pre [(direction #{:vertical :horizontal})]} 139 | ; (if (not @frame) 140 | ; (log/warn "No frame, nothing to split") 141 | ; (let [term-panel (get-focused-term-panel @frame) 142 | ; term-widget (.getParent term-panel) 143 | ; container (.getParent term-widget)] 144 | ; (condp = (type container) 145 | ; javax.swing.JPanel (split-panel direction container term-widget new-term) 146 | ; javax.swing.JSplitPane (split-splitpane direction container term-widget new-term)) 147 | ; (.requestFocus (.getTerminalPanel new-term))))) 148 | 149 | (defn split [term-widget direction new-terminal] 150 | {:pre [(direction #{:vertical :horizontal})]} 151 | (let [term-panel (.getTerminalPanel term-widget) 152 | container (.getParent term-widget)] 153 | (condp = (type container) 154 | javax.swing.JPanel (split-panel direction container term-widget (:widget new-terminal)) 155 | ;javax.swing.JSplitPane (split-splitpane direction container term-widget (:widget new-terminal)) 156 | JMultimuxSplit (split-splitpane direction container term-widget (:widget new-terminal))) 157 | (.requestFocus (.getTerminalPanel (:widget new-terminal)))) 158 | new-terminal) 159 | 160 | (defn give-focus-to-term [component] 161 | (condp = (type component) 162 | JediTermWidget (.requestFocus (.getTerminalPanel component)) 163 | JMultimuxSplit (give-focus-to-term (or (.getLeftComponent component) (.getTopComponent component))) 164 | (log/warn "I don't know how to give focus to a" (type component)))) 165 | 166 | (defn destroy-in-split [split term-widget] 167 | (.remove split term-widget) 168 | (let [split-container (.getParent split) 169 | other-term (or (.getLeftComponent split) (.getRightComponent split) 170 | (.getTopComponent split) (.getBottomComponent split))] 171 | (.remove split-container split) 172 | (condp = (type split-container) 173 | javax.swing.JPanel (do (.remove split-container split) 174 | (.add split-container other-term)) 175 | JMultimuxSplit (if (= (.getOrientation split-container) JSplitPane/HORIZONTAL_SPLIT) 176 | (if (.getLeftComponent split-container) 177 | (.setRightComponent split-container other-term) 178 | (.setLeftComponent split-container other-term)) 179 | (if (.getTopComponent split-container) 180 | (.setBottomComponent split-container other-term) 181 | (.setTopComponent split-container other-term)))) 182 | (.revalidate split-container) 183 | (give-focus-to-term other-term))) 184 | 185 | (defn destroy [term-widget] 186 | (let [container (.getParent term-widget)] 187 | (condp = (type container) 188 | javax.swing.JPanel false 189 | JMultimuxSplit (do (destroy-in-split container term-widget) true)))) 190 | 191 | 192 | ;; Term register 193 | (defrecord TermRegister [terminals followers]) 194 | 195 | (defn create-term-register [] 196 | (->TermRegister {} {})) 197 | 198 | (defn add-to-register [register terminal] 199 | (assoc-in register [:terminals (:keyboard terminal)] terminal)) 200 | 201 | (defn remove-from-register [register terminal] 202 | (update-in register [:terminals] dissoc (:keyboard terminal))) 203 | 204 | (defn register-follow-process [register terminal process-id] 205 | (let [term (assoc terminal :process-id process-id)] 206 | (-> register 207 | (update-in [:followers process-id] #(if % (conj % term) #{term})) 208 | (assoc-in [:terminals (:keyboard terminal)] term)))) 209 | 210 | (defn widget-to-term 211 | "Performs a linear search in the tem register and return the terminal associated with 212 | the terminal panel" 213 | [register widget] 214 | (first (filter #(= (:widget %) widget) (vals (:terminals register))))) 215 | -------------------------------------------------------------------------------- /clojure/src/multimux/core.clj: -------------------------------------------------------------------------------- 1 | (ns multimux.core 2 | (:gen-class) 3 | (:require [multimux.terminal :as term] 4 | [multimux.serialize :as ser] 5 | [multimux.connection :as conn] 6 | [taoensso.timbre :as log] 7 | [clojure.string :as str] 8 | [clojure.core.async :refer [>!! [] - 29 | (format "%s %s [%s] - %s%s" 30 | timestamp (-> level name str/upper-case) ns (or message "") 31 | (or (log/stacktrace throwable "\n" (when nofonts? {})) "")))})) 32 | 33 | (defn incoming-message-handler [message chan term-register] 34 | (condp = (:message-type message) 35 | :stdout (doseq [term (get-in @term-register [:followers (:process-id message)])] 36 | (>!! (:screen term) (:bytes message))) 37 | :createProcess (let [term (first (filter #(= (:process-id %) -1) (vals (:terminals @term-register))))] 38 | (if term 39 | (dosync (alter term-register term/register-follow-process term (:process-id message))) 40 | (log/error "Received process creation feedback, but no unattached terminal is present"))) 41 | (log/error "Unknown message type" (:message-type message)))) 42 | 43 | (defn term-write-handler [[input-type payload] keyboard-chan term-register msg-write-handler] 44 | (let [term (get (:terminals @term-register) keyboard-chan)] 45 | (if (>= (:process-id term) 0) 46 | (let [message (condp = input-type 47 | :input (ser/create-stdin-message (:process-id term) payload) 48 | :resize (ser/create-resize-message (:process-id term) payload) 49 | :initialize (log/error "Terminal is already initialized"))] 50 | (when message (>!! msg-write-handler message))) 51 | (if (= input-type :initialize) 52 | (>!! msg-write-handler (apply ser/create-create-process-message (term/get-size term))) 53 | (log/warn "No process id associated to message" input-type))))) 54 | 55 | (defn message-handler [msg-read-chan msg-write-chan term-register] 56 | (async/thread 57 | (loop [] 58 | (let [[data chan] (alts!! (conj (keys (:terminals @term-register)) msg-read-chan))] 59 | (if (= chan msg-read-chan) 60 | (incoming-message-handler (ser/decode-message data) chan term-register) 61 | (term-write-handler data chan term-register msg-write-chan))) 62 | (recur)))) 63 | 64 | ; (defn get-focused-term-panel [frame] 65 | ; (let [component (.getMostRecentFocusOwner frame)] 66 | ; (if (= (type component) com.jediterm.terminal.ui.TerminalPanel) 67 | ; component 68 | ; (log/warn "Focused object is not a terminal")))) 69 | 70 | ; (defn get-term-container [term] 71 | ; (condp = (type term) 72 | ; com.jediterm.terminal.ui.TerminalPanel (.getParent (.getParent term)) 73 | ; com.jediterm.terminal.ui.JediTermWidget (.getParent term) 74 | ; (log/warn "get-term-container of" (type term)))) 75 | 76 | ; (defn resize-handler [] 77 | ; (proxy [JediTerminal$ResizeHandler] [] 78 | ; (sizeUpdated [width height cursorY] 79 | ; (log/info "Terminal resized" width height cursorY)))) 80 | 81 | 82 | (def ^:dynamic *term-register* (ref nil)) 83 | 84 | (defn close-jframe [frame] 85 | (.dispatchEvent frame (WindowEvent. frame WindowEvent/WINDOW_CLOSING))) 86 | 87 | (defn create-terminal-and-process [columns rows key-listener] 88 | (let [terminal (term/create-term columns rows key-listener)] 89 | (dosync (alter *term-register* term/add-to-register terminal)) 90 | terminal)) 91 | 92 | (defn get-component-win-coordinates 93 | "Returns the 4 coordinates of a component relative to the window" 94 | ([component] 95 | (get-component-win-coordinates component (SwingUtilities/getWindowAncestor component))) 96 | ([component window] 97 | (let [xy (SwingUtilities/convertPoint component 0 0 window) 98 | x (.x xy) y (.y xy)] 99 | [x y (+ x (.getWidth component)) (+ y (.getHeight component))]))) 100 | 101 | (defn get-term-win-coordinates-with-borders [term] 102 | (let [[width height] (term/get-font-size)] 103 | (map (fn [[term [x y xe ye]]] 104 | [term [x y (+ xe width) (+ ye height)]])))) 105 | 106 | (defn get-cursor-win-coordinater [term-widget] 107 | (let [[width height] (term/get-font-size) 108 | [c r] (term/get-cursor-position term-widget) 109 | window (SwingUtilities/getWindowAncestor term-widget) 110 | xy (SwingUtilities/convertPoint term-widget (* c width) (* r height) window) 111 | x (.x xy) y (.y xy)] 112 | [x y])) 113 | 114 | (defn get-terminal-coordinates [register] 115 | (let [[width height] (term/get-font-size)] 116 | (map (fn [term] 117 | (let [[x y xe ye] (get-component-win-coordinates (:widget term))] 118 | ;;[term [x y (+ xe width) (+ ye width)]] 119 | [term [x y xe ye]])) 120 | (vals (:terminals register))))) 121 | 122 | (defmulti find-terminal 123 | "Given a current term-widget, the terminal registry and a direction, finds the closest terminal 124 | in that direction" 125 | (fn [register term-widget direction] direction)) 126 | 127 | (defmethod find-terminal :left [register term-widget _] 128 | (let [[cx cy] (get-cursor-win-coordinater term-widget) 129 | coords (get-terminal-coordinates register)] 130 | (first 131 | (last ;; The closest one 132 | (sort-by (fn [[term [x y xe ye]]] x) 133 | (filter (fn [[term [x y xe ye]]] 134 | (and (< xe cx) (<= y cy) (>= ye cy))) 135 | coords)))))) 136 | 137 | (defmethod find-terminal :right [register term-widget _] 138 | (let [[cx cy] (get-cursor-win-coordinater term-widget) 139 | coords (get-terminal-coordinates register)] 140 | (first 141 | (first ;; The closest one 142 | (sort-by (fn [[term [x y xe ye]]] x) 143 | (filter (fn [[term [x y xe ye]]] 144 | (and (> x cx) (<= y cy) (>= ye cy))) 145 | coords)))))) 146 | 147 | (defmethod find-terminal :up [register term-widget _] 148 | (let [[cx cy] (get-cursor-win-coordinater term-widget) 149 | coords (get-terminal-coordinates register)] 150 | (first 151 | (last ;; The closest one 152 | (sort-by (fn [[term [x y xe ye]]] y) 153 | (filter (fn [[term [x y xe ye]]] 154 | (and (< ye cy) (<= x cx) (>= xe cx))) 155 | coords)))))) 156 | 157 | (defmethod find-terminal :down [register term-widget _] 158 | (let [[cx cy] (get-cursor-win-coordinater term-widget) 159 | coords (get-terminal-coordinates register)] 160 | (first 161 | (first ;; The closest one 162 | (sort-by (fn [[term [x y xe ye]]] y) 163 | (filter (fn [[term [x y xe ye]]] 164 | (and (> y cy) (<= x cx) (>= xe cx))) 165 | coords)))))) 166 | 167 | (defn term-key-listener [term-widget event] 168 | (let [keyCode (.getKeyCode event) 169 | close-frame-for-widget #(close-jframe (SwingUtilities/getWindowAncestor term-widget)) 170 | switch-term #(let [term (find-terminal @*term-register* term-widget %)] 171 | (when term (.requestFocus (.getTerminalPanel (:widget term)))) 172 | true)] 173 | (when (.isAltDown event) 174 | (condp = keyCode 175 | KeyEvent/VK_S (term/split term-widget :horizontal (create-terminal-and-process 80 24 term-key-listener)) 176 | KeyEvent/VK_V (term/split term-widget :vertical (create-terminal-and-process 80 24 term-key-listener)) 177 | KeyEvent/VK_Q (close-frame-for-widget) 178 | KeyEvent/VK_D (do (when (not (term/destroy term-widget)) 179 | (close-frame-for-widget)) 180 | ;(term/remove-term-from-register register term-widget) 181 | (dosync (alter *term-register* term/remove-from-register 182 | (term/widget-to-term @*term-register* term-widget))) 183 | true) 184 | KeyEvent/VK_H (switch-term :left) 185 | KeyEvent/VK_L (switch-term :right) 186 | KeyEvent/VK_K (switch-term :up) 187 | KeyEvent/VK_J (switch-term :down) 188 | nil)))) 189 | 190 | (defn create-and-show-frame [title on-close] 191 | (let [newFrame (JFrame. title) 192 | terminal (create-terminal-and-process 75 28 term-key-listener)] 193 | (doto newFrame 194 | (.add (:widget terminal)) 195 | ;(.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) 196 | (.addWindowListener 197 | (proxy [WindowListener] [] 198 | (windowOpened [evt]) 199 | (windowActivated [evt]) 200 | (windowDeactivated [evt]) 201 | (windowClosing [evt] 202 | (on-close) 203 | (log/info "GUI closed")))) 204 | (.setSize 1000 800) 205 | (.pack) 206 | (.setVisible true)))) 207 | 208 | (defn -main [& args] 209 | (BasicConfigurator/configure) 210 | (.setLevel (Logger/getRootLogger) (Level/INFO)) 211 | (configure-logger!) 212 | (.put (UIManager/getDefaults) "SplitPane.border", (BorderFactory/createEmptyBorder)) 213 | (UIManager/put "SplitDivider.background", (Color. 6 26 39)) 214 | (UIManager/put "SplitDivider.foreground", (Color. 96 109 117)) 215 | ;(UIManager/setLookAndFeel (UIManager/getSystemLookAndFeelClassName)) 216 | (if (not (System/getenv "TEST_PWD")) 217 | (log/error "Missing password") 218 | (if-let [;connection (conn/open (conn/create-socket-connection "localhost" 3333)) 219 | connection (conn/open (conn/create-ssh-unix-connection "fede" (System/getenv "TEST_PWD") "localhost"))] 220 | ;; TODO: protocol connection check, echo? 221 | (let [msg-read-chan (chan 100) 222 | msg-write-chan (chan 100)] 223 | (dosync (ref-set *term-register* (term/create-term-register))) 224 | (create-and-show-frame "Multimux" #(when connection (conn/close connection))) 225 | (log/info "GUI started") 226 | (ser/message-to-connection-worker connection msg-read-chan msg-write-chan) 227 | (message-handler msg-read-chan msg-write-chan *term-register*)) 228 | (log/error "Connection not established")))) 229 | -------------------------------------------------------------------------------- /clojure/LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | --------------------------------------------------------------------------------