TerminalEmulator:SimplePythonExample
lastmod 2025-05-28created 2025-05-281 edits
A simple terminal emulator built with pure Python and tkinter
Features
- Terminal screen implemented with the tkinter ScrolledText widget
- Connects to a bash process via subprocess
- Handles command input/output in real time
- Special handling for the cd and exit commands
Usage
- Running the script opens a terminal window with a black background
- Regular bash commands can be used (ls, pwd, cat, etc.)
- Run commands with Enter
- Quit with exit
Limitations
- A very basic implementation, so complex interactive programs are limited
- No support for color codes or advanced terminal features
The code is about 100 lines, kept as concise as possible. Once run, it can be used as a bash terminal right away.
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()