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 (
+ }
+ onClick={() => {
+ tableRef.current.reload();
+ }}
+ />
)
}
diff --git a/web/src/components/terminal.js b/web/src/components/terminal.js
index eaecb2f..a2b4dfa 100644
--- a/web/src/components/terminal.js
+++ b/web/src/components/terminal.js
@@ -28,14 +28,14 @@ class TerminalModal extends React.Component {
cursorStyle: "block",
fontFamily: "Hack, monospace",
fontSize: 16,
- logLevel: process.env.NODE_ENV === "development" ? "info" : "off",
+ logLevel: "off",
});
this.doResize.call(this);
}
initialize(ev) {
ev?.dispose();
- let buffer = { content: '' };
+ let buffer = { content: '', output: '' };
let termEv = null;
// Windows doesn't support pty, so we still use traditional way.
// And we need to handle arrow events manually.
@@ -58,11 +58,19 @@ class TerminalModal extends React.Component {
if (this.conn) {
if (data?.act === 'outputTerminal') {
data = ab2str(hex2buf(data?.data?.output));
+ if (buffer.output.length > 0) {
+ data = buffer.output + data;
+ buffer.output = '';
+ }
if (buffer.content.length > 0) {
- // check if data starts with buffer, if so, remove buffer.
- if (data.startsWith(buffer.content)) {
- data = data.substring(buffer.content.length);
- buffer.content = '';
+ if (data.length > buffer.content.length) {
+ if (data.startsWith(buffer.content)) {
+ data = data.substring(buffer.content.length);
+ buffer.content = '';
+ }
+ } else {
+ buffer.output = data;
+ return;
}
}
this.term.write(data);
@@ -110,7 +118,7 @@ class TerminalModal extends React.Component {
if (!this.conn) {
if (e === '\r' || e === '\n' || e === ' ') {
this.term.write(`\n${i18n.t('reconnecting')}\n`);
- this.termEv = this.initialize(termEv);
+ this.termEv = this.initialize(this.termEv);
}
return;
}
@@ -158,12 +166,17 @@ class TerminalModal extends React.Component {
cursor--;
}
break;
- case '\n':
case '\r':
- this.term.write('\n');
- this.sendInput(cmd + '\n');
+ case '\n':
+ if (cmd === 'clear' || cmd === 'cls') {
+ clearTerm.call(this);
+ this.term.clear();
+ } else {
+ this.term.write('\n');
+ this.sendInput(cmd + '\n');
+ buffer.content = cmd + '\n';
+ }
if (cmd.length > 0) history.push(cmd);
- buffer.content = cmd + '\n';
cursor = 0;
cmd = '';
if (history.length > 128) {
@@ -173,20 +186,13 @@ class TerminalModal extends React.Component {
tempCursor = 0;
index = history.length;
break;
- case '\u0003':
- this.term.write('^C');
- this.sendInput('\u0003');
- cursor = 0;
- cmd = '';
- break;
- case '\u007F':
+ case '\u007F': // backspace.
if (cmd.length > 0 && cursor > 0) {
cursor--;
let charWidth = wcwidth(cmd[cursor]);
let before = cmd.substring(0, cursor);
let after = cmd.substring(cursor+1);
cmd = before + after;
-
this.term.write('\b'.repeat(charWidth));
this.term.write(after + ' '.repeat(charWidth));
this.term.write('\u001b\u005b\u0044'.repeat(wcwidth(after) + charWidth));
@@ -205,17 +211,16 @@ class TerminalModal extends React.Component {
this.term.write(e);
}
cursor += e.length;
- return;
}
}
}.bind(this);
}
- onUnixOSInput(buffer) {
+ onUnixOSInput(_) {
return function (e) {
if (!this.conn) {
if (e === '\r' || e === ' ') {
this.term.write(`\n${i18n.t('reconnecting')}\n`);
- this.termEv = this.initialize(termEv);
+ this.termEv = this.initialize(this.termEv);
}
return;
}
@@ -327,6 +332,7 @@ class TerminalModal extends React.Component {
render() {
return (