Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

일기장/일상

마우스 오토 클릭 프로그램 코드

새벽_글쓴이 2025. 5. 7. 23:14
반응형

다운로드는

https://tears2am.tistory.com/67

 

import pyautogui
import time
import threading
import tkinter as tk
from tkinter import simpledialog, messagebox
import keyboard
import csv
import os
from pynput import mouse

click_positions = []

# 기본 딜레이
delay_s = 0
delay_ds = 0
delay_cs = 0

clicking = False
click_thread = None

save_folder = "저장된 좌표 목록"
if not os.path.exists(save_folder):
    os.makedirs(save_folder)

def get_default_delay():
    try:
        s = float(entry_s.get()) if entry_s.get() else 0
        ds = float(entry_ds.get()) if entry_ds.get() else 0
        cs = float(entry_cs.get()) if entry_cs.get() else 0
    except ValueError:
        s, ds, cs = 0, 0, 0
    return s + (ds * 0.1) + (cs * 0.01)

def record_position(insert_at=None, pos=None):
    if pos is None:
        pos = pyautogui.position()
    delay = get_default_delay()
    data = (pos[0], pos[1], delay)
    if insert_at is not None:
        click_positions.insert(insert_at, data)
    else:
        click_positions.append(data)
    update_position_list()
    status_label.config(text=f"좌표 기록됨: {pos} (딜레이: {delay}초)")

def update_position_list():
    pos_listbox.delete(0, tk.END)
    for idx, (x, y, d) in enumerate(click_positions):
        pos_listbox.insert(tk.END, f"{idx}: ({x}, {y}) - 딜레이 {d:.2f}초")

def update_file_list():
    file_listbox.delete(0, tk.END)
    files = [f for f in os.listdir(save_folder) if f.endswith(".csv")]
    for f in files:
        file_listbox.insert(tk.END, f)

def start_clicking():
    global clicking, click_thread
    if not click_positions:
        messagebox.showwarning("좌표 없음", "기록된 좌표가 없습니다.")
        return
    if clicking:
        return
    clicking = True
    click_thread = threading.Thread(target=click_loop, daemon=True)
    click_thread.start()
    status_label.config(text="클릭 시작됨")

def stop_clicking():
    global clicking
    clicking = False
    status_label.config(text="클릭 중지됨")

def click_loop():
    index = 0
    while clicking:
        if not click_positions:
            break
        x, y, delay = click_positions[index]
        time.sleep(delay)
        pyautogui.moveTo(x, y)
        pyautogui.click()
        print(f"클릭 위치: ({x}, {y}), 딜레이: {delay}초")
        index = (index + 1) % len(click_positions)

def adjust_delay(entry, delta):
    value = entry.get()
    try:
        value = int(value) if value else 0
    except ValueError:
        value = 0
    value += delta
    value = max(0, value)
    entry.delete(0, tk.END)
    entry.insert(0, str(value))

def edit_position_delay(event):
    selection = pos_listbox.curselection()
    if selection:
        index = selection[0]
        x, y, old_delay = click_positions[index]
        new_delay = simpledialog.askfloat("딜레이 수정", f"현재 딜레이: {old_delay:.2f}초\n새 딜레이 입력:", initialvalue=old_delay)
        if new_delay is not None:
            click_positions[index] = (x, y, new_delay)
            update_position_list()

# 전역 마우스 클릭으로 좌표 삽입
def insert_position():
    selection = pos_listbox.curselection()
    if selection:
        insert_at = selection[0]
    else:
        insert_at = None

    status_label.config(text="원하는 위치를 클릭하세요 (ESC로 취소)")

    def on_click(x, y, button, pressed):
        if pressed and button == mouse.Button.left:
            listener.stop()
            record_position(insert_at=insert_at, pos=(x, y))
            status_label.config(text="좌표 삽입 완료")

    listener = mouse.Listener(on_click=on_click)
    listener.start()

def delete_position():
    selection = pos_listbox.curselection()
    if selection:
        index = selection[0]
        del click_positions[index]
        update_position_list()

def delete_all():
    global click_positions
    click_positions = []
    update_position_list()

def save_positions():
    filename = simpledialog.askstring("파일 이름 입력", "저장할 파일 이름(확장자는 자동 추가됩니다):")
    if filename:
        filepath = os.path.join(save_folder, f"{filename}.csv")
        if os.path.exists(filepath):
            overwrite = messagebox.askyesno("파일 덮어쓰기", f"{filename}.csv 파일이 이미 존재합니다.\n덮어쓰시겠습니까?")
            if not overwrite:
                status_label.config(text="저장 취소됨")
                return
        with open(filepath, "w", newline="") as f:
            writer = csv.writer(f)
            for x, y, delay in click_positions:
                writer.writerow([x, y, delay])
        status_label.config(text=f"저장됨: {filename}.csv")
        update_file_list()

def load_selected_file(event):
    selection = file_listbox.curselection()
    if selection:
        filename = file_listbox.get(selection[0])
        filepath = os.path.join(save_folder, filename)
        load_positions(filepath)

def load_positions(filepath):
    global click_positions
    with open(filepath, "r") as f:
        reader = csv.reader(f)
        click_positions = []
        for row in reader:
            x, y, delay = float(row[0]), float(row[1]), float(row[2])
            click_positions.append((x, y, delay))
    update_position_list()
    status_label.config(text=f"불러온 파일: {os.path.basename(filepath)}")

def delete_selected_file():
    selection = file_listbox.curselection()
    if selection:
        filename = file_listbox.get(selection[0])
        filepath = os.path.join(save_folder, filename)
        if messagebox.askyesno("파일 삭제", f"{filename} 파일을 삭제하시겠습니까?"):
            os.remove(filepath)
            update_file_list()
            status_label.config(text=f"파일 삭제됨: {filename}")

def new_file():
    global click_positions
    click_positions = []
    update_position_list()
    status_label.config(text="새 리스트 생성됨")

def move_up():
    selection = pos_listbox.curselection()
    if selection and selection[0] > 0:
        idx = selection[0]
        click_positions[idx-1], click_positions[idx] = click_positions[idx], click_positions[idx-1]
        update_position_list()
        pos_listbox.select_set(idx-1)

def move_down():
    selection = pos_listbox.curselection()
    if selection and selection[0] < len(click_positions) - 1:
        idx = selection[0]
        click_positions[idx+1], click_positions[idx] = click_positions[idx], click_positions[idx+1]
        update_position_list()
        pos_listbox.select_set(idx+1)

# 🖥 GUI 시작 전에 좌표 리스트 무조건 초기화
click_positions.clear()

# 🖥 GUI
root = tk.Tk()
root.title("마우스 오토 클릭")
root.resizable(False, False)  # 창 크기 고정!

# === 딜레이 설정 ===
delay_frame = tk.LabelFrame(root, text="기본 딜레이 설정", padx=5, pady=5)
delay_frame.grid(row=0, column=0, sticky="w", padx=10, pady=5)

tk.Label(delay_frame, text="초 (s):").grid(row=0, column=0)
entry_s = tk.Entry(delay_frame, width=5)
entry_s.insert(0, str(delay_s))
entry_s.grid(row=0, column=1)

tk.Button(delay_frame, text="+", command=lambda: adjust_delay(entry_s, 1)).grid(row=0, column=2)
tk.Button(delay_frame, text="-", command=lambda: adjust_delay(entry_s, -1)).grid(row=0, column=3)

tk.Label(delay_frame, text="1/10초:").grid(row=0, column=4)
entry_ds = tk.Entry(delay_frame, width=5)
entry_ds.insert(0, str(delay_ds))
entry_ds.grid(row=0, column=5)

tk.Button(delay_frame, text="+", command=lambda: adjust_delay(entry_ds, 1)).grid(row=0, column=6)
tk.Button(delay_frame, text="-", command=lambda: adjust_delay(entry_ds, -1)).grid(row=0, column=7)

tk.Label(delay_frame, text="1/100초:").grid(row=0, column=8)
entry_cs = tk.Entry(delay_frame, width=5)
entry_cs.insert(0, str(delay_cs))
entry_cs.grid(row=0, column=9)

tk.Button(delay_frame, text="+", command=lambda: adjust_delay(entry_cs, 1)).grid(row=0, column=10)
tk.Button(delay_frame, text="-", command=lambda: adjust_delay(entry_cs, -1)).grid(row=0, column=11)

# === 파일 목록 (오른쪽에 세로 길게 배치) ===
file_frame = tk.LabelFrame(root, text="저장된 파일 목록")
file_frame.grid(row=0, column=1, rowspan=5, padx=10, pady=5, sticky="ns")

file_listbox = tk.Listbox(file_frame, width=30, height=20)
file_listbox.pack()
file_listbox.bind("<Double-Button-1>", load_selected_file)

btn_delete_file = tk.Button(file_frame, text="선택 파일 삭제", command=delete_selected_file)
btn_delete_file.pack(pady=5)

# === 좌표 리스트 상단 버튼 프레임 ===
top_btn_frame = tk.Frame(root)
top_btn_frame.grid(row=1, column=0, sticky="w", padx=10, pady=2)

tk.Button(top_btn_frame, text="새로 만들기", width=12, command=new_file).grid(row=0, column=0, padx=2)
tk.Button(top_btn_frame, text="저장", width=12, command=save_positions).grid(row=0, column=1, padx=2)


# === 좌표 리스트 위 라벨 ===
tk.Label(root, text="기록된 좌표 (더블 클릭으로 딜레이 수정):").grid(row=2, column=0, sticky="w", padx=10)

# === 좌표 리스트 ===
pos_listbox = tk.Listbox(root, width=60, height=10)
pos_listbox.grid(row=3, column=0, padx=10)
pos_listbox.bind("<Double-Button-1>", edit_position_delay)

# === 좌표 리스트 아래 버튼 프레임 ===
btn_frame = tk.Frame(root)
btn_frame.grid(row=4, column=0, pady=5)

tk.Button(btn_frame, text="선택 위치 삽입", width=13, command=insert_position).grid(row=0, column=0)
tk.Button(btn_frame, text="선택 삭제", width=10, command=delete_position).grid(row=0, column=1)
tk.Button(btn_frame, text="전체 삭제", width=10, command=delete_all).grid(row=0, column=2)
tk.Button(btn_frame, text="위로 이동", width=10, command=move_up).grid(row=0, column=3)
tk.Button(btn_frame, text="아래로 이동", width=10, command=move_down).grid(row=0, column=4)

# === 상태 표시 ===
status_label = tk.Label(root, text="상태: 준비됨", fg="blue")
status_label.grid(row=5, column=0, columnspan=2, pady=5)

update_position_list()
update_file_list()

# === 핫키 ===
def listen_hotkeys():
    keyboard.add_hotkey('F2', record_position)
    keyboard.add_hotkey('F3', start_clicking)
    keyboard.add_hotkey('F4', stop_clicking)
    print("""
[F2] → 현재 마우스 좌표 기록 (맨 끝에 추가)
[F3] → 클릭 시작
[F4] → 클릭 중지
""")
    while True:
        time.sleep(0.1)

hotkey_thread = threading.Thread(target=listen_hotkeys, daemon=True)
hotkey_thread.start()

root.mainloop()
반응형

'일기장 > 일상' 카테고리의 다른 글

MS 오피스21 CMD 인증  (2) 2025.08.05
마우스 오토 클릭 프로그램  (0) 2025.05.06
2024 지스타(G-STAR) 컨퍼런스 현장에 가다  (1) 2024.11.18