Skip to content

Commit

Permalink
optimize: terminal for windows client
Browse files Browse the repository at this point in the history
  • Loading branch information
XZB-1248 committed Jul 9, 2022
1 parent 5558508 commit aadf368
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 67 deletions.
4 changes: 2 additions & 2 deletions README.ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@
| 屏幕快照 | ✔ | ✔ | ✔ |
| * 关机 | ✔ | ✔ | ✔ |
| * 重启 | ✔ | ✔ | ✔ |
| * 注销 | ✔ | ❌ | ✔ |
| * 睡眠 | ✔ | ❌ | ✔ |
| * 休眠 | ✔ | ❌ | ❌ |
| * 睡眠 | ✔ | ❌ | ❌ |
| * 注销 | ✔ | ❌ | ❌ |
| * 锁屏 | ✔ | ❌ | ❌ |

* 空单元格代表目前暂未测试。
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ Only local installation are available yet.
| Screenshot | ✔ | ✔ | ✔ |
| * Shutdown | ✔ | ✔ | ✔ |
| * Reboot | ✔ | ✔ | ✔ |
| * Log off | ✔ | ❌ | ✔ |
| * Sleep | ✔ | ❌ | ✔ |
| * Hibernate | ✔ | ❌ | ❌ |
| * Sleep | ✔ | ❌ | ❌ |
| * Log off | ✔ | ❌ | ❌ |
| * Lock screen | ✔ | ❌ | ❌ |

