0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ろうとるがPythonを扱う、、(その9:まとも版コマンドプロンプトもどき改良版)

Posted at

リアルタイム出力および割り込み対応

その4の改良版。tkinterを使ったコマンドプロンプトもどきプログラムの出力結果をリアルタイムに表示させる、割り込み対応とする、、が今回の趣旨。

手段

いろいろ試したのであるが、リアルタイム出力と割り込み対応を同時に行うには、(その8:tkinter&Subprocessでの割り込み)の内容を用いて、Subprocessをスレッド化し、その結果を1行ずつボックス(Text Widget)に出力することで実現できた。

実際の結果

本稿の背景には、”ping”の無限実行をリアルタイムに見る、その実行を”Ctrl-C”入力に中断する、、というものであった。下記がその状況。
スクリーンショット 2022-11-12 20.42.41.png

ソースコード

ポイントとなる点を記載。コマンドプロンプトもどきプログラムの実現については、その3を参照

コンソールでの割り込み

import signal
import tkinter as tk

def check():
    root.after(500, check)

# Start
signal.signal(signal.SIGINT, lambda x,y : close()) # x,y: Number of signal is 2

root = tk.Tk()
root.geometry('600x550')
root.title('Like command prompt 3')

root.after(500, check)		# for easily catch ctrc-c in console (context switch)
  • コンソールでの「Ctrl-C」シグナルハンドラの定義(close(後述)):ハンドラは2つの引数を取るので、lambda(無名関数)に”x,y”の2つの記述がある。
  • check():コンソールでの「Ctrl-C」入力をキャッチしやすくする。その8参照。

コマンドプロンプトWindow(ボックス:TextWidget)

frm = tk.Frame()
frm.place(x = 40, y = 20)

result = tk.Text(frm, font=("", 10), width=70, height=35, wrap="word")
ysc = tk.Scrollbar(frm, orient=tk.VERTICAL, command=result.yview)
ysc.pack(side=tk.RIGHT, fill="y")
result["yscrollcommand"] = ysc.set
result.insert(tk.END, '> ')	# insert prompt('> ')
result.pack()
result.bind('<Return>', func)
result.bind('<Control-c>', ctrlC)

root.protocol("WM_DELETE_WINDOW", close)
root.mainloop()
  • 「Return/Enter」キー入力時のコールされる関数:func(後述)
  • コマンドプロンプトWindowsで「Ctrl-C」入力時にコールされる関数:ctrlC(後述)
  • コマンドプロンプトWindowsで「☓」がクリックされたときにコールされる関数:close(後述)

スレッド

import threading

def func(event):
    th = threading.Thread(target=cmdThread, daemon=True)
    # daemon=True for RuntimeError: main thread is not in main loop
    th.start()
    return "break"

Subprocess実行スレッド

def cmdThread():
    global proc
    current = result.get('1.0', tk.END)
    lastline = current.rsplit('\n')[-2]
    laststr = lastline.split(' ', 1)[1]
    cmd_arg = laststr.split(' ')
    cmd = cmd_arg[0].lower()
    if cmd == "exit":                    # exit program
        root.quit()
        return
    try:
        proc = subprocess.Popen(cmd_arg, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        # "shell=True" prevents ctrl-c in GUI
    except: # for internal command like "dir"
        # proc = subprocess.Popen(cmd_arg, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        # FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。
        proc = subprocess.Popen(cmd_arg, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        proc.wait()
    result.insert(tk.END, '\n')
    while True:
        output = proc.stdout.readline()
        output = output.decode('shift_jis')
        if output != '':   
            result.insert(tk.END, output)
            result.see('end')
        else:
            if proc.poll() is not None:
                break
    result.insert(tk.END, '\n> ')
    result.see('end')
    return
  • キー入力されたコマンドおよび引数の取得。
  • Subprocessによりコマンドの実行(Poepn)。
  • 結果を1行ずつ読み込み、ボックス(TextWidget)に書き込み。
  • Subprocess終了時に次のコマンドプロンプトをボックスに書き込んでリターン。
  • トライしたところ下記のようなことが判明。
    • ”shell=True”があると、コマンドプロンプトWindowsでの「Ctrl-C」入力が正しく機能しない。
    • ”dir”ような内部コマンド実行には、”shell=True”が必要。そのため、”shell=True”なしのPopenで例外発生時に、再度”shell=True”ありのPopenを実行。
    • 上記コメント参照。

各種割り込み時にコールされる関数

def killProcess():
    if 'proc' in globals():
        proc.terminate()
    return

def ctrlC(event):
    killProcess()
    return

def close():
    killProcess()
    root.quit()
    return
  • コマンドプロンプトでの「Ctrl-C」入力時はSubprocessをkill。
  • コンソールでの「Ctrl-C」入力時およびコマンドプロンプトWindowsで「☓」クリック時はSubprocessをkillしプログラムを終了。

全体

# -*- coding: utf-8 -*-
import subprocess
import signal
import threading
import time
import tkinter as tk

#
def killProcess():
    if 'proc' in globals():
        proc.terminate()
    return

def ctrlC(event):
    killProcess()
    return

def close():
    killProcess()
    root.quit()
    return

def cmdThread():
    global proc
    current = result.get('1.0', tk.END)
    lastline = current.rsplit('\n')[-2]
    laststr = lastline.split(' ', 1)[1]
    cmd_arg = laststr.split(' ')
    cmd = cmd_arg[0].lower()
    if cmd == "exit":                    # exit program
        root.quit()
        return
    try:
        proc = subprocess.Popen(cmd_arg, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        # "shell=True" prevents ctrl-c in GUI
    except: # for internal command like "dir"
        #proc = subprocess.Popen(cmd_arg, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        # FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。
        proc = subprocess.Popen(cmd_arg, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        proc.wait()
    result.insert(tk.END, '\n')
    while True:
        output = proc.stdout.readline()
        output = output.decode('shift_jis')
        if output != '':   
            result.insert(tk.END, output)
            result.see('end')
        else:
            if proc.poll() is not None:
                break
    result.insert(tk.END, '\n> ')
    result.see('end')
    return

def func(event):
    th = threading.Thread(target=cmdThread, daemon=True)
    # daemon=True for RuntimeError: main thread is not in main loop
    th.start()
    return "break"

def check():
    root.after(500, check)

# Start
signal.signal(signal.SIGINT, lambda x,y : close()) # x,y: Number of signal is 2

root = tk.Tk()
root.geometry('600x550')
root.title('Like command prompt 3')

root.after(500, check)		# for easily catch ctrc-c in console (context switch)

frm = tk.Frame()
frm.place(x = 40, y = 20)

result = tk.Text(frm, font=("", 10), width=70, height=35, wrap="word")
ysc = tk.Scrollbar(frm, orient=tk.VERTICAL, command=result.yview)
ysc.pack(side=tk.RIGHT, fill="y")
result["yscrollcommand"] = ysc.set
result.insert(tk.END, '> ')	# insert prompt('> ')
result.pack()
result.bind('<Return>', func)
result.bind('<Control-c>', ctrlC)

root.protocol("WM_DELETE_WINDOW", close)
root.mainloop()

EOF

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?