From f69822b3338b760412ba1c7ef9296e9ffe854f6a Mon Sep 17 00:00:00 2001 From: XZB-1248 Date: Tue, 20 Sep 2022 17:41:13 +0800 Subject: [PATCH] optimize: terminal for Windows --- CHANGELOG.md | 4 +- client/common/common.go | 2 +- client/core/core.go | 2 +- client/core/handler.go | 6 +- client/service/desktop/desktop.go | 8 +-- client/service/terminal/terminal_others.go | 4 +- client/service/terminal/terminal_windows.go | 75 +++++++-------------- server/common/common.go | 2 +- server/handler/bridge/bridge.go | 10 +-- server/handler/utility/utility.go | 8 +-- server/main.go | 2 +- web/src/components/desktop.css | 10 --- web/src/components/desktop.js | 3 +- web/src/components/explorer.js | 4 +- web/src/components/modal.js | 4 +- web/src/components/procmgr.js | 18 ++++- web/src/components/terminal.js | 50 ++++++++------ web/src/components/wrapper.css | 12 ++++ 18 files changed, 110 insertions(+), 114 deletions(-) delete mode 100644 web/src/components/desktop.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 501973b..56368fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ ## v0.1.5 * Optimize: performance of desktop viewer on Windows. +* Optimize: terminal for Windows. * Remove: deprecated ioutil package. * 优化:Windows下的远程桌面性能表现。 +* 优化:Windows的终端表现。 * 移除:已经废弃的ioutil包。 @@ -112,7 +114,7 @@ * 新增:服务端和客户端已支持macOS系统。 * 新增:macOS现在将支持关机和重启功能(需要root权限)。 * 更新:类unix系统的终端现已改用pty实现,以提供完整的终端功能。 -* 更新:改进了windows下的终端表现,修复了一些bug。 +* 更新:改进了Windows下的终端表现,修复了一些bug。 diff --git a/client/common/common.go b/client/common/common.go index 696bfb3..5568c8c 100644 --- a/client/common/common.go +++ b/client/common/common.go @@ -47,7 +47,7 @@ func (wsConn *Conn) SendData(data []byte) error { return wsConn.WriteMessage(ws.BinaryMessage, data) } -func (wsConn *Conn) SendPack(pack interface{}) error { +func (wsConn *Conn) SendPack(pack any) error { Mutex.Lock() defer Mutex.Unlock() data, err := utils.JSON.Marshal(pack) diff --git a/client/core/core.go b/client/core/core.go index 6d9d32d..6900a3b 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -19,7 +19,7 @@ import ( ) // simplified type of map -type smap map[string]interface{} +type smap map[string]any var stop bool var ( diff --git a/client/core/handler.go b/client/core/handler.go index 8a7e225..928a377 100644 --- a/client/core/handler.go +++ b/client/core/handler.go @@ -195,7 +195,7 @@ func removeFiles(pack modules.Packet, wsConn *common.Conn) { wsConn.SendCallback(modules.Packet{Code: 1, Msg: `${i18n|fileOrDirNotExist}`}, pack) return } else { - slice := val.([]interface{}) + slice := val.([]any) for i := 0; i < len(slice); i++ { file, ok := slice[i].(string) if ok { @@ -225,7 +225,7 @@ func uploadFiles(pack modules.Packet, wsConn *common.Conn) { wsConn.SendCallback(modules.Packet{Code: 1, Msg: `${i18n|fileOrDirNotExist}`}, pack) return } else { - slice := val.([]interface{}) + slice := val.([]any) for i := 0; i < len(slice); i++ { file, ok := slice[i].(string) if ok { @@ -291,7 +291,7 @@ func listProcesses(pack modules.Packet, wsConn *common.Conn) { if err != nil { wsConn.SendCallback(modules.Packet{Code: 1, Msg: err.Error()}, pack) } else { - wsConn.SendCallback(modules.Packet{Code: 0, Data: map[string]interface{}{`processes`: processes}}, pack) + wsConn.SendCallback(modules.Packet{Code: 0, Data: map[string]any{`processes`: processes}}, pack) } } diff --git a/client/service/desktop/desktop.go b/client/service/desktop/desktop.go index d060a9c..a7e4616 100644 --- a/client/service/desktop/desktop.go +++ b/client/service/desktop/desktop.go @@ -62,7 +62,7 @@ var lock = &sync.Mutex{} var working = false var sessions = cmap.New() var prevDesktop *image.RGBA -var ErrNoImage = errors.New("no image yet") +var ErrNoImage = errors.New(`no image yet`) func init() { go healthCheck() @@ -108,7 +108,7 @@ func worker() { diff := imageCompare(img, prevDesktop, compress) if diff != nil && len(diff) > 0 { prevDesktop = img - sessions.IterCb(func(uuid string, t interface{}) bool { + sessions.IterCb(func(uuid string, t any) bool { desktop := t.(*session) desktop.lock.Lock() if !desktop.escape { @@ -134,7 +134,7 @@ func worker() { func quitAll(info string) { keys := make([]string, 0) - sessions.IterCb(func(uuid string, t interface{}) bool { + sessions.IterCb(func(uuid string, t any) bool { keys = append(keys, uuid) desktop := t.(*session) desktop.escape = true @@ -433,7 +433,7 @@ func healthCheck() { timestamp := now.Unix() // stores sessions to be disconnected keys := make([]string, 0) - sessions.IterCb(func(uuid string, t interface{}) bool { + sessions.IterCb(func(uuid string, t any) bool { desktop := t.(*session) if timestamp-desktop.lastPack > MaxInterval { keys = append(keys, uuid) diff --git a/client/service/terminal/terminal_others.go b/client/service/terminal/terminal_others.go index 9d8204b..a865fc7 100644 --- a/client/service/terminal/terminal_others.go +++ b/client/service/terminal/terminal_others.go @@ -41,7 +41,7 @@ func InitTerminal(pack modules.Packet) error { buffer := make([]byte, 512) n, err := ptySession.Read(buffer) buffer = buffer[:n] - common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{ + common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{ `output`: hex.EncodeToString(buffer), }}, pack) termSession.lastPack = common.Unix @@ -167,7 +167,7 @@ func healthCheck() { timestamp := now.Unix() // stores sessions to be disconnected queue := make([]string, 0) - terminals.IterCb(func(uuid string, t interface{}) bool { + terminals.IterCb(func(uuid string, t any) bool { termSession := t.(*terminal) if timestamp-termSession.lastPack > MaxInterval { queue = append(queue, uuid) diff --git a/client/service/terminal/terminal_windows.go b/client/service/terminal/terminal_windows.go index f28baf4..d6f4b79 100644 --- a/client/service/terminal/terminal_windows.go +++ b/client/service/terminal/terminal_windows.go @@ -3,17 +3,12 @@ package terminal import ( "Spark/client/common" "Spark/modules" - "bytes" "encoding/hex" "io" - "os" "os/exec" "reflect" - "runtime" + "syscall" "time" - - "golang.org/x/text/encoding/simplifiedchinese" - "golang.org/x/text/transform" ) type terminal struct { @@ -25,7 +20,13 @@ type terminal struct { stdin *io.WriteCloser } +var defaultCmd = `` + func init() { + defer func() { + recover() + }() + syscall.NewLazyDLL(`kernel32.dll`).NewProc(`SetConsoleCP`).Call(65001) go healthCheck() } @@ -34,16 +35,19 @@ func InitTerminal(pack modules.Packet) error { stdout, err := cmd.StdoutPipe() if err != nil { cmd.Process.Kill() + cmd.Process.Release() return err } stderr, err := cmd.StderrPipe() if err != nil { cmd.Process.Kill() + cmd.Process.Release() return err } stdin, err := cmd.StdinPipe() if err != nil { cmd.Process.Kill() + cmd.Process.Release() return err } termSession := &terminal{ @@ -62,13 +66,7 @@ func InitTerminal(pack modules.Packet) error { n, err := rc.Read(buffer) buffer = buffer[:n] - // clear screen - if len(buffer) == 1 && buffer[0] == 12 { - buffer = []byte{27, 91, 72, 27, 91, 50, 74} - } - - buffer, _ = encodeUTF8(buffer) - common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{ + common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{ `output`: hex.EncodeToString(buffer), }}, pack) termSession.lastPack = common.Unix @@ -106,11 +104,6 @@ func InputTerminal(pack modules.Packet) error { return nil } terminal := val.(*terminal) - if len(data) == 1 && data[0] == '\x03' { - terminal.cmd.Process.Signal(os.Interrupt) - return nil - } - data, _ = decodeUTF8(data) (*terminal.stdin).Write(data) terminal.lastPack = common.Unix return nil @@ -159,45 +152,25 @@ func doKillTerminal(terminal *terminal) { (*terminal.stdin).Close() if terminal.cmd.Process != nil { terminal.cmd.Process.Kill() + terminal.cmd.Process.Release() } } func getTerminal() string { - return `cmd.exe` -} - -func encodeUTF8(s []byte) ([]byte, error) { - if runtime.GOOS == `windows` { - return gbkToUtf8(s) - } else { - return s, nil + var cmdTable = []string{ + `powershell.exe`, + `cmd.exe`, } -} - -func decodeUTF8(s []byte) ([]byte, error) { - if runtime.GOOS == `windows` { - return utf8ToGbk(s) - } else { - return s, nil - } -} - -func gbkToUtf8(s []byte) ([]byte, error) { - reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GB18030.NewDecoder()) - d, e := io.ReadAll(reader) - if e != nil { - return nil, e + if defaultCmd != `` { + return defaultCmd } - return d, nil -} - -func utf8ToGbk(s []byte) ([]byte, error) { - reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GB18030.NewEncoder()) - d, e := io.ReadAll(reader) - if e != nil { - return nil, e + for _, cmd := range cmdTable { + if _, err := exec.LookPath(cmd); err == nil { + defaultCmd = cmd + return cmd + } } - return d, nil + return `cmd.exe` } func healthCheck() { @@ -206,7 +179,7 @@ func healthCheck() { timestamp := now.Unix() // stores sessions to be disconnected keys := make([]string, 0) - terminals.IterCb(func(uuid string, t interface{}) bool { + terminals.IterCb(func(uuid string, t any) bool { termSession := t.(*terminal) if timestamp-termSession.lastPack > MaxInterval { keys = append(keys, uuid) diff --git a/server/common/common.go b/server/common/common.go index 29e271b..0f8268f 100644 --- a/server/common/common.go +++ b/server/common/common.go @@ -141,7 +141,7 @@ func CheckDevice(deviceID, connUUID string) (string, bool) { } } else { tempConnUUID := `` - Devices.IterCb(func(uuid string, v interface{}) bool { + Devices.IterCb(func(uuid string, v any) bool { device := v.(*modules.Device) if device.ID == deviceID { tempConnUUID = uuid diff --git a/server/handler/bridge/bridge.go b/server/handler/bridge/bridge.go index f4af825..04a735a 100644 --- a/server/handler/bridge/bridge.go +++ b/server/handler/bridge/bridge.go @@ -23,7 +23,7 @@ type Bridge struct { lock *sync.Mutex Dst *gin.Context Src *gin.Context - ext interface{} + ext any OnPull func(bridge *Bridge) OnPush func(bridge *Bridge) OnFinish func(bridge *Bridge) @@ -36,7 +36,7 @@ func init() { for now := range time.NewTicker(15 * time.Second).C { var queue []string timestamp := now.Unix() - bridges.IterCb(func(k string, v interface{}) bool { + bridges.IterCb(func(k string, v any) bool { b := v.(*Bridge) if timestamp-b.creation > 60 && !b.using { b.lock.Lock() @@ -181,7 +181,7 @@ func BridgePull(ctx *gin.Context) { } } -func AddBridge(ext interface{}, uuid string) *Bridge { +func AddBridge(ext any, uuid string) *Bridge { bridge := &Bridge{ creation: common.Unix, uuid: uuid, @@ -193,7 +193,7 @@ func AddBridge(ext interface{}, uuid string) *Bridge { return bridge } -func AddBridgeWithSrc(ext interface{}, uuid string, Src *gin.Context) *Bridge { +func AddBridgeWithSrc(ext any, uuid string, Src *gin.Context) *Bridge { bridge := &Bridge{ creation: common.Unix, uuid: uuid, @@ -206,7 +206,7 @@ func AddBridgeWithSrc(ext interface{}, uuid string, Src *gin.Context) *Bridge { return bridge } -func AddBridgeWithDst(ext interface{}, uuid string, Dst *gin.Context) *Bridge { +func AddBridgeWithDst(ext any, uuid string, Dst *gin.Context) *Bridge { bridge := &Bridge{ creation: common.Unix, uuid: uuid, diff --git a/server/handler/utility/utility.go b/server/handler/utility/utility.go index c225622..7beeca6 100644 --- a/server/handler/utility/utility.go +++ b/server/handler/utility/utility.go @@ -22,7 +22,7 @@ type Sender func(pack modules.Packet, session *melody.Session) bool // CheckForm checks if the form contains the required fields. // Every request must contain connection UUID or device ID. -func CheckForm(ctx *gin.Context, form interface{}) (string, bool) { +func CheckForm(ctx *gin.Context, form any) (string, bool) { var base struct { Conn string `json:"uuid" yaml:"uuid" form:"uuid"` Device string `json:"device" yaml:"device" form:"device"` @@ -71,7 +71,7 @@ func OnDevicePack(data []byte, session *melody.Session) error { // If so, then find the session and let client quit. // This will keep only one connection remained per device. exSession := `` - common.Devices.IterCb(func(uuid string, v interface{}) bool { + common.Devices.IterCb(func(uuid string, v any) bool { device := v.(*modules.Device) if device.ID == pack.Device.ID { exSession = uuid @@ -173,8 +173,8 @@ func CheckUpdate(ctx *gin.Context) { // GetDevices will return all info about all clients. func GetDevices(ctx *gin.Context) { - devices := map[string]interface{}{} - common.Devices.IterCb(func(uuid string, v interface{}) bool { + devices := map[string]any{} + common.Devices.IterCb(func(uuid string, v any) bool { device := v.(*modules.Device) devices[uuid] = *device return true diff --git a/server/main.go b/server/main.go index 0644b9e..a1aa7ef 100644 --- a/server/main.go +++ b/server/main.go @@ -293,7 +293,7 @@ func authCheck() gin.HandlerFunc { go func() { for now := range time.NewTicker(60 * time.Second).C { var queue []string - tokens.IterCb(func(key string, v interface{}) bool { + tokens.IterCb(func(key string, v any) bool { if now.Unix()-v.(int64) > 1800 { queue = append(queue, key) } diff --git a/web/src/components/desktop.css b/web/src/components/desktop.css deleted file mode 100644 index afddee9..0000000 --- a/web/src/components/desktop.css +++ /dev/null @@ -1,10 +0,0 @@ -.header-button { - background: transparent; - position: absolute; - fontWeight: 700; - cursor: pointer; - border: none; - top: 3px; - height: 50px; - width: 50px; -} \ No newline at end of file diff --git a/web/src/components/desktop.js b/web/src/components/desktop.js index a8436da..67fdce1 100644 --- a/web/src/components/desktop.js +++ b/web/src/components/desktop.js @@ -5,7 +5,6 @@ import DraggableModal from "./modal"; import CryptoJS from "crypto-js"; import {Button, message} from "antd"; import {FullscreenOutlined, ReloadOutlined} from "@ant-design/icons"; -import "./desktop.css"; let ws = null; let ctx = null; @@ -74,8 +73,8 @@ function ScreenModal(props) { clearInterval(ticker); ticker = setInterval(() => { setBandwidth(bytes); - bytes = 0; setFps(frames); + bytes = 0; frames = 0; ticks++; if (ticks > 10 && conn) { diff --git a/web/src/components/explorer.js b/web/src/components/explorer.js index bbbd4e9..ec33af7 100644 --- a/web/src/components/explorer.js +++ b/web/src/components/explorer.js @@ -20,9 +20,9 @@ import i18n from "../locale/locale"; import {VList} from "virtuallist-antd"; import { CloseOutlined, - ExclamationCircleOutlined, HomeOutlined, - LoadingOutlined, QuestionCircleOutlined, + LoadingOutlined, + QuestionCircleOutlined, ReloadOutlined, UploadOutlined } from "@ant-design/icons"; diff --git a/web/src/components/modal.js b/web/src/components/modal.js index 43985bb..035acc6 100644 --- a/web/src/components/modal.js +++ b/web/src/components/modal.js @@ -42,7 +42,9 @@ function DraggableModal(props) { setDisabled(true); }} onFocus={() => {}} - onBlur={() => {}} + onBlur={() => { + setDisabled(true); + }} > {props.modalTitle} diff --git a/web/src/components/procmgr.js b/web/src/components/procmgr.js index b516804..4dbda5d 100644 --- a/web/src/components/procmgr.js +++ b/web/src/components/procmgr.js @@ -1,10 +1,11 @@ import React, {useEffect, useMemo, useRef, useState} from 'react'; -import {message, Popconfirm} from "antd"; +import {Button, message, Popconfirm} from "antd"; import ProTable from '@ant-design/pro-table'; import {request, waitTime} from "../utils/utils"; import i18n from "../locale/locale"; import {VList} from "virtuallist-antd"; import DraggableModal from "./modal"; +import {ReloadOutlined} from "@ant-design/icons"; function ProcessMgr(props) { const [loading, setLoading] = useState(false); @@ -35,6 +36,7 @@ function ProcessMgr(props) { ]; const options = { show: true, + reload: false, density: false, setting: false, }; @@ -90,6 +92,7 @@ function ProcessMgr(props) { return ( +