Executando verificação de segurança...
2

Duplo clique fantasma no mouse Logitech: contornei no Linux com Python (evdev)

Meu mouse Logitech começou a dar o famoso duplo clique fantasma: selecionar texto virava “abre/fecha”, arrastar arquivo “soltava” no meio, etc.

Em vez de trocar switch/soldar ou tentar limpar e não ter garantia de resolução, resolvi ver se o amigo GPT consegue tratar isso via software, pra minha sorte meu SO é o Linux então deve ser mais capaz, já que eu vi não é viavel no Windows: capturar os eventos do mouse real, filtrar o bounce e reenviar tudo por um mouse virtual “limpo”.


O que está acontecendo

Isso quase sempre é desgaste do switch mecânico: o contato “vibra” por alguns milissegundos antes de estabilizar, e o sistema interpreta como cliques separados.

A correção clássica é debounce: ignorar cliques repetidos dentro de uma janela pequena como no caso foi usado o 100ms.


A ideia da solução

  1. Ler eventos do mouse físico via evdev
  2. Dar grab no dispositivo físico (o sistema para de receber eventos dele)
  3. Filtrar “press” repetido em menos de 100ms
  4. Repassar eventos válidos para um mouse virtual via uinput

Aviso: grab() captura o dispositivo no nível do sistema.
A partir daí o mouse físico para de enviar eventos diretamente para o desktop — só o mouse virtual (via uinput) continua funcionando.
Se o processo cair/travar, o mouse pode ficar “mudo” até você encerrar o processo que está segurando o device.


Como rodar

Instale a lib:

pip install evdev

Descubra o caminho do seu mouse:

ls -l /dev/input/by-id/ | grep -i mouse

você vai ver linhas parecidas com:

usb-Logitech_USB_Receiver-event-mouse -> ../event7
usb-Logitech_USB_Receiver-mouse       -> ../mouse1

O que você faz com isso:

  1. Use o que termina em -event-mouse (é o que o evdev lê melhor pra eventos):
  • Exemplo:

    • /dev/input/by-id/usb-Logitech_USB_Receiver-event-mouse

Cole esse caminho no script:

MOUSE_DEV = '/dev/input/by-id/usb-Logitech_USB_Receiver-event-mouse'

Depois rode (geralmente precisa de root por causa de /dev/input e uinput):

sudo python3 debounced_mouse.py

O código (completo)

Só precisa ajustar o MOUSE_DEV pro seu caso.

#!/usr/bin/env python3
"""
Filtro global de mouse para eliminar "duplo clique fantasma" (debounce).
Ideia: Lê eventos do mouse físico, cria um mouse virtual via uinput e
repassa tudo, EXCETO cliques repetidos dentro do intervalo de debounce.
"""

from evdev import InputDevice, ecodes, UInput
import datetime
import signal
import sys

# Identificação do dispositivo físico (ajuste para o seu modelo)
MOUSE_DEV = '/dev/input/by-id/usb-Logitech_Wireless_Receiver-event-mouse'

# 100ms: qualquer segundo clique no mesmo botão abaixo disso é descartado
DEBOUNCE_SECONDS = 0.10


def main():
    try:
        dev = InputDevice(MOUSE_DEV)
    except FileNotFoundError:
        print(f"Erro: Dispositivo {MOUSE_DEV} não encontrado.")
        print("Dica: rode `ls -l /dev/input/by-id/ | grep -i mouse` e ajuste o caminho.")
        return

    # Capacidades do mouse virtual (botões e eixos de movimento)
    uinput_caps = {
        ecodes.EV_KEY: [
            ecodes.BTN_LEFT,
            ecodes.BTN_RIGHT,
            ecodes.BTN_MIDDLE,
            ecodes.BTN_SIDE,
            ecodes.BTN_EXTRA,
        ],
        ecodes.EV_REL: [
            ecodes.REL_X,
            ecodes.REL_Y,
            ecodes.REL_WHEEL,
            ecodes.REL_HWHEEL,
        ],
    }

    ui = UInput(
        uinput_caps,
        name='debounced-mouse',
        vendor=dev.info.vendor,
        product=dev.info.product,
    )

    def cleanup_and_exit(_signum=None, _frame=None):
        try:
            dev.ungrab()
        except Exception:
            pass
        try:
            ui.close()
        except Exception:
            pass
        try:
            dev.close()
        except Exception:
            pass
        print("\nEncerrado. Mouse físico liberado.")
        sys.exit(0)

    signal.signal(signal.SIGINT, cleanup_and_exit)
    signal.signal(signal.SIGTERM, cleanup_and_exit)

    # Dá um "grab" no device real (o SO para de ouvir o mouse físico defeituoso)
    dev.grab()

    last_press_time = {}
    ignore_buttons = set()

    # Botões que vamos filtrar (pode incluir SIDE/EXTRA se quiser)
    watched_buttons = {ecodes.BTN_LEFT, ecodes.BTN_RIGHT, ecodes.BTN_MIDDLE}

    print(f"Monitorando {MOUSE_DEV} (Threshold: {DEBOUNCE_SECONDS * 1000:.0f}ms)...")

    try:
        for event in dev.read_loop():
            # Tratamos apenas botões (EV_KEY), movimento (EV_REL) e sincronização (EV_SYN)
            if event.type not in (ecodes.EV_KEY, ecodes.EV_REL, ecodes.EV_SYN):
                continue

            if event.type == ecodes.EV_KEY and event.code in watched_buttons:
                btn = event.code
                t = event.sec + event.usec / 1_000_000.0

                if event.value == 1:  # 1 = Press
                    last = last_press_time.get(btn, 0.0)
                    dt = t - last
                    dt_ms = dt * 1000.0

                    if dt < DEBOUNCE_SECONDS:
                        now_str = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
                        print(f"[{now_str}] ⚡ Filtro ativado! Clique fantasma no botão {btn} ({dt_ms:.1f} ms)")
                        ignore_buttons.add(btn)
                        continue
                    else:
                        last_press_time[btn] = t

                # Se estamos ignorando esse botão, descartamos até o Release
                if btn in ignore_buttons:
                    if event.value == 0:  # 0 = Release
                        ignore_buttons.discard(btn)
                    continue

            # Repassa o evento filtrado para o mouse virtual
            if event.type == ecodes.EV_SYN:
                ui.syn()
            else:
                ui.write_event(event)

    finally:
        cleanup_and_exit()


if __name__ == '__main__':
    main()

O “log da vergonha” do hardware

Sem filtro, isso virava clique errado na prática:

[09:43:36.184] ⚡ Filtro ativado! Clique fantasma no botão 272 (96.1 ms)
[09:47:34.983] ⚡ Filtro ativado! Clique fantasma no botão 272 (32.0 ms)
[09:55:25.742] ⚡ Filtro ativado! Clique fantasma no botão 272 (31.7 ms)
[11:13:20.114] ⚡ Filtro ativado! Clique fantasma no botão 272 (31.4 ms)
[11:17:26.591] ⚡ Filtro ativado! Clique fantasma no botão 272 (27.9 ms)

Resultado

  • Economizei um mouse novo (por enquanto kkkk).
  • Voltei a ter paz pra trabalhar sem “duplo clique do nada”.
  • Não obtive muito bons resultados em arrastar arquivos sem "soltar" inesperadamente mas resolveu o duplo click que acava executando outras ações.
Carregando publicação patrocinada...