* Blank cell means the situation is not tested yet.
Expand Down
14 changes: 9 additions & 5 deletions web/src/components/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,10 @@ function FileBrowser(props) {
}
function textEdit(file) {
// Only edit text file smaller than 2MB.
if (file.size > 2 << 20) return;
if (file.size > 2 << 20) {
message.warn(i18n.t('fileTooLarge'));
return;
}
if (editingFile) return;
setLoading(true);
request('/api/device/file/text', {device: props.device, file: path + file.name}, {}, {
Expand Down Expand Up @@ -619,13 +622,13 @@ function TextEditor(props) {

function onFontMenuClick(e) {
let currentFontSize = parseInt(editorRef.current.editor.getFontSize());
currentFontSize = isNaN(currentFontSize) ? 12 : currentFontSize;
currentFontSize = isNaN(currentFontSize) ? 15 : currentFontSize;
if (e.key === 'enlarge') {
currentFontSize++;
editorRef.current.editor.setFontSize(currentFontSize + 1);
} else if (e.key === 'shrink') {
if (currentFontSize <= 3) {
message.warn('字体已经达到最小');
if (currentFontSize <= 14) {
message.warn(i18n.t('minFontSize'));
return;
}
currentFontSize--;
Expand Down Expand Up @@ -810,7 +813,7 @@ function getEditorConfig() {
}
if (!config) {
config = {
fontSize: 12,
fontSize: 15,
theme: 'idle_fingers',
};
}
Expand Down Expand Up @@ -950,6 +953,7 @@ function FileUploader(props) {
okText={i18n.t(status === 1 ? 'uploading' : 'upload')}
onOk={onConfirm}
onCancel={onCancel}
modalTitle={i18n.t(status === 1 ? 'uploading' : 'upload')}
okButtonProps={{disabled: status !== 0}}
cancelButtonProps={{disabled: status > 1}}
width={550}
Expand Down
195 changes: 138 additions & 57 deletions web/src/components/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,59 +112,14 @@ class TerminalModal extends React.Component {

initialize(ev) {
ev?.dispose();
let buffer = '';
let buffer = { content: '' };
let termEv = null;
// Windows doesn't support pty, so we still use traditional way.
// And we need to handle arrow events manually.
if (this.props.device.os === 'windows') {
let cmd = '';
termEv = this.term.onData((e) => {
if (!this.conn) {
if (e === '\r' || e === ' ') {
this.term.write(`\n${i18n.t('reconnecting')}\n`);
this.termEv = this.initialize(termEv);
}
return;
}
switch (e) {
case '\r':
this.term.write('\n');
this.sendInput(cmd + '\n');
buffer = cmd + '\n';
cmd = '';
break;
case '\u0003':
this.term.write('^C');
this.sendInput('\u0003');
break;
case '\u007F':
if (cmd.length > 0) {
let charWidth = wcwidth(cmd[cmd.length - 1]);
cmd = cmd.substring(0, cmd.length - 1);

this.term.write('\b'.repeat(charWidth));
this.term.write(' '.repeat(charWidth));
this.term.write('\b'.repeat(charWidth));
}
break;
default:
if ((e >= String.fromCharCode(0x20) && e <= String.fromCharCode(0x7B)) || e >= '\u00a0') {
cmd += e;
this.term.write(e);
return;
}
}
});
termEv = this.term.onData(this.onWindowsInput.call(this, buffer));
} else {
termEv = this.term.onData((e) => {
if (!this.conn) {
if (e === '\r' || e === ' ') {
this.term.write(`\n${i18n.t('reconnecting')}\n`);
this.termEv = this.initialize(termEv);
}
return;
}
this.sendInput(e);
});
termEv = this.term.onData(this.onUnixOSInput.call(this, buffer));
}

this.ws = new WebSocket(`${getBaseURL(true)}?device=${this.props.device.id}&secret=${this.secret}`);
Expand All @@ -180,11 +135,11 @@ class TerminalModal extends React.Component {
if (this.conn) {
if (data?.act === 'outputTerminal') {
data = ab2str(hex2buf(data?.data?.output));
if (buffer.length > 0) {
if (buffer.content.length > 0) {
// check if data starts with buffer, if so, remove buffer.
if (data.startsWith(buffer)) {
data = data.substring(buffer.length);
buffer = '';
if (data.startsWith(buffer.content)) {
data = data.substring(buffer.content.length);
buffer.content = '';
}
}
this.term.write(data);
Expand Down Expand Up @@ -216,6 +171,136 @@ class TerminalModal extends React.Component {
}
return termEv;
}
onWindowsInput(buffer) {
let cmd = '';
let index = 0;
let cursor = 0;
let history = [];
let tempCmd = '';
let tempCursor = 0;
function clearTerm() {
let before = cmd.substring(0, cursor);
let after = cmd.substring(cursor);
this.term.write('\b'.repeat(wcwidth(before)));
this.term.write(' '.repeat(wcwidth(cmd)));
this.term.write('\b'.repeat(wcwidth(cmd)));
}
return function (e) {
if (!this.conn) {
if (e === '\r' || e === '\n' || e === ' ') {
this.term.write(`\n${i18n.t('reconnecting')}\n`);
this.termEv = this.initialize(termEv);
}
return;
}
switch (e) {
case '\u001b\u005b\u0041': // up arrow.
if (index > 0 && index <= history.length) {
if (index === history.length) {
tempCmd = cmd;
tempCursor = cursor;
}
index--;
clearTerm.call(this);
cmd = history[index];
cursor = cmd.length;
this.term.write(cmd);
}
break;
case '\u001b\u005b\u0042': // down arrow.
if (index + 1 < history.length) {
index++;
clearTerm.call(this);
cmd = history[index];
cursor = cmd.length;
this.term.write(cmd);
} else if (index + 1 <= history.length) {
clearTerm.call(this);
index++;
cmd = tempCmd;
cursor = tempCursor;
this.term.write(cmd);
this.term.write('\u001b\u005b\u0044'.repeat(wcwidth(cmd.substring(cursor))));
tempCmd = '';
tempCursor = 0;
}
break;
case '\u001b\u005b\u0043': // right arrow.
if (cursor < cmd.length) {
this.term.write('\u001b\u005b\u0043'.repeat(wcwidth(cmd[cursor])));
cursor++;
}
break;
case '\u001b\u005b\u0044': // left arrow.
if (cursor > 0) {
this.term.write('\u001b\u005b\u0044'.repeat(wcwidth(cmd[cursor-1])));
cursor--;
}
break;
case '\n':
case '\r':
this.term.write('\n');
this.sendInput(cmd + '\n');
if (cmd.length > 0) history.push(cmd);
buffer.content = cmd + '\n';
cursor = 0;
cmd = '';
if (history.length > 128) {
history = history.slice(history.length - 128);
}
tempCmd = '';
tempCursor = 0;
index = history.length;
break;
case '\u0003':
this.term.write('^C');
this.sendInput('\u0003');
cursor = 0;
cmd = '';
break;
case '\u007F':
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));
}
break;
default:
if ((e >= String.fromCharCode(0x20) && e <= String.fromCharCode(0x7B)) || e >= '\u00a0') {
if (cursor < cmd.length) {
let before = cmd.substring(0, cursor);
let after = cmd.substring(cursor);
cmd = before + e + after;
this.term.write(e + after);
this.term.write('\u001b\u005b\u0044'.repeat(wcwidth(after)));
} else {
cmd += e;
this.term.write(e);
}
cursor += e.length;
return;
}
}
}.bind(this);
}
onUnixOSInput(buffer) {
return function (e) {
if (!this.conn) {
if (e === '\r' || e === ' ') {
this.term.write(`\n${i18n.t('reconnecting')}\n`);
this.termEv = this.initialize(termEv);
}
return;
}
this.sendInput(e);
}.bind(this);
}

encrypt(data) {
let json = JSON.stringify(data);
Expand All @@ -227,7 +312,6 @@ class TerminalModal extends React.Component {
});
return wordArray2Uint8Array(encrypted.ciphertext);
}

decrypt(data) {
data = CryptoJS.lib.WordArray.create(data);
let decrypted = CryptoJS.AES.encrypt(data, this.secret, {
Expand All @@ -248,7 +332,6 @@ class TerminalModal extends React.Component {
});
}
}

sendData(data) {
if (this.conn) {
this.ws.send(this.encrypt(data));
Expand Down Expand Up @@ -288,7 +371,6 @@ class TerminalModal extends React.Component {
}
}
}

componentWillUnmount() {
window.onresize = null;
if (this.conn) {
Expand All @@ -315,7 +397,6 @@ class TerminalModal extends React.Component {
});
}
}

onResize() {
if (typeof this.doResize === 'function') {
debounce(this.doResize.bind(this), 70);
Expand Down
1 change: 1 addition & 0 deletions web/src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"fileDoNotSave": "Don't save",
"fileSaveSuccess": "File saved successfully",
"fileSaveFailed": "Fail to save file",
"minFontSize": "Font size is already minimum",
"save": "Save",
"search": "Search",
"replace": "Replace",
Expand Down
3 changes: 2 additions & 1 deletion web/src/locale/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@
"fileOrDirNotExist": "文件或目录不存在",
"fileOverwriteConfirm": "文件[ {0} ]已经存在,是否覆盖?",
"fileOverwrite": "覆盖",
"fileTooLarge": "文件太大,读取失败",
"fileTooLarge": "文件太大,无法读取",
"fileEncodingUnsupported": "不支持该文件编码",
"fileNotSaveConfirm": "文件已修改,是否保存?",
"fileSaveSuccess": "文件已保存",
"fileSaveFailed": "文件保存失败",
"minFontSize": "字体已经达到最小",
"save": "保存",
"search": "查找",
"replace": "替换",
Expand Down

0 comments on commit aadf368

Please sign in to comment.