윈도우에서 사용자가 원하는 위치를 설정해 놓으면 자동으로 클릭되는 '오토마우스(Auto Clicker)' 를 개발중이다.
몇몇 공개된 프로그램들이 있는데, 사용하다보니 몇가지 아쉬운 부분이 있어서 내 필요에 맞게 활용하고자 C# 을 학습하며 개발하고 있다.
아래 대략적인 화면 디자인이다.
싱글과 멀티 모드로 구현할 계획이다.
'Pick' 버튼을 클릭하면, 현재 창이 최소화되고 사용자가 원하는 위치(프로그램)에 마우스를 클릭하면 그 좌표값이 각 항목에 입력되도록 구현하였다.
// 마우스 기본 커서 모양 변경
[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);
[DllImport("user32.dll")]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll")]
public static extern IntPtr CopyIcon(IntPtr pcur);
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
// 마우스 클릭 이벤트
[DllImport("user32.dll")]
static extern short GetAsyncKeyState(int vKey);
// 커서 위치 확인 API
[DllImport("user32.dll")]
static extern bool GetCursorPos(out POINT lpPoint);
// 좌표를 저장할 구조체
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
// 가상 키 코드 정의
const int VK_LBUTTON = 0x01; // 왼쪽
const int VK_RBUTTON = 0x02; // 오른쪽
const int VK_MBUTTON = 0x04; // 휠(가운데)
private void btnPick_Click(object sender, EventArgs e)
{
// 폼이 일반 또는 최대화 인경우 폼 숨기기
Form1_Resized(this, e);
if (this.WindowState == FormWindowState.Minimized) {
try {
// 마우스 커서 모양 변경하기
this.Cursor = Cursors.Cross;
Cursor.Current = Cursors.Cross;
SetGlobalCursor(); // 시스템의 기본 화살표 커서를 십자 모양(Cross)으로 변경
WaitForGlobalPick(); // 마우스 클릭 좌표 가져오기
Form1_Resized(this, e); // 폼을 원래 기본 크기로 복원
} catch (Exception ex) {
// 에러 발생시 기본 모양으로 변경
this.Cursor = Cursors.Default;
MessageBox.Show(ex.Message);
} finally {
// 기본 마우스 모양
this.Cursor = Cursors.Default;
Cursor.Current = Cursors.Default;
RefreshSystemCursor(); // 시스템의 십자 모양(Cross) 커서를 기본 화살표로 변경
}
}
}
public static void SetGlobalCursor() {
// 시스템의 기본 화살표 커서를 십자 모양(Cross)으로 변경
IntPtr crossCursor = LoadCursor(IntPtr.Zero, 32515); // 32515는 OCR_CROSS
SetSystemCursor(CopyIcon(crossCursor), OCR_NORMAL);
}
public static void RefreshSystemCursor() {
// 시스템의 기본 커서를 화살표 커서로 복원
SystemParametersInfo(0x0057, 0, IntPtr.Zero, 0);
}
private void Form1_Resized(object sender, EventArgs e) {
if (this.WindowState == FormWindowState.Normal || this.WindowState == FormWindowState.Maximized) {
this.Hide(); // 폼 숨기기, 다시 보일 때: this.Show();
this.ShowInTaskbar = false; // 작업 표시줄에 아이콘 감추기
notifyIcon1.Visible = true; // 트레이 아이콘 표시
this.WindowState = FormWindowState.Minimized;
System.Diagnostics.Debug.WriteLine("Window is minimized.");
} else if (this.WindowState == FormWindowState.Minimized) {
// Runs after the state changes to minimized
this.WindowState = FormWindowState.Normal; // 창을 기본으로 되돌리기
notifyIcon1.Visible = false; // 트레이 아이콘 감추기
this.Show();
this.ShowInTaskbar = true; // 작업 표시줄에 아이콘 표시, 감추기 = false
System.Diagnostics.Debug.WriteLine("Window State changed to Normal.");
}
}
public async Task WaitForGlobalPick() {
POINT pt;
// 1. 이미 버튼이 눌려있는 경우를 대비해 떼어질 때까지 대기 (중복 감지 방지)
while ((GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0 ||
(GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0 ||
(GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0) {
Thread.Sleep(10);
}
// 2. 클릭이 발생할 때까지 무한 루프
while (true) {
// 0x8000 비트가 설정되어 있으면 현재 눌려 있는 상태임
if ((GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0) {
GetCursorPos(out pt);
txtXco.Text = pt.X.ToString();
txtYco.Text = pt.Y.ToString();
combAction.SelectedIndex = 0; // 왼쪽 버튼 선택
System.Diagnostics.Debug.WriteLine($" 왼쪽 버튼 | 좌표: X={pt.X}, Y={pt.Y}");
break; // 한 번만 감지하고 루프 탈출
} else if ((GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0) {
GetCursorPos(out pt);
txtXco.Text = pt.X.ToString();
txtYco.Text = pt.Y.ToString();
combAction.SelectedIndex = 1; // 오른쪽 버튼 선택
System.Diagnostics.Debug.WriteLine($" 오른쪽 버튼 | 좌표: X={pt.X}, Y={pt.Y}");
break; // 한 번만 감지하고 루프 탈출
} else if ((GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0) {
GetCursorPos(out pt);
txtXco.Text = pt.X.ToString();
txtYco.Text = pt.Y.ToString();
combAction.SelectedIndex = 2; // 가운데 버튼 선택
System.Diagnostics.Debug.WriteLine($" 가운데 버튼 | 좌표: X={pt.X}, Y={pt.Y}");
break; // 한 번만 감지하고 루프 탈출
}
Thread.Sleep(100); // CPU 점유율 과부하 방지
}
}