오토마우스

오토마우스 제작기 3

잡코신 2024. 3. 25. 18:00
728x90
반응형

지난 시간

지난번엔 GUI를 적용해서 오토마우스 프로그램을 만들어봤다.

이번엔 지난번 코드를 더 발전시켜서 GUI를 바꾸고 키를 처리해 본다.

오토마우스 만들기

이번 글은 GUI를 최적화하고 더 편리한 프로그램으로 만드는 과정이다.

지난번엔 그냥 생각나는 대로 GUI를 적용하려고 하고 그렇게 했기 때문에 너무 힘들었다.

그래서 이번엔 체계적으로 만들어볼 것이다.

바로 피그마로 들어가 작업을 진행해주었다.

프그마로 간단 작업하는 법은 여기서 배울 수 있다.

 

바로 GUI코드를 갈아엎고 다시 만들어보자.

1.GUI 만들기

    def __init__(self):
        super().__init__()
        self.title("AutoClicker")
        self.geometry("400x250")
        self.resizable(width=False, height=False)
        self.protocol("WM_DELETE_WINDOW", self.destroy_window)

        self.function = ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", 'F9', "F10", "F11", "F12"]
        self.choice = tk.StringVar()

        self.key_end = tk.StringVar(self)
        self.key_end.set(self.function[1])

        self.key_start = tk.StringVar(self)
        self.key_start.set(self.function[0])

        self.create_widgets()

    def create_widgets(self):
    
    
if __name__ == "__main__":
    app = AutoClickGUI()
    app.mainloop()

본 코드에서 수정하는 것은 GUI에서만 일어나는 오류인지 판단이 어렵기 때문에

디자인만 예시로 하는 테스트 파일을 만들어주고 기본 코드를 작성해놨다.

이제 피그마에 디자인된 순서대로 def create_widgets() 함수 부분에 코드를 넣을 것이다.

 

1. 마우스 위치 표시

        self.position_label = tk.Label(self, text="마우스 좌표:")
        self.position_label.grid(row=0, column=0)

지난 코드에도 있던 부분이라 빠르게 넘어간다.

달라진 점은 저번엔 대충 pack 레이아웃을 사용했다면 이번엔 grid 레아이웃을 사용한다는 점이다.

피그마 디자인을 보면 격자형태로 나눌 수 있다는 사실을 알 수 있다.

그렇기에 pack 레이아웃보다 다루기 쉬울 것이라고 판단되어 레이아웃을 변경했다.

 

2. 좌표 입력 칸

        position_frame = tk.Frame(self)
        position_frame.grid(row=1, column=0, columnspan=2, pady=5)

        self.coordinate_label = tk.Label(position_frame, text="좌표 지정")
        self.coordinate_label.grid(row=0, column=1, padx=10)

        # 체크박스 생성
        self.coordinate_checkbox = tk.Checkbutton(position_frame)
        self.coordinate_checkbox.grid(row=1, column=1)

        # 텍스트 라벨 생성
        self.X_coordinate_label = tk.Label(position_frame, text="X:")
        self.X_coordinate_label.grid(row=0, column=2, padx=5)

        # 입력 칸 생성
        self.X_coordinate_entry = tk.Entry(position_frame, state=tk.DISABLED,  width=10)
        self.X_coordinate_entry.grid(row=0, column=3, padx=5)

        # 텍스트 라벨 생성
        self.Y_coordinate_label = tk.Label(position_frame, text="Y:")
        self.Y_coordinate_label.grid(row=1, column=2, padx=5)

        # 입력 칸 생성
        self.Y_coordinate_entry = tk.Entry(position_frame, state=tk.DISABLED,  width=10)
        self.Y_coordinate_entry.grid(row=1, column=3, padx=5)

고정 좌표 클릭을 만들기 위해 좌표 입력 칸을 만들었다.

하지만 피그마로 만들었던 모양과 조금 다른데 그 이유는 가로로 모두 나열하니까 혼자 너무 길었다.

길이가 맞지 않아서 혼자 튀어나와있었다.

또 체크박스를 추가했는데 그냥 체크박스 없이 입력으로 하면 그냥인지 아닌지 헷갈릴 수 도 있을 것 같아서

체크박스를 만들어 체크박스에 체크 했을 때만 입력칸이 활성화 되도록 만들었다.

3. 라디오 버튼들

        radio_frame = tk.Frame(self)
        radio_frame.grid(row=2, column=0, columnspan=2, pady=5)

        self.create_button(radio_frame, "L-click", 0, 0)
        self.create_button(radio_frame, "R-click", 0, 1)
        # self.create_button("a", 1, 0)
        # self.create_button("i", 1, 1)
        self.choice.set(None) # 초기값 초기화
        
    def create_button(self, frame, text, row, column):
        button = tk.Radiobutton(frame, text=text, variable=self.choice, value=text)
        button.grid(row=row, column=column, padx=5)
        
    def destroy_window(self):
        self.destroy()

라디오 버튼은 같은 형식이고 개수는 많아서 함수형태로 만들고 창 x버튼 처리를 해줬다.

create_button이라는 함수를 만들어서 라디오버튼을 손 쉽게 만들어줬다.

다음으로 넘어가기 전에 tkinter의 Radiobutton에 대해 알아보고 가도록 하겠다.

 

Radiobutton은 사용자가 여러 옵션 중 하나를 선택할 수 있는 위젯이다.

먼저 버튼을 배치할 부모 위젯을 설정하고 다음에 다양한 옵셥을 설정한다.

다양한 옵션엔 어떤 종류가 있는지 알아보자.

이름 의미 기본값 속성
text 라디오버튼에 표시할 문자열 none 텍스트
textvariable 라디오버튼에 표시할 문자열을 가져올 변수 none none
anchor 라디오버튼안의 문자열 또는 이미지의 위치 center n, ne, e, se, s, sw, w, nw, center
justify 라디오버튼의 문자열이 여러 줄 일 경우 정렬 방법 center center, left, right
wraplength 자동 줄내림 설정 너비 0 상수
variable 라디오버튼 그룹 내에 선택된 옵션의 값을 저장하는 변수를 지정 none none
value 해당 Radiobutton이 선택되었을 때 연결된 변수에 설정될 값 none none
state 상태 설정 normal normal, active, disabled
activeforeground active 상태일 때 라디오버튼의 문자열 색상 SystemButtonText color
activebackground 라디오 버튼이 활성화됐을 때의 배경색을 지정 SystemButtonFace color
disabledforeground disabeld 상태일 때 라디오버튼의 문자열 색상 SystemDisabledText color
width 라디오버튼의 너비 0 상수
height 라디오버튼의 높이 0 상수
relief 라디오버튼의 테두리 모양 flat flat, groove, raised, ridge, solid, sunken
overrelief 라디오버튼에 마우스를 올렸을 때 라디오버튼의 테두리 모양 raised flat, groove, raised, ridge, solid, sunken
underline 텍스트에서 밑줄을 추가할 문자의 인덱스를 지정 0
background=bg 라디오버튼의 배경 색상 SystemButtonFace color
foreground=fg 라디오버튼의 문자열 색상 SystemButtonFace color
selectcolor         라디오버튼 상태의 배경 색상       SystemWindow                   color                  
padx 라디오버튼의 테두리와 내용의 가로 여백 1 상수
pady 라디오버튼의 테두리와 내용의 세로 여백 1 상수
highlightcolor 라디오버튼이 선택되었을 때 색상 SystemWindowFrame color
highlightbackground 라디오버튼이 선택되지 않았을 때 색상 SystemButtonFace color
highlightthickness 라디오버튼이 선택되었을 때 두께 (두께 설정) 0 상수
command Radiobutton이 선택될 때 호출될 함수를 지정 none 함수
bitmap 라디오버튼에 포함할 기본 이미지 none info, warning, error, question, questhead, hourglass, gray12, gray25, gray50, gray75
image 포함할  none none
selectimage 라디오버튼의 체크 상태일 때 표시할 임의 이미지 none none
font 라디오버튼의 문자열 글꼴 설정 TkDefaultFont font
cursor 라디오버튼의 마우스 커서 모양 none 커서 속성

이외에도 더 있고 다 적어보려 했지만 너무 많아서 힘들어서 여기까지만 적겠다.

 

3. 드롭박스 버튼

        end_frame = tk.Frame(self)
        end_frame.grid(row=3, column=0, columnspan=2)

        self.x_label = tk.Label(end_frame, text="시작")
        self.x_label.grid(row=3, column=0)

        self.y_label = tk.Label(end_frame, text="종료")
        self.y_label.grid(row=3, column=2)

        # 드롭다운 메뉴 생성
        self.start_menu = tk.OptionMenu(end_frame, self.choice, *self.function)
        self.start_menu.grid(row=3, column=1, padx=5)
        self.choice.set(self.function[0])

        # 드롭다운 메뉴 생성
        self.end_menu = tk.OptionMenu(end_frame, self.choice, *self.function)
        self.end_menu.grid(row=3, column=3, padx=5)
        self.choice.set(self.function[0])

종료와 시작버튼을 대신하는 키를 설정하는 드롭박스다.

다른 코드들은 다 배웠지만 처음보는 OptionMenu에 대해 간단하게 알아보고 가겠다.

 

OptionMenu는 Tkinter에서 사용되는 드롭다운 옵션 메뉴 위젯이다.

tk.OptionMenu(master, variable, *values, **kwargs)

보통 이런식으로 사용되며 주요 매개변수에는 다음의 종류가 있다.

이름 동작
master 옵션 메뉴를 배치할 부모 위젯
variable 선택한 항목의 값을 저장할 변수다. 보통 tk.StringVar() 또는 tk.IntVar()와 같은 변수를 사용
*values 옵션 메뉴에 표시될 항목들의 목록
**kwargs 다른 옵션들을 지정하는 키워드 인수
command 사용자가 항목을 선택했을 때 호출될 콜백 함수를 지정

 

2. 기능 만들기

드디어 GUI를 모두 새로 만들었다.

이제 기능을 넣어줄 것이다.

먼저 키보드로 작동하는 시작과 종료 버튼을 만들 것이다.

import keyboard

 

프로그램 밖에서 키보드 입력을 감지해야하기엔 또 다른 라이브러리가 필요하다.

keyboard 라이브러리는 Python에서 키보드 이벤트를 모니터링하고 제어하는 데 사용되는 라이브러리다.

키보드를 인식하는 방법엔 여러가지가 있다. 당장 우리가 알고 있는 방법만 해도 2가지가 있다.

  • pygame.event.get() : pygame으로 이벤트를 받아 그 키가 무엇인지 비교하는 방법
  • bind(버튼, 함수) : 키 버튼을 인식하는 bind함수를 사용하는 방법

하지만 난 프로그램이 실행될 때 프로그램 밖에서도 그 키를 인식하기 위해서 새로운 라이브러리를 사용하는 것이다.

import keyboard

# 키가 눌릴 때 실행할 함수
def on_key_pressed(event):
    print(f"Key {event.name} pressed")

# 키가 떼어질 때 실행할 함수
def on_key_released(event):
    print(f"Key {event.name} released")
    
# 'a' 키가 눌리거나 떼어질 때 이벤트 처리
keyboard.on_press_key('a', on_key_pressed)
keyboard.on_release_key('a', on_key_released)

# 핫키로 등록해서 람다 함수 이벤트 처리
keyboard.add_hotkey('ctrl+shift+a', lambda: print("Ctrl + Shift + A가 눌렸습니다!"))

# 'q' 키를 누르면 프로그램을 종료
keyboard.add_hotkey('q', lambda: quit())

# 프로그램이 종료될 때 훅을 제거하여 리소스를 정리
keyboard.wait('esc')
keyboard.unhook_all()

keyboard 라이브러리는 보통 위 처럼 사용한다.

pyautogui처럼 키 입력, 핫 키 등록도 되고 이벤트 감지까지 가능하다.

하지만 키보드밖에 되지 않아 mouse라는 라이브러리도 다운받아야 마우스도 가능하다.

 

이제 본격적으로 코드에 기능을 추가해보겠다.

    def __init__(self):
        super().__init__()
        self.title("AutoClicker")
        self.geometry("400x250")
        self.resizable(width=False, height=False)
        self.protocol("WM_DELETE_WINDOW", self.destroy_window)

        self.running = False
        self.check_thread = None
        self.function = ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", 'F9', "F10", "F11", "F12"]
        self.choice = tk.StringVar()

        self.key_end = tk.StringVar(self)
        self.key_end.set(self.function[1])

        self.key_start = tk.StringVar(self)
        self.key_start.set(self.function[0])

        self.create_widgets()

먼저 원래 코드에서  init부분을 보고 수정해준다.

    def start_auto_mouse(self):
        self.running = True

        self.check_thread = threading.Thread(target=self.auto_click)
        self.check_thread.start()

    def stop_auto_mouse(self):
        self.running = False

또 오토마우스 시작부분과 종료부분 함수도 가져와준다.

시작부분과 종료부분을 작동시킬 hotkey를 등록해준다.

if __name__ == "__main__":
    app = AutoMouse()
    keyboard.add_hotkey(app.key_start.get(), app.start_auto_mouse)
    keyboard.add_hotkey(app.key_end.get(), app.stop_auto_mouse)

    app.mainloop()

keyboard라이브러리에 add_hotkey 함수를 이용했다.

오토마우스 객체를 만들었으니 객체를 이용해서 내부 값과 함수를 실행시킨다.

하지만 이렇게 된다면 초기값만 적용되고 드롭다운을 바꾼다고 해도 hot_key가 변하지 않는다.

    def update_hotkey(self, event):
        keyboard.remove_all_hotkeys()
        keyboard.add_hotkey(self.key_start.get(), self.start_auto_mouse)
        keyboard.add_hotkey(self.key_end.get(), self.stop_auto_mouse)

그런 상황을 대비하여 드롭다운에 커맨드로 위 함수를 달고 함수를 추가해준다.

드롭다운을 업데이트하면 기존에 있던 핫키들을 모두 지우고 새로 받아와 설정해주는 코드다.

 

마지막으로 최종 오토 클릭 함수를 수정한다.

    def auto_click(self):
        match self.choice.get():
            case "L-click":
                Direction = "left"
            case "R-click":
                Direction = "right"
            case _:
                Direction = "Unknown"
        
        while self.running:
            pyautogui.click(button=Direction)

지난번까진 코드가 상당히 길었는데 전부 없어졌다.

이유는 5초 기다리는 코드와 좌표찍는 코드를 아직 추가하지 않았기 때문이다.

이젠 좌표로 오토 클릭을 시작하지 않고 키로 시작해서 코드가 줄어든것이다.

 

위에 파이썬 match문은 다른 언어에서 switch 문으로 사용되는 문법으로 값을 받아오면 case로 분리해서 받는다.

저 코드로 라디오 버튼에서 선택한 버튼을 받고 그에 해당하는 클릭을 진행 할 수 있게 된다.

오토마우스(ver.2) 사용하기

import pyautogui
import time
import threading
import keyboard
import tkinter as tk

class AutoMouse(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("AutoClicker")
        self.geometry("400x250")
        self.resizable(width=False, height=False)
        self.protocol("WM_DELETE_WINDOW", self.destroy_window)

        self.running = False
        self.check_thread = None
        self.function = ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", 'F9', "F10", "F11", "F12"]
        self.choice = tk.StringVar()

        self.key_end = tk.StringVar(self)
        self.key_end.set(self.function[1])

        self.key_start = tk.StringVar(self)
        self.key_start.set(self.function[0])

        self.create_widgets()

    def create_widgets(self):
        self.position_label = tk.Label(self, text="마우스 좌표:")
        self.position_label.grid(row=0, column=0)

        position_frame = tk.Frame(self)
        position_frame.grid(row=1, column=0, columnspan=2, pady=5)

        self.coordinate_label = tk.Label(position_frame, text="좌표 지정")
        self.coordinate_label.grid(row=0, column=1, padx=10)

        self.coordinate_checkbox = tk.Checkbutton(position_frame)
        self.coordinate_checkbox.grid(row=1, column=1)

        self.X_coordinate_label = tk.Label(position_frame, text="X:")
        self.X_coordinate_label.grid(row=0, column=2, padx=5)

        self.X_coordinate_entry = tk.Entry(position_frame, state=tk.DISABLED,  width=10)
        self.X_coordinate_entry.grid(row=0, column=3, padx=5)

        self.Y_coordinate_label = tk.Label(position_frame, text="Y:")
        self.Y_coordinate_label.grid(row=1, column=2, padx=5)

        self.Y_coordinate_entry = tk.Entry(position_frame, state=tk.DISABLED,  width=10)
        self.Y_coordinate_entry.grid(row=1, column=3, padx=5)

        radio_frame = tk.Frame(self)
        radio_frame.grid(row=2, column=0, columnspan=2, pady=5)

        self.create_button(radio_frame, "L-click", 0, 0)
        self.create_button(radio_frame, "R-click", 0, 1)
        # self.create_button("1", 4, 0)
        # self.create_button("2", 4, 1)
        self.choice.set("L-click")
        # ... 나머지 버튼들도 이와 같은 방식으로 추가

        end_frame = tk.Frame(self)
        end_frame.grid(row=3, column=0, columnspan=2)

        self.x_label = tk.Label(end_frame, text="시작")
        self.x_label.grid(row=3, column=0)

        self.y_label = tk.Label(end_frame, text="종료")
        self.y_label.grid(row=3, column=2)

        self.start_menu = tk.OptionMenu(end_frame, self.key_start, *self.function, command=self.update_hotkey)
        self.start_menu.grid(row=3, column=1, padx=5)

        self.end_menu = tk.OptionMenu(end_frame, self.key_end, *self.function, command=self.update_hotkey)
        self.end_menu.grid(row=3, column=3, padx=5)

    def create_button(self, frame, text, row, column):
        button = tk.Radiobutton(frame, text=text, variable=self.choice, value=text)
        button.grid(row=row, column=column, padx=5)

    def destroy_window(self):
        self.running = False
        self.destroy()

    def start_auto_mouse(self):
        self.running = True

        self.check_thread = threading.Thread(target=self.auto_click)
        self.check_thread.start()

    def stop_auto_mouse(self):
        self.running = False

    def update_hotkey(self, event):
        keyboard.remove_all_hotkeys()
        keyboard.add_hotkey(self.key_start.get(), self.start_auto_mouse)
        keyboard.add_hotkey(self.key_end.get(), self.stop_auto_mouse)

    def auto_click(self):
        match self.choice.get():
            case "L-click":
                Direction = "left"
            case "R-click":
                Direction = "right"
            case _:
                Direction = "Unknown"
        
        print(Direction)
        while self.running:
            pyautogui.click(button=Direction)

if __name__ == "__main__":
    app = AutoMouse()
    keyboard.add_hotkey(app.key_start.get(), app.start_auto_mouse)
    keyboard.add_hotkey(app.key_end.get(), app.stop_auto_mouse)

    app.mainloop() 

GUI를 다 적용한 최종 코드다.

잘 작동하는 것을 확인할 수 있었다.

 

Next

다음엔 마우스 좌표를 실시간으로 보이게 하고 고정 좌표 지정 오토 클릭도 만들어보도록 하겠다.

 

728x90
반응형

'오토마우스' 카테고리의 다른 글

오토마우스 제작기 4  (0) 2024.04.01
오토마우스 제작기 2  (0) 2024.03.11
오토마우스 프로그램 제작기 1  (0) 2024.03.04