Compare commits

..

1 Commits

Author SHA1 Message Date
Maxwell_YCM 50fbdef3af add terminal 2024-10-15 22:34:45 +08:00
8 changed files with 357 additions and 2 deletions

View File

@ -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孵化及运营的开源项目目标是面向全场景、全连接、全智能时代基于开源的方式搭建一个智能终端设备操作系统的框架和平台促进万物互联产业的繁荣发展。

View File

@ -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"
}
]
}
}

View File

@ -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")
}
}

View File

@ -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 })
}
}

View File

@ -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 = ''
}
}

View File

@ -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]
}
}
}
}

View File

@ -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}`;
}
}
}

View File

@ -0,0 +1,12 @@
{
"string": [
{
"name": "entry_MainAbility",
"value": "KaiHong Terminal"
},
{
"name": "mainability_description",
"value": "KaiHong Terminal for OpenHarmony"
}
]
}