ろうとるがPythonを扱う、、（その８：tkinter&Subprocessでの割り込み）

# tkinterでSubprocess利用時の割り込み（中断）
PythonでのGUIフレームワークであるtkinterプログラムにて、Subprocessを利用したときに、各種割り込みを使う方法を記載。

## 割り込み方法
Subprocessによる実行中プロセスを中断する方法として、次のものを取り上げる。
- 専用ボタンクリック
- Ctrl-c
- Windows Close（”☓”クリック）

## プログラム
見た目は次のようなもの。
![GUI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/84a86edb-706e-2787-b333-93c3c3cd29f1.png)
「Start subprocess」クリックにより指定されたプログラムが実行され、「Stop subprocess」（専用ボタン）クリックにより、そのプログラムが中断する。また、実行中に、「Ctrl-c」入力、右上の”☓”（）クリックでもプログラムを中断させる。なお、ここでは、中断＝プログラム終了とした。

### ソースコード
ポイントを中心に説明。なお、実行結果はコンソールに出力（print()）している。

#### メイン
```python
import signal
import tkinter as tk

def check(): 
    root.after(500, check)  #  time in ms.

signal.signal(signal.SIGINT, handler) # ctrl-c from console

root = tk.Tk()
root.after(500, check)  #  time in ms.

tk.Button(root, text="Start subprocess", command=start).pack()
tk.Button(root, text="Stop subprocess", command=stop).pack()
root.bind('<Control-c>', ctrlC) 
root.protocol("WM_DELETE_WINDOW", stop)

root.mainloop()
print("exited")
```
各種定義およびtkinter。
- コンソールで「Ctrl-C」をCatchしやすくするための関数（check()）および呼び出し
- コンソールで「Ctrl-C」が入力されたときに呼び出される関数（ハンドラ(handler)）
- ”Start”および”Stop”用ボタン
- GUI(tkinter)上で「Ctrl-C」が入力されたときの関数（ctrlC）
- Windows Close時の関数（stop）
- 終了時にコンソールに表示されるメッセージ（exited）

check()については、下記サイト情報を利用。
- [Exiting a tkinter app with Ctrl-C and catching SIGINT](https://stackoverflow.com/questions/39840815/exiting-a-tkinter-app-with-ctrl-c-and-catching-sigint)

#### Subprocess実行および中断
```python
from subprocess import Popen, PIPE, STDOUT

cmd = ['ping', '-t', '192.168.10.1']

def stop():
    print("stop")
    killProcess()

def start():
    global proc
    print("start")
    proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
```
- Subprocess（cmd）：pingの無限実行（オプション：-t）を実施
- Start関数：コンソール表示（start）およびPoepnによるSubprocess実行
- Stop関数：コンソール表示（stop）およびSubprocess中断関数（killProcess）呼び出し

#### Ctrl-c
```python
def handler(signum, root):
    print("handler")
    killProcess()

def ctrlC(event):
    print("ctrlC")
    killProcess()
```
- GUI上での「Ctrl-c」：コンソール表示（ctrlC）および中断関数（killProcess）呼び出し
- コンソール上での「Ctrl-c」：コンソール表示（ctrlC）および中断関数（killProcess）呼び出し

#### リアル中断関数
```python
def killProcess():
    if 'proc' in globals():
        proc.terminate()
        proc.wait()
        output, err = proc.communicate()
        output = output.decode('shift_jis')
        print(output)
    root.destroy()
```
- Subprocess実行時：プロセス中断（terminate）、実行結果の取得（communicate)、コンソールへの出力
- tkinterプログラムの終了（root.destroy()）

#### 全体
```python
from subprocess import Popen, PIPE, STDOUT
import signal
import tkinter as tk

cmd = ['ping', '-t', '192.168.10.1']

def killProcess():
    if 'proc' in globals():
        proc.terminate()
        proc.wait()
        output, err = proc.communicate()
        output = output.decode('shift_jis')
        print(output)
    root.destroy()

def handler(signum, root):
    print("handler")
    killProcess()

def ctrlC(event):
    print("ctrlC")
    killProcess()

def stop():
    print("stop")
    killProcess()

def start():
    global proc
    print("start")
    proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)

def check(): 
    root.after(500, check)  #  time in ms.
#
signal.signal(signal.SIGINT, handler) # ctrl-c from console

root = tk.Tk()
root.after(500, check)  #  time in ms.

tk.Button(root, text="Start subprocess", command=start).pack()
tk.Button(root, text="Stop subprocess", command=stop).pack()
root.bind('<Control-c>', ctrlC) 
root.protocol("WM_DELETE_WINDOW", stop)

root.mainloop()
print("exited")
```

### 結果
いずれも期待どおりの動作。

#### 実行中に「Stop subprocess」クリックまたはWindows Close（”☓”クリック） 
```
start
stop

192.168.10.1 に ping を送信しています 32 バイトのデータ:
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63

exited
```
なお、ここでは、リアルタイムにコマンド（ping）の出力が表示されるのではなく、中断後に出力される（プログラムの構成上）。以下、同様である。

#### 実行中にGUI上で「Ctrl-c」入力
```
start
ctrlC

192.168.10.1 に ping を送信しています 32 バイトのデータ:
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63

exited
```
#### 実行中にコンソール上で「Ctrl-c」入力
```
start
handler

192.168.10.1 に ping を送信しています 32 バイトのデータ:
192.168.10.1 からの応答: バイト数 =32 時間 =1ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =3ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =1ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =2ms TTL=63
192.168.10.1 からの応答: バイト数 =32 時間 =1ms TTL=63

192.168.10.1 の ping 統計:
    パケット数: 送信 = 8、受信 = 8、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 1ms、最大 = 3ms、平均 = 1ms
Ctrl+C

exited
```
#### 実行前の中断（おまけ）
```
stop
exited
```
```
ctrlC
exited
```
```
handler
exited
```

### 参考にしたリンク
- [Tkinter read subprocess output and put it into the gui](https://stackoverflow.com/questions/67920470/tkinter-read-subprocess-output-and-put-it-into-the-gui)

EOF