지난 시간
지난번엔 간단한 코딩으로 오토마우스를 만들어봤다.
이번엔 지난번 코드를 더 발전시켜서 GUI를 적용시키고 좌표를 처리해 본다.
오토마우스 만들기
이번 글에선 GUI가 최대 주제이기 때문에 먼저 종료와 시작 버튼만 있는 GUI화면이라도 만들어주겠다.
import tkinter as tk
import threading
사용할 라이브러리를 임포트 해준다. tkinter는 GUI를 지원해 주는 모듈이고
threading은 thread를 사용하게 해주는 모듈이다.
여기서 쓰레드(thread)가 뭔지 알고 넘어가자
스레드를 알려면 프로세스도 알아야 하기에 간단하게 설명하고 넘어간다.
- 프로세스는 운영체제에서 실행 중인 프로그램의 인스턴스다.
- 각 프로세스는 최소한 하나의 스레드를 가지며, 한 프로세스가 여러 개의 스레드를 가질 수 있다.
- 또 프로세스는 운영체제에 의해 독립적으로 관리되며, 각각이 별도의 주소 공간을 가지고 있다.
이제 스레드에 대해서도 간단하게 설명하겠다.
- 스레드는 프로세스 내에서 실행되는 작은 실행 단위이다.
- 하나의 프로세스 내에서 여러 개의 스레드가 동시에 실행될 수 있다.
- 스레드는 프로세스의 자원을 공유하며, 각각의 스레드는 같은 메모리 공간을 공유한다.
- 스레드는 운영체제에 의해 스케줄링되어 CPU를 할당받고, 각 스레드는 독립적으로 실행된다.
대충 이론적이게는 이렇게 되는데 간단히 설명하자면
1. 우리는 마우스가 자동으로 클릭되게 하고 싶다. (프로젝트의 목표)
2. 그렇기에 마우스가 자동으로 클릭되게 하는 프로그램(프로세스)을 실행시켜야 한다.
3. 프로그램은 동시에 좌표를 보여주고(스레드 1) 마우스를 클릭하는 작업(스레드 2)을 진행한다.
이래서 스레드가 필요한 것이다.
import threading
thread_1 = threading.Thread(target = 스레드 동작 함수, arg = (필요한 인자값))
thread_1.start() # 스레드 동작 시작
threading 라이브러리의 예시 코드이다.
이런 식으로 사용할 수 있다.
다시 본론으로 넘어와서 GUI 틀을 만들어준다.
class AutoMouse(tk.Tk):
def __init__(self):
super().__init__()
self.title("Auto Mouse Clicker")
self.geometry("300x150")
self.running = False
self.check_thread = None
self.create_widgets()
self.protocol("WM_DELETE_WINDOW", self.destroy_window)
클래스 형식으로 AutoMouse는 tk.Tk 클래스를 상속하여 GUI를 생성한다.
클래스의 __init__ 메서드는 객체가 생성될 때 호출되어, GUI의 초기 설정 및 구성을 담당한다.
- super().__init__() 호출 : 부모 클래스인 tk.Tk의 초기화 메서드를 호출하여 기본적인 Tkinter 창을 생성
- self.title("Auto Mouse Clicker") : 생성된 창의 제목을 "Auto Mouse Clicker"로 설정
- self.geometry("300x150"): 생성된 창의 크기를 300x150 픽셀로 설정
- self.running 및 self.check_thread 초기화: self.running을 False로 초기화하고, 스레드 객체인 self.check_thread를 None으로 초기화
- self.create_widgets() : create_widgets 함수를 이용하여 위젯들을 만든다.
- self.protocol("WM_DELETE_WINDOW", self.destroy_window) : Tkinter 애플리케이션의 윈도우가 닫힐 때 실행할 함수를 지정하는 메서드다. 이 부분은 후 destroy_window 함수를 설명할 때 추가 설명하겠다.
def create_widgets(self):
self.start_button = tk.Button(self, text="시작", command=self.start_auto_mouse)
self.start_button.pack(pady=10)
self.stop_button = tk.Button(self, text="중지", command=self.stop_auto_mouse, state=tk.DISABLED)
self.stop_button.pack(pady=5)
위젯은 만들기 위해 위젯을 담아줄 create_widget함수부터 작성해 보도록 하겠다.
먼저 시작 버튼과 중지 버튼을 추가한다.
중지를 눌렀다고 해서 프로그램은 다시 시작할 수 있게 중지되지만 종료되는 것이 아니기에 중지라고 표현했다.
저번에 tkinter의 레이아웃과 Grid 레이아웃에 대해 알아봤는데 이번엔 Pack 레이아웃에 대하며 알아보겠다.
pack 레이아웃은 위젯.pack(매개변수) 형식으로 사용되는데 이 매개변수엔 무엇이 있는지 알아보자.
이름 |
의미 | 기본값 | 속성 |
side | 위젯이 배치될 방향을 지정 | top | "left", "right", "top", "bottom" |
anchor | 젯이 부모 위젯 내에서 어디에 위치할지를 지정 | center | "n", "s", "e", "w", "center", "ne", "nw", "se", "sw" |
fill | 위젯이 부모 위젯의 공간을 어떻게 채울지를 지정 | none | "none", "x", "y", "both" |
expand | 위젯이 부모 위젯의 모든 공간을 차지하는지 여부를 지정 | False | "True", "False" |
before | 다른 위젯 앞 또는 뒤에 위젯을 배치 | none | 위젯 |
after | 다른 위젯 앞 또는 뒤에 위젯을 배치 | none | 위젯 |
ipadx | 위젯에 내부의 x방향(수평) 패딩 | 0 | 상수 |
ipady | 위젯에 내부의 y방향(수직) 패딩 | 0 | 상수 |
padx | 위젯 주위의 x방향(수평) 패딩 | 0 | 상수 |
pady | 위젯 주위의 y방향(수직) 패딩 | 0 | 상수 |
예시 코드로 어떻게 사용하는지 알아보자.
import tkinter as tk
root = tk.Tk()
button1 = tk.Button(root, text="Button 1")
button1.pack(side="left", padx=5, pady=5)
button2 = tk.Button(root, text="Button 2")
button2.pack(side="left", padx=5, pady=5)
root.mainloop()
이렇게 pack레이아웃을 사용할 수 있다.
사용법이 간단하고 pack만 해주면 되기 때문에 간단한 GUI에 많이 사용된다.
다시 코드로 돌아와 방금 만들어준 버튼에 사용되는 함수를 만들어준다.
def start_auto_mouse(self):
self.running = True
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.check_thread = threading.Thread(target=self.auto_click)
self.check_thread.start()
def stop_auto_mouse(self):
self.running = False
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
start_auto_mouse 메서드는 running 플래그를 True로 변경하고 시작 버튼을 또 누르지 못하게 바꾼다.
auto_click 함수를 실행시키는 스레드를 연결하고 실행시킨다.
stop_auto_mouse 메서드는 running 플래그를 False로 변경하고 중지 버튼을 또 누르지 못하게 바꾼다.
그리고 시작버튼을 다시 누를 수 있게 바꾼다.
def destroy_window(self):
self.running = False
self.destroy()
아까 protocol() 함수에 적용해 줬던 함수이다.
이 함수가 실행되면 running을 비활성화하고 윈도우를 파괴하는 함수를 실행한다.
이런 함수를 만드는 이유가 뭐냐면 프로그램을 정상 종료한다면 괜찮지만 탭 x를 눌러 종료하게 된다면 강제 종료가 되어 나머지 함수들이 작동을 하지 않는다. 이런 상황을 방지하기 위해 protocol 함수를 이용하여 처리하는 것이다.
protocol() 함수에 대해 설명하자면
Tkinter에서 윈도우 이벤트에 대한 처리 방법을 정의하는 데 사용되는 함수다.
widget.protocol(protocol_name, callback)
이 함수는 위와 같은 형식으로 출력되는데 각 변수가 뜻하는 것은
- widget: 이벤트를 수신하고자 하는 위젯, 대개 Tk 객체 또는 Toplevel 객체가 된다.
- protocol_name: 처리하고자 하는 이벤트의 종류를 지정하는 문자열
- callback: 지정된 이벤트가 발생했을 때 호출될 콜백 함수, 이 콜백 함수는 이벤트가 발생했을 때 실행된다.
그렇다면 protocol name에는 무엇이 있는지 알아보자.
프로토콜 이름 | 기능 | |
WM_DELETE_WINDOW | 윈도우의 닫기 버튼을 클릭했을 때 발생하는 이벤트 | |
WM_TAKE_FOCUS | 윈도우가 포커스를 받을 때 발생하는 이벤트 | |
WM_PROTOCOLS | 다른 윈도우 관련 프로토콜들의 집합을 정의하는 이벤트 | |
WM_SAVE_YOURSELF | 윈도우 매니저가 윈도우를 저장하도록 요청할 때 발생하는 이벤트 | |
WM_COLORMAP_NOTIFY | 컬러 맵의 변경 사항을 알리기 위해 윈도우 매니저에 의해 발생하는 이벤트 |
자주 사용되는 것은 이 정도뿐이지만 이외에도 다른 것들이 존재한다.
이 중 우리가 사용할 수 있을 정도는 위에 2개뿐일 것이다.
이제 프로그램의 메인 실행 부분이자, 전 글에서 만들었던 auto_click 함수를 수정해한다.
def auto_click(self):
prev_x, prev_y = pyautogui.position()
print("0pre", prev_x, prev_y)
while self.running:
time.sleep(5)
current_x, current_y = pyautogui.position()
print("1cur", current_x, current_y)
print("1pre", prev_x, prev_y)
if current_x == prev_x and current_y == prev_y:
print("2cur", current_x, current_y)
print("2pre", prev_x, prev_y)
while current_x == prev_x and current_y == prev_y:
pyautogui.click()
current_x, current_y = pyautogui.position()
else:
prev_x, prev_y = current_x, current_y
print("3cur", current_x, current_y)
print("3pre", prev_x, prev_y)
전 코드에서 while 1: 으로 오토클릭 전체를 감쌌던 while문을 함수 안으로 넣었다.
그렇기에 else문으로 넘어갈 때 break가 아닌 좌표 수정 코드를 넣었다.
print문은 좌표가 변하는 것을 보기 위해 넣었다.
if __name__ == "__main__":
app = AutoMouse()
app.mainloop()
모든 클래스 메서드 세팅이 끝났으니 클래스 객체를 만들어주고 실행시킨다.
이제 GUI만 적용한 오토마우스가 완성되었다.
하지만 이렇게 끝내기엔 뭔가 아쉽다.
print로 보여주던 좌표를 GUI에 표시해 주겠다.
self.position_label = tk.Label(self, text="마우스 좌표:")
self.position_label.pack(pady=5)
라벨을 만들어주고 pack 레이아웃으로 배치해준다.
self.position_label.config(text=f"현재 좌표:{current_x}, {current_y}")
그리고 좌표를 표시해 주는 코드를 추가한다.
오토마우스(ver.1) 사용하기
import pyautogui
import time
import threading
import tkinter as tk
class AutoMouse(tk.Tk):
def __init__(self):
super().__init__()
self.title("Auto Mouse Clicker")
self.geometry("300x150")
self.running = False
self.check_thread = None
self.create_widgets()
self.protocol("WM_DELETE_WINDOW", self.destroy_window)
def create_widgets(self):
self.position_label = tk.Label(self, text="마우스 좌표:")
self.position_label.pack(pady=5)
button_frame = tk.Frame(self)
button_frame.pack(pady=5)
self.start_button = tk.Button(button_frame, text="시작", command=self.start_auto_mouse)
self.start_button.pack(side=tk.LEFT, padx=10)
self.stop_button = tk.Button(button_frame, text="중지", command=self.stop_auto_mouse, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=10)
def start_auto_mouse(self):
self.running = True
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.check_thread = threading.Thread(target=self.auto_click)
self.check_thread.start()
def stop_auto_mouse(self):
self.running = False
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
def destroy_window(self):
self.running = False
self.destroy()
def auto_click(self):
prev_x, prev_y = pyautogui.position()
## self.position_label.config(text=f"초기 좌표:{prev_x}, {prev_y}")
while self.running:
time.sleep(5)
current_x, current_y = pyautogui.position()
self.position_label.config(text=f"현재 좌표:{current_x}, {current_y}")
if current_x == prev_x and current_y == prev_y:
while current_x == prev_x and current_y == prev_y:
pyautogui.click()
current_x, current_y = pyautogui.position()
else:
prev_x, prev_y = current_x, current_y
if __name__ == "__main__":
app = AutoMouse()
app.mainloop()
위 코드가 오늘의 최종 완성형 코드이다.
잘 작동되는 것을 확인할 수 있었다.
Next
다음엔 클릭 방식을 5초 기다리는 것 말고 다른 방식으로 바꾸고 좌표 지정 클릭도 만들어보도록 하겠다.
'오토마우스' 카테고리의 다른 글
오토마우스 제작기 4 (0) | 2024.04.01 |
---|---|
오토마우스 제작기 3 (1) | 2024.03.25 |
오토마우스 프로그램 제작기 1 (0) | 2024.03.04 |