diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..47eac12 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: build + +on: + push: + branches: ['**'] + pull_request: + +# Node 20 deprecation uyarisini engelle: JS action'lari Node 24 ile calistir +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + cache: maven + + - name: Derle + test + paketle (fat-jar) + run: mvn -B -ntp clean verify + + - name: Fat-jar artifact yukle + uses: actions/upload-artifact@v4 + with: + name: autoclicker-jar + path: target/autoclicker.jar + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..97100cc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: release + +on: + push: + tags: ['v*'] + +permissions: + contents: write + +# Node 20 deprecation uyarisini engelle: JS action'lari Node 24 ile calistir +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + +jobs: + release: + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + cache: maven + + - name: Surum etiketi (v-onsuz) + id: ver + shell: bash + run: echo "v=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Fat-jar derle + test + run: mvn -B -ntp clean verify + + - name: jpackage app-image (JRE gomulu) + shell: bash + run: | + jpackage --type app-image --input target --main-jar autoclicker.jar \ + --name AutoClicker --app-version "${{ steps.ver.outputs.v }}" --dest dist \ + --java-options "-Dfile.encoding=UTF-8" + + - name: app-image'i zip'le + shell: bash + run: | + cd dist + if [ "${{ runner.os }}" = "Windows" ]; then + 7z a "../AutoClicker-${{ runner.os }}.zip" "AutoClicker" >/dev/null + else + zip -r "../AutoClicker-${{ runner.os }}.zip" AutoClicker* >/dev/null + fi + + - name: Release'e OS paketini yukle + uses: softprops/action-gh-release@v2 + with: + prerelease: true + generate_release_notes: true + files: AutoClicker-${{ runner.os }}.zip + + - name: Release'e fat-jar yukle (yalnizca Linux) + if: runner.os == 'Linux' + uses: softprops/action-gh-release@v2 + with: + prerelease: true + files: target/autoclicker.jar diff --git a/.gitignore b/.gitignore index a7e2898..17a9e56 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,14 @@ build/ out/ bin/ target/ +dist/ # ──────────────────────────────────────── # Runtime config (config.properties vb.) # ──────────────────────────────────────── *.properties +# Maven wrapper properties'i *.properties'e ragmen izlenmeli +!.mvn/wrapper/maven-wrapper.properties # ──────────────────────────────────────── # IDE dosyaları diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ffcab66 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/README.md b/README.md index bb0e485..d72dc95 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ **Java tabanlı · modern · çok dilli · insan benzeri davranış üreten profesyonel makro motoru.** -[![Java](https://img.shields.io/badge/Java-8%2B-orange?logo=oracle&logoColor=white)](https://www.oracle.com/java/) +[![build](https://github.com/Ohualtex/Auto_Clicker/actions/workflows/build.yml/badge.svg)](https://github.com/Ohualtex/Auto_Clicker/actions/workflows/build.yml) +[![Java](https://img.shields.io/badge/Java-21%2B-orange?logo=oracle&logoColor=white)](https://www.oracle.com/java/) +[![Build](https://img.shields.io/badge/Build-Maven-C71A36?logo=apachemaven&logoColor=white)](https://maven.apache.org/) [![FlatLaf](https://img.shields.io/badge/UI-FlatLaf%203.4.1-blueviolet)](https://www.formdev.com/flatlaf/) [![JNativeHook](https://img.shields.io/badge/Hook-JNativeHook%202.2.2-success)](https://github.com/kwhat/jnativehook) [![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)]() @@ -17,22 +19,39 @@ Oyunları kurallarına, hayatı akışına bırakan profesyonel araca hoş geldi --- +## ⬇️ İndir (en kolay yol — Java gerekmez) + +[**Releases**](https://github.com/Ohualtex/Auto_Clicker/releases/latest) sayfasından işletim sisteminize uygun paketi indirin — **JRE gömülüdür, ayrıca Java kurmanıza gerek yoktur**: + +| Platform | Dosya | Çalıştırma | +|---|---|---| +| Windows | `AutoClicker-Windows.zip` | Aç → `AutoClicker\AutoClicker.exe` | +| macOS | `AutoClicker-macOS.zip` | Aç → `AutoClicker.app` | +| Linux | `AutoClicker-Linux.zip` | Aç → `AutoClicker/bin/AutoClicker` | +| Hepsi | `autoclicker.jar` | `java -jar autoclicker.jar` *(Java 21+ gerekir)* | + +--- + ## 🌟 Öne Çıkan Özellikler ### Otomasyon Çekirdeği -- **👁️ Piksel Tarayıcı (Reaction Bot)** — Belirlediğiniz (X,Y) noktasındaki renkte **ayarlanabilir tolerans (%1–100, varsayılan %5)** ile eşleşme veya değişim yakalandığında anında: fare tıklayabilir, özel bir tuşa basabilir, uyarı zili çalabilir. Envanter yönetiminden alarm sistemlerine kadar geniş kullanım alanı. -- **🔗 Zincir Makro (Combo) Motoru** — Fare hareketi (X,Y), klavye vuruşları ve bekleme (ms) adımlarını sıralayarak sonsuz döngülü dev kombinasyonlar inşa edin. -- **🖱️ Klasik Auto-Clicker & Auto-Typer** — Sol/Sağ/Orta/Çift tıklama ve istediğiniz tuşa periyodik basma (1–100 CPS), kilitli hedef koordinat opsiyonu. +- **👁️ Piksel Tarayıcı (Reaction Bot)** — Bir (X,Y) noktasındaki renkte **ayarlanabilir tolerans (%1–100)** ile eşleşme/değişim yakalandığında: fare tıklar, özel tuşa basar veya zil çalar. +- **🔗 Zincir Makro (Combo) Motoru** — Fare hareketi, tuş ve bekleme adımlarını sıralayıp sonsuz döngülü kombinasyonlar kurun. +- **⏺️ Makro Kaydet/Oynat (Record)** — Zincir sekmesinde **● Kaydet** ile gerçek tıklama/tuş/gecikmelerinizi yakalayıp makroya dönüştürün (ESC ile durdurun). +- **🖱️ Klasik Auto-Clicker & Auto-Typer** — Sol/Sağ/Orta/Çift tıklama, tuşa periyodik basma (1–100 CPS), kilitli hedef koordinat. ### Akıllı Davranış -- **🛡️ İnsan Modu (Humanizer / Anti-Ban)** — Saniye/milisaniye komutlarına ±%15 rastgele, **organik dalgalanma** ekleyerek bot korumalarını insan olduğunuza ikna eder. -- **🛑 Akıllı Limitör (Otomatik Kapanış)** — `Dakika` veya `Döngü Sayısı` dolduğunda makroyu durdurur; isterseniz **bilgisayarı güvenli kapatma**. Windows / macOS / Linux için uygun komutu çağırır, bilinmeyen sistemde sessiz kalmaz uyarı verir. - -### Kullanıcı Deneyimi -- **🌐 6 Dilde Anlık Çeviri (i18n)** — TR · EN · DE · FR · IT · RU. Dil geçişi gerçek zamanlı, yeniden başlatma yok. -- **🎨 Canlı Tema & Renk Sistemi** — FlatLaf Dark/Light tonu, metin rengi ve font boyutu çalışma esnasında değiştirilebilir. -- **ⓘ Bağlam Bilgi Butonları** — Her karmaşık ayarın yanındaki bilgi ikonu özelliği anlık açıklar (CPS, piksel koşulu, tarama hızı, anti-ban, limitör...). -- **🗃️ Global Hotkey & Otomatik Kayıt** — Tetik tuşunu serbestçe atayın (varsayılan **F6**), pencere arka planda olsa da çalışır. Tüm ayarlar, zincirler ve renk profili `config.properties` dosyasında kalıcı saklanır. +- **🛡️ İnsan Modu (Anti-Ban)** — Gecikmelere ±%15 organik dalgalanma ekler; **yumuşak (eğrisel) fare hareketi** ile ışınlanma yerine insan benzeri geçiş. +- **🛑 Akıllı Limitör + Güvenli Kapatma** — `Dakika`/`Döngü` dolunca durur; isteğe bağlı **PC kapatma** (Win/macOS/Linux'a uygun komut) — kapanış öncesi **iptal penceresi** ile geri alınabilir. +- **⏱️ Zamanlayıcı** — Makroyu N saniye gecikmeyle başlatın (geri sayımlı). + +### Güvenlik & Kullanıcı Deneyimi +- **🆘 ESC Panik Tuşu** — Her zaman açık, değiştirilemez acil durdurma. +- **👤 Profiller** — Birden çok kayıtlı ayar seti (Kaydet/Yükle/Sil). +- **📤 JSON Dışa/İçe Aktarma** — Zincir makrolarını paylaşılabilir dosyaya aktarın. +- **🗂️ Sistem Tepsisi (Tray)** — Simge durumuna küçültünce tepsiye gizlenir; tepsiden başlat/durdur/çıkış. +- **⌨️ Global + Per-Sekme Hotkey** — Tek global tetik (varsayılan **F6**) veya her makro sekmesine ayrı kısayol. +- **🌐 6 Dilde Anlık Çeviri · 🎨 Canlı Tema/Renk/Font · ⓘ Bağlam Bilgi Butonları** --- @@ -40,33 +59,32 @@ Oyunları kurallarına, hayatı akışına bırakan profesyonel araca hoş geldi | Sekme | Açıklama | |---|---| -| 🖱️ **Fare** | Klasik auto-clicker. CPS slider, tıklama tipi, kilitli koordinat seçeneği. | +| 🖱️ **Fare** | Klasik auto-clicker. CPS, tıklama tipi, kilitli koordinat. | | ⌨️ **Klavye** | Belirli bir tuşa periyodik basma. | -| 🔗 **Zincir** | Combo macro: tıklama + tuş + ışınlama + bekleme adımlarından oluşan sıralı senaryo. | -| 👁️ **Piksel** | Renk algılama tabanlı tepki botu. Eşleşme/Değişim koşulu + tolerans. | -| ⚙️ **Ayarlar** | Hotkey, dil, tema, renk, limitör konfigürasyonu. | +| 🔗 **Zincir** | Combo macro + JSON içe/dışa aktar + **kayıt (record)**. | +| 👁️ **Piksel** | Renk algılama tabanlı tepki botu (eşleşme/değişim + tolerans). | +| ⚙️ **Ayarlar** | Hotkey, per-sekme hotkey, limitör, zamanlayıcı, profil, dil, tema. | --- -## 🚀 Kurulum +## 🚀 Kaynaktan Çalıştırma / Derleme + +Proje **Maven** ile derlenir; **Maven kurmanıza gerek yok** — depoyla gelen wrapper (`mvnw`) yeterli. Yalnızca **JDK 21+** gerekir. -### Gereksinimler -- **Java JDK veya JRE 8+** (minimum gereksinim; geliştirme/test JDK 25 ile yapıldı — `java -version` ile kontrol) -- Windows için `run.bat` her şeyi halleder; diğer platformlarda aşağıdaki manuel adımlar. +```bash +# Derle + test + tek-dosya (fat) jar üret +./mvnw clean package # Windows: mvnw.cmd clean package + +# Çalıştır +java -jar target/autoclicker.jar +``` -### Hızlı Başlangıç (Windows) -1. **`run.bat`** dosyasına çift tıklayın. -2. İlk çalıştırmada FlatLaf ve JNativeHook jar'ları PowerShell ile `lib/` altına otomatik indirilir. -3. Açılan arayüzde sekmenizi seçin; her ayarın yanındaki **ⓘ** butonuna tıklayarak işlevini öğrenin. -4. Oyun/uygulama içindeyken hotkey'inizle (varsayılan **F6**) başlatın/durdurun. +**Windows kısayolu:** `run.bat` çift tıklayın — `mvnw` ile derleyip başlatır. -### Manuel Derleme (macOS / Linux) +**Native installer (JRE gömülü) üretmek:** ```bash -mkdir -p lib -curl -sL https://repo1.maven.org/maven2/com/github/kwhat/jnativehook/2.2.2/jnativehook-2.2.2.jar -o lib/jnativehook.jar -curl -sL https://repo1.maven.org/maven2/com/formdev/flatlaf/3.4.1/flatlaf-3.4.1.jar -o lib/flatlaf.jar -javac -encoding UTF-8 -cp ".:lib/*" AutoClicker.java -java -Dfile.encoding=UTF-8 -cp ".:lib/*" AutoClicker +scripts/make-installer.sh # macOS/Linux -> dist/AutoClicker +scripts\make-installer.bat # Windows -> dist\AutoClicker\AutoClicker.exe ``` --- @@ -76,35 +94,49 @@ java -Dfile.encoding=UTF-8 -cp ".:lib/*" AutoClicker | Tuş | Eylem | |---|---| | **F6** *(varsayılan)* | Aktif sekmenin makrosunu **Başlat / Durdur** | +| **ESC** | **Acil durdurma** (panik) — her zaman açık, değiştirilemez | | **Orta Tık** | Konum seçme modunda hedefi kilitler | -| **Ayarlar → Hotkey** | İstediğiniz herhangi bir tuşa kısayol atama | +| Ayarlar → **Sekme Kısayolları** | Her makroya ayrı hotkey ata | --- ## 🛠️ Mimari & Teknolojiler -| Katman | Bileşen | Sürüm | Görev | -|---|---|---|---| -| UI | Java Swing + FormDev FlatLaf | 3.4.1 | Sekmeler, ikonlar, canlı tema | -| Donanım | `java.awt.Robot` + `MouseInfo` | JDK | DPI uyumlu koordinat, tıklama/tuş simülasyonu | -| Global Hook | JNativeHook | 2.2.2 | OS-seviyesi (kernel) tuş & fare dinleme | -| i18n | In-memory `String[][]` mapping | — | 6 dil hot-reload, restart yok | -| Persistence | `java.util.Properties` | JDK | `config.properties` — ayarlar, zincirler, renkler | +Tek dosyadan paket-katmanlı yapıya geçildi (v7): + +``` +com.ohualtex.autoclicker +├── AutoClicker Swing UI + orkestrasyon (MacroEngine/Strategy) +├── config/ ConfigStore (config + profil dosya I/O) +├── core/ Humanizer · ShutdownCommand · ConfigPaths · MousePath (saf, JUnit testli) +├── i18n/ Lang (6 dilli sözlük) +├── model/ ActionType · MacroAction +└── ui/ Icons (vektör ikonlar) +``` + +| Katman | Bileşen | Görev | +|---|---|---| +| Build | Maven (+ wrapper) · shade · jpackage | fat-jar, native installer | +| UI | Java Swing + FlatLaf 3.4.1 | sekmeler, ikonlar, canlı tema | +| Donanım | `java.awt.Robot` + `MouseInfo` | koordinat, tıklama/tuş simülasyonu | +| Global Hook | JNativeHook 2.2.2 | OS-seviyesi tuş & fare dinleme + kayıt | +| Veri | Gson · `java.util.Properties` | JSON makro · config/profil | +| CI/CD | GitHub Actions | her push'ta derle+test; tag'de çok-OS release | --- ## 📌 Notlar & Uyumluluk -- **Karakter kodlaması:** `run.bat`, `javaw` çağrısına `-Dfile.encoding=UTF-8` ekler. Türkçe karakterler `ç ğ ı ö ş ü` doğru render edilir. -- **Otomatik kapatma:** Limitör → *Shutdown* seçimi Windows'ta `shutdown -s -t 15`, macOS/Linux'ta `shutdown -h +1` çağırır. Bilinmeyen sistemde kullanıcıya uyarı verir, müdahale etmez. -- **JNativeHook + macOS:** macOS'ta *Erişilebilirlik* izni gerekir (`System Settings → Privacy & Security → Accessibility`). -- **Anti-cheat oyunlar:** Bu araç öğrenme ve verimlilik amacıyla yapılmıştır. Çok oyunculu / anti-cheat korumalı oyunlarda kullanmak hesap askısı veya ban riski taşır — sorumluluk kullanıcıya aittir. +- **Ayar konumu:** Ayarlar `%APPDATA%\AutoClicker\config.properties` (Windows) veya `~/.autoclicker/` (macOS/Linux) altında saklanır; installer ile kurulumda bile yazılabilir. +- **Otomatik kapatma:** Limitör → *Kapat* seçimi OS'a uygun komutu çalıştırır ve **iptal penceresi** sunar; AFK iseniz kapanır, başınızdaysanız iptal edebilirsiniz. Desteklenmeyen OS'ta uyarı verir. +- **macOS izni:** JNativeHook için *Erişilebilirlik* izni gerekir (`System Settings → Privacy & Security → Accessibility`). +- **Anti-cheat oyunlar:** Bu araç öğrenme ve verimlilik amacıyladır. Anti-cheat korumalı oyunlarda kullanmak ban riski taşır — sorumluluk kullanıcıya aittir. ---
-**AutoClicker Ultimate** — V6 sürüm hattı +**AutoClicker Ultimate** — V7 sürüm hattı Made with ☕ and Java Swing
diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e04cd08 --- /dev/null +++ b/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + com.ohualtex + autoclicker + 7.5 + jar + + AutoClicker Ultimate + Java Swing tabanli otomatik tiklayici / makro araci (FlatLaf + JNativeHook) + + + 17 + UTF-8 + com.ohualtex.autoclicker.AutoClicker + + 3.4.1 + 2.2.2 + 5.10.2 + + + + + com.formdev + flatlaf + ${flatlaf.version} + + + com.github.kwhat + jnativehook + ${jnativehook.version} + + + com.google.code.gson + gson + 2.11.0 + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + autoclicker + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + ${main.class} + + ${project.version} + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + diff --git a/run.bat b/run.bat index 6c3fbdf..160fd37 100644 --- a/run.bat +++ b/run.bat @@ -2,28 +2,14 @@ setlocal cd /d "%~dp0" -IF NOT EXIST "lib" ( - mkdir lib -) - -IF NOT EXIST "lib\jnativehook.jar" ( - echo [INFO] JNativeHook kutuphanesi indiriliyor... - powershell -Command "Invoke-WebRequest -Uri 'https://repo1.maven.org/maven2/com/github/kwhat/jnativehook/2.2.2/jnativehook-2.2.2.jar' -OutFile 'lib\jnativehook.jar'" -) - -IF NOT EXIST "lib\flatlaf.jar" ( - echo [INFO] FlatLaf Dark Mode kutuphanesi indiriliyor... - powershell -Command "Invoke-WebRequest -Uri 'https://repo1.maven.org/maven2/com/formdev/flatlaf/3.4.1/flatlaf-3.4.1.jar' -OutFile 'lib\flatlaf.jar'" -) - -echo [INFO] Kod derleniyor... -javac -encoding UTF-8 -cp ".;lib\jnativehook.jar;lib\flatlaf.jar" AutoClicker.java +echo [INFO] Maven (wrapper) ile derleniyor (fat-jar)... +call mvnw.cmd -q clean package IF %ERRORLEVEL% NEQ 0 ( - echo [ERROR] Derleme hatasi olustu! Lutfen dosyayi kontrol edin. + echo [ERROR] Derleme hatasi olustu! Lutfen ciktiyi kontrol edin. pause exit /b %ERRORLEVEL% ) echo [INFO] AutoClicker baslatiliyor... -start javaw -Dfile.encoding=UTF-8 -cp ".;lib\jnativehook.jar;lib\flatlaf.jar" AutoClicker +start javaw -jar target\autoclicker.jar exit diff --git a/scripts/make-installer.bat b/scripts/make-installer.bat new file mode 100644 index 0000000..33e8644 --- /dev/null +++ b/scripts/make-installer.bat @@ -0,0 +1,24 @@ +@echo off +REM AutoClicker - Windows native paket (JRE gomulu, kullanicida Java gerekmez) +REM app-image: WiX gerektirmez, dist\AutoClicker\AutoClicker.exe uretir. +REM .exe/.msi installer icin WiX Toolset kurulu olmali; o zaman --type msi kullanin. +setlocal +cd /d "%~dp0\.." + +echo [INFO] Fat-jar derleniyor... +call mvnw.cmd -q clean package +IF %ERRORLEVEL% NEQ 0 ( echo [ERROR] Derleme hatasi! & exit /b 1 ) + +set "JPACKAGE=jpackage" +if defined JAVA_HOME if exist "%JAVA_HOME%\bin\jpackage.exe" set "JPACKAGE=%JAVA_HOME%\bin\jpackage.exe" + +if exist dist rmdir /s /q dist + +echo [INFO] app-image olusturuluyor (JRE gomulu)... +"%JPACKAGE%" --type app-image --input target --main-jar autoclicker.jar ^ + --name AutoClicker --app-version 7.2 --dest dist ^ + --java-options "-Dfile.encoding=UTF-8" +IF %ERRORLEVEL% NEQ 0 ( echo [ERROR] jpackage basarisiz! & exit /b 1 ) + +echo [OK] Olusturuldu: dist\AutoClicker\AutoClicker.exe +echo (.msi installer icin WiX Toolset kurup --type msi kullanin) diff --git a/scripts/make-installer.sh b/scripts/make-installer.sh new file mode 100644 index 0000000..0779864 --- /dev/null +++ b/scripts/make-installer.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# AutoClicker - macOS/Linux native paket (JRE gomulu, kullanicida Java gerekmez) +# Linux'ta .deb/.rpm icin sirasiyla dpkg/rpmbuild gerekir; macOS'ta .dmg uretilir. +# Bagimsiz calistirilabilir image icin: --type app-image +set -e +cd "$(dirname "$0")/.." + +echo "[INFO] Fat-jar derleniyor..." +./mvnw -q clean package + +JPACKAGE="jpackage" +if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/jpackage" ]; then + JPACKAGE="$JAVA_HOME/bin/jpackage" +fi + +rm -rf dist +echo "[INFO] app-image olusturuluyor (JRE gomulu)..." +"$JPACKAGE" --type app-image --input target --main-jar autoclicker.jar \ + --name AutoClicker --app-version 7.2 --dest dist \ + --java-options "-Dfile.encoding=UTF-8" + +echo "[OK] Olusturuldu: dist/AutoClicker" diff --git a/AutoClicker.java b/src/main/java/com/ohualtex/autoclicker/AutoClicker.java similarity index 61% rename from AutoClicker.java rename to src/main/java/com/ohualtex/autoclicker/AutoClicker.java index bac13a5..806babe 100644 --- a/AutoClicker.java +++ b/src/main/java/com/ohualtex/autoclicker/AutoClicker.java @@ -1,3 +1,18 @@ +package com.ohualtex.autoclicker; + +import com.ohualtex.autoclicker.config.ConfigStore; +import com.ohualtex.autoclicker.core.Humanizer; +import com.ohualtex.autoclicker.core.MousePath; +import com.ohualtex.autoclicker.core.ShutdownCommand; +import com.ohualtex.autoclicker.i18n.Lang; +import com.ohualtex.autoclicker.model.ActionType; +import com.ohualtex.autoclicker.model.MacroAction; +import com.ohualtex.autoclicker.ui.Icons; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.FlatLightLaf; import com.github.kwhat.jnativehook.GlobalScreen; @@ -11,6 +26,7 @@ import javax.swing.plaf.FontUIResource; import java.awt.*; import java.awt.event.*; +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Properties; @@ -20,118 +36,8 @@ public class AutoClicker extends JFrame implements NativeKeyListener, NativeMouseListener { - static class Lang { - static int L = 0; // 0=TR, 1=EN, 2=DE, 3=FR, 4=IT, 5=RU - static java.util.Map d = new java.util.HashMap<>(); - static { - d.put("st_idle", new String[]{"DURUM: BEKLIYOR", "STATUS: IDLE", "STATUS: BEREIT", "STATUT: EN ATTENTE", "STATO: IN ATTESA", "СТАТУС: ОЖИДАНИЕ"}); - d.put("st_run", new String[]{"DURUM: CALISIYOR", "STATUS: RUNNING", "STATUS: LÄUFT", "STATUT: EN COURS", "STATO: IN ESECUZIONE", "СТАТУС: РАБОТАЕТ"}); - d.put("st_lim", new String[]{"DURUM: LIMIT YENDI", "STATUS: LIMIT HIT", "STATUS: LIMIT ERREICHT", "STATUT: LIMITE", "STATO: LIMITE RAGGIUNTO", "СТАТУС: ЛИМИТ"}); - - d.put("t_mouse", new String[]{"Fare", "Mouse", "Maus", "Souris", "Mouse", "Мышь"}); - d.put("t_key", new String[]{"Klavye", "Keyboard", "Tastatur", "Clavier", "Tastiera", "Клавиатура"}); - d.put("t_chain", new String[]{"Zincir", "Chain", "Kette", "Chaîne", "Catena", "Цепь"}); - d.put("t_px", new String[]{"Piksel", "Pixel", "Pixel", "Pixel", "Pixel", "Пиксель"}); - d.put("t_set", new String[]{"Ayarlar", "Settings", "Einstellungen", "Paramètres", "Impostazioni", "Настройки"}); - - d.put("c_type", new String[]{"Tiklama Tipi: ", "Click Type: ", "Klick-Typ: ", "Type de clic: ", "Tipo di clic: ", "Тип клика: "}); - d.put("l_click", new String[]{"Sol Tik", "Left Click", "Linksklick", "Clic Gauche", "Clic Sinistro", "Левый Клик"}); - d.put("r_click", new String[]{"Sag Tik", "Right Click", "Rechtsklick", "Clic Droit", "Clic Destro", "Правый Клик"}); - d.put("m_click", new String[]{"Orta Tik", "Mid Click", "Mittelklick", "Clic Milieu", "Clic Centrale", "Средний Клик"}); - d.put("d_click", new String[]{"Cift Sol Tik", "Double Click", "Doppelklick", "Double Clic", "Doppio Clic", "Двойной Клик"}); - - d.put("fix_loc", new String[]{"Sabit Konuma Tikla", "Click Fixed Loc", "Feste Position", "Bouton Fixe", "Pos. Fissa", "Фикс. Место"}); - d.put("pick_loc", new String[]{"Konum Sec", "Pick Loc", "Wählen", "Choisir", "Seleziona", "Выбрать"}); - d.put("wait_mid", new String[]{"Bekliyor (Orta Tik)", "Waiting (Mid Click)", "Wartet (Mittelklick)", "Attente (Milieu)", "Attesa (Centrale)", "Ожидание (Ср. клик)"}); - - d.put("anti_ban", new String[]{"Insan Modu (Rastgele Gecikme)", "Humanizer (Random Delay)", "Menschenmodus (Zufall)", "Mode Humain (Délais Aléat.)", "Modalità Umana (Ritardo)", "Анти-Бан (случайная задержка)"}); - d.put("cps", new String[]{"Fare Hizi (CPS):", "Mouse Speed (CPS):", "Maus-Geschw. (CPS):", "Vitesse Souris (CPS):", "Velocità Mouse (CPS):", "Скорость Мыши (CPS):"}); - d.put("key_cps", new String[]{"Klavye Hizi (Saniyede):", "Key Speed (/sec):", "Tasten-Geschw. (/s):", "Vitesse Touche (/s):", "Velocità Tasto (/s):", "Скор. Клавиатуры (/с):"}); - - d.put("key_trg", new String[]{"Basilacak Tus:", "Target Key:", "Zieltaste:", "Touche Cible:", "Tasto Obiettivo:", "Целевая Клавиша:"}); - d.put("set_k", new String[]{"Tus Ata", "Set Key", "Setzen", "Définir", "Assegna", "Назначить"}); - d.put("press_k", new String[]{"Basin...", "Press...", "Drücken...", "Appuyez...", "Premi...", "Нажмите..."}); - - d.put("add_act", new String[]{"(+) Eylem Ekle", "(+) Add Action", "(+) Aktion", "(+) Ajouter", "(+) Aggiungi", "(+) Добавить"}); - d.put("del_act", new String[]{"(-) Secileni Sil", "(-) Remove", "(-) Löschen", "(-) Supprimer", "(-) Rimuovi", "(-) Удалить"}); - d.put("clr_act", new String[]{"(x) Temizle", "(x) Clear", "(x) Leeren", "(x) Effacer", "(x) Pulisci", "(x) Очистить"}); - - d.put("act_typ", new String[]{"Eylem Turu:", "Action Type:", "Aktionsart:", "Type d'action:", "Tipo di Azione:", "Тип Действия:"}); - d.put("ms_delay", new String[]{"Milisaniye (Gecikme):", "Delay (ms):", "Verzögerung (ms):", "Délai (ms):", "Ritardo (ms):", "Задержка (мс):"}); - d.put("list_add", new String[]{"[v] Listeye Ekle", "[v] Add to List", "[v] Hinzufügen", "[v] Ajouter", "[v] Aggiungi", "[v] Добавить"}); - d.put("tnt_mouse", new String[]{"Fare Tiklamasi", "Mouse Click", "Mausklick", "Clic Souris", "Clic Mouse", "Клик Мыши"}); - d.put("tnt_key", new String[]{"Klavye Tusu", "Key Press", "Taste Drücken", "Appui Touche", "Pressione Tasto", "Нажатие Клавиши"}); - d.put("tnt_move", new String[]{"Fareyi Tasi", "Move Mouse", "Maus Bewegen", "Bouger Souris", "Sposta Mouse", "Сдвинуть Мышь"}); - - d.put("px_1", new String[]{"1. Gozetlenecek Piksel", "1. Target Pixel", "1. Zielpixel", "1. Pixel Cible", "1. Pixel Obiettivo", "1. Цель Пиксель"}); - d.put("px_clr", new String[]{"Renk:", "Color:", "Farbe:", "Couleur:", "Colore:", "Цвет:"}); - d.put("px_2", new String[]{"2. Tetiklenme Sartlari", "2. Conditions", "2. Bedingungen", "2. Conditions", "2. Condizioni", "2. Условия"}); - d.put("px_cond1", new String[]{"Renk ESLESTIGINDE", "When Color MATCHES", "Farbe ÜBEREINSTIMMT", "Couleur CORRESPOND", "Colore CORRISPONDE", "Когда совпадает"}); - d.put("px_cond2", new String[]{"Renk DEGISTIGINDE", "When Color CHANGES", "Farbe WECHSELT", "Couleur CHANGE", "Colore CAMBIA", "Когда меняется"}); - d.put("px_tol", new String[]{"Tolerans Payi (%):", "Tolerance (%):", "Toleranz (%):", "Tolérance (%):", "Tolleranza (%):", "Допуск (%):"}); - d.put("px_3", new String[]{"3. Gerceklesecek Tepki", "3. Reaction Action", "3. Reaktion", "3. Réaction", "3. Reazione", "3. Реакция"}); - d.put("px_act3", new String[]{"Ozel Tusa Bas", "Press Custom Key", "Zieltaste drücken", "Appuyer Touche", "Premi Tasto", "Своя Клавиша"}); - d.put("px_act4", new String[]{"Sadece Zili Cal", "Only Beep", "Nur Piepen", "Bip Sonore", "Suona Solo", "Только Звук"}); - d.put("px_rate", new String[]{"Tarama Hizi (Ms):", "Scanner Rate (Ms):", "Scanrate (Ms):", "Vitesse Scan (Ms):", "Velocità Scan (Ms):", "Скорость (Мс):"}); - - d.put("set_hk", new String[]{"Kisayol Tusu (Baslat/Durdur):", "Start/Stop Hotkey:", "Start/Stopp Hotkey:", "Raccourci Start/Stop:", "Tasto Avvio/Arresto:", "Горячая Клавиша:"}); - d.put("lim_title", new String[]{"Otomatik Durdurma Sinirlari", "Auto Stop Limiters", "Auto-Stopp Begrenzungen", "Limites Arrêt Auto", "Limiti Arresto Auto", "Лимиты Автоостановки"}); - d.put("lim_use", new String[]{"Limitoru Aktif Et", "Enable Limiter", "Limiter aktivieren", "Activer Limiteur", "Abilita Limitatore", "Вкл. Лимитер"}); - d.put("lim_after", new String[]{"Sinir:", "Limit:", "Limit:", "Dans:", "Dopo:", "Лимит:"}); - d.put("lim_min", new String[]{"Dakika Sonra", "Minutes Later", "Minuten", "Minutes", "Minuti", "Минут"}); - d.put("lim_iter", new String[]{"Dongu Sonra", "Iterations Later", "Iterationen", "Itérations", "Iterazioni", "Итераций"}); - d.put("lim_stop", new String[]{"Sadece Makroyu Durdur", "Just Stop Macro", "Makro stoppen", "Arrêter Macro", "Ferma Macro", "Только стоп"}); - d.put("lim_shut", new String[]{"Bilgisayari Kapat", "Shutdown PC", "PC herunterfahren", "Éteindre le PC", "Spegni PC", "Выкл ПК"}); - - d.put("style", new String[]{"(O) Gorunum Ozellestirme", "(O) UI Customization", "(O) UI Anpassung", "(O) Apparence UI", "(O) Aspetto UI", "(O) Дизайн UI"}); - d.put("theme", new String[]{"Ana Tema:", "Main Theme:", "Hauptthema:", "Thème:", "Tema Principale:", "Тема:"}); - d.put("theme_d", new String[]{"Karanlik Mod", "Dark Mode", "Dunkler Modus", "Sombre", "Modo Scuro", "Темный"}); - d.put("theme_l", new String[]{"Aydinlik Mod", "Light Mode", "Heller Modus", "Clair", "Modo Chiaro", "Светлый"}); - d.put("txt_size", new String[]{"Yazi Boyutu:", "Text Size:", "Textgröße:", "Taille:", "Dimensione:", "Размер:"}); - d.put("txt_col", new String[]{"Metin Rengi:", "Text Color:", "Textfarbe:", "Couleur Texte:", "Colore Testo:", "Цвет Текста:"}); - d.put("col_pick", new String[]{"Renk Sec", "Pick Color", "Farbe", "Choisir", "Seleziona", "Выбрать Цвет"}); - d.put("apply_s", new String[]{"Uygula/Kaydet", "Apply/Save", "Anwenden", "Appliquer", "Applica", "Применить"}); - d.put("lang_title", new String[]{"(L) Dil (Language)", "(L) Language", "(L) Sprache", "(L) Langue", "(L) Lingua", "(L) Язык"}); - - d.put("info_hum", new String[]{"Anti-Ban: Robotik tiklamalari saptirmak icin gecikmelere minik sapmalar ekler.", "Anti-Ban: Adds random fluctuations to delays to simulate human behavior and evade detection.", "Anti-Ban: Fügt den Verzögerungen zufällige Schwankungen hinzu.", "Anti-Ban: Ajoute des fluctuations aléatoires aux délais.", "Anti-Ban: Aggiunge fluttuazioni casuali ai ritardi.", "Анти-Бан: Добавляет случайные колебания к задержкам."}); - d.put("info_lim", new String[]{"Otomasyonu belirli bir sure sonra kapatir.", "Stops the automation automatically after a set time or cycle count. Ideal for AFK macros.", "Stoppt die Automatisierung automatisch nach einer Weile.", "Arrête automatiquement l'automatisation.", "Ferma l'automazione in base a limiti.", "Останавливает автоматизацию при достижении лимита."}); - d.put("info_px", new String[]{"Renk Toleransi: Ufak golge farkliliklarinin renk algisini bozmasini engeller.", "Color Tolerance: Prevents minor in-game shading/lighting shifts from ruining detection.", "Farbtoleranz: Verhindert kleine Schattenfehler.", "Tolérance: Empêche les petits changements de lumière de fausser la détection.", "Tolleranza: Evita che l'illuminazione rompa l'algoritmo.", "Допуск: Игнорирует изменения освещения."}); - d.put("info_cps", new String[]{"Saniyedeki tiklama hizini belirler (Click Per Second).", "Sets the click speed per second (CPS).", "Legt die Klicks pro Sekunde fest.", "Définit la vitesse de clic (CPS).", "Imposta i clic al secondo (CPS).", "Устанавливает кликов в секунду (CPS)."}); - d.put("info_px_cond", new String[]{"Eslestiginde: Renk gorundugunde tepki verir.\nDegistiginde: Renk kayboldugunda tepki verir.", "Matches: Reacts when color appears.\nChanges: Reacts when color disappears.", "Stimmt überein: Reagiert, wenn die Farbe erscheint.", "Correspond: Agit quand la couleur apparait.", "Corrisponde: Agisce quando il colore appare.", "Совпадает: Реагирует на появление цвета."}); - d.put("info_px_rate", new String[]{"Tarama Hizi: Ekranin ne siklikla kontrol edilecegini belirler (Milisaniye).", "Scan Rate: How often to check the screen (Milliseconds).", "Scanrate: Wie oft der Bildschirm überprüft wird (ms).", "Taux de scan: Fréquence de vérification (ms).", "Velocità scan: Frequenza di controllo schermo (ms).", "Скорость: Частота проверки экрана (мс)."}); - - d.put("info_title", new String[]{"Bilgi", "Info", "Info", "Info", "Info", "Информация"}); - d.put("reset", new String[]{"Sifirla", "Reset", "Zurücksetzen", "Réinit.", "Reimposta", "Сброс"}); - d.put("shut_ok", new String[]{"Limit asildi! Bilgisayar %s icinde kapatilacak.", "Limit reached! PC will shut down in %s.", "Limit erreicht! PC fährt in %s herunter.", "Limite atteinte! Le PC s'eteindra dans %s.", "Limite raggiunto! Il PC si spegnera tra %s.", "Лимит достигнут! ПК выключится через %s."}); - d.put("shut_fail", new String[]{"Kapatma komutu basarisiz oldu: ", "Shutdown command failed: ", "Herunterfahren fehlgeschlagen: ", "Echec de la commande d'arret: ", "Comando di spegnimento fallito: ", "Сбой команды выключения: "}); - d.put("shut_unsup", new String[]{"Bu isletim sisteminde otomatik kapatma desteklenmiyor: ", "Automatic shutdown not supported on this OS: ", "Automatisches Herunterfahren auf diesem OS nicht unterstützt: ", "Arret automatique non supporte sur cet OS: ", "Spegnimento automatico non supportato su questo OS: ", "Автовыключение не поддерживается в этой ОС: "}); - } - static String get(String key) { return d.containsKey(key) ? d.get(key)[L] : key; } - } - - enum ActionType { MOUSE_CLICK, KEY_PRESS, MOUSE_MOVE, DELAY } - - static class MacroAction { - ActionType type; - int p1, p2; - public MacroAction(ActionType type, int p1, int p2) { this.type = type; this.p1 = p1; this.p2 = p2; } - public String toString() { - switch(type) { - case MOUSE_CLICK: return Lang.get("tnt_mouse") + ": " + (p1==InputEvent.BUTTON1_DOWN_MASK ? Lang.get("l_click") : p1==InputEvent.BUTTON3_DOWN_MASK ? Lang.get("r_click") : p1==999 ? Lang.get("d_click") : Lang.get("m_click")); - case KEY_PRESS: return Lang.get("tnt_key") + ": " + KeyEvent.getKeyText(p1); - case MOUSE_MOVE: return Lang.get("tnt_move") + ": X=" + p1 + ", Y=" + p2; - case DELAY: return Lang.get("ms_delay") + " " + p1; - default: return "?"; - } - } - public String serialize() { return type.name()+":"+p1+":"+p2; } - public static MacroAction deserialize(String s) { - String[] parts = s.split(":"); - return new MacroAction(ActionType.valueOf(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2])); - } - } - private final String CONFIG_FILE = "config.properties"; + private final ConfigStore configStore = new ConfigStore(); private Properties props = new Properties(); private volatile int triggerKey = NativeKeyEvent.VC_F6; @@ -145,6 +51,7 @@ public static MacroAction deserialize(String s) { // UI private JLabel statusLabel; private JButton hotkeyBtn; + private JTabbedPane tabbedPane; // Limits private JCheckBox useLimitBox; @@ -194,6 +101,27 @@ public static MacroAction deserialize(String s) { private JPanel customColorPreview; private JComboBox langBox; + // Scheduler (gecikmeli baslat) + private JCheckBox schedulerBox; + private JTextField schedulerDelayField; + private javax.swing.Timer scheduleTimer; + + // System tray + private TrayIcon trayIcon; + + // Per-sekme (per-makro) hotkey: 0=Fare 1=Klavye 2=Zincir 3=Piksel (0 = atanmamis) + private final int[] tabHotkey = new int[4]; + private final JButton[] tabHotkeyBtns = new JButton[4]; + private volatile int listeningForTabHotkey = -1; + + // Profiller + private JComboBox profileBox; + + // Record/replay (gercek tiklama/tus kaydi -> zincire) + private volatile boolean recording = false; + private volatile long recordLastTime = 0L; + private JButton recordBtn; + public AutoClicker() { try { robot = new Robot(); @@ -207,7 +135,7 @@ public AutoClicker() { Lang.L = Integer.parseInt(props.getProperty("langIndex", "0")); applyInitialTheme(); - setTitle("AutoClicker Ultimate v6.1"); + setTitle("AutoClicker Ultimate v" + appVersion()); setSize(600, 780); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); @@ -215,7 +143,8 @@ public AutoClicker() { initUI(); initJNativeHook(); - + initTray(); + addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -224,8 +153,55 @@ public void windowClosing(WindowEvent e) { }); } + // Sistem tepsisi ikonu: simge durumuna kuculunce gizle, tray menusunden goster/baslat-durdur/cikis + private void initTray() { + if (!SystemTray.isSupported()) return; + try { + PopupMenu menu = new PopupMenu(); + MenuItem showItem = new MenuItem(Lang.get("tray_show")); + showItem.addActionListener(e -> restoreFromTray()); + MenuItem toggleItem = new MenuItem(Lang.get("tray_toggle")); + toggleItem.addActionListener(e -> SwingUtilities.invokeLater(this::toggle)); + MenuItem exitItem = new MenuItem(Lang.get("tray_exit")); + exitItem.addActionListener(e -> { saveConfig(); System.exit(0); }); + menu.add(showItem); menu.add(toggleItem); menu.addSeparator(); menu.add(exitItem); + + trayIcon = new TrayIcon(buildTrayImage(), "AutoClicker", menu); + trayIcon.setImageAutoSize(true); + trayIcon.addActionListener(e -> restoreFromTray()); // cift tik + SystemTray.getSystemTray().add(trayIcon); + + addWindowStateListener(e -> { + if ((e.getNewState() & JFrame.ICONIFIED) != 0) setVisible(false); // simge durumu -> tepsiye gizle + }); + } catch (Exception ex) { + trayIcon = null; + } + } + + private void restoreFromTray() { + SwingUtilities.invokeLater(() -> { + setVisible(true); + setExtendedState(JFrame.NORMAL); + toFront(); + requestFocus(); + }); + } + + private Image buildTrayImage() { + java.awt.image.BufferedImage bi = new java.awt.image.BufferedImage(16, 16, java.awt.image.BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(new Color(90, 160, 255)); + g.fillOval(1, 1, 14, 14); + g.setColor(Color.WHITE); + g.fillOval(6, 6, 4, 4); + g.dispose(); + return bi; + } + private void rebuildUI() { - JTabbedPane tabs = (JTabbedPane) ((BorderLayout) getContentPane().getLayout()).getLayoutComponent(BorderLayout.CENTER); + JTabbedPane tabs = tabbedPane; int currentTab = (tabs != null) ? tabs.getSelectedIndex() : 0; int scrollVal = 0; if (currentTab == 4 && tabs != null) { @@ -238,7 +214,7 @@ private void rebuildUI() { applyInitialTheme(); initUI(); - tabs = (JTabbedPane) ((BorderLayout) getContentPane().getLayout()).getLayoutComponent(BorderLayout.CENTER); + tabs = tabbedPane; if (tabs != null) { tabs.setSelectedIndex(currentTab); if (currentTab == 4) { @@ -251,57 +227,8 @@ private void rebuildUI() { repaint(); } - // Native 2D Icons - static class MouseIcon implements Icon { - public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(c.getForeground()); g2.drawRoundRect(x+3, y+1, 10, 14, 6, 6); g2.drawLine(x+8, y+1, x+8, y+5); g2.drawOval(x+7, y+3, 2, 4); g2.dispose(); - } - } - static class KeyIcon implements Icon { - public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(c.getForeground()); g2.drawRoundRect(x+1, y+4, 14, 8, 2, 2); - g2.fillRect(x+3, y+6, 2, 2); g2.fillRect(x+7, y+6, 2, 2); g2.fillRect(x+11, y+6, 2, 2); g2.fillRect(x+5, y+9, 6, 2); g2.dispose(); - } - } - static class ChainIcon implements Icon { - public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setStroke(new BasicStroke(2)); g2.setColor(c.getForeground()); - g2.drawOval(x+1, y+5, 8, 6); g2.drawOval(x+7, y+5, 8, 6); g2.dispose(); - } - } - static class PixelIcon implements Icon { - public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(c.getForeground()); g2.drawOval(x+2, y+2, 12, 12); - g2.drawLine(x+8, y+0, x+8, y+4); g2.drawLine(x+8, y+12, x+8, y+16); - g2.drawLine(x+0, y+8, x+4, y+8); g2.drawLine(x+12, y+8, x+16, y+8); g2.fillOval(x+6, y+6, 4, 4); g2.dispose(); - } - } - static class GearIcon implements Icon { - public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(c.getForeground()); g2.setStroke(new BasicStroke(2)); g2.drawOval(x+4, y+4, 8, 8); - for(int i=0; i<8; i++) { double a = i * Math.PI/4; g2.drawLine(x+8+(int)(4*Math.cos(a)), y+8+(int)(4*Math.sin(a)), x+8+(int)(7*Math.cos(a)), y+8+(int)(7*Math.sin(a))); } g2.dispose(); - } - } - static class InfoIcon implements Icon { - public int getIconWidth() { return 14; } public int getIconHeight() { return 14; } - public void paintIcon(Component c, Graphics g, int x, int y) { - Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(c.getForeground()); g2.drawOval(x+1, y+1, 12, 12); g2.fillRect(x+6, y+4, 2, 2); g2.fillRect(x+6, y+7, 2, 4); g2.dispose(); - } - } - private JButton createInfoButton(String tooltipKey) { - JButton btn = new JButton(new InfoIcon()); + JButton btn = new JButton(new Icons.InfoIcon()); btn.setMargin(new Insets(0,0,0,0)); btn.setBorderPainted(false); btn.setContentAreaFilled(false); @@ -380,21 +307,54 @@ private void applyThemeAndFont(boolean isDark, int fontSize, Color fgColor) { } private void loadConfig() { - try (FileInputStream fis = new FileInputStream(CONFIG_FILE)) { - props.load(fis); - triggerKey = Integer.parseInt(props.getProperty("hotkey", String.valueOf(NativeKeyEvent.VC_F6))); - } catch (Exception e) { - // Ilk acilista dosya yoklugu normaldir; digerlerini bildir - if (!(e instanceof java.io.FileNotFoundException)) { - System.err.println("[AutoClicker] Config okunamadi: " + e.getMessage()); - } - } + props = configStore.load(); + triggerKey = Integer.parseInt(props.getProperty("hotkey", String.valueOf(NativeKeyEvent.VC_F6))); } private void saveConfig() { - try (FileOutputStream fos = new FileOutputStream(CONFIG_FILE)) { + gatherProps(); + configStore.store(props); + } + + // ---- Profiller (ConfigStore: config dizininde profile-.properties) ---- + private String[] listProfiles() { + return configStore.listProfiles(); + } + + private void saveProfile(String name) { + gatherProps(); // mevcut UI durumunu props'a topla + configStore.saveProfile(props, name); + } + + private void deleteProfile(String name) { + configStore.deleteProfile(name); + } + + private void loadProfile(String name) { + Properties np = configStore.loadProfile(name); + if (np == null) return; + this.props = np; + triggerKey = Integer.parseInt(props.getProperty("hotkey", String.valueOf(NativeKeyEvent.VC_F6))); + Lang.L = Integer.parseInt(props.getProperty("langIndex", "0")); + // UI'yi yeni profile gore tamamen yeniden kur (tema/dil/renk dahil) + getContentPane().removeAll(); + applyInitialTheme(); + initUI(); + revalidate(); + repaint(); + saveConfig(); // aktif config'i yuklenen profile esitle + } + + private void refreshProfiles(String selectName) { + if (profileBox == null) return; + profileBox.setModel(new DefaultComboBoxModel<>(listProfiles())); + if (selectName != null) profileBox.setSelectedItem(selectName); + } + + // UI durumunu props'a toplar (dosyaya yazma ConfigStore'da) + private void gatherProps() { props.setProperty("hotkey", String.valueOf(triggerKey)); - + props.setProperty("mouseCps", String.valueOf(mouseCpsSlider.getValue())); props.setProperty("mouseBtn", String.valueOf(mouseBtnBox.getSelectedIndex())); props.setProperty("mouseHumanizer", String.valueOf(mouseHumanizerBox.isSelected())); @@ -446,10 +406,12 @@ private void saveConfig() { props.setProperty("langIndex", String.valueOf(langBox.getSelectedIndex())); - props.store(fos, "AutoClicker Configuration"); - } catch (Exception e) { - System.err.println("[AutoClicker] Config kaydedilemedi: " + e.getMessage()); - } + props.setProperty("schedUse", String.valueOf(schedulerBox.isSelected())); + props.setProperty("schedDelay", schedulerDelayField.getText()); + + for (int i = 0; i < tabHotkey.length; i++) { + props.setProperty("tabHotkey" + i, String.valueOf(tabHotkey[i])); + } } private void initUI() { @@ -462,15 +424,15 @@ private void initUI() { statusLabel.setForeground(new Color(255, 90, 90)); headerPanel.add(statusLabel); - JTabbedPane tabbedPane = new JTabbedPane(); + tabbedPane = new JTabbedPane(); tabbedPane.setFont(new Font("Segoe UI", Font.PLAIN, 14)); - tabbedPane.addTab(Lang.get("t_mouse"), new MouseIcon(), buildMousePanel()); - tabbedPane.addTab(Lang.get("t_key"), new KeyIcon(), buildKeyboardPanel()); - tabbedPane.addTab(Lang.get("t_chain"), new ChainIcon(), buildChainPanel()); - tabbedPane.addTab(Lang.get("t_px"), new PixelIcon(), buildPixelPanel()); + tabbedPane.addTab(Lang.get("t_mouse"), new Icons.MouseIcon(), buildMousePanel()); + tabbedPane.addTab(Lang.get("t_key"), new Icons.KeyIcon(), buildKeyboardPanel()); + tabbedPane.addTab(Lang.get("t_chain"), new Icons.ChainIcon(), buildChainPanel()); + tabbedPane.addTab(Lang.get("t_px"), new Icons.PixelIcon(), buildPixelPanel()); JScrollPane setScroll = new JScrollPane(buildSettingsPanel()); setScroll.setBorder(null); - tabbedPane.addTab(Lang.get("t_set"), new GearIcon(), setScroll); + tabbedPane.addTab(Lang.get("t_set"), new Icons.GearIcon(), setScroll); add(headerPanel, BorderLayout.NORTH); add(tabbedPane, BorderLayout.CENTER); @@ -538,6 +500,16 @@ private void applyConfigToUI() { if(props.containsKey("fontColor")) customColorPreview.setBackground(new Color(Integer.parseInt(props.getProperty("fontColor")))); langBox.setSelectedIndex(Integer.parseInt(props.getProperty("langIndex", "0"))); + schedulerBox.setSelected(Boolean.parseBoolean(props.getProperty("schedUse", "false"))); + schedulerDelayField.setText(props.getProperty("schedDelay", "5")); + + for (int i = 0; i < tabHotkey.length; i++) { + tabHotkey[i] = Integer.parseInt(props.getProperty("tabHotkey" + i, "0")); + if (tabHotkeyBtns[i] != null) { + tabHotkeyBtns[i].setText(tabHotkey[i] != 0 ? NativeKeyEvent.getKeyText(tabHotkey[i]) : "-"); + } + } + } catch (Exception e) { System.err.println("[AutoClicker] Config UI'ye uygulanamadi: " + e.getMessage()); } @@ -654,7 +626,25 @@ private JPanel buildChainPanel() { rightPanel.add(remBtn); rightPanel.add(Box.createRigidArea(new Dimension(0, 10))); rightPanel.add(clrBtn); - + + JButton exportBtn = new JButton(Lang.get("io_export")); + JButton importBtn = new JButton(Lang.get("io_import")); + exportBtn.setMaximumSize(new Dimension(150, 35)); + importBtn.setMaximumSize(new Dimension(150, 35)); + exportBtn.addActionListener(e -> exportChain()); + importBtn.addActionListener(e -> importChain()); + rightPanel.add(Box.createRigidArea(new Dimension(0, 10))); + rightPanel.add(exportBtn); + rightPanel.add(Box.createRigidArea(new Dimension(0, 10))); + rightPanel.add(importBtn); + + recordBtn = new JButton(recording ? Lang.get("rec_stop") : Lang.get("rec_start")); + recordBtn.setMaximumSize(new Dimension(150, 35)); + if (recording) recordBtn.setForeground(Color.RED); + recordBtn.addActionListener(e -> toggleRecording()); + rightPanel.add(Box.createRigidArea(new Dimension(0, 10))); + rightPanel.add(recordBtn); + JPanel pAnti = new JPanel(new FlowLayout(FlowLayout.LEFT)); chainHumanizerBox = new JCheckBox(Lang.get("anti_ban")); pAnti.add(chainHumanizerBox); pAnti.add(createInfoButton("info_hum")); @@ -665,6 +655,89 @@ private JPanel buildChainPanel() { return panel; } + // Zinciri JSON dosyasina aktarir + private void exportChain() { + if (chainModel.isEmpty()) { Toolkit.getDefaultToolkit().beep(); return; } + JFileChooser fc = new JFileChooser(); + fc.setSelectedFile(new File("macro.json")); + if (fc.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return; + File f = fc.getSelectedFile(); + if (!f.getName().toLowerCase().endsWith(".json")) f = new File(f.getParentFile(), f.getName() + ".json"); + + JsonArray arr = new JsonArray(); + for (int i = 0; i < chainModel.getSize(); i++) { + MacroAction a = chainModel.get(i); + JsonObject o = new JsonObject(); + o.addProperty("type", a.type.name()); + o.addProperty("p1", a.p1); + o.addProperty("p2", a.p2); + arr.add(o); + } + JsonObject doc = new JsonObject(); + doc.addProperty("app", "AutoClicker"); + doc.addProperty("version", appVersion()); + doc.add("actions", arr); + + try (java.io.Writer w = new java.io.FileWriter(f)) { + new Gson().toJson(doc, w); + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, Lang.get("io_fail") + ex.getMessage()); + } + } + + // JSON dosyasindan zincir yukler (mevcut listenin yerine) + private void importChain() { + JFileChooser fc = new JFileChooser(); + if (fc.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return; + try (java.io.Reader r = new java.io.FileReader(fc.getSelectedFile())) { + JsonObject doc = JsonParser.parseReader(r).getAsJsonObject(); + JsonArray arr = doc.getAsJsonArray("actions"); + chainModel.clear(); + for (JsonElement el : arr) { + JsonObject o = el.getAsJsonObject(); + ActionType t = ActionType.valueOf(o.get("type").getAsString()); + int p1 = o.get("p1").getAsInt(); + int p2 = o.has("p2") ? o.get("p2").getAsInt() : 0; + chainModel.addElement(new MacroAction(t, p1, p2)); + } + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, Lang.get("io_fail") + ex.getMessage()); + } + } + + // ---- Record/replay: gercek girdiyi zincire kaydeder ---- + private void toggleRecording() { + if (recording) stopRecording(); else startRecording(); + } + + private void startRecording() { + recording = true; + recordLastTime = 0L; + if (recordBtn != null) { recordBtn.setText(Lang.get("rec_stop")); recordBtn.setForeground(Color.RED); } + statusLabel.setText(Lang.get("rec_status")); + statusLabel.setForeground(new Color(255, 80, 80)); + } + + private void stopRecording() { + recording = false; + if (recordBtn != null) { recordBtn.setText(Lang.get("rec_start")); recordBtn.setForeground(UIManager.getColor("Button.foreground")); } + statusLabel.setText(Lang.get("st_idle")); + statusLabel.setForeground(new Color(255, 90, 90)); + } + + // Bir onceki olaydan bu yana gecen sureyi DELAY olarak ekler (zamanlama korunur) + private void appendRecordedDelay() { + long now = System.currentTimeMillis(); + if (recordLastTime != 0L) { + long delta = now - recordLastTime; + if (delta > 15 && delta < 600000) { + final int d = (int) delta; + SwingUtilities.invokeLater(() -> chainModel.addElement(new MacroAction(ActionType.DELAY, d, 0))); + } + } + recordLastTime = now; + } + private JPanel buildPixelPanel() { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); @@ -890,6 +963,71 @@ private JPanel buildSettingsPanel() { limitPanel.add(p1); limitPanel.add(p2); limitPanel.add(p3); panel.add(limitPanel); + // SCHEDULER (gecikmeli baslat) + JPanel schedPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + schedPanel.setBorder(BorderFactory.createTitledBorder(Lang.get("sched_title"))); + schedulerBox = new JCheckBox(Lang.get("sched_use")); + schedulerDelayField = new JTextField("5", 4); + schedPanel.add(schedulerBox); + schedPanel.add(schedulerDelayField); + schedPanel.add(new JLabel(Lang.get("sched_sec"))); + panel.add(schedPanel); + + // PER-SEKME HOTKEY + JPanel tabHkPanel = new JPanel(); + tabHkPanel.setLayout(new BoxLayout(tabHkPanel, BoxLayout.Y_AXIS)); + tabHkPanel.setBorder(BorderFactory.createTitledBorder(Lang.get("tabhk_title"))); + String[] tabKeys = { "t_mouse", "t_key", "t_chain", "t_px" }; + for (int i = 0; i < 4; i++) { + final int idx = i; + JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT)); + row.add(new JLabel(Lang.get(tabKeys[i]) + ": ")); + JButton b = new JButton(tabHotkey[i] != 0 ? NativeKeyEvent.getKeyText(tabHotkey[i]) : "-"); + b.addActionListener(e -> { + listeningForTabHotkey = idx; + b.setText(Lang.get("press_k")); + b.setForeground(Color.RED); + }); + tabHotkeyBtns[i] = b; + row.add(b); + JButton clr = new JButton("x"); + clr.addActionListener(e -> { + tabHotkey[idx] = 0; + b.setText("-"); + b.setForeground(UIManager.getColor("Button.foreground")); + saveConfig(); + }); + row.add(clr); + tabHkPanel.add(row); + } + panel.add(tabHkPanel); + + // PROFILLER + JPanel profPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + profPanel.setBorder(BorderFactory.createTitledBorder(Lang.get("prof_title"))); + profileBox = new JComboBox<>(listProfiles()); + profileBox.setEditable(true); + profileBox.setPreferredSize(new Dimension(150, 28)); + JButton saveProfBtn = new JButton(Lang.get("prof_save")); + JButton loadProfBtn = new JButton(Lang.get("prof_load")); + JButton delProfBtn = new JButton(Lang.get("prof_del")); + saveProfBtn.addActionListener(e -> { + String name = String.valueOf(profileBox.getEditor().getItem()).trim(); + if (name.isEmpty()) { Toolkit.getDefaultToolkit().beep(); return; } + saveProfile(name); + refreshProfiles(name); + }); + loadProfBtn.addActionListener(e -> { + Object sel = profileBox.getSelectedItem(); + if (sel != null && !String.valueOf(sel).trim().isEmpty()) loadProfile(String.valueOf(sel).trim()); + }); + delProfBtn.addActionListener(e -> { + Object sel = profileBox.getSelectedItem(); + if (sel != null && !String.valueOf(sel).trim().isEmpty()) { deleteProfile(String.valueOf(sel).trim()); refreshProfiles(null); } + }); + profPanel.add(profileBox); profPanel.add(saveProfBtn); profPanel.add(loadProfBtn); profPanel.add(delProfBtn); + panel.add(profPanel); + // UI STYLE PANEL V5.1 JPanel stylePanel = new JPanel(); stylePanel.setLayout(new BoxLayout(stylePanel, BoxLayout.Y_AXIS)); @@ -956,10 +1094,14 @@ private JPanel createCpsPanel(String title, String defaultVal, int max, String i slider.addChangeListener(e -> field.setText(String.valueOf(slider.getValue()))); field.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { + boolean ok = false; try { - int val = Integer.parseInt(field.getText()); - if (val >= slider.getMinimum() && val <= max) slider.setValue(val); - } catch (Exception ex){} + int val = Integer.parseInt(field.getText().trim()); + if (val >= slider.getMinimum() && val <= max) { slider.setValue(val); ok = true; } + } catch (Exception ex) { } + // Bos alan notr; gecersiz/araliga sigmayan deger kirmizi cerceve + field.putClientProperty("JComponent.outline", (ok || field.getText().trim().isEmpty()) ? null : "error"); + field.repaint(); } }); @@ -1024,26 +1166,8 @@ private boolean checkLimits(LimitConfig cfg, long startTime, int iterationCount) private void executeLimitAction(int action) { isRunning = false; if(action == 1) { // bilgisayari kapat - String os = System.getProperty("os.name").toLowerCase(); - String[] cmd = null; - String delayLabel = null; - if (os.contains("win")) { cmd = new String[]{"shutdown", "-s", "-t", "15"}; delayLabel = "15 sn"; } - else if (os.contains("mac")) { cmd = new String[]{"shutdown", "-h", "+1"}; delayLabel = "1 dk"; } - else if (os.contains("nux") || os.contains("nix") || os.contains("aix")) { cmd = new String[]{"shutdown", "-h", "+1"}; delayLabel = "1 dk"; } - - if (cmd != null) { - final String label = delayLabel; - try { - Runtime.getRuntime().exec(cmd); - SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, String.format(Lang.get("shut_ok"), label))); - } catch (Exception e) { - final String err = String.valueOf(e.getMessage()); - SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, Lang.get("shut_fail") + err)); - } - } else { - final String osName = os; - SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, Lang.get("shut_unsup") + osName)); - } + ShutdownCommand.Result sc = ShutdownCommand.forOs(System.getProperty("os.name")); + SwingUtilities.invokeLater(() -> executeShutdownWithCancel(sc)); } else { SwingUtilities.invokeLater(() -> Toolkit.getDefaultToolkit().beep()); } @@ -1053,6 +1177,33 @@ private void executeLimitAction(int action) { }); } + // EDT'de: OS gecikmeli kapatmayi baslatir, gecikme penceresinde iptal sunar. + // AFK ise (kimse iptal etmezse) bilgisayar kapanir; kullanici varsa iptal edebilir. + private void executeShutdownWithCancel(ShutdownCommand.Result sc) { + if (sc == null || !sc.isSupported()) { + JOptionPane.showMessageDialog(this, Lang.get("shut_unsup") + System.getProperty("os.name")); + return; + } + try { + Runtime.getRuntime().exec(sc.command); // OS gecikmeli kapatma baslar + } catch (Exception e) { + JOptionPane.showMessageDialog(this, Lang.get("shut_fail") + e.getMessage()); + return; + } + int sel = JOptionPane.showOptionDialog(this, + String.format(Lang.get("shut_pending"), sc.delayLabel), + Lang.get("shut_title"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, + null, new Object[]{ Lang.get("shut_cancel_btn") }, Lang.get("shut_cancel_btn")); + if (sel == 0 && sc.cancelCommand != null) { + try { + Runtime.getRuntime().exec(sc.cancelCommand); + JOptionPane.showMessageDialog(this, Lang.get("shut_cancelled")); + } catch (Exception e) { + JOptionPane.showMessageDialog(this, Lang.get("shut_fail") + e.getMessage()); + } + } + } + private void stopWorker() { isRunning = false; Thread t = workerThread; @@ -1063,109 +1214,195 @@ private void stopWorker() { workerThread = null; } + // Limitor aciksa deger pozitif tam sayi olmali; degilse alani kirmizi cerceveler ve false doner + private boolean validateLimitInput() { + if (!useLimitBox.isSelected()) { + limitValField.putClientProperty("JComponent.outline", null); + limitValField.repaint(); + return true; + } + int v; + try { v = Integer.parseInt(limitValField.getText().trim()); } catch (Exception e) { v = -1; } + boolean ok = v > 0; + limitValField.putClientProperty("JComponent.outline", ok ? null : "error"); + limitValField.repaint(); + if (!ok) Toolkit.getDefaultToolkit().beep(); + return ok; + } + + // ESC panik tusu: kosulsuz durdurma + sesli uyari (EDT'de calisir) + private void stopFromPanic() { + if (!isRunning) return; + stopWorker(); + statusLabel.setText(Lang.get("st_idle")); + statusLabel.setForeground(new Color(255, 90, 90)); + Toolkit.getDefaultToolkit().beep(); + } + private void toggle() { + // Zamanlayici geri sayimi devam ediyorsa hotkey iptal eder + if (scheduleTimer != null) { + scheduleTimer.stop(); + scheduleTimer = null; + statusLabel.setText(Lang.get("st_idle")); + statusLabel.setForeground(new Color(255, 90, 90)); + return; + } + if (isRunning) { stopWorker(); statusLabel.setText(Lang.get("st_idle")); statusLabel.setForeground(new Color(255, 90, 90)); + return; + } + + // Eski worker hala canliysa once tamamen durdur (cift makro yarisini onler) + stopWorker(); + + // Limitor aciksa gecersiz/0 deger ani durdurma/kapatma yapmasin + if (!validateLimitInput()) { + statusLabel.setText(Lang.get("lim_invalid")); + statusLabel.setForeground(new Color(255, 165, 0)); + return; + } + + // Zamanlayici aciksa once geri sayim + int delaySec = schedulerDelaySeconds(); + if (delaySec > 0) { + startScheduledCountdown(delaySec); + return; + } + + startActiveTab(); + } + + // Aktif sekmenin makrosunu baslatir ve durumu gunceller (toggle ve zamanlayici kullanir) + private void startActiveTab() { + JTabbedPane tabs = tabbedPane; + int selectedTab = tabs.getSelectedIndex(); + + boolean started; + if (selectedTab == 0) started = startMouseMacro(); + else if (selectedTab == 1) started = startKeyMacro(); + else if (selectedTab == 2) started = startChainMacro(); + else if (selectedTab == 3) started = startPixelMacro(); + else return; + + if (started) { + statusLabel.setText(Lang.get("st_run")); + statusLabel.setForeground(new Color(90, 255, 90)); } else { - // Eski worker hala canliysa once tamamen durdur (cift makro yarisini onler) - stopWorker(); + statusLabel.setText(Lang.get("st_idle")); + statusLabel.setForeground(new Color(255, 90, 90)); + } + } - JTabbedPane tabs = (JTabbedPane) ((BorderLayout) getContentPane().getLayout()).getLayoutComponent(BorderLayout.CENTER); - int selectedTab = tabs.getSelectedIndex(); + // Per-sekme hotkey: idle ise i. sekmeye gecip baslatir; calisirken/sayimda durdurur + private void startTab(int index) { + SwingUtilities.invokeLater(() -> { + if (scheduleTimer != null || isRunning) { toggle(); return; } + JTabbedPane tabs = tabbedPane; + if (tabs == null || index < 0 || index >= 4) return; + tabs.setSelectedIndex(index); + toggle(); + }); + } - boolean started; - if (selectedTab == 0) started = startMouseMacro(); - else if (selectedTab == 1) started = startKeyMacro(); - else if (selectedTab == 2) started = startChainMacro(); - else if (selectedTab == 3) started = startPixelMacro(); - else return; + // Zamanlayici acik ve gecerli pozitif deger ise saniye, aksi halde 0 + private int schedulerDelaySeconds() { + if (schedulerBox == null || !schedulerBox.isSelected()) return 0; + try { + int s = Integer.parseInt(schedulerDelayField.getText().trim()); + return s > 0 ? s : 0; + } catch (Exception e) { return 0; } + } - if (started) { - statusLabel.setText(Lang.get("st_run")); - statusLabel.setForeground(new Color(90, 255, 90)); + // Geri sayim sonunda aktif sekme makrosunu baslatir (EDT Swing Timer) + private void startScheduledCountdown(int totalSec) { + final int[] remaining = { totalSec }; + statusLabel.setForeground(new Color(255, 165, 0)); + statusLabel.setText(String.format(Lang.get("sched_countdown"), remaining[0])); + scheduleTimer = new javax.swing.Timer(1000, null); + scheduleTimer.addActionListener(e -> { + remaining[0]--; + if (remaining[0] <= 0) { + scheduleTimer.stop(); + scheduleTimer = null; + startActiveTab(); } else { - statusLabel.setText(Lang.get("st_idle")); - statusLabel.setForeground(new Color(255, 90, 90)); + statusLabel.setText(String.format(Lang.get("sched_countdown"), remaining[0])); } - } + }); + scheduleTimer.setInitialDelay(1000); + scheduleTimer.start(); } - private boolean startMouseMacro() { - int cps = mouseCpsSlider.getValue(); - int baseDelay = 1000 / Math.max(cps, 1); - int mode = mouseBtnBox.getSelectedIndex(); - boolean useHumanizer = mouseHumanizerBox.isSelected(); - boolean useCoord = targetCoordBox.isSelected() && targetPoint != null; - final LimitConfig limits = snapshotLimits(); - - long startTime = System.currentTimeMillis(); + // Tek iterasyonun govdesi (Strategy). InterruptedException firlatabilir; motor yakalar. + @FunctionalInterface + private interface MacroBody { void iterate() throws InterruptedException; } + // Ortak makro motoru: worker thread + while(isRunning) + iterasyon sayaci + limit kontrolu. + // body her dongude calisir; onEnd (varsa) dongu bittiginde calisir. Tum start*Macro bunu kullanir. + private boolean startMacro(LimitConfig limits, MacroBody body, Runnable onEnd) { + final long startTime = System.currentTimeMillis(); isRunning = true; workerThread = new Thread(() -> { int iteration = 0; while (isRunning) { iteration++; if (!checkLimits(limits, startTime, iteration)) break; - try { - if (useCoord) { - robot.mouseMove(targetPoint.x, targetPoint.y); - } - - switch (mode) { - case 0: doMouseClick(InputEvent.BUTTON1_DOWN_MASK); break; - case 1: doMouseClick(InputEvent.BUTTON3_DOWN_MASK); break; - case 2: doMouseClick(InputEvent.BUTTON2_DOWN_MASK); break; - case 3: - doMouseClick(InputEvent.BUTTON1_DOWN_MASK); - robot.delay(40); - doMouseClick(InputEvent.BUTTON1_DOWN_MASK); - break; - } - - int sleepTime = getHumanizedDelay(baseDelay, useHumanizer); - Thread.sleep(sleepTime); - } catch (InterruptedException e) { Thread.currentThread().interrupt(); } + body.iterate(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } } - }); + if (onEnd != null) onEnd.run(); + }, "AutoClicker-Macro"); workerThread.start(); return true; } - private boolean startKeyMacro() { - int cps = keyCpsSlider.getValue(); - int baseDelay = Math.max(1, 1000 / cps); - boolean useHumanizer = keyHumanizerBox.isSelected(); - final int targetKey = selectedNativeKeyCode; + private boolean startMouseMacro() { + final int baseDelay = 1000 / Math.max(mouseCpsSlider.getValue(), 1); + final int mode = mouseBtnBox.getSelectedIndex(); + final boolean useHumanizer = mouseHumanizerBox.isSelected(); + final boolean useCoord = targetCoordBox.isSelected() && targetPoint != null; final LimitConfig limits = snapshotLimits(); - long startTime = System.currentTimeMillis(); - - isRunning = true; - workerThread = new Thread(() -> { - int iteration = 0; - while (isRunning) { - iteration++; - if (!checkLimits(limits, startTime, iteration)) break; + return startMacro(limits, () -> { + if (useCoord) robot.mouseMove(targetPoint.x, targetPoint.y); + switch (mode) { + case 0: doMouseClick(InputEvent.BUTTON1_DOWN_MASK); break; + case 1: doMouseClick(InputEvent.BUTTON3_DOWN_MASK); break; + case 2: doMouseClick(InputEvent.BUTTON2_DOWN_MASK); break; + case 3: + doMouseClick(InputEvent.BUTTON1_DOWN_MASK); + robot.delay(40); + doMouseClick(InputEvent.BUTTON1_DOWN_MASK); + break; + } + Thread.sleep(getHumanizedDelay(baseDelay, useHumanizer)); + }, null); + } - try { - // press->release araligini kisa tut ve CPS butcesinden dus ki gercek hiz hedefe yapissin - int releaseGap = Math.min(baseDelay / 2, 5 + random.nextInt(10)); - if (releaseGap < 2) releaseGap = 2; - robot.keyPress(targetKey); - robot.delay(releaseGap); - robot.keyRelease(targetKey); + private boolean startKeyMacro() { + final int baseDelay = Math.max(1, 1000 / keyCpsSlider.getValue()); + final boolean useHumanizer = keyHumanizerBox.isSelected(); + final int targetKey = selectedNativeKeyCode; + final LimitConfig limits = snapshotLimits(); - int remaining = Math.max(1, baseDelay - releaseGap); - int sleepTime = getHumanizedDelay(remaining, useHumanizer); - Thread.sleep(sleepTime); - } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - } - }); - workerThread.start(); - return true; + return startMacro(limits, () -> { + // press->release araligini kisa tut ve CPS butcesinden dus ki gercek hiz hedefe yapissin + int releaseGap = Math.min(baseDelay / 2, 5 + random.nextInt(10)); + if (releaseGap < 2) releaseGap = 2; + robot.keyPress(targetKey); + robot.delay(releaseGap); + robot.keyRelease(targetKey); + int remaining = Math.max(1, baseDelay - releaseGap); + Thread.sleep(getHumanizedDelay(remaining, useHumanizer)); + }, null); } private boolean startChainMacro() { @@ -1173,61 +1410,42 @@ private boolean startChainMacro() { SwingUtilities.invokeLater(() -> Toolkit.getDefaultToolkit().beep()); return false; } - - boolean useHumanizer = chainHumanizerBox.isSelected(); + final boolean useHumanizer = chainHumanizerBox.isSelected(); final LimitConfig limits = snapshotLimits(); // Worker thread'in DefaultListModel'a EDT-disi erismesini onlemek icin diziye snapshot al final MacroAction[] actions = new MacroAction[chainModel.getSize()]; for (int i = 0; i < actions.length; i++) actions[i] = chainModel.get(i); - long startTime = System.currentTimeMillis(); - - isRunning = true; - workerThread = new Thread(() -> { - int iteration = 0; - while (isRunning) { - iteration++; - if (!checkLimits(limits, startTime, iteration)) break; - for (int i = 0; i < actions.length && isRunning; i++) { - MacroAction action = actions[i]; - final int idx = i; - SwingUtilities.invokeLater(() -> chainList.setSelectedIndex(idx)); - - try { - switch (action.type) { - case MOUSE_CLICK: - if (action.p1 == 999) { - doMouseClick(InputEvent.BUTTON1_DOWN_MASK); - robot.delay(40); - doMouseClick(InputEvent.BUTTON1_DOWN_MASK); - } else { - doMouseClick(action.p1); - } - break; - case KEY_PRESS: - robot.keyPress(action.p1); - robot.delay(20 + random.nextInt(30)); - robot.keyRelease(action.p1); - break; - case MOUSE_MOVE: - robot.mouseMove(action.p1, action.p2); - break; - case DELAY: - int sleepTime = getHumanizedDelay(action.p1, useHumanizer); - Thread.sleep(sleepTime); - break; + return startMacro(limits, () -> { + for (int i = 0; i < actions.length && isRunning; i++) { + MacroAction action = actions[i]; + final int idx = i; + SwingUtilities.invokeLater(() -> chainList.setSelectedIndex(idx)); + switch (action.type) { + case MOUSE_CLICK: + if (action.p1 == 999) { + doMouseClick(InputEvent.BUTTON1_DOWN_MASK); + robot.delay(40); + doMouseClick(InputEvent.BUTTON1_DOWN_MASK); + } else { + doMouseClick(action.p1); } - Thread.sleep(5); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); break; - } + case KEY_PRESS: + robot.keyPress(action.p1); + robot.delay(20 + random.nextInt(30)); + robot.keyRelease(action.p1); + break; + case MOUSE_MOVE: + smoothMove(action.p1, action.p2); + break; + case DELAY: + Thread.sleep(getHumanizedDelay(action.p1, useHumanizer)); + break; } + Thread.sleep(5); } - if (!isRunning) SwingUtilities.invokeLater(() -> chainList.clearSelection()); - }); - workerThread.start(); - return true; + }, () -> SwingUtilities.invokeLater(() -> chainList.clearSelection())); } private boolean startPixelMacro() { @@ -1235,49 +1453,32 @@ private boolean startPixelMacro() { SwingUtilities.invokeLater(() -> Toolkit.getDefaultToolkit().beep()); return false; } - - int scanRate = pxRateSlider.getValue(); - int tolerancePercent = pxToleranceSlider.getValue(); - int condition = pxConditionBox.getSelectedIndex(); - int action = pxActionBox.getSelectedIndex(); - int targetKey = pxSelectedKey; + final int scanRate = pxRateSlider.getValue(); + final int tolerancePercent = pxToleranceSlider.getValue(); + final int condition = pxConditionBox.getSelectedIndex(); + final int action = pxActionBox.getSelectedIndex(); + final int targetKey = pxSelectedKey; final LimitConfig limits = snapshotLimits(); - long startTime = System.currentTimeMillis(); - isRunning = true; - - workerThread = new Thread(() -> { - int iteration = 0; - while(isRunning) { - iteration++; - if (!checkLimits(limits, startTime, iteration)) break; - - Color current = robot.getPixelColor(pixelPoint.x, pixelPoint.y); - boolean isMatch = colorDistance(pixelColor, current) <= (tolerancePercent * 4.4167); - - boolean shouldTrigger = false; - if(condition == 0 && isMatch) shouldTrigger = true; - else if(condition == 1 && !isMatch) shouldTrigger = true; - - if (shouldTrigger) { - if(action == 0 || action == 1) { - robot.mouseMove(pixelPoint.x, pixelPoint.y); - int mask = (action == 0) ? InputEvent.BUTTON1_DOWN_MASK : InputEvent.BUTTON3_DOWN_MASK; - doMouseClick(mask); - } else if(action == 2) { - robot.keyPress(targetKey); - robot.delay(20 + random.nextInt(30)); - robot.keyRelease(targetKey); - } else if(action == 3) { - Toolkit.getDefaultToolkit().beep(); - } - try { Thread.sleep(1000); } catch(Exception e){} + return startMacro(limits, () -> { + Color current = robot.getPixelColor(pixelPoint.x, pixelPoint.y); + boolean isMatch = colorDistance(pixelColor, current) <= (tolerancePercent * 4.4167); + boolean shouldTrigger = (condition == 0 && isMatch) || (condition == 1 && !isMatch); + if (shouldTrigger) { + if (action == 0 || action == 1) { + robot.mouseMove(pixelPoint.x, pixelPoint.y); + doMouseClick(action == 0 ? InputEvent.BUTTON1_DOWN_MASK : InputEvent.BUTTON3_DOWN_MASK); + } else if (action == 2) { + robot.keyPress(targetKey); + robot.delay(20 + random.nextInt(30)); + robot.keyRelease(targetKey); + } else if (action == 3) { + Toolkit.getDefaultToolkit().beep(); } - try { Thread.sleep(scanRate); } catch(Exception e) {} + Thread.sleep(1000); } - }); - workerThread.start(); - return true; + Thread.sleep(scanRate); + }, null); } private double colorDistance(Color c1, Color c2) { @@ -1292,16 +1493,41 @@ private void doMouseClick(int mask) { robot.mouseRelease(mask); } + // Mevcut konumdan (x,y)'ye insan benzeri egriyle hareket (zincir "Fareyi Tasi" icin) + private void smoothMove(int x, int y) { + Point cur = MouseInfo.getPointerInfo().getLocation(); + int steps = MousePath.stepsFor(cur.x, cur.y, x, y); + for (Point p : MousePath.interpolate(cur.x, cur.y, x, y, steps)) { + robot.mouseMove(p.x, p.y); + robot.delay(2); + } + } + private int getHumanizedDelay(int baseDelay, boolean useHumanizer) { - if (!useHumanizer || baseDelay < 5) return Math.max(1, baseDelay); - int variance = (int)(baseDelay * 0.15); - if (variance == 0) variance = 1; - int offset = random.nextInt(variance * 2) - variance; - return Math.max(1, baseDelay + offset); + return Humanizer.humanizedDelay(baseDelay, useHumanizer, random); } @Override public void nativeKeyPressed(NativeKeyEvent ev) { + // Kayit modu: ESC kaydi durdurur, diger tuslar zincire eklenir + if (recording) { + if (ev.getKeyCode() == NativeKeyEvent.VC_ESCAPE) { + SwingUtilities.invokeLater(this::stopRecording); + } else { + appendRecordedDelay(); + final int awt = ev.getRawCode(); // Windows VK ~ AWT KeyEvent VK kodlari + SwingUtilities.invokeLater(() -> chainModel.addElement(new MacroAction(ActionType.KEY_PRESS, awt, 0))); + } + return; + } + + // Her zaman acik ACIL DURDURMA (panik) tusu: ESC. Yapilandirilabilir hotkey'den bagimsizdir + // ve degistirilemez; makro cok hizliyken garantili cikis saglar. + if (ev.getKeyCode() == NativeKeyEvent.VC_ESCAPE) { + if (isRunning) SwingUtilities.invokeLater(this::stopFromPanic); + return; // ESC asla hotkey olarak atanmaz / toggle tetiklemez + } + if (listeningForHotkey) { triggerKey = ev.getKeyCode(); listeningForHotkey = false; @@ -1313,6 +1539,31 @@ public void nativeKeyPressed(NativeKeyEvent ev) { return; } + // Per-sekme hotkey atama modu + if (listeningForTabHotkey >= 0) { + final int idx = listeningForTabHotkey; + tabHotkey[idx] = ev.getKeyCode(); + listeningForTabHotkey = -1; + SwingUtilities.invokeLater(() -> { + if (tabHotkeyBtns[idx] != null) { + tabHotkeyBtns[idx].setText(NativeKeyEvent.getKeyText(tabHotkey[idx])); + tabHotkeyBtns[idx].setForeground(UIManager.getColor("Button.foreground")); + } + saveConfig(); + }); + return; + } + + // Per-sekme hotkey eslesmesi: ilgili sekmeye gecip baslat + if (!listeningForCoordParams && !listeningForMacroCoord && !listeningForPixelCoord) { + for (int i = 0; i < tabHotkey.length; i++) { + if (tabHotkey[i] != 0 && ev.getKeyCode() == tabHotkey[i]) { + startTab(i); + return; + } + } + } + if (ev.getKeyCode() == triggerKey && !listeningForCoordParams && !listeningForMacroCoord && !listeningForPixelCoord) { SwingUtilities.invokeLater(this::toggle); } @@ -1366,9 +1617,28 @@ public void nativeMouseClicked(NativeMouseEvent e) { }); } } - @Override public void nativeMousePressed(NativeMouseEvent nativeMouseEvent) {} + @Override public void nativeMousePressed(NativeMouseEvent e) { + if (!recording) return; + appendRecordedDelay(); + final Point p = MouseInfo.getPointerInfo().getLocation(); + // JNativeHook buton -> AWT InputEvent maskesi (BUTTON2=sag, BUTTON3=orta farkina dikkat) + final int mask; + if (e.getButton() == NativeMouseEvent.BUTTON2) mask = InputEvent.BUTTON3_DOWN_MASK; // sag + else if (e.getButton() == NativeMouseEvent.BUTTON3) mask = InputEvent.BUTTON2_DOWN_MASK; // orta + else mask = InputEvent.BUTTON1_DOWN_MASK; // sol + SwingUtilities.invokeLater(() -> { + chainModel.addElement(new MacroAction(ActionType.MOUSE_MOVE, p.x, p.y)); + chainModel.addElement(new MacroAction(ActionType.MOUSE_CLICK, mask, 0)); + }); + } @Override public void nativeMouseReleased(NativeMouseEvent nativeMouseEvent) {} + /** Surum bilgisini JAR manifest'inden (pom version) okur; IDE/class'tan calistirinca 'dev'. */ + private static String appVersion() { + String v = AutoClicker.class.getPackage().getImplementationVersion(); + return (v != null) ? v : "dev"; + } + public static void main(String[] args) { SwingUtilities.invokeLater(() -> new AutoClicker().setVisible(true)); } diff --git a/src/main/java/com/ohualtex/autoclicker/config/ConfigStore.java b/src/main/java/com/ohualtex/autoclicker/config/ConfigStore.java new file mode 100644 index 0000000..4faafe2 --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/config/ConfigStore.java @@ -0,0 +1,94 @@ +package com.ohualtex.autoclicker.config; + +import com.ohualtex.autoclicker.core.ConfigPaths; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +/** + * Config ve profil dosyalarinin disk islemlerini yonetir (yazilabilir kullanici dizininde). + * UI'dan bagimsiz: Properties alir/dondurur; anahtar-deger toplama cagiran tarafta kalir. + */ +public final class ConfigStore { + + private final File dir; + private final File configFile; + + public ConfigStore() { + dir = new File(ConfigPaths.configDir( + System.getProperty("os.name"), System.getenv("APPDATA"), System.getProperty("user.home"))); + try { dir.mkdirs(); } catch (Exception ignore) {} + configFile = new File(dir, "config.properties"); + migrateLegacy(); + } + + // Eski CWD config.properties varsa bir kez tasi (geriye uyum) + private void migrateLegacy() { + File legacy = new File("config.properties"); + if (!configFile.exists() && legacy.exists()) { + try { java.nio.file.Files.copy(legacy.toPath(), configFile.toPath()); } catch (Exception ignore) {} + } + } + + public Properties load() { + Properties p = new Properties(); + try (FileInputStream fis = new FileInputStream(configFile)) { + p.load(fis); + } catch (FileNotFoundException e) { + // ilk acilis - normal + } catch (Exception e) { + System.err.println("[AutoClicker] Config okunamadi: " + e.getMessage()); + } + return p; + } + + public void store(Properties p) { storeTo(p, configFile); } + + private void storeTo(Properties p, File f) { + try (FileOutputStream fos = new FileOutputStream(f)) { + p.store(fos, "AutoClicker Configuration"); + } catch (Exception e) { + System.err.println("[AutoClicker] Config kaydedilemedi: " + e.getMessage()); + } + } + + // ---- Profiller ---- + private String sanitize(String n) { return n.replaceAll("[^A-Za-z0-9_-]", "_"); } + + public File profileFile(String name) { return new File(dir, "profile-" + sanitize(name) + ".properties"); } + + public void saveProfile(Properties p, String name) { storeTo(p, profileFile(name)); } + + public void deleteProfile(String name) { + File f = profileFile(name); + if (f.exists()) f.delete(); + } + + /** Profili yukler; yoksa veya bozuksa null. */ + public Properties loadProfile(String name) { + File f = profileFile(name); + if (!f.exists()) return null; + Properties p = new Properties(); + try (FileInputStream fis = new FileInputStream(f)) { p.load(fis); } + catch (Exception e) { return null; } + return p; + } + + public String[] listProfiles() { + File[] fs = dir.listFiles((d, n) -> n.startsWith("profile-") && n.endsWith(".properties")); + if (fs == null) return new String[0]; + List names = new ArrayList<>(); + for (File f : fs) { + String n = f.getName(); + names.add(n.substring("profile-".length(), n.length() - ".properties".length())); + } + Collections.sort(names); + return names.toArray(new String[0]); + } +} diff --git a/src/main/java/com/ohualtex/autoclicker/core/ConfigPaths.java b/src/main/java/com/ohualtex/autoclicker/core/ConfigPaths.java new file mode 100644 index 0000000..ee66a98 --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/core/ConfigPaths.java @@ -0,0 +1,27 @@ +package com.ohualtex.autoclicker.core; + +import java.io.File; + +/** + * Config dosyasinin yazilabilir kullanici dizinini secer. Saf ve test edilebilir + * (filesystem'e dokunmaz); installer ile Program Files'a kurulunca ayarlarin + * salt-okunur dizine yazilmaya calisilmasini onler. + */ +public final class ConfigPaths { + + private ConfigPaths() {} + + /** + * @param osName System.getProperty("os.name") + * @param appData Windows %APPDATA% (yoksa null/bos) + * @param userHome System.getProperty("user.home") + * @return config dizini (Windows: %APPDATA%\AutoClicker, digerleri: ~/.autoclicker) + */ + public static String configDir(String osName, String appData, String userHome) { + String os = (osName == null) ? "" : osName.toLowerCase(); + if (os.contains("win") && appData != null && !appData.isEmpty()) { + return appData + File.separator + "AutoClicker"; + } + return userHome + File.separator + ".autoclicker"; + } +} diff --git a/src/main/java/com/ohualtex/autoclicker/core/Humanizer.java b/src/main/java/com/ohualtex/autoclicker/core/Humanizer.java new file mode 100644 index 0000000..728fa53 --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/core/Humanizer.java @@ -0,0 +1,27 @@ +package com.ohualtex.autoclicker.core; + +import java.util.Random; + +/** + * Anti-ban "insan modu" gecikme hesabi. Saf (Swing'den bagimsiz) ve test edilebilir; + * rastgelelik disaridan enjekte edilir (seedlenebilir test icin). + */ +public final class Humanizer { + + private Humanizer() {} + + /** + * Temel gecikmeye +-%15 organik sapma ekler. + * @param baseDelay temel gecikme (ms) + * @param useHumanizer kapaliysa baseDelay aynen (min 1) doner + * @param rng rastgelelik kaynagi (uretimde paylasimli, testte seedli) + * @return en az 1 ms olan gecikme + */ + public static int humanizedDelay(int baseDelay, boolean useHumanizer, Random rng) { + if (!useHumanizer || baseDelay < 5) return Math.max(1, baseDelay); + int variance = (int) (baseDelay * 0.15); + if (variance == 0) variance = 1; + int offset = rng.nextInt(variance * 2) - variance; + return Math.max(1, baseDelay + offset); + } +} diff --git a/src/main/java/com/ohualtex/autoclicker/core/MousePath.java b/src/main/java/com/ohualtex/autoclicker/core/MousePath.java new file mode 100644 index 0000000..8ca5b37 --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/core/MousePath.java @@ -0,0 +1,39 @@ +package com.ohualtex.autoclicker.core; + +import java.awt.Point; + +/** + * Iki nokta arasinda insan benzeri (ease-in-out) ara noktalar uretir. + * Saf ve test edilebilir; gercek fare hareketi cagiran taraf bu noktalari kullanir. + */ +public final class MousePath { + + private MousePath() {} + + /** + * (x1,y1) -> (x2,y2) arasi ara noktalar. Ilk nokta baslangic, son nokta tam hedeftir. + * Ease-in-out (smoothstep) ile hizlanip yavaslar. + * @param steps en az 1; mesafeye gore cagiran taraf belirleyebilir + */ + public static Point[] interpolate(int x1, int y1, int x2, int y2, int steps) { + if (steps < 1) steps = 1; + Point[] pts = new Point[steps + 1]; + for (int i = 0; i <= steps; i++) { + double t = (double) i / steps; + double e = t * t * (3 - 2 * t); // smoothstep ease-in-out + int x = (int) Math.round(x1 + (x2 - x1) * e); + int y = (int) Math.round(y1 + (y2 - y1) * e); + pts[i] = new Point(x, y); + } + return pts; + } + + /** Mesafeye gore makul adim sayisi (yakin = az adim, uzak = cok, ust sinir 60). */ + public static int stepsFor(int x1, int y1, int x2, int y2) { + double dist = Math.hypot(x2 - x1, y2 - y1); + int steps = (int) (dist / 12); + if (steps < 1) steps = 1; + if (steps > 60) steps = 60; + return steps; + } +} diff --git a/src/main/java/com/ohualtex/autoclicker/core/ShutdownCommand.java b/src/main/java/com/ohualtex/autoclicker/core/ShutdownCommand.java new file mode 100644 index 0000000..1891cde --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/core/ShutdownCommand.java @@ -0,0 +1,40 @@ +package com.ohualtex.autoclicker.core; + +/** + * Isletim sistemine gore guvenli kapatma komutunu secer. Saf ve test edilebilir; + * gercek exec/UI ile karismaz (boylece test gercekten PC kapatmadan tum dallari dogrular). + */ +public final class ShutdownCommand { + + private ShutdownCommand() {} + + /** + * Secilen kapatma komutu, iptal komutu ve gecikme etiketi. + * command null ise OS desteklenmiyor demektir. + */ + public static final class Result { + public final String[] command; + public final String[] cancelCommand; + public final String delayLabel; + public Result(String[] command, String[] cancelCommand, String delayLabel) { + this.command = command; + this.cancelCommand = cancelCommand; + this.delayLabel = delayLabel; + } + public boolean isSupported() { return command != null; } + } + + public static Result forOs(String osName) { + String os = (osName == null) ? "" : osName.toLowerCase(); + if (os.contains("win")) { + return new Result(new String[]{"shutdown", "-s", "-t", "15"}, new String[]{"shutdown", "-a"}, "15 sn"); + } + if (os.contains("mac")) { + return new Result(new String[]{"shutdown", "-h", "+1"}, new String[]{"killall", "shutdown"}, "1 dk"); + } + if (os.contains("nux") || os.contains("nix") || os.contains("aix")) { + return new Result(new String[]{"shutdown", "-h", "+1"}, new String[]{"shutdown", "-c"}, "1 dk"); + } + return new Result(null, null, null); + } +} diff --git a/src/main/java/com/ohualtex/autoclicker/i18n/Lang.java b/src/main/java/com/ohualtex/autoclicker/i18n/Lang.java new file mode 100644 index 0000000..dee2a63 --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/i18n/Lang.java @@ -0,0 +1,113 @@ +package com.ohualtex.autoclicker.i18n; + +public final class Lang { + public static int L = 0; // 0=TR, 1=EN, 2=DE, 3=FR, 4=IT, 5=RU + static java.util.Map d = new java.util.HashMap<>(); + static { + d.put("st_idle", new String[]{"DURUM: BEKLIYOR", "STATUS: IDLE", "STATUS: BEREIT", "STATUT: EN ATTENTE", "STATO: IN ATTESA", "СТАТУС: ОЖИДАНИЕ"}); + d.put("st_run", new String[]{"DURUM: CALISIYOR", "STATUS: RUNNING", "STATUS: LÄUFT", "STATUT: EN COURS", "STATO: IN ESECUZIONE", "СТАТУС: РАБОТАЕТ"}); + d.put("st_lim", new String[]{"DURUM: LIMIT YENDI", "STATUS: LIMIT HIT", "STATUS: LIMIT ERREICHT", "STATUT: LIMITE", "STATO: LIMITE RAGGIUNTO", "СТАТУС: ЛИМИТ"}); + + d.put("t_mouse", new String[]{"Fare", "Mouse", "Maus", "Souris", "Mouse", "Мышь"}); + d.put("t_key", new String[]{"Klavye", "Keyboard", "Tastatur", "Clavier", "Tastiera", "Клавиатура"}); + d.put("t_chain", new String[]{"Zincir", "Chain", "Kette", "Chaîne", "Catena", "Цепь"}); + d.put("t_px", new String[]{"Piksel", "Pixel", "Pixel", "Pixel", "Pixel", "Пиксель"}); + d.put("t_set", new String[]{"Ayarlar", "Settings", "Einstellungen", "Paramètres", "Impostazioni", "Настройки"}); + + d.put("c_type", new String[]{"Tiklama Tipi: ", "Click Type: ", "Klick-Typ: ", "Type de clic: ", "Tipo di clic: ", "Тип клика: "}); + d.put("l_click", new String[]{"Sol Tik", "Left Click", "Linksklick", "Clic Gauche", "Clic Sinistro", "Левый Клик"}); + d.put("r_click", new String[]{"Sag Tik", "Right Click", "Rechtsklick", "Clic Droit", "Clic Destro", "Правый Клик"}); + d.put("m_click", new String[]{"Orta Tik", "Mid Click", "Mittelklick", "Clic Milieu", "Clic Centrale", "Средний Клик"}); + d.put("d_click", new String[]{"Cift Sol Tik", "Double Click", "Doppelklick", "Double Clic", "Doppio Clic", "Двойной Клик"}); + + d.put("fix_loc", new String[]{"Sabit Konuma Tikla", "Click Fixed Loc", "Feste Position", "Bouton Fixe", "Pos. Fissa", "Фикс. Место"}); + d.put("pick_loc", new String[]{"Konum Sec", "Pick Loc", "Wählen", "Choisir", "Seleziona", "Выбрать"}); + d.put("wait_mid", new String[]{"Bekliyor (Orta Tik)", "Waiting (Mid Click)", "Wartet (Mittelklick)", "Attente (Milieu)", "Attesa (Centrale)", "Ожидание (Ср. клик)"}); + + d.put("anti_ban", new String[]{"Insan Modu (Rastgele Gecikme)", "Humanizer (Random Delay)", "Menschenmodus (Zufall)", "Mode Humain (Délais Aléat.)", "Modalità Umana (Ritardo)", "Анти-Бан (случайная задержка)"}); + d.put("cps", new String[]{"Fare Hizi (CPS):", "Mouse Speed (CPS):", "Maus-Geschw. (CPS):", "Vitesse Souris (CPS):", "Velocità Mouse (CPS):", "Скорость Мыши (CPS):"}); + d.put("key_cps", new String[]{"Klavye Hizi (Saniyede):", "Key Speed (/sec):", "Tasten-Geschw. (/s):", "Vitesse Touche (/s):", "Velocità Tasto (/s):", "Скор. Клавиатуры (/с):"}); + + d.put("key_trg", new String[]{"Basilacak Tus:", "Target Key:", "Zieltaste:", "Touche Cible:", "Tasto Obiettivo:", "Целевая Клавиша:"}); + d.put("set_k", new String[]{"Tus Ata", "Set Key", "Setzen", "Définir", "Assegna", "Назначить"}); + d.put("press_k", new String[]{"Basin...", "Press...", "Drücken...", "Appuyez...", "Premi...", "Нажмите..."}); + + d.put("add_act", new String[]{"(+) Eylem Ekle", "(+) Add Action", "(+) Aktion", "(+) Ajouter", "(+) Aggiungi", "(+) Добавить"}); + d.put("del_act", new String[]{"(-) Secileni Sil", "(-) Remove", "(-) Löschen", "(-) Supprimer", "(-) Rimuovi", "(-) Удалить"}); + d.put("clr_act", new String[]{"(x) Temizle", "(x) Clear", "(x) Leeren", "(x) Effacer", "(x) Pulisci", "(x) Очистить"}); + + d.put("act_typ", new String[]{"Eylem Turu:", "Action Type:", "Aktionsart:", "Type d'action:", "Tipo di Azione:", "Тип Действия:"}); + d.put("ms_delay", new String[]{"Milisaniye (Gecikme):", "Delay (ms):", "Verzögerung (ms):", "Délai (ms):", "Ritardo (ms):", "Задержка (мс):"}); + d.put("list_add", new String[]{"[v] Listeye Ekle", "[v] Add to List", "[v] Hinzufügen", "[v] Ajouter", "[v] Aggiungi", "[v] Добавить"}); + d.put("tnt_mouse", new String[]{"Fare Tiklamasi", "Mouse Click", "Mausklick", "Clic Souris", "Clic Mouse", "Клик Мыши"}); + d.put("tnt_key", new String[]{"Klavye Tusu", "Key Press", "Taste Drücken", "Appui Touche", "Pressione Tasto", "Нажатие Клавиши"}); + d.put("tnt_move", new String[]{"Fareyi Tasi", "Move Mouse", "Maus Bewegen", "Bouger Souris", "Sposta Mouse", "Сдвинуть Мышь"}); + + d.put("px_1", new String[]{"1. Gozetlenecek Piksel", "1. Target Pixel", "1. Zielpixel", "1. Pixel Cible", "1. Pixel Obiettivo", "1. Цель Пиксель"}); + d.put("px_clr", new String[]{"Renk:", "Color:", "Farbe:", "Couleur:", "Colore:", "Цвет:"}); + d.put("px_2", new String[]{"2. Tetiklenme Sartlari", "2. Conditions", "2. Bedingungen", "2. Conditions", "2. Condizioni", "2. Условия"}); + d.put("px_cond1", new String[]{"Renk ESLESTIGINDE", "When Color MATCHES", "Farbe ÜBEREINSTIMMT", "Couleur CORRESPOND", "Colore CORRISPONDE", "Когда совпадает"}); + d.put("px_cond2", new String[]{"Renk DEGISTIGINDE", "When Color CHANGES", "Farbe WECHSELT", "Couleur CHANGE", "Colore CAMBIA", "Когда меняется"}); + d.put("px_tol", new String[]{"Tolerans Payi (%):", "Tolerance (%):", "Toleranz (%):", "Tolérance (%):", "Tolleranza (%):", "Допуск (%):"}); + d.put("px_3", new String[]{"3. Gerceklesecek Tepki", "3. Reaction Action", "3. Reaktion", "3. Réaction", "3. Reazione", "3. Реакция"}); + d.put("px_act3", new String[]{"Ozel Tusa Bas", "Press Custom Key", "Zieltaste drücken", "Appuyer Touche", "Premi Tasto", "Своя Клавиша"}); + d.put("px_act4", new String[]{"Sadece Zili Cal", "Only Beep", "Nur Piepen", "Bip Sonore", "Suona Solo", "Только Звук"}); + d.put("px_rate", new String[]{"Tarama Hizi (Ms):", "Scanner Rate (Ms):", "Scanrate (Ms):", "Vitesse Scan (Ms):", "Velocità Scan (Ms):", "Скорость (Мс):"}); + + d.put("set_hk", new String[]{"Kisayol Tusu (Baslat/Durdur):", "Start/Stop Hotkey:", "Start/Stopp Hotkey:", "Raccourci Start/Stop:", "Tasto Avvio/Arresto:", "Горячая Клавиша:"}); + d.put("lim_title", new String[]{"Otomatik Durdurma Sinirlari", "Auto Stop Limiters", "Auto-Stopp Begrenzungen", "Limites Arrêt Auto", "Limiti Arresto Auto", "Лимиты Автоостановки"}); + d.put("lim_use", new String[]{"Limitoru Aktif Et", "Enable Limiter", "Limiter aktivieren", "Activer Limiteur", "Abilita Limitatore", "Вкл. Лимитер"}); + d.put("lim_after", new String[]{"Sinir:", "Limit:", "Limit:", "Dans:", "Dopo:", "Лимит:"}); + d.put("lim_min", new String[]{"Dakika Sonra", "Minutes Later", "Minuten", "Minutes", "Minuti", "Минут"}); + d.put("lim_iter", new String[]{"Dongu Sonra", "Iterations Later", "Iterationen", "Itérations", "Iterazioni", "Итераций"}); + d.put("lim_stop", new String[]{"Sadece Makroyu Durdur", "Just Stop Macro", "Makro stoppen", "Arrêter Macro", "Ferma Macro", "Только стоп"}); + d.put("lim_shut", new String[]{"Bilgisayari Kapat", "Shutdown PC", "PC herunterfahren", "Éteindre le PC", "Spegni PC", "Выкл ПК"}); + + d.put("style", new String[]{"(O) Gorunum Ozellestirme", "(O) UI Customization", "(O) UI Anpassung", "(O) Apparence UI", "(O) Aspetto UI", "(O) Дизайн UI"}); + d.put("theme", new String[]{"Ana Tema:", "Main Theme:", "Hauptthema:", "Thème:", "Tema Principale:", "Тема:"}); + d.put("theme_d", new String[]{"Karanlik Mod", "Dark Mode", "Dunkler Modus", "Sombre", "Modo Scuro", "Темный"}); + d.put("theme_l", new String[]{"Aydinlik Mod", "Light Mode", "Heller Modus", "Clair", "Modo Chiaro", "Светлый"}); + d.put("txt_size", new String[]{"Yazi Boyutu:", "Text Size:", "Textgröße:", "Taille:", "Dimensione:", "Размер:"}); + d.put("txt_col", new String[]{"Metin Rengi:", "Text Color:", "Textfarbe:", "Couleur Texte:", "Colore Testo:", "Цвет Текста:"}); + d.put("col_pick", new String[]{"Renk Sec", "Pick Color", "Farbe", "Choisir", "Seleziona", "Выбрать Цвет"}); + d.put("apply_s", new String[]{"Uygula/Kaydet", "Apply/Save", "Anwenden", "Appliquer", "Applica", "Применить"}); + d.put("lang_title", new String[]{"(L) Dil (Language)", "(L) Language", "(L) Sprache", "(L) Langue", "(L) Lingua", "(L) Язык"}); + + d.put("info_hum", new String[]{"Anti-Ban: Robotik tiklamalari saptirmak icin gecikmelere minik sapmalar ekler.", "Anti-Ban: Adds random fluctuations to delays to simulate human behavior and evade detection.", "Anti-Ban: Fügt den Verzögerungen zufällige Schwankungen hinzu.", "Anti-Ban: Ajoute des fluctuations aléatoires aux délais.", "Anti-Ban: Aggiunge fluttuazioni casuali ai ritardi.", "Анти-Бан: Добавляет случайные колебания к задержкам."}); + d.put("info_lim", new String[]{"Otomasyonu belirli bir sure sonra kapatir.", "Stops the automation automatically after a set time or cycle count. Ideal for AFK macros.", "Stoppt die Automatisierung automatisch nach einer Weile.", "Arrête automatiquement l'automatisation.", "Ferma l'automazione in base a limiti.", "Останавливает автоматизацию при достижении лимита."}); + d.put("info_px", new String[]{"Renk Toleransi: Ufak golge farkliliklarinin renk algisini bozmasini engeller.", "Color Tolerance: Prevents minor in-game shading/lighting shifts from ruining detection.", "Farbtoleranz: Verhindert kleine Schattenfehler.", "Tolérance: Empêche les petits changements de lumière de fausser la détection.", "Tolleranza: Evita che l'illuminazione rompa l'algoritmo.", "Допуск: Игнорирует изменения освещения."}); + d.put("info_cps", new String[]{"Saniyedeki tiklama hizini belirler (Click Per Second).", "Sets the click speed per second (CPS).", "Legt die Klicks pro Sekunde fest.", "Définit la vitesse de clic (CPS).", "Imposta i clic al secondo (CPS).", "Устанавливает кликов в секунду (CPS)."}); + d.put("info_px_cond", new String[]{"Eslestiginde: Renk gorundugunde tepki verir.\nDegistiginde: Renk kayboldugunda tepki verir.", "Matches: Reacts when color appears.\nChanges: Reacts when color disappears.", "Stimmt überein: Reagiert, wenn die Farbe erscheint.", "Correspond: Agit quand la couleur apparait.", "Corrisponde: Agisce quando il colore appare.", "Совпадает: Реагирует на появление цвета."}); + d.put("info_px_rate", new String[]{"Tarama Hizi: Ekranin ne siklikla kontrol edilecegini belirler (Milisaniye).", "Scan Rate: How often to check the screen (Milliseconds).", "Scanrate: Wie oft der Bildschirm überprüft wird (ms).", "Taux de scan: Fréquence de vérification (ms).", "Velocità scan: Frequenza di controllo schermo (ms).", "Скорость: Частота проверки экрана (мс)."}); + + d.put("info_title", new String[]{"Bilgi", "Info", "Info", "Info", "Info", "Информация"}); + d.put("reset", new String[]{"Sifirla", "Reset", "Zurücksetzen", "Réinit.", "Reimposta", "Сброс"}); + d.put("shut_ok", new String[]{"Limit asildi! Bilgisayar %s icinde kapatilacak.", "Limit reached! PC will shut down in %s.", "Limit erreicht! PC fährt in %s herunter.", "Limite atteinte! Le PC s'eteindra dans %s.", "Limite raggiunto! Il PC si spegnera tra %s.", "Лимит достигнут! ПК выключится через %s."}); + d.put("shut_title", new String[]{"Bilgisayar Kapatiliyor", "Shutting Down", "Herunterfahren", "Arret du PC", "Spegnimento PC", "Выключение ПК"}); + d.put("shut_pending", new String[]{"Limit asildi! Bilgisayar %s icinde kapanacak.\nVazgecmek icin asagidaki butona basin.", "Limit reached! PC shuts down in %s.\nPress the button below to cancel.", "Limit erreicht! PC fährt in %s herunter.\nZum Abbrechen unten klicken.", "Limite atteinte! Arret dans %s.\nCliquez ci-dessous pour annuler.", "Limite raggiunto! Spegnimento tra %s.\nPremi sotto per annullare.", "Лимит достигнут! Выключение через %s.\nНажмите ниже для отмены."}); + d.put("shut_cancel_btn", new String[]{"Kapatmayi Iptal Et", "Cancel Shutdown", "Abbrechen", "Annuler l'arret", "Annulla spegnimento", "Отменить выключение"}); + d.put("shut_cancelled", new String[]{"Bilgisayar kapatma iptal edildi.", "Shutdown cancelled.", "Herunterfahren abgebrochen.", "Arret annule.", "Spegnimento annullato.", "Выключение отменено."}); + d.put("lim_invalid", new String[]{"DURUM: GECERSIZ LIMIT DEGERI", "STATUS: INVALID LIMIT VALUE", "STATUS: UNGULTIGER LIMITWERT", "STATUT: LIMITE INVALIDE", "STATO: LIMITE NON VALIDO", "СТАТУС: НЕВЕРНЫЙ ЛИМИТ"}); + d.put("sched_title", new String[]{"(Z) Zamanlayici", "(Z) Scheduler", "(Z) Zeitplaner", "(Z) Planificateur", "(Z) Pianificatore", "(Z) Планировщик"}); + d.put("sched_use", new String[]{"Gecikmeli baslat", "Delayed start", "Verzögerter Start", "Démarrage différé", "Avvio ritardato", "Запуск с задержкой"}); + d.put("sched_sec", new String[]{"saniye sonra", "seconds later", "Sekunden später", "secondes après", "secondi dopo", "секунд спустя"}); + d.put("sched_countdown", new String[]{"DURUM: BASLIYOR (%d sn)", "STATUS: STARTING (%d s)", "STATUS: START IN (%d s)", "STATUT: DEBUT (%d s)", "STATO: AVVIO (%d s)", "СТАТУС: СТАРТ (%d с)"}); + d.put("tray_show", new String[]{"Goster", "Show", "Anzeigen", "Afficher", "Mostra", "Показать"}); + d.put("tray_toggle", new String[]{"Baslat / Durdur", "Start / Stop", "Start / Stopp", "Demarrer / Arreter", "Avvia / Ferma", "Старт / Стоп"}); + d.put("tray_exit", new String[]{"Cikis", "Exit", "Beenden", "Quitter", "Esci", "Выход"}); + d.put("tabhk_title", new String[]{"(K) Sekme Kisayollari", "(K) Per-Tab Hotkeys", "(K) Tab-Hotkeys", "(K) Raccourcis par onglet", "(K) Tasti per scheda", "(K) Горячие клавиши вкладок"}); + d.put("io_export", new String[]{"Disa Aktar (JSON)", "Export (JSON)", "Export (JSON)", "Exporter (JSON)", "Esporta (JSON)", "Экспорт (JSON)"}); + d.put("io_import", new String[]{"Ice Aktar (JSON)", "Import (JSON)", "Import (JSON)", "Importer (JSON)", "Importa (JSON)", "Импорт (JSON)"}); + d.put("io_fail", new String[]{"Dosya islemi basarisiz: ", "File operation failed: ", "Dateioperation fehlgeschlagen: ", "Echec du fichier: ", "Operazione file fallita: ", "Сбой работы с файлом: "}); + d.put("prof_title", new String[]{"(P) Profiller", "(P) Profiles", "(P) Profile", "(P) Profils", "(P) Profili", "(P) Профили"}); + d.put("prof_save", new String[]{"Kaydet", "Save", "Speichern", "Enregistrer", "Salva", "Сохранить"}); + d.put("prof_load", new String[]{"Yukle", "Load", "Laden", "Charger", "Carica", "Загрузить"}); + d.put("prof_del", new String[]{"Sil", "Delete", "Löschen", "Supprimer", "Elimina", "Удалить"}); + d.put("rec_start", new String[]{"● Kaydet", "● Record", "● Aufnehmen", "● Enregistrer", "● Registra", "● Запись"}); + d.put("rec_stop", new String[]{"■ Durdur (ESC)", "■ Stop (ESC)", "■ Stopp (ESC)", "■ Arreter (ESC)", "■ Ferma (ESC)", "■ Стоп (ESC)"}); + d.put("rec_status", new String[]{"DURUM: KAYDEDILIYOR (ESC=dur)", "STATUS: RECORDING (ESC=stop)", "STATUS: AUFNAHME (ESC=stopp)", "STATUT: ENREGISTREMENT (ESC)", "STATO: REGISTRAZIONE (ESC)", "СТАТУС: ЗАПИСЬ (ESC=стоп)"}); + d.put("shut_fail", new String[]{"Kapatma komutu basarisiz oldu: ", "Shutdown command failed: ", "Herunterfahren fehlgeschlagen: ", "Echec de la commande d'arret: ", "Comando di spegnimento fallito: ", "Сбой команды выключения: "}); + d.put("shut_unsup", new String[]{"Bu isletim sisteminde otomatik kapatma desteklenmiyor: ", "Automatic shutdown not supported on this OS: ", "Automatisches Herunterfahren auf diesem OS nicht unterstützt: ", "Arret automatique non supporte sur cet OS: ", "Spegnimento automatico non supportato su questo OS: ", "Автовыключение не поддерживается в этой ОС: "}); + } + public static String get(String key) { return d.containsKey(key) ? d.get(key)[L] : key; } +} diff --git a/src/main/java/com/ohualtex/autoclicker/model/ActionType.java b/src/main/java/com/ohualtex/autoclicker/model/ActionType.java new file mode 100644 index 0000000..c92a5da --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/model/ActionType.java @@ -0,0 +1,4 @@ +package com.ohualtex.autoclicker.model; + +/** Zincir makro adim turleri. */ +public enum ActionType { MOUSE_CLICK, KEY_PRESS, MOUSE_MOVE, DELAY } diff --git a/src/main/java/com/ohualtex/autoclicker/model/MacroAction.java b/src/main/java/com/ohualtex/autoclicker/model/MacroAction.java new file mode 100644 index 0000000..b264454 --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/model/MacroAction.java @@ -0,0 +1,31 @@ +package com.ohualtex.autoclicker.model; + +import com.ohualtex.autoclicker.i18n.Lang; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +/** Zincir makrosundaki tek bir adim (tur + iki parametre). Listede toString ile gosterilir, serialize ile saklanir. */ +public class MacroAction { + public ActionType type; + public int p1, p2; + + public MacroAction(ActionType type, int p1, int p2) { this.type = type; this.p1 = p1; this.p2 = p2; } + + public String toString() { + switch(type) { + case MOUSE_CLICK: return Lang.get("tnt_mouse") + ": " + (p1==InputEvent.BUTTON1_DOWN_MASK ? Lang.get("l_click") : p1==InputEvent.BUTTON3_DOWN_MASK ? Lang.get("r_click") : p1==999 ? Lang.get("d_click") : Lang.get("m_click")); + case KEY_PRESS: return Lang.get("tnt_key") + ": " + KeyEvent.getKeyText(p1); + case MOUSE_MOVE: return Lang.get("tnt_move") + ": X=" + p1 + ", Y=" + p2; + case DELAY: return Lang.get("ms_delay") + " " + p1; + default: return "?"; + } + } + + public String serialize() { return type.name()+":"+p1+":"+p2; } + + public static MacroAction deserialize(String s) { + String[] parts = s.split(":"); + return new MacroAction(ActionType.valueOf(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2])); + } +} diff --git a/src/main/java/com/ohualtex/autoclicker/ui/Icons.java b/src/main/java/com/ohualtex/autoclicker/ui/Icons.java new file mode 100644 index 0000000..482312e --- /dev/null +++ b/src/main/java/com/ohualtex/autoclicker/ui/Icons.java @@ -0,0 +1,63 @@ +package com.ohualtex.autoclicker.ui; + +import javax.swing.Icon; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +/** Sekme ve bilgi butonlari icin kodla cizilen vektor ikonlar (FlatLaf foreground rengini kullanir). */ +public final class Icons { + + private Icons() {} + + public static class MouseIcon implements Icon { + public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(c.getForeground()); g2.drawRoundRect(x+3, y+1, 10, 14, 6, 6); g2.drawLine(x+8, y+1, x+8, y+5); g2.drawOval(x+7, y+3, 2, 4); g2.dispose(); + } + } + public static class KeyIcon implements Icon { + public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(c.getForeground()); g2.drawRoundRect(x+1, y+4, 14, 8, 2, 2); + g2.fillRect(x+3, y+6, 2, 2); g2.fillRect(x+7, y+6, 2, 2); g2.fillRect(x+11, y+6, 2, 2); g2.fillRect(x+5, y+9, 6, 2); g2.dispose(); + } + } + public static class ChainIcon implements Icon { + public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setStroke(new BasicStroke(2)); g2.setColor(c.getForeground()); + g2.drawOval(x+1, y+5, 8, 6); g2.drawOval(x+7, y+5, 8, 6); g2.dispose(); + } + } + public static class PixelIcon implements Icon { + public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(c.getForeground()); g2.drawOval(x+2, y+2, 12, 12); + g2.drawLine(x+8, y+0, x+8, y+4); g2.drawLine(x+8, y+12, x+8, y+16); + g2.drawLine(x+0, y+8, x+4, y+8); g2.drawLine(x+12, y+8, x+16, y+8); g2.fillOval(x+6, y+6, 4, 4); g2.dispose(); + } + } + public static class GearIcon implements Icon { + public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(c.getForeground()); g2.setStroke(new BasicStroke(2)); g2.drawOval(x+4, y+4, 8, 8); + for(int i=0; i<8; i++) { double a = i * Math.PI/4; g2.drawLine(x+8+(int)(4*Math.cos(a)), y+8+(int)(4*Math.sin(a)), x+8+(int)(7*Math.cos(a)), y+8+(int)(7*Math.sin(a))); } g2.dispose(); + } + } + public static class InfoIcon implements Icon { + public int getIconWidth() { return 14; } public int getIconHeight() { return 14; } + public void paintIcon(Component c, Graphics g, int x, int y) { + Graphics2D g2 = (Graphics2D)g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(c.getForeground()); g2.drawOval(x+1, y+1, 12, 12); g2.fillRect(x+6, y+4, 2, 2); g2.fillRect(x+6, y+7, 2, 4); g2.dispose(); + } + } +} diff --git a/src/test/java/com/ohualtex/autoclicker/core/ConfigPathsTest.java b/src/test/java/com/ohualtex/autoclicker/core/ConfigPathsTest.java new file mode 100644 index 0000000..00351eb --- /dev/null +++ b/src/test/java/com/ohualtex/autoclicker/core/ConfigPathsTest.java @@ -0,0 +1,34 @@ +package com.ohualtex.autoclicker.core; + +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.*; + +class ConfigPathsTest { + + @Test + void windowsWithAppData_usesAppData() { + String dir = ConfigPaths.configDir("Windows 11", "C:\\Users\\u\\AppData\\Roaming", "C:\\Users\\u"); + assertEquals("C:\\Users\\u\\AppData\\Roaming" + File.separator + "AutoClicker", dir); + } + + @Test + void windowsWithoutAppData_fallsBackToHome() { + String dir = ConfigPaths.configDir("Windows 11", null, "C:\\Users\\u"); + assertEquals("C:\\Users\\u" + File.separator + ".autoclicker", dir); + } + + @Test + void linux_usesHomeDotDir() { + String dir = ConfigPaths.configDir("Linux", null, "/home/u"); + assertEquals("/home/u" + File.separator + ".autoclicker", dir); + } + + @Test + void mac_usesHomeDotDir() { + String dir = ConfigPaths.configDir("Mac OS X", null, "/Users/u"); + assertEquals("/Users/u" + File.separator + ".autoclicker", dir); + } +} diff --git a/src/test/java/com/ohualtex/autoclicker/core/HumanizerTest.java b/src/test/java/com/ohualtex/autoclicker/core/HumanizerTest.java new file mode 100644 index 0000000..2beeb29 --- /dev/null +++ b/src/test/java/com/ohualtex/autoclicker/core/HumanizerTest.java @@ -0,0 +1,52 @@ +package com.ohualtex.autoclicker.core; + +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +class HumanizerTest { + + @Test + void humanizerOff_returnsBaseClampedToOne() { + assertEquals(100, Humanizer.humanizedDelay(100, false, new Random(1))); + assertEquals(1, Humanizer.humanizedDelay(0, false, new Random(1))); + assertEquals(1, Humanizer.humanizedDelay(-5, false, new Random(1))); + } + + @Test + void smallBaseDelay_isReturnedUnchanged() { + // baseDelay < 5 -> sapma uygulanmaz, aynen (min 1) doner + for (int b = 1; b <= 4; b++) { + assertEquals(b, Humanizer.humanizedDelay(b, true, new Random(7))); + } + } + + @Test + void withinFifteenPercentWindow() { + Random rng = new Random(42); + int base = 100; + int variance = (int) (base * 0.15); // 15 + for (int i = 0; i < 10_000; i++) { + int d = Humanizer.humanizedDelay(base, true, rng); + // offset = nextInt(30) - 15 -> [-15, 14] + assertTrue(d >= base - variance && d <= base + variance - 1, "menzil disi: " + d); + } + } + + @Test + void neverZeroOrNegative() { + Random rng = new Random(3); + for (int i = 0; i < 10_000; i++) { + assertTrue(Humanizer.humanizedDelay(5, true, rng) >= 1); + } + } + + @Test + void deterministicWithSameSeed() { + assertEquals( + Humanizer.humanizedDelay(50, true, new Random(99)), + Humanizer.humanizedDelay(50, true, new Random(99))); + } +} diff --git a/src/test/java/com/ohualtex/autoclicker/core/MousePathTest.java b/src/test/java/com/ohualtex/autoclicker/core/MousePathTest.java new file mode 100644 index 0000000..3866e9e --- /dev/null +++ b/src/test/java/com/ohualtex/autoclicker/core/MousePathTest.java @@ -0,0 +1,43 @@ +package com.ohualtex.autoclicker.core; + +import org.junit.jupiter.api.Test; + +import java.awt.Point; + +import static org.junit.jupiter.api.Assertions.*; + +class MousePathTest { + + @Test + void startsAtSourceEndsAtTarget() { + Point[] p = MousePath.interpolate(10, 20, 110, 220, 10); + assertEquals(new Point(10, 20), p[0]); + assertEquals(new Point(110, 220), p[p.length - 1]); + } + + @Test + void hasStepsPlusOnePoints() { + assertEquals(11, MousePath.interpolate(0, 0, 100, 0, 10).length); + } + + @Test + void progressIsMonotonicOnAxis() { + Point[] p = MousePath.interpolate(0, 0, 100, 0, 20); + for (int i = 1; i < p.length; i++) { + assertTrue(p[i].x >= p[i - 1].x, "x geriye gitti: " + p[i - 1].x + " -> " + p[i].x); + } + } + + @Test + void stepsAtLeastOneEvenForZeroDistance() { + assertEquals(1, MousePath.stepsFor(50, 50, 50, 50)); + Point[] p = MousePath.interpolate(50, 50, 50, 50, MousePath.stepsFor(50, 50, 50, 50)); + assertEquals(new Point(50, 50), p[0]); + assertEquals(new Point(50, 50), p[p.length - 1]); + } + + @Test + void stepsAreCappedForLongDistance() { + assertTrue(MousePath.stepsFor(0, 0, 5000, 5000) <= 60); + } +} diff --git a/src/test/java/com/ohualtex/autoclicker/core/ShutdownCommandTest.java b/src/test/java/com/ohualtex/autoclicker/core/ShutdownCommandTest.java new file mode 100644 index 0000000..bc81c89 --- /dev/null +++ b/src/test/java/com/ohualtex/autoclicker/core/ShutdownCommandTest.java @@ -0,0 +1,39 @@ +package com.ohualtex.autoclicker.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ShutdownCommandTest { + + @Test + void windows_usesShutdownDashS() { + ShutdownCommand.Result r = ShutdownCommand.forOs("Windows 11"); + assertTrue(r.isSupported()); + assertArrayEquals(new String[]{"shutdown", "-s", "-t", "15"}, r.command); + assertArrayEquals(new String[]{"shutdown", "-a"}, r.cancelCommand); + assertEquals("15 sn", r.delayLabel); + } + + @Test + void mac_usesShutdownDashH() { + assertArrayEquals(new String[]{"shutdown", "-h", "+1"}, ShutdownCommand.forOs("Mac OS X").command); + } + + @Test + void linux_usesShutdownDashH() { + assertArrayEquals(new String[]{"shutdown", "-h", "+1"}, ShutdownCommand.forOs("Linux").command); + } + + @Test + void unknownOs_isUnsupported() { + ShutdownCommand.Result r = ShutdownCommand.forOs("SomeExoticOS"); + assertFalse(r.isSupported()); + assertNull(r.command); + } + + @Test + void nullOsName_isUnsupported() { + assertFalse(ShutdownCommand.forOs(null).isSupported()); + } +}