TerminalEmulator:SimplePythonExample
순수 Python과 tkinter 로 만든 간단한 터미널 에뮬레이터
Features
- tkinter ScrolledText 위젯으로 터미널 화면 구현
- subprocess로 bash 프로세스 연결
- 실시간 명령어 입력/출력 처리
- cd, exit 명령어 특별 처리
Usage
- 스크립트 실행하면 검은 바탕의 터미널 창 열림
- 일반적인 bash 명령어 사용 가능 (ls, pwd, cat 등)
- Enter로 명령어 실행
- exit으로 종료
제한사항
- 매우 기본적인 구현으로 복잡한 interactive 프로그램은 제한적
- 색상 코드나 고급 터미널 기능 미지원
코드가 약 100줄로 최대한 간결하게 구현했습니다. 실행하면 바로 bash 터미널로 사용할 수 있습니다.
Source code
#!/usr/bin/env python3
import tkinter as tk
from tkinter import scrolledtext
import subprocess
import threading
import os
class Terminal:
def __init__(self):
self.root = tk.Tk()
self.root.title("터미널 에뮬레이터")
self.root.geometry("800x600")
self.root.configure(bg='black')
# 터미널 출력창
self.output = scrolledtext.ScrolledText(
self.root,
bg='black',
fg='white',
font=('Consolas', 10),
insertbackground='white'
)
self.output.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# bash 프로세스 시작
self.process = subprocess.Popen(
['bash'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=0
)
# 현재 입력 라인 시작 위치
self.prompt_start = '1.0'
# 키 바인딩
self.output.bind('<Key>', self.on_key)
self.output.bind('<Return>', self.on_enter)
self.output.focus_set()
# 출력 읽기 스레드 시작
self.output_thread = threading.Thread(target=self.read_output, daemon=True)
self.output_thread.start()
# 초기 프롬프트 표시
self.show_prompt()
def show_prompt(self):
"""프롬프트 표시"""
cwd = os.getcwd()
prompt = f"user@terminal:{os.path.basename(cwd)}$ "
self.output.insert(tk.END, prompt)
self.prompt_start = self.output.index(tk.END + "-1c")
self.output.see(tk.END)
def on_key(self, event):
"""키 입력 처리"""
# 프롬프트 이전 영역 수정 방지
current_pos = self.output.index(tk.INSERT)
if self.output.compare(current_pos, '<', self.prompt_start):
return 'break'
# 특수 키들은 허용
if event.keysym in ['Up', 'Down', 'Left', 'Right', 'BackSpace', 'Delete']:
if event.keysym == 'BackSpace':
if self.output.compare(tk.INSERT, '<=', self.prompt_start):
return 'break'
return None
def on_enter(self, event):
"""Enter 키 처리"""
# 현재 라인의 명령어 추출
line_start = self.output.index("insert linestart")
line_end = self.output.index("insert lineend")
line_content = self.output.get(line_start, line_end)
# 프롬프트 이후의 명령어만 추출
prompt_line = self.output.get(self.prompt_start + " linestart", self.prompt_start + " lineend")
if "$ " in prompt_line:
prompt_end = prompt_line.rfind("$ ") + 2
command = line_content[prompt_end:].strip()
else:
command = line_content.strip()
# 새 줄 추가
self.output.insert(tk.END, "\n")
# 명령어 실행
if command:
self.execute_command(command)
else:
self.show_prompt()
return 'break'
def execute_command(self, command):
"""명령어 실행"""
try:
# cd 명령어 특별 처리
if command.startswith('cd '):
path = command[3:].strip() or os.path.expanduser('~')
try:
os.chdir(os.path.expanduser(path))
self.show_prompt()
except FileNotFoundError:
self.output.insert(tk.END, f"bash: cd: {path}: No such file or directory\n")
self.show_prompt()
return
# exit 명령어
if command in ['exit', 'quit']:
self.root.quit()
return
# 다른 명령어들
self.process.stdin.write(command + '\n')
self.process.stdin.flush()
except Exception as e:
self.output.insert(tk.END, f"Error: {str(e)}\n")
self.show_prompt()
def read_output(self):
"""프로세스 출력 읽기"""
while True:
try:
output = self.process.stdout.readline()
if output:
self.root.after(0, lambda: self.display_output(output))
elif self.process.poll() is not None:
break
except:
break
def display_output(self, output):
"""출력 표시"""
self.output.insert(tk.END, output)
self.output.see(tk.END)
# 명령어 완료 후 새 프롬프트 표시
if output.strip() == "" or not output.endswith('\n'):
self.show_prompt()
def run(self):
"""GUI 실행"""
try:
self.root.mainloop()
finally:
if self.process.poll() is None:
self.process.terminate()
if __name__ == "__main__":
terminal = Terminal()
terminal.run()