#!/usr/bin/python3
import gi, subprocess
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gdk
import cairo, math, time

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Click Assist — Dwell Clicker')
        self.set_default_size(420, 320)
        self.enabled = False
        self.dwell_time = 1.0
        self.dwell_start = 0
        self.dwell_pos = None
        self.progress = 0.0
        self.timer_id = None
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vbox.set_margin_top(16); vbox.set_margin_bottom(16)
        vbox.set_margin_start(16); vbox.set_margin_end(16)
        self.add(vbox)
        self.toggle_btn = Gtk.ToggleButton(label='Enable Dwell Click')
        self.toggle_btn.connect('toggled', self.toggle)
        vbox.pack_start(self.toggle_btn, False, False, 0)
        grid = Gtk.Grid(column_spacing=10, row_spacing=8)
        grid.attach(Gtk.Label(label='Dwell delay (s):', xalign=0), 0, 0, 1, 1)
        self.delay_scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0.5, 3.0, 0.1)
        self.delay_scale.set_value(1.0)
        self.delay_scale.connect('value-changed', lambda s: setattr(self, 'dwell_time', s.get_value()))
        grid.attach(self.delay_scale, 1, 0, 2, 1)
        vbox.pack_start(grid, False, False, 0)
        self.da = Gtk.DrawingArea(); self.da.set_size_request(200, 200)
        self.da.set_halign(Gtk.Align.CENTER)
        self.da.connect('draw', self.draw_ring)
        vbox.pack_start(self.da, True, True, 0)
        self.status = Gtk.Label(label='Disabled. Enable to activate dwell clicking.')
        self.status.set_line_wrap(True)
        vbox.pack_start(self.status, False, False, 0)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
        self.connect('motion-notify-event', self.on_motion)
    def toggle(self, btn):
        self.enabled = btn.get_active()
        if self.enabled:
            self.status.set_text(f'Active: hover for {self.dwell_time:.1f}s to click')
            self.timer_id = GLib.timeout_add(50, self.tick)
        else:
            self.status.set_text('Disabled.')
            if self.timer_id: GLib.source_remove(self.timer_id); self.timer_id = None
            self.progress = 0; self.da.queue_draw()
    def on_motion(self, w, e):
        if not self.enabled: return
        pos = (e.x_root, e.y_root)
        if self.dwell_pos is None:
            self.dwell_pos = pos; self.dwell_start = time.time()
        else:
            dx = pos[0]-self.dwell_pos[0]; dy = pos[1]-self.dwell_pos[1]
            if math.sqrt(dx*dx+dy*dy) > 8:
                self.dwell_pos = pos; self.dwell_start = time.time()
                self.progress = 0
    def tick(self):
        if not self.enabled: return False
        if self.dwell_pos:
            elapsed = time.time() - self.dwell_start
            self.progress = min(1.0, elapsed / self.dwell_time)
            if self.progress >= 1.0:
                self.do_click()
                self.dwell_pos = None; self.progress = 0
        self.da.queue_draw()
        return True
    def do_click(self):
        x, y = self.dwell_pos
        try: subprocess.run(['xdotool','click','--clearmodifiers','1'], capture_output=True)
        except Exception: pass
        self.status.set_text(f'Clicked at ({int(x)},{int(y)})')
    def draw_ring(self, w, cr):
        alloc = w.get_allocation()
        cx, cy = alloc.width/2, alloc.height/2
        r = min(cx,cy) - 10
        cr.set_source_rgb(0.9,0.9,0.9); cr.paint()
        cr.set_source_rgb(0.8,0.8,0.8); cr.set_line_width(12)
        cr.arc(cx, cy, r, 0, math.pi*2); cr.stroke()
        if self.progress > 0:
            col = (0.2, 0.8, 0.2) if self.progress < 0.8 else (0.9, 0.4, 0.1)
            cr.set_source_rgb(*col)
            cr.set_line_width(14)
            cr.arc(cx, cy, r, -math.pi/2, -math.pi/2 + self.progress*math.pi*2)
            cr.stroke()
        cr.set_source_rgb(0.3,0.3,0.3); cr.set_font_size(16)
        txt = f'{int(self.progress*100)}%'
        ext = cr.text_extents(txt)
        cr.move_to(cx-ext.width/2, cy+ext.height/2)
        cr.show_text(txt)

win = App()
win.connect('destroy', Gtk.main_quit)
win.show_all()
Gtk.main()
