はじめに
前回作成したマウス・キーボードの自動操作ツールの機能追加を行いました。
機能追加の内容は、以下となります。
- 環境設定とログ設定の編集をメニューバーから呼び出せるようにしました。
- マウスカーソルの座標確認を行う機能を追加しました。
- ホットキー(Ctrl+cなど)を扱えるようにしました。
ツール(プログラム)一式のダウンロード
- Pythonのプログラムと設定ファイル、検証用ファイルの一式を下記からダウンロードできます。
Pythonで作るマウス・キーボードの自動操作ツール_ツール一式_v1.1.0
1 ファイル 13.23 MB
- ダウンロードファイルの中身
プログラム一式とツール一式でダウンロードする中身の違いは、プログラムが入っているか、exe化したプログラムが入っているか、の違いのみです。
ファイル名 | 内容 |
config.ini | アプリを実行するためにアプリのフルパスを記載する。 |
LICENSE | ライセンスはMIT。再配布、改変など自由に行えます。 |
logging.conf | ログ出力の設定。 |
mouse_keyboard_control.py mouse_keyboard_control.exe |
ツールを実行するためのプログラムの本体 exe化したプログラム |
検証用動作設定ファイル_アプリ起動.xlsx | 検証用ファイル。不要になったら削除しても問題ありません。 |
検証用動作設定ファイル_マウス移動.xlsx | 検証用ファイル。不要になったら削除しても問題ありません。 |
ツールの実行方法
ツールの実行方法は前回から変更はありません。プログラムから実行する場合は、前回の実行方法を参照してください。EXEファイルから実行する場合、実行環境の準備などは不要です。「mouse_keyboard_control.exe」を実行してください。
前回の実行方法は下記を参照してください。
Pythonで作るマウス・キーボードの自動操作ツール①
はじめに
Google Colaboratoryを使用して機械学習のプログラムを作成しています。機械学習のモデル作成中にGoogle colaboratoryとのセッション切れると、モデル作成を最初からやり直す必要があります。セッションが...
ツールの画面遷移
メニューバーに「ファイル」「ツール」「ヘルプ」を追加しました。
各メニューの画面遷移を下記となります。
ファイルメニューの画面遷移
- 環境設定は、メモ帳で「config.ini」を開きます。
- ログ設定は、メモ帳で「logging.conf」を開きます。
- 環境設定とログ設定の再読み込みは、内部処。理となるでトップ画面に戻ります。
- 終了は、ツールを閉じます。
ツールメニューの画面遷移
- 座標確認ウインドウは、常に最前面で表示されます。
- 座標確認ウインドウを閉じるためには、ウインドウの「✕」をクリックします。
ヘルプメニューの画面遷移
- マニュアルは、ブラウザでマニュアルページ(作成中)を開きます。
- ログ表示は、メモ帳で「mouse_keyboard_control.log」を開きます。
- バージョン情報は、バージョン/リリース日/ライセンス/製品ページを表示します。
- 製品ページは、ブラウザで製品ページを表示します。
通常利用の画面遷移
- 強制終了ウインドウは常に最前面に表示されます。
動作設定ファイルを未選択の状態で「実行」をクリックした場合の画面遷移
シート名が正しくない動作設定ファイルを選択したときの画面遷移
プログラムの説明
前バージョンからの追加・変更点に絞って下記に記載しました。
作成した関数の一覧と概要
太字が追加・変更した関数となります。
行数 | 概要 | 関数名 | 引数 | 戻値 |
55 | メイン処理の開始位置 | main() | 無 | 無 |
72 | 環境設定とログ設定を読み込む | read_conf() | 無 | 無 |
89 | 動作設定ファイルを読み込む | read_action_setting_file() | 無 | 読み込んだ動作設定ファイルのシート内容 |
117 | 本ツールを実行したときのプロセスIDを記録する。(強制終了の際、Killする対象のプロセスIDを記録する) | record_pid() | 無 | 無 |
126 | 引数に応じたpyautoguiの関数を実行する | execute_action(action, option1, option2, option3) | 動作,オプション1,オプション2,オプション3 | 無 |
177 | 動作設定ファイルを選択したかを判定する | execute_if_action_setting_file_selected() | 無 | 無 |
190 | 終了ボタンを押したときの処理 | exit_button() | 無 | 無 |
207 | ファイル選択ダイアログを表示して、動作設定ファイルを選択する | select_action_setting_file() | 無 | 無 |
220 | 動作設定シートからactionとoption1~3を抽出し、execute_action関数を実行する | execute_action_list() | 無 | 無 |
244 | 強制終了のウインドウを表示する | forced_close_window() | 無 | 無 |
260 | マウスカーソルの座標をサブウインドウに表示する | mouse_position() | 無 | 無 |
281 | マウスカーソルの座標を更新する | update_mouse_position() | 無 | 無 |
290 | マウス・キーボードの自動操作のプロセスを終了する | terminate_process | 無 | 無 |
308 | 動作設定ファイルを読み込んで実行するのと、強制終了ウインドウを表示する | execute_and_show_forced_close_window() | 無 | 無 |
320 | 任意のアプリで指定したファイルを編集する | edit_file(app_path, file_path) | アプリのパス、ファイルのパス | 無 |
328 | バージョン情報を表示する | show_version() | 無 | 無 |
360 | 指定したURLをブラウザを開く | open_link_browser(url) | URL | 無 |
367 | 動作設定ファイルを選択するためのウインドウを表示する | main_window() | 無 | 無 |
「72行目:環境設定とログ設定を読み込む」の説明とコード
説明
- 環境設定とログ設定の再読み込み機能を追加するにあたり、環境設定とログ設定を読み込む処理を関数にまとめました。
- configとloggerは関数外から参照することがあるので、グローバル変数にしています。
コード
def read_conf():
# 環境設定を読み込む
global config
config = configparser.ConfigParser()
config.read(config_path)
# ログ設定を読み込み、ロガー設定する
global logger
logging.config.fileConfig(logging_path)
logger = logging.getLogger()
logger.info('環境設定とログ設定の読み込み完了')
「260行目:マウスカーソルの座標をサブウインドウに表示する」の説明とコード
説明
- マウスカーソルの座標を表示するウインドウを作成します。
- 作成したウインドウは常に最前面に表示します。
- マウスカーソルの座標を更新する「update_mouse_position」関数を呼び出しています。
コード
def mouse_position():
logger.info('マウスカーソルの座標確認を実行')
# サブウインドウを作成する
sub_window = tkinter.Toplevel(root)
sub_window.attributes('-topmost', True)
# マウスカーソルの座標のラベルを設定する
global position_label
position_label = tkinter.Label(sub_window, font=('meiryo', 16))
position_label.pack(padx=50, pady=20)
# ウインドウの「×」をクリックした時の動作を設定する
sub_window.protocol('WM_DELETE_WINDOW', sub_window.destroy)
# マウスカーソルの座標を更新する
update_mouse_position()
「281行目:マウスカーソルの座標を更新する」の説明とコード
説明
- マウスカーソルの座標は「pyautogui.position()」で取得しています。
- afterメソッドを使用して、指定した時間で定期的に指定した関数を実行しています。
- afterメソッドの第1引数で時間(ミリ秒)を指定し、第2引数で関数を指定します。
コード
def update_mouse_position():
x, y = pyautogui.position()
position_label.config(text='X: {0}, Y: {1}'.format(x, y))
root.after(50, update_mouse_position)
「320行目:任意のアプリで指定したファイルを編集する」の説明とコード
説明
- 「subprocess.run」を使用してアプリを実行しています。
- 「subprocess.run」は同期処理となるので、処理が終わるまで次の処理に進みません。
コード
def edit_file(app_path, file_path):
logger.info('"{0}" で "{1}" を開く'.format(app_path, file_path))
subprocess.run([app_path, file_path])
「328行目:バージョン情報を表示する」の説明とコード
説明
- 製品名/バージョン/リリース日/ライセンス/製品ページを表示します。
- 製品ページにカーソルを合わせるとアイコンが変わるように「cursor=hand2」で設定しています。
- 製品ページをブラウザで開くのは「open_link_browser」で実施しています。
- 製品ページのURLは「config.ini」で指定しています。
コード
def show_version():
version_w = tkinter.Toplevel()
# 製品名を表示する
product_name_label = tkinter.Label(version_w, text='製品名 : マウス・キーボード自動操作ツール')
product_name_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# バージョンを表示する
version_label = tkinter.Label(version_w, text='バージョン : 1.1.0')
version_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# リリース日を表示する
release_label = tkinter.Label(version_w, text='リリース日 : 2023/09/03')
release_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# ライセンスを表示する
license_label = tkinter.Label(version_w, text='ライセンス : MIT License')
license_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# 製品ホームページのリンクを設定する
homepage_label = tkinter.Label(version_w, text='製品ページ', fg="blue", cursor="hand2")
homepage_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
homepage_label.bind("", lambda e: open_link_browser(config['manual_path']['manual']))
# バージョン情報ウインドウを閉じる
close_button = tkinter.Button(version_w, text='閉じる', command=lambda: version_w.destroy())
close_button.pack(padx=10, pady=5, ipadx=20)
「360行目:指定したURLをブラウザを開く」の説明とコード
説明
- ブラウザを開くために標準ライブラリの「webbrowser」を使用しています。
コード
def open_link_browser(url):
webbrowser.open_new(url)
次回、試したいこと
試したいことは色々とあるので、アプリのデザインも勉強しながら下記を試していきます。
- 動作設定ファイルの記載に誤りがあった場合、エラーメッセージを表示する。
- エラー内容をログに残す。
- 読み込んだ動作設定ファイルの内容をツールに表示する。
- 画面の画像を認識し、指定した画像と合致した箇所をクリックする。
プログラムの全コード
# Excelで作成した動作設定ファイルに記載されたマウス・キーボードの操作を実行するプログラムです
# 実行方法
# python mouse_keyboard_control.py
# 動作設定ファイル
# ファイル形式:Excelファイル(.xlsx)
# ファイル名:任意
# シート名:動作設定
# 1列目:action,2列目:option1,3列目:option2,4列目:option3,5列目:comment
#
# マウス・キーボードを操作するライブラリ
import pyautogui
import pyperclip
# Excelファイルを扱うためのライブラリ
import openpyxl
# プロセス/システムの利用状況を取得するライブラリ
import psutil
# 標準ライブラリ
import os
import sys
import time
import webbrowser
import subprocess
import configparser
import logging.config
# ダイアログを制御するライブラリ
import tkinter
from tkinter import messagebox
from tkinter import filedialog
from tkinter import StringVar
# スレッド処理のライブラリを読み込む
import threading
# tkinterの設定
root = tkinter.Tk()
root.title('マウス・キーボードの自動操作')
# 動作設定ファイルのパスを保存する変数
action_setting_file = StringVar()
# 環境設定、ログ設定、ログファイルのパスを設定
config_path = r'./config.ini'
logging_path = r'./logging.conf'
logfile_path = r'./mouse_keyboard_control.log'
# メイン処理
# 引数:無し
# 戻値:無し
def main():
# 環境設定とログ設定を読み込む
read_conf()
# プロセスIDを記録する
record_pid()
# 実行開始のログを記録する
logger.info('マウス・キーボードの自動操作を開始する')
# 動作設定ファイルを選択するウインドウを表示する
main_window()
# 環境設定とログ設定を読み込む
# 引数:無し
# 戻値:無し
def read_conf():
# 環境設定を読み込む
global config
config = configparser.ConfigParser()
config.read(config_path)
# ログ設定を読み込み、ロガー設定する
global logger
logging.config.fileConfig(logging_path)
logger = logging.getLogger()
logger.info('環境設定とログ設定の読み込み完了')
# 動作設定ファイルを読み込む
# 引数:無
# 戻値:読み込んだシート(sheet)
def read_action_setting_file():
logger.info('選択した動作設定ファイル:{0}'.format(action_setting_file.get()))
# 選択された動作設定ファイル(excelファイル)を開く
# excelファイルのブックを取得する
book = openpyxl.load_workbook(action_setting_file.get(), read_only=True, data_only=True)
# [動作設定]シートを選択する
try:
sheet = book['動作設定']
except Exception as e:
logger.warning('動作設定シートが見つかりませんでした')
logger.warning('{0}'.format(e))
messagebox.showerror('マウス・キーボードの自動操作',
'選択した動作設定ファイルから動作設定シートが見つかりませんでした。\n動作設定ファイルを選択してください。')
# 終了プロセスを実行する
terminate_process()
# プログラムを終了する
sys.exit()
return sheet
# 本ツールを実行したときのプロセスIDを記録する
# 強制終了の際、Killする対象のプロセスIDを記録する
# 引数:無し
# 戻値:無し
def record_pid():
pid = os.getpid()
with open('pid_mouse_keyboard_control.txt', 'w') as f:
f.write(str(pid))
# 引数に応じたpyautoguiの関数を実行する
# 引数:action(str),option1(int/str),option2(int/str),option3(int/str)
# 戻値:無し
def execute_action(action, option1, option2, option3):
# option1とoption2を必要とするclick,move,rightClick,doubleClickを実行する処理
for act in ['click', 'move', 'rightClick', 'doubleClick', 'hotkey']:
if action == act:
if option1 is not None and option2 is not None:
logger.info('option1:{0}, option2:{1}, {2}'.format(option1, option2, action))
if action == 'click':
pyautogui.click(option1, option2)
elif action == 'move':
pyautogui.moveTo(option1, option2)
elif action == 'rightClick':
pyautogui.rightClick(option1, option2)
elif action == 'doubleClick':
pyautogui.doubleClick(option1, option2)
elif action == 'hotkey':
pyautogui.hotkey(option1, option2)
else:
logger.warning('{0}, optionの設定に誤りがあります'.format(action))
# option1を必要とするpress,scroll,sleep,inputを実行する
for act in ['press', 'scroll', 'sleep', 'input']:
if action == act:
if option1 is not None and option2 is None:
logger.info('{0}, {1}'.format(option1, action))
if action == 'press':
pyautogui.press(option1)
elif action == 'scroll':
pyautogui.scroll(option1)
elif action == 'sleep':
time.sleep(option1)
elif action == 'input':
pyperclip.copy(option1)
pyautogui.hotkey('ctrl', 'v')
else:
logger.warning('{0}, optionの設定に誤りがあります'.format(action))
# アプリを実行する
for act in ['edge', 'word', 'excel', 'powerpoint', 'memo']:
if action == act:
app_path = config['application_path'][act]
if option1 is not None:
logger.info('{0}, {1}'.format(action, option1))
subprocess.Popen([app_path, option1])
else:
logger.info('{0}'.format(action))
subprocess.Popen(app_path)
# 動作設定ファイルを選択したかを判定する
# 引数:無し
# 戻値:無し
def execute_if_action_setting_file_selected():
is_file = os.path.isfile(action_setting_file.get())
if is_file:
execute_and_show_forced_close_window()
else:
# 終了メッセージを表示する
messagebox.showerror('マウス・キーボードの自動操作', '動作設定ファイルを選択してください。')
# 終了ボタンの処理
# 引数:無し
# 戻値:無し
def exit_button():
# rootウインドウを閉じる
root.destroy()
# マウス・キーボードの自動操作のプロセスIDを保存しているファイルを削除する
os.remove('./pid_mouse_keyboard_control.txt')
# 終了ボタンを選択したことをログに記録する
logger.info('終了ボタンを選択しました')
# プログラムを終了する
sys.exit()
# ファイル選択ダイアログを表示して、動作設定ファイルを選択する
# 引数:無し
# 戻値:無し
def select_action_setting_file():
file_select = filedialog.askopenfilename(title='動作設定ファイルを選択してください',
filetypes=[('EXCEL file', '.xlsx')],
initialdir='./')
action_setting_file.set(file_select)
# 選択した動作設定ファイルのパスをメインウインドウに表示する
select_file_path['text'] = file_select
# 動作設定シートからactionとoption1~3を抽出し、execute_action関数を実行する
# 引数:無し
# 戻値:無し
def execute_action_list():
sheet = read_action_setting_file()
# 動作設定シートに記載されている内容を実行する
action_list = 2
while action_list <= sheet.max_row:
action = sheet.cell(row=action_list, column=1).value
option1 = sheet.cell(row=action_list, column=2).value
option2 = sheet.cell(row=action_list, column=3).value
option3 = sheet.cell(row=action_list, column=4).value
execute_action(action, option1, option2, option3)
action_list += 1
# 終了メッセージを表示する
messagebox.showinfo('マウス・キーボードの自動操作', 'マウス・キーボードの自動操作が終了しました')
# 終了プロセスを実行する
terminate_process()
# 強制終了のウインドウを表示する
# 引数:無し
# 戻値:無し
def forced_close_window():
# サブウインドウの作成
sub_window = tkinter.Toplevel(root)
sub_window.attributes('-topmost', True)
# 強制終了ボタンを設置する
button = tkinter.Button(sub_window,
text='強制終了',
font=('meiryo', 15),
command=terminate_process)
button.pack(padx=50, pady=25)
# マウスカーソルの座標をサブウインドウに表示する
# 引数:無し
# 戻値:無し
def mouse_position():
logger.info('マウスカーソルの座標確認を実行')
# サブウインドウを作成する
sub_window = tkinter.Toplevel(root)
sub_window.attributes('-topmost', True)
# マウスカーソルの座標のラベルを設定する
global position_label
position_label = tkinter.Label(sub_window, font=('meiryo', 16))
position_label.pack(padx=50, pady=20)
# ウインドウの「×」をクリックした時の動作を設定する
sub_window.protocol('WM_DELETE_WINDOW', sub_window.destroy)
# マウスカーソルの座標を更新する
update_mouse_position()
# マウスカーソルの座標を更新する
# 引数:無し
# 戻値:無し
def update_mouse_position():
x, y = pyautogui.position()
position_label.config(text='X: {0}, Y: {1}'.format(x, y))
root.after(50, update_mouse_position)
# マウス・キーボードの自動操作のプロセスを終了する
# 引数:無し
# 戻値:無し
def terminate_process():
# 実行終了のログを記録する
logger.info('マウス・キーボードの自動操作を終了する')
with open('./pid_mouse_keyboard_control.txt') as f:
pid = f.read()
# マウス・キーボードの自動操作のプロセスIDを保存しているファイルを削除する
os.remove('./pid_mouse_keyboard_control.txt')
# プロセスを止める
p = psutil.Process(int(pid))
p.terminate()
# 動作設定ファイルを読み込んで実行するのと、強制終了ウインドウを表示する
# 引数:無し
# 戻値:無し
def execute_and_show_forced_close_window():
# 動作設定ファイルに登録されているアクションを実行する
thread = threading.Thread(target=execute_action_list)
thread.start()
# 強制終了のウインドウを表示する
forced_close_window()
# 任意のアプリで指定したファイルを編集する
# 引数:編集アプリのパス, 編集するファイルのパス
# 戻値:無し
def edit_file(app_path, file_path):
logger.info('"{0}" で "{1}" を開く'.format(app_path, file_path))
subprocess.run([app_path, file_path])
# バージョン情報を表示する
# 引数:無し
# 戻値:無し
def show_version():
version_w = tkinter.Toplevel()
# 製品名を表示する
product_name_label = tkinter.Label(version_w, text='製品名 : マウス・キーボード自動操作ツール')
product_name_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# バージョンを表示する
version_label = tkinter.Label(version_w, text='バージョン : 1.1.0')
version_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# リリース日を表示する
release_label = tkinter.Label(version_w, text='リリース日 : 2023/09/03')
release_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# ライセンスを表示する
license_label = tkinter.Label(version_w, text='ライセンス : MIT License')
license_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
# 製品ホームページのリンクを設定する
homepage_label = tkinter.Label(version_w, text='製品ページ', fg="blue", cursor="hand2")
homepage_label.pack(padx=10, pady=5, side=tkinter.TOP, anchor=tkinter.W)
homepage_label.bind("", lambda e: open_link_browser(config['manual_path']['manual']))
# バージョン情報ウインドウを閉じる
close_button = tkinter.Button(version_w, text='閉じる', command=lambda: version_w.destroy())
close_button.pack(padx=10, pady=5, ipadx=20)
# 引数で指定したURLをブラウザで開く
# 引数:URL
# 戻値:無し
def open_link_browser(url):
webbrowser.open_new(url)
# 動作設定ファイルを選択するためのウインドウを表示する
# 引数:無し
# 戻値:無し
def main_window():
# 文言1
label1 = tkinter.Label(root,
text='マウス・キーボードの自動操作を行います。',
font=('meiryo', 10))
label1.pack(padx=10, side=tkinter.TOP, anchor=tkinter.W)
# 文言2
label2 = tkinter.Label(root,
text='「ファイルの選択」をクリックし、動作設定ファイルを選択してください。',
font=('meiryo', 10))
label2.pack(padx=10, side=tkinter.TOP, anchor=tkinter.W)
# ファイルの選択ボタンを配置する
select_button = tkinter.Button(text='ファイルの選択', command=select_action_setting_file)
select_button.pack(padx=10, pady=10, ipadx=20, side=tkinter.TOP, anchor=tkinter.W)
# 文言3
label3 = tkinter.Label(root, text='選択したファイル')
label3.pack(padx=10, pady=10, side=tkinter.TOP, anchor=tkinter.W)
# 選択したファイルのパスを表示
global select_file_path
select_file_path = tkinter.Label(root, text='動作設定ファイルは未選択です。', anchor=tkinter.W, relief='groove', bd=1)
select_file_path.pack(padx=10, side=tkinter.TOP, fill=tkinter.X, anchor=tkinter.W)
# フレーム
frame = tkinter.LabelFrame(root, text='', relief=tkinter.FLAT)
frame.pack(padx=10, pady=5, side=tkinter.RIGHT)
# 実行ボタンを配置する
button_read = tkinter.Button(frame, text='実行', command=execute_if_action_setting_file_selected)
button_read.pack(padx=10, pady=10, ipadx=30, side=tkinter.LEFT, anchor=tkinter.W)
# 終了ボタンを配置する
button_exit = tkinter.Button(frame, text='終了', command=exit_button)
button_exit.pack(padx=10, pady=10, ipadx=30, side=tkinter.LEFT, anchor=tkinter.W)
# ウインドウの「×」をクリックした時の動作を設定する
root.protocol('WM_DELETE_WINDOW', exit_button)
# メニューバーを作成する
menubar = tkinter.Menu(root)
root.configure(menu=menubar)
# メニューバーにファイルを配置する
file_menu = tkinter.Menu(menubar, tearoff=0)
menubar.add_cascade(label='ファイル', menu=file_menu)
# ファイルに環境設定を配置する
file_menu.add_command(label='環境設定',
command=lambda: edit_file(config['application_path']['memo'],
config_path))
# ファイルにログ設定を配置する
file_menu.add_command(label='ログ設定',
command=lambda: edit_file(config['application_path']['memo'],
logging_path))
# ファイルに環境設定とログ設定の再読み込みを配置する
file_menu.add_command(label='環境設定とログ設定の再読み込み', command=read_conf)
# セパレータを追加する
file_menu.add_separator()
# ファイルに終了を配置する
file_menu.add_command(label='終了', command=exit_button)
# ツールバーを作成する
tool_menu = tkinter.Menu(menubar, tearoff=0)
menubar.add_cascade(label='ツール', menu=tool_menu)
# ツールに座標確認を配置する
tool_menu.add_command(label='座標確認', command=mouse_position)
# ヘルプバーを作成する
help_menu = tkinter.Menu(menubar, tearoff=0)
menubar.add_cascade(label='ヘルプ', menu=help_menu)
# ヘルプにマニュアルを配置する
help_menu.add_command(label='マニュアル',
command=lambda: edit_file(config['application_path']['edge'],
config['manual_path']['manual']))
# ヘルプにログ表示を配置する
help_menu.add_command(label='ログ表示',
command=lambda: edit_file(config['application_path']['memo'],
logfile_path))
# ヘルプにバージョン情報を配置する
help_menu.add_command(label='バージョン情報', command=show_version)
# ウインドウを維持する
root.mainloop()
if __name__ == '__main__':
main()
コメント