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.**
-[](https://www.oracle.com/java/)
+[](https://github.com/Ohualtex/Auto_Clicker/actions/workflows/build.yml)
+[](https://www.oracle.com/java/)
+[](https://maven.apache.org/)
[](https://www.formdev.com/flatlaf/)
[](https://github.com/kwhat/jnativehook)
[]()
@@ -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());
+ }
+}