Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
50fbdef3af |
31
README.md
31
README.md
|
|
@ -1,3 +1,30 @@
|
|||
# kaihong
|
||||
# KaiHong Terminal
|
||||
|
||||
KaiHong Terminal 是一个为OpenHarmony平台开发的高级终端应用程序。它旨在提供类似于Ubuntu的Terminal或MacOS的iTerm的功能和用户体验。
|
||||
|
||||
## 项目概述
|
||||
|
||||
OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。
|
||||
|
||||
KaiHong Terminal 项目是为OpenHarmony平台开发的一个重要组件,旨在提供强大、灵活且用户友好的命令行界面。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 基本功能
|
||||
- Terminal窗口显示、输入输出
|
||||
- 多标签支持
|
||||
- 命令行工具运行支持
|
||||
- 基本个性化设置(字体、背景等)
|
||||
|
||||
### 高级功能
|
||||
- 多样式显示(高亮、加粗等)
|
||||
- 丰富的配色主题(如Solarized、Dark等)
|
||||
- oh-my-zsh 支持,提供丰富的交互样式
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 环境设置
|
||||
|
||||
|
||||
### 构建和运行
|
||||
|
||||
OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"app": {
|
||||
"bundleName": "com.example.kaihong_terminal",
|
||||
"vendor": "example",
|
||||
"version": {
|
||||
"code": 1000000,
|
||||
"name": "1.0.0"
|
||||
}
|
||||
},
|
||||
"deviceConfig": {},
|
||||
"module": {
|
||||
"package": "com.example.kaihong_terminal",
|
||||
"name": ".MyApplication",
|
||||
"mainAbility": ".MainAbility",
|
||||
"deviceType": [
|
||||
"phone"
|
||||
],
|
||||
"distro": {
|
||||
"deliveryWithInstall": true,
|
||||
"moduleName": "entry",
|
||||
"moduleType": "entry",
|
||||
"installationFree": false
|
||||
},
|
||||
"abilities": [
|
||||
{
|
||||
"skills": [
|
||||
{
|
||||
"entities": [
|
||||
"entity.system.home"
|
||||
],
|
||||
"actions": [
|
||||
"action.system.home"
|
||||
]
|
||||
}
|
||||
],
|
||||
"orientation": "unspecified",
|
||||
"visible": true,
|
||||
"name": ".MainAbility",
|
||||
"icon": "$media:icon",
|
||||
"description": "$string:mainability_description",
|
||||
"label": "$string:entry_MainAbility",
|
||||
"type": "page",
|
||||
"launchType": "standard"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import Ability from '@ohos.application.Ability'
|
||||
|
||||
export default class MainAbility extends Ability {
|
||||
onCreate(want, launchParam) {
|
||||
console.log("[KaiHong] MainAbility onCreate")
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
console.log("[KaiHong] MainAbility onDestroy")
|
||||
}
|
||||
|
||||
onWindowStageCreate(windowStage) {
|
||||
console.log("[KaiHong] MainAbility onWindowStageCreate")
|
||||
windowStage.setUIContent(this.context, "pages/Index", null)
|
||||
}
|
||||
|
||||
onWindowStageDestroy() {
|
||||
console.log("[KaiHong] MainAbility onWindowStageDestroy")
|
||||
}
|
||||
|
||||
onForeground() {
|
||||
console.log("[KaiHong] MainAbility onForeground")
|
||||
}
|
||||
|
||||
onBackground() {
|
||||
console.log("[KaiHong] MainAbility onBackground")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
@Component
|
||||
export struct Tab {
|
||||
@Prop isActive: boolean
|
||||
@Prop onActivate: () => void
|
||||
@Prop onClose: () => void
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Text(`Terminal ${this.isActive ? '(Active)' : ''}`)
|
||||
.fontSize(14)
|
||||
.fontColor(this.isActive ? '#000000' : '#666666')
|
||||
.backgroundColor(this.isActive ? '#ffffff' : '#e0e0e0')
|
||||
.padding(5)
|
||||
.onClick(() => this.onActivate())
|
||||
|
||||
Button('X')
|
||||
.fontSize(12)
|
||||
.onClick(() => this.onClose())
|
||||
}
|
||||
.margin({ right: 5 })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { CommandExecutor } from '../utils/CommandExecutor'
|
||||
|
||||
@Component
|
||||
export struct Terminal {
|
||||
@Prop tabId: number
|
||||
@State input: string = ''
|
||||
@State output: string = ''
|
||||
private commandExecutor: CommandExecutor = new CommandExecutor()
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.output)
|
||||
.fontSize(16)
|
||||
.margin({ bottom: 10 })
|
||||
.width('100%')
|
||||
|
||||
TextInput({ placeholder: 'Enter command...' })
|
||||
.onChange((value: string) => {
|
||||
this.input = value
|
||||
})
|
||||
.onSubmit(() => {
|
||||
this.executeCommand()
|
||||
})
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.padding(10)
|
||||
.backgroundColor('#000000')
|
||||
}
|
||||
|
||||
async executeCommand() {
|
||||
const result = await this.commandExecutor.execute(this.input)
|
||||
this.output += `> ${this.input}\n${result}\n`
|
||||
this.input = ''
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { Terminal } from '../components/Terminal'
|
||||
import { Tab } from '../components/Tab'
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
@State currentTab: number = 0
|
||||
@State tabs: number[] = [0]
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Row() {
|
||||
ForEach(this.tabs, (tab) => {
|
||||
Tab({
|
||||
isActive: tab === this.currentTab,
|
||||
onActivate: () => this.currentTab = tab,
|
||||
onClose: () => this.closeTab(tab)
|
||||
})
|
||||
})
|
||||
Button('+')
|
||||
.onClick(() => this.addTab())
|
||||
}
|
||||
.width('100%')
|
||||
.height(40)
|
||||
.backgroundColor('#f0f0f0')
|
||||
|
||||
Terminal({ tabId: this.currentTab })
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
|
||||
addTab() {
|
||||
this.tabs.push(this.tabs.length)
|
||||
this.currentTab = this.tabs.length - 1
|
||||
}
|
||||
|
||||
closeTab(tab: number) {
|
||||
if (this.tabs.length > 1) {
|
||||
this.tabs = this.tabs.filter(t => t !== tab)
|
||||
if (this.currentTab === tab) {
|
||||
this.currentTab = this.tabs[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import shell from '@ohos.process';
|
||||
import fileio from '@ohos.fileio';
|
||||
import preferences from '@ohos.data.preferences';
|
||||
|
||||
export class CommandExecutor {
|
||||
private currentDirectory: string = '/';
|
||||
private preferences: preferences.Preferences;
|
||||
|
||||
constructor() {
|
||||
this.initPreferences();
|
||||
}
|
||||
|
||||
private async initPreferences() {
|
||||
try {
|
||||
const context = getContext(this);
|
||||
this.preferences = await preferences.getPreferences(context, 'terminal_settings');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize preferences:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async execute(command: string): Promise<string> {
|
||||
const [cmd, ...args] = command.trim().split(' ');
|
||||
|
||||
switch (cmd.toLowerCase()) {
|
||||
case 'cd':
|
||||
return this.changeDirectory(args[0]);
|
||||
case 'pwd':
|
||||
return this.currentDirectory;
|
||||
case 'ls':
|
||||
return this.listDirectory();
|
||||
case 'cat':
|
||||
return this.catFile(args[0]);
|
||||
case 'echo':
|
||||
return args.join(' ');
|
||||
case 'set-font':
|
||||
return this.setFont(args[0]);
|
||||
case 'set-background':
|
||||
return this.setBackground(args[0]);
|
||||
default:
|
||||
return this.runCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
private async changeDirectory(path: string): Promise<string> {
|
||||
if (!path) return 'Usage: cd <directory>';
|
||||
|
||||
const newPath = fileio.joinPath(this.currentDirectory, path);
|
||||
try {
|
||||
const stat = await fileio.stat(newPath);
|
||||
if (stat.isDirectory()) {
|
||||
this.currentDirectory = newPath;
|
||||
return '';
|
||||
} else {
|
||||
return `cd: ${path}: Not a directory`;
|
||||
}
|
||||
} catch (error) {
|
||||
return `cd: ${path}: No such file or directory`;
|
||||
}
|
||||
}
|
||||
|
||||
private async listDirectory(): Promise<string> {
|
||||
try {
|
||||
const files = await fileio.listFile(this.currentDirectory);
|
||||
return files.join('\n');
|
||||
} catch (error) {
|
||||
return `ls: cannot access '${this.currentDirectory}': No such file or directory`;
|
||||
}
|
||||
}
|
||||
|
||||
private async catFile(filename: string): Promise<string> {
|
||||
if (!filename) return 'Usage: cat <filename>';
|
||||
|
||||
const filePath = fileio.joinPath(this.currentDirectory, filename);
|
||||
try {
|
||||
const content = await fileio.readText(filePath);
|
||||
return content;
|
||||
} catch (error) {
|
||||
return `cat: ${filename}: No such file or directory`;
|
||||
}
|
||||
}
|
||||
|
||||
private async runCommand(command: string): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const child = shell.execFile('sh', ['-c', command], {
|
||||
cwd: this.currentDirectory,
|
||||
timeout: 0,
|
||||
killSignal: shell.SIG.SIGKILL,
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
let output = '';
|
||||
child.on('close', (code, signal) => {
|
||||
resolve(output || `Command exited with code ${code}`);
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
resolve(`Error executing command: ${err.message}`);
|
||||
});
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
output += data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async setFont(font: string): Promise<string> {
|
||||
if (!font) return 'Usage: set-font <font-name>';
|
||||
|
||||
try {
|
||||
await this.preferences.put('font', font);
|
||||
await this.preferences.flush();
|
||||
return `Font set to ${font}`;
|
||||
} catch (error) {
|
||||
return `Error setting font: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async setBackground(color: string): Promise<string> {
|
||||
if (!color) return 'Usage: set-background <color>';
|
||||
|
||||
try {
|
||||
await this.preferences.put('background', color);
|
||||
await this.preferences.flush();
|
||||
return `Background color set to ${color}`;
|
||||
} catch (error) {
|
||||
return `Error setting background color: ${error.message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "entry_MainAbility",
|
||||
"value": "KaiHong Terminal"
|
||||
},
|
||||
{
|
||||
"name": "mainability_description",
|
||||
"value": "KaiHong Terminal for OpenHarmony"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue