오토마우스

오토마우스 제작기 4

잡코신 2024. 4. 1. 18:00
728x90
반응형

지난 시간

지난번엔 GUI를 업데이트해서 훨씬 편한 오토마우스 프로그램을 만들어봤다.

이번엔 지난번 코드에서 채우지 못한 기능들을 만들어볼 것이다.

오토마우스 만들기

지난번에 GUI를 완성하고 기능을 전부 채우지 않았다.

이번엔 남은 기능들을 완성해 보자.

    def update_mouse_position(self):
        x, y = pyautogui.position()
        self.position_label.config(text=f'마우스 좌표: ({x}, {y})')
        self.after(100, self.update_mouse_position)

GUI를 고치기 전에 사용했던 코드를 가져와 함수로 만들어줬다.

마우스의 좌표를 가져와 100ms 단위로 마우스 좌표를 새로고침한다.

여기서 또 새로운 함수인 after가 나오는데 이에 대해 간단하게 알아보자

after(delay_ms, callback=None, *args)

tkinterafter 함수는 일정한 시간이 지난 후에 지정된 함수를 실행하는 데 사용된다.

이 함수는 프로그램의 비동기적인 실행을 가능하게 한다.

스레드와 이 함수 중 무엇을 사용할지 고민했었는데 이게 더 간단한 방법이어서 이렇게 코드를 수정했다.

  • delay_ms: 함수가 실행되기 전에 대기해야 하는 밀리초(ms) 단위의 시간을 나타내는 정수다.
  • callback: 지연 후 호출될 함수다. 이 함수를 지정하지 않으면 지정된 시간 후에 아무런 작업을 수행하지 않는다.
  • *args: 필요한 경우 콜백 함수에 전달할 추가적인 인수들이다.
    def fixed_coordinates(self):
        if self.checkbox_choice.get() == 1:
            self.X_coordinate_entry.config(state=tk.NORMAL)
            self.Y_coordinate_entry.config(state=tk.NORMAL)
        else:
            self.X_coordinate_entry.delete(0, tk.END)
            self.X_coordinate_entry.config(state=tk.DISABLED)

            self.Y_coordinate_entry.delete(0, tk.END)
            self.Y_coordinate_entry.config(state=tk.DISABLED)

다음엔 고정 좌표 클릭 부분이다.

체크 박스에 체크가 되어 있다면 두 개의 엔트리가 활성화된다.

체크박스에 체크가 없다면 0번째부터 끝까지 입력되어 있는 문자를 삭제하고 비활성화시킨다.

여기서도 처음 보는 delete라는 함수에 대해서 간단하게 설명하고 넘어가자.

widget.delete(index1, index2=None)

delete 메서드는 tkinter 위젯에서 텍스트나 아이템을 삭제하는 데 사용된다.

이 메서드는 주로 텍스트 위젯이나 리스트박스와 같은 위젯에서 특정 범위의 텍스트나 항목을 삭제하는 데 사용된다.

  • index1: 삭제할 첫 번째 항목의 인덱스를 지정된다.
  • index2: 옵션입니다. 범위를 지정된다. 이 값이 지정되지 않으면 index1에 지정된 항목만 삭제된다.

이제 마지막으로 고정좌표에 맞춰 메인함수인 오토 클릭함수를 수정해야 한다.

    def auto_click(self):
        x_coord = self.X_coordinate_entry.get()
        y_coord = self.Y_coordinate_entry.get()

        if x_coord and y_coord: # 입력된 값이 있다면
            try:
                x_coord = int(x_coord) # 정수화
                y_coord = int(y_coord)
            except ValueError:
                print("좌표 값은 정수로 입력되어야 합니다.")
                return
            match self.radio_choice.get():
                case "L-click":
                    Direction = "left"
                case "R-click":
                    Direction = "right"
                case _:
                    Direction = "Unknown"
        
            while self.running:
                pyautogui.FAILSAFE = False # 마우스 이동 막기
                pyautogui.click(x_coord, y_coord, button=Direction)
        else:
            match self.radio_choice.get():
                case "L-click":
                    Direction = "left"
                case "R-click":
                    Direction = "right"
                case _:
                    Direction = "Unknown"
            
            print(Direction)
            while self.running:
                pyautogui.click(button=Direction)

코드가 많이 늘어났지만 달라진 것은 많이 없다.

먼저 엔트리에서 입력된 텍스트를 받아온다.

두 텍스트 모두 입력되어 있으면 고정좌표 클릭하고 없다면 일반 클릭으로 넘어간다.

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

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("350x250")
        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.radio_choice = tk.StringVar()
        self.checkbox_choice = tk.IntVar()

        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()
        self.update_mouse_position()

    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, variable=self.checkbox_choice, command=self.fixed_coordinates)
        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.radio_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.radio_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 fixed_coordinates(self):
        if self.checkbox_choice.get() == 1:
            self.X_coordinate_entry.config(state=tk.NORMAL)
            self.Y_coordinate_entry.config(state=tk.NORMAL)
        else:
            self.X_coordinate_entry.delete(0, tk.END)
            self.X_coordinate_entry.config(state=tk.DISABLED)

            self.Y_coordinate_entry.delete(0, tk.END)
            self.Y_coordinate_entry.config(state=tk.DISABLED)

    def update_mouse_position(self):
        x, y = pyautogui.position()
        self.position_label.config(text=f'마우스 좌표: ({x}, {y})')
        self.after(100, self.update_mouse_position)

    def auto_click(self):
        x_coord = self.X_coordinate_entry.get()
        y_coord = self.Y_coordinate_entry.get()

        if x_coord and y_coord:
            try:
                x_coord = int(x_coord)
                y_coord = int(y_coord)
            except ValueError:
                print("좌표 값은 정수로 입력되어야 합니다.")
                return
            match self.radio_choice.get():
                case "L-click":
                    Direction = "left"
                case "R-click":
                    Direction = "right"
                case _:
                    Direction = "Unknown"
        
            while self.running:
                pyautogui.FAILSAFE = False
                pyautogui.click(x_coord, y_coord, button=Direction)
        else:
            match self.radio_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()

최종 코드가 완성됐다.

드디어 프로그램 같은 실행 결과가 나오기에 진짜로 프로그램 화 시켜보도록 하겠다.

 

파이썬 코드를 실행 파일로 변환하기

Python으로 작업한 프로그램을 다른 Python 인터프리터가 없는 컴퓨터에서도 프로그램이 돌아가게 하기 위해서는 프로그램을 실행 파일(.exe)로 만들어야 한다.

Python 배포파일을 생성하기 위한 방법은 아래 두 가지 방법이 있다.

  1. PyInstaller
  2. cx_freeze

난 1번 PyInstaller를 사용할 것이다.

pip install pyinstaller

먼저 인스톨해준다.

 

그리고 이제 터미널에 명령어를 입력하면서 배포를 시작하는데 그에 대한 명령어를 알아본다.

pyinstaller test.py

 제일 기본적인 방법이다. 모든 값이 default값으로 설정되며 build폴더, dist폴더, spec(옵션파일) 파일이 생성되는데 dist폴더는 최종 결과물이 들어있다.

pyinstaller --onefile test.py

하나의 파일로 배포하는 방법이다.

pyinstaller --onedir basic_test.py

하나의 폴더로 배포하는 방법이다.

pyinstaller --noconsole test.py

콘솔을 표시하지 않게 배포하는 방법이다.

GUI가 없는 프로그램이라면 콘솔창으로 입/출력하기 때문에 적용하면 안 된다.

 

아이콘을 바꾸고 상세 설정을 해야 하는 것이라면 SPEC파일도 건드려야 하지만

지금은 그냥 시험 삼아 테스트버전을 배포하는 것이기에 그냥 이 정도 명령어만 알고 배포하자.

 

pyinstaller --onefile --noconsole autoClicker.py

내가 입력할 명령어다 하나의 파일이고 GUI를 사용하기에 콘솔이 없다.

그러면 이렇게 만들어진다.

autoClicker.exe
17.00MB

 

완성된 파일이다.

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

728x90
반응형

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

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