Mover: utilidad para mover el ratón tras inactividad
- Mover.ps1: activación tras inactividad, parada al detectar uso, deslizamiento animado visible, mensajes de estado y cierre de instancias previas. - main.py: movimiento solo tras 3 minutos de inactividad. - Documentación: README, AGENTS, CHANGELOG y TODO. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
commit
5ecdde187e
8 files changed
+511
No files matched your search
@@ -0,0 +1,4 @@
|
||||
* text=auto
|
||||
*.ps1 text eol=crlf
|
||||
*.md text eol=crlf
|
||||
*.py text eol=lf
|
||||
@@ -0,0 +1,3 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.lnk
|
||||
@@ -0,0 +1,44 @@
|
||||
# AGENTS.md
|
||||
|
||||
Guía para agentes de IA y colaboradores que trabajen en este repositorio.
|
||||
|
||||
## Proyecto
|
||||
|
||||
Utilidad para Windows que mueve el puntero del ratón solo tras un periodo de inactividad del
|
||||
usuario y se detiene al detectar uso real. Ver `README.md`.
|
||||
|
||||
- `Mover.ps1`: implementación principal (PowerShell + C# embebido con `Add-Type`).
|
||||
- `main.py`: implementación original en Python (`pyautogui`), mantenida como referencia.
|
||||
|
||||
## Normas
|
||||
|
||||
- Idioma: comentarios, documentación y mensajes en **castellano**.
|
||||
- Toda función nueva debe llevar documentación (`<# .SYNOPSIS #>` en PowerShell,
|
||||
`/// <summary>` en C#, docstrings en Python). Actualizar la documentación al modificarlas.
|
||||
- No modificar ficheros sin autorización previa del usuario.
|
||||
- Registrar los cambios en `CHANGELOG.md` y las tareas pendientes en `TODO.md`.
|
||||
|
||||
## Particularidades técnicas
|
||||
|
||||
- **Codificación:** `Mover.ps1` debe mantenerse en **UTF-8 con BOM**; sin BOM, Windows
|
||||
PowerShell 5.1 lo lee como ANSI y corrompe las tildes. Tras editar, comprobar los primeros
|
||||
bytes (`239,187,191`).
|
||||
- **Detección de actividad:** los movimientos propios actualizan `GetLastInputInfo`. Por eso se
|
||||
guarda `dwTime` tras cada movimiento y cualquier cambio posterior se interpreta como uso real.
|
||||
No romper este mecanismo.
|
||||
- **Instancias:** el script cierra al arrancar otras instancias que ejecuten `Mover.ps1` con
|
||||
`-File`. Durante las pruebas no dejar instancias huérfanas.
|
||||
- **Movimiento:** se usa `SendInput` con `MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK` para
|
||||
evitar la aceleración del puntero y soportar varios monitores.
|
||||
- `Add-Type` puede tardar ~20 s en un proceso nuevo; no confundir con un bloqueo.
|
||||
|
||||
## Validación
|
||||
|
||||
```powershell
|
||||
# Sintaxis
|
||||
$err=$null; [System.Management.Automation.Language.Parser]::ParseFile('Mover.ps1',[ref]$null,[ref]$err) | Out-Null; $err.Count
|
||||
# Prueba funcional rápida
|
||||
powershell -ExecutionPolicy Bypass -File .\Mover.ps1 -TiempoInactividad 10
|
||||
# Python
|
||||
python -m py_compile main.py
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
Todos los cambios relevantes del proyecto se documentan en este fichero.
|
||||
El formato se basa en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/).
|
||||
|
||||
## [1.0.0] - 2026-10-07
|
||||
|
||||
### Añadido
|
||||
- `Mover.ps1`: implementación en PowerShell sin dependencias externas.
|
||||
- Activación solo tras `-TiempoInactividad` segundos sin uso (por defecto 180) mediante
|
||||
`GetLastInputInfo`.
|
||||
- Parada inmediata al detectar uso real de teclado o ratón.
|
||||
- Deslizamiento animado y visible del puntero con `SendInput` en coordenadas absolutas,
|
||||
limitado al monitor actual (`-DesplazamientoMin`, `-DesplazamientoMax`, `-DuracionMs`).
|
||||
- Mensajes con hora al iniciar y detener el movimiento.
|
||||
- Cierre automático de instancias anteriores al arrancar.
|
||||
- Documentación: `README.md`, `AGENTS.md`, `CHANGELOG.md` y `TODO.md`.
|
||||
|
||||
### Cambiado
|
||||
- `main.py`: el movimiento solo se activa tras 3 minutos de inactividad y se detiene al
|
||||
detectar uso del usuario. Se sustituye `except:` por `except Exception` para permitir
|
||||
detenerlo con `Ctrl+C`.
|
||||
|
||||
## [0.1.0]
|
||||
|
||||
### Añadido
|
||||
- `main.py`: versión inicial que movía el ratón aleatoriamente de forma continua.
|
||||
@@ -0,0 +1,258 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Mueve el ratón aleatoriamente solo tras un periodo de inactividad del usuario.
|
||||
|
||||
.DESCRIPTION
|
||||
Espera a que transcurran TiempoInactividad segundos sin uso de teclado ni ratón y, a partir
|
||||
de entonces, desliza el cursor de forma animada y visible hacia un punto aleatorio cada 2-4
|
||||
segundos. En cuanto detecta uso real del teclado o del ratón deja de moverlo y vuelve a
|
||||
esperar. Muestra un mensaje con la hora al iniciar y al detener el movimiento. Al arrancar
|
||||
cierra cualquier instancia anterior del script que siga en ejecución. Solo funciona en Windows.
|
||||
|
||||
.PARAMETER TiempoInactividad
|
||||
Segundos sin actividad antes de empezar a mover el ratón (por defecto 180).
|
||||
|
||||
.PARAMETER DesplazamientoMin
|
||||
Distancia mínima en píxeles de cada deslizamiento (por defecto 150).
|
||||
|
||||
.PARAMETER DesplazamientoMax
|
||||
Distancia máxima en píxeles de cada deslizamiento (por defecto 300).
|
||||
|
||||
.PARAMETER DuracionMs
|
||||
Duración en milisegundos de la animación de cada deslizamiento (por defecto 600).
|
||||
|
||||
.EXAMPLE
|
||||
.\Mover.ps1
|
||||
.\Mover.ps1 -TiempoInactividad 60 -DesplazamientoMax 400
|
||||
#>
|
||||
param(
|
||||
[int]$TiempoInactividad = 180,
|
||||
[int]$DesplazamientoMin = 150,
|
||||
[int]$DesplazamientoMax = 300,
|
||||
[int]$DuracionMs = 600
|
||||
)
|
||||
|
||||
# Intervalo de comprobación de actividad durante las pausas (milisegundos)
|
||||
$IntervaloComprobacionMs = 200
|
||||
# Número de pasos intermedios de cada animación
|
||||
$PasosAnimacion = 40
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Cierra las instancias anteriores de este script que sigan en ejecución.
|
||||
.DESCRIPTION
|
||||
Busca procesos de PowerShell cuya línea de comandos ejecute este mismo fichero mediante
|
||||
-File (excluyendo el proceso actual) y los detiene, para evitar que varias instancias se
|
||||
interpreten mutuamente sus movimientos como actividad del usuario.
|
||||
#>
|
||||
function Detener-InstanciasPrevias {
|
||||
$nombre = [regex]::Escape((Split-Path -Leaf $PSCommandPath))
|
||||
$patron = "-File\s+`"?[^`"]*$nombre"
|
||||
$previas = Get-CimInstance Win32_Process -Filter "Name='powershell.exe' OR Name='pwsh.exe'" |
|
||||
Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -match $patron }
|
||||
foreach ($proceso in $previas) {
|
||||
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Cerrando instancia anterior (PID $($proceso.ProcessId))." -ForegroundColor Cyan
|
||||
Stop-Process -Id $proceso.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Detener-InstanciasPrevias
|
||||
|
||||
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
||||
|
||||
# La compilación del código C# puede tardar varios segundos en el primer arranque
|
||||
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Inicializando..."
|
||||
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// Acceso a las APIs de Win32 para consultar la inactividad y mover el ratón.
|
||||
/// </summary>
|
||||
public static class Inactividad
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct LASTINPUTINFO { public uint cbSize; public uint dwTime; }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MOUSEINPUT
|
||||
{
|
||||
public int dx; public int dy; public uint mouseData;
|
||||
public uint dwFlags; public uint time; public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct INPUT { public uint type; public MOUSEINPUT mi; }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINT { public int x; public int y; }
|
||||
|
||||
[DllImport("user32.dll")] private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
||||
[DllImport("kernel32.dll")] private static extern uint GetTickCount();
|
||||
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT lpPoint);
|
||||
[DllImport("user32.dll")] private static extern int GetSystemMetrics(int nIndex);
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el instante (ticks del sistema, ms) de la última entrada de teclado o ratón.
|
||||
/// </summary>
|
||||
public static uint ObtenerUltimaEntrada()
|
||||
{
|
||||
LASTINPUTINFO info = new LASTINPUTINFO();
|
||||
info.cbSize = (uint)Marshal.SizeOf(info);
|
||||
if (!GetLastInputInfo(ref info)) throw new System.ComponentModel.Win32Exception();
|
||||
return info.dwTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula los segundos transcurridos desde la última entrada de teclado o ratón.
|
||||
/// </summary>
|
||||
public static double SegundosInactivo()
|
||||
{
|
||||
// La resta sin signo tolera el desbordamiento de GetTickCount (~49,7 días)
|
||||
return unchecked(GetTickCount() - ObtenerUltimaEntrada()) / 1000.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mueve el puntero a una posición absoluta de pantalla mediante SendInput (entrada real,
|
||||
/// sin verse afectada por la aceleración del puntero).
|
||||
/// </summary>
|
||||
/// <param name="x">Coordenada X de destino en el escritorio virtual.</param>
|
||||
/// <param name="y">Coordenada Y de destino en el escritorio virtual.</param>
|
||||
public static void MoverAbsoluto(int x, int y)
|
||||
{
|
||||
int vx = GetSystemMetrics(76), vy = GetSystemMetrics(77); // SM_X/YVIRTUALSCREEN
|
||||
int vw = GetSystemMetrics(78), vh = GetSystemMetrics(79); // SM_CX/CYVIRTUALSCREEN
|
||||
INPUT[] entradas = new INPUT[1];
|
||||
entradas[0].type = 0; // INPUT_MOUSE
|
||||
// Normalización a 0..65535 sobre el escritorio virtual (todos los monitores)
|
||||
entradas[0].mi.dx = (int)Math.Round((x - vx) * 65535.0 / (vw - 1));
|
||||
entradas[0].mi.dy = (int)Math.Round((y - vy) * 65535.0 / (vh - 1));
|
||||
// MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
|
||||
entradas[0].mi.dwFlags = 0x0001 | 0x8000 | 0x4000;
|
||||
SendInput(1, entradas, Marshal.SizeOf(typeof(INPUT)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la posición actual del puntero.
|
||||
/// </summary>
|
||||
/// <returns>Array con las coordenadas {x, y}.</returns>
|
||||
public static int[] ObtenerPosicion()
|
||||
{
|
||||
POINT p;
|
||||
GetCursorPos(out p);
|
||||
return new int[] { p.x, p.y };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desliza el puntero de forma animada y visible desde su posición actual hasta el destino.
|
||||
/// </summary>
|
||||
/// <param name="xDestino">Coordenada X de destino.</param>
|
||||
/// <param name="yDestino">Coordenada Y de destino.</param>
|
||||
/// <param name="duracionMs">Duración total de la animación en milisegundos.</param>
|
||||
/// <param name="pasos">Número de pasos intermedios de la animación.</param>
|
||||
/// <returns>False si el usuario ha movido el ratón durante la animación; true en caso contrario.</returns>
|
||||
public static bool Deslizar(int xDestino, int yDestino, int duracionMs, int pasos)
|
||||
{
|
||||
int[] origen = ObtenerPosicion();
|
||||
int xEsperada = origen[0], yEsperada = origen[1];
|
||||
for (int i = 1; i <= pasos; i++)
|
||||
{
|
||||
// Si el puntero no está donde lo dejamos, el usuario lo ha movido
|
||||
int[] actual = ObtenerPosicion();
|
||||
if (Math.Abs(actual[0] - xEsperada) > 2 || Math.Abs(actual[1] - yEsperada) > 2) return false;
|
||||
|
||||
// Curva de suavizado (aceleración y frenado) para un movimiento natural
|
||||
double t = (double)i / pasos;
|
||||
double s = t * t * (3 - 2 * t);
|
||||
xEsperada = (int)Math.Round(origen[0] + (xDestino - origen[0]) * s);
|
||||
yEsperada = (int)Math.Round(origen[1] + (yDestino - origen[1]) * s);
|
||||
MoverAbsoluto(xEsperada, yEsperada);
|
||||
System.Threading.Thread.Sleep(duracionMs / pasos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Bloquea hasta que el usuario lleve TiempoInactividad segundos sin usar teclado ni ratón.
|
||||
#>
|
||||
function Esperar-Inactividad {
|
||||
while ([Inactividad]::SegundosInactivo() -lt $TiempoInactividad) {
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Calcula un destino aleatorio para el puntero dentro del monitor en el que se encuentra.
|
||||
.DESCRIPTION
|
||||
Elige una dirección aleatoria y una distancia entre DesplazamientoMin y DesplazamientoMax.
|
||||
Si el destino queda fuera del monitor, se invierte la dirección y, en último caso, se limita
|
||||
a sus bordes para no perder el puntero entre pantallas.
|
||||
.OUTPUTS
|
||||
Array con las coordenadas {x, y} de destino.
|
||||
#>
|
||||
function Obtener-DestinoAleatorio {
|
||||
$pos = [Inactividad]::ObtenerPosicion()
|
||||
$limites = [System.Windows.Forms.Screen]::FromPoint((New-Object System.Drawing.Point($pos[0], $pos[1]))).Bounds
|
||||
$angulo = (Get-Random -Minimum 0.0 -Maximum 1.0) * 2 * [Math]::PI
|
||||
$distancia = Get-Random -Minimum $DesplazamientoMin -Maximum ($DesplazamientoMax + 1)
|
||||
$dx = [int]($distancia * [Math]::Cos($angulo))
|
||||
$dy = [int]($distancia * [Math]::Sin($angulo))
|
||||
|
||||
# Invierte el sentido en el eje que se salga del monitor
|
||||
if ($pos[0] + $dx -lt $limites.Left -or $pos[0] + $dx -ge $limites.Right) { $dx = -$dx }
|
||||
if ($pos[1] + $dy -lt $limites.Top -or $pos[1] + $dy -ge $limites.Bottom) { $dy = -$dy }
|
||||
|
||||
$x = [Math]::Min([Math]::Max($pos[0] + $dx, $limites.Left + 5), $limites.Right - 6)
|
||||
$y = [Math]::Min([Math]::Max($pos[1] + $dy, $limites.Top + 5), $limites.Bottom - 6)
|
||||
return @($x, $y)
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Mueve el ratón aleatoriamente hasta detectar actividad real del usuario.
|
||||
.DESCRIPTION
|
||||
Desliza el puntero de forma animada para que el desplazamiento sea visible. Los movimientos
|
||||
propios también actualizan GetLastInputInfo, por lo que tras cada deslizamiento se guarda el
|
||||
instante resultante; cualquier cambio posterior indica uso real del usuario. Si el usuario
|
||||
mueve el ratón durante la animación, ésta se interrumpe.
|
||||
#>
|
||||
function Mover-MientrasInactivo {
|
||||
while ($true) {
|
||||
$destino = Obtener-DestinoAleatorio
|
||||
if (-not [Inactividad]::Deslizar($destino[0], $destino[1], $DuracionMs, $PasosAnimacion)) {
|
||||
return
|
||||
}
|
||||
Start-Sleep -Milliseconds 50
|
||||
$ultimaPropia = [Inactividad]::ObtenerUltimaEntrada()
|
||||
|
||||
$finPausa = (Get-Date).AddSeconds((Get-Random -Minimum 2 -Maximum 5))
|
||||
while ((Get-Date) -lt $finPausa) {
|
||||
Start-Sleep -Milliseconds $IntervaloComprobacionMs
|
||||
if ([Inactividad]::ObtenerUltimaEntrada() -ne $ultimaPropia) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Mover iniciado. Esperando $TiempoInactividad s de inactividad (Ctrl+C para salir)."
|
||||
|
||||
# Bucle principal: espera la inactividad, mueve el ratón y se detiene al detectar uso
|
||||
while ($true) {
|
||||
try {
|
||||
Esperar-Inactividad
|
||||
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Inactividad detectada: iniciando movimiento del ratón." -ForegroundColor Yellow
|
||||
Mover-MientrasInactivo
|
||||
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Actividad del usuario detectada: movimiento detenido." -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# Mover
|
||||
|
||||
Utilidad para Windows que evita que el equipo se bloquee o entre en suspensión moviendo el
|
||||
puntero del ratón **solo cuando el usuario lleva un tiempo sin usar el teclado ni el ratón**.
|
||||
En cuanto se detecta uso real, el movimiento se detiene y se vuelve a esperar.
|
||||
|
||||
## Contenido
|
||||
|
||||
| Fichero | Descripción |
|
||||
|--------------|-------------|
|
||||
| `Mover.ps1` | Implementación principal en PowerShell. Sin dependencias externas. |
|
||||
| `main.py` | Implementación original en Python (requiere `pyautogui`). Funcionalidad básica, sin animación. |
|
||||
|
||||
## Requisitos
|
||||
|
||||
- Windows (usa las APIs `user32`/`kernel32`).
|
||||
- `Mover.ps1`: Windows PowerShell 5.1 o PowerShell 7.
|
||||
- `main.py`: Python 3 y `pip install pyautogui`.
|
||||
|
||||
## Uso de `Mover.ps1`
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\Mover.ps1
|
||||
```
|
||||
|
||||
### Parámetros
|
||||
|
||||
| Parámetro | Por defecto | Descripción |
|
||||
|----------------------|-------------|-------------|
|
||||
| `-TiempoInactividad` | `180` | Segundos sin uso de teclado/ratón antes de empezar a mover. |
|
||||
| `-DesplazamientoMin` | `150` | Distancia mínima (píxeles) de cada deslizamiento. |
|
||||
| `-DesplazamientoMax` | `300` | Distancia máxima (píxeles) de cada deslizamiento. |
|
||||
| `-DuracionMs` | `600` | Duración de la animación de cada deslizamiento. |
|
||||
|
||||
Ejemplo de prueba rápida:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\Mover.ps1 -TiempoInactividad 10
|
||||
```
|
||||
|
||||
### Mensajes
|
||||
|
||||
```
|
||||
[07:45:57] Inicializando...
|
||||
[07:45:58] Mover iniciado. Esperando 180 s de inactividad (Ctrl+C para salir).
|
||||
[07:49:01] Inactividad detectada: iniciando movimiento del ratón.
|
||||
[07:50:12] Actividad del usuario detectada: movimiento detenido.
|
||||
```
|
||||
|
||||
Para detenerlo: `Ctrl+C` o cerrar la ventana.
|
||||
|
||||
### Acceso directo
|
||||
|
||||
Se puede crear un acceso directo con:
|
||||
|
||||
- **Destino:** `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "D:\Toni\Desarrollo\Mover\Mover.ps1"`
|
||||
- **Ejecutar:** Minimizada.
|
||||
|
||||
## Funcionamiento
|
||||
|
||||
1. Al arrancar cierra cualquier instancia anterior de `Mover.ps1` (varias instancias se
|
||||
interpretarían mutuamente sus movimientos como actividad del usuario).
|
||||
2. Consulta la inactividad con `GetLastInputInfo`/`GetTickCount`.
|
||||
3. Superado el umbral, desliza el puntero con una animación suavizada mediante `SendInput`
|
||||
(coordenadas absolutas), lo que genera entrada real y evita la aceleración del puntero.
|
||||
El destino se mantiene dentro del monitor actual.
|
||||
4. Tras cada deslizamiento guarda el instante de la última entrada; si cambia, ha sido el
|
||||
usuario y se detiene. Si el usuario mueve el ratón durante la animación, ésta se interrumpe.
|
||||
|
||||
## Notas
|
||||
|
||||
- La primera compilación de `Add-Type` puede tardar hasta ~20 s en un proceso nuevo.
|
||||
- El fichero `Mover.ps1` debe guardarse en **UTF-8 con BOM** para que Windows PowerShell 5.1
|
||||
muestre correctamente las tildes.
|
||||
@@ -0,0 +1,19 @@
|
||||
# TODO
|
||||
|
||||
## Pendiente
|
||||
|
||||
- [ ] Igualar `main.py` con `Mover.ps1` (deslizamiento animado, cierre de instancias previas,
|
||||
mensajes) o retirarlo si deja de usarse.
|
||||
- [ ] Añadir un icono en la bandeja del sistema para pausar/salir sin abrir la consola.
|
||||
- [ ] Opción para impedir la suspensión con `SetThreadExecutionState` sin mover el puntero.
|
||||
- [ ] Registro opcional en fichero de los periodos de actividad/inactividad.
|
||||
- [ ] Opción de arranque automático con Windows (carpeta Inicio o Programador de tareas).
|
||||
- [ ] Reducir el tiempo de arranque de `Add-Type` (p. ej. cachear el ensamblado compilado).
|
||||
|
||||
## Hecho
|
||||
|
||||
- [x] Activar el movimiento solo tras un periodo de inactividad.
|
||||
- [x] Detener el movimiento al detectar uso del teclado o del ratón.
|
||||
- [x] Movimiento visible con animación.
|
||||
- [x] Mensajes al iniciar y detener el movimiento.
|
||||
- [x] Cerrar instancias anteriores al arrancar.
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Mueve el ratón aleatoriamente solo tras un periodo de inactividad del usuario (solo Windows)."""
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
import random
|
||||
import time
|
||||
|
||||
import pyautogui
|
||||
|
||||
# Segundos sin uso de teclado/ratón antes de empezar a mover el cursor
|
||||
TIEMPO_INACTIVIDAD = 180
|
||||
# Desplazamiento máximo (píxeles) en cada eje
|
||||
DESPLAZAMIENTO_MAX = 50
|
||||
# Intervalo de comprobación de actividad (segundos)
|
||||
INTERVALO_COMPROBACION = 0.2
|
||||
|
||||
|
||||
class LASTINPUTINFO(ctypes.Structure):
|
||||
"""Estructura de Win32 que recibe el instante de la última entrada del usuario."""
|
||||
|
||||
_fields_ = [("cbSize", ctypes.wintypes.UINT), ("dwTime", ctypes.wintypes.DWORD)]
|
||||
|
||||
|
||||
def obtener_ultima_entrada():
|
||||
"""
|
||||
Obtiene el instante (en ticks del sistema, ms) de la última entrada de teclado o ratón.
|
||||
|
||||
:return: valor dwTime devuelto por GetLastInputInfo.
|
||||
"""
|
||||
info = LASTINPUTINFO()
|
||||
info.cbSize = ctypes.sizeof(LASTINPUTINFO)
|
||||
if not ctypes.windll.user32.GetLastInputInfo(ctypes.byref(info)):
|
||||
raise ctypes.WinError()
|
||||
return info.dwTime
|
||||
|
||||
|
||||
def segundos_inactivo():
|
||||
"""
|
||||
Calcula los segundos transcurridos desde la última entrada de teclado o ratón.
|
||||
|
||||
:return: segundos de inactividad.
|
||||
"""
|
||||
ahora = ctypes.windll.kernel32.GetTickCount() & 0xFFFFFFFF
|
||||
# Máscara para tolerar el desbordamiento de GetTickCount (~49,7 días)
|
||||
return ((ahora - obtener_ultima_entrada()) & 0xFFFFFFFF) / 1000.0
|
||||
|
||||
|
||||
def esperar_inactividad():
|
||||
"""Bloquea hasta que el usuario lleve TIEMPO_INACTIVIDAD segundos sin usar teclado ni ratón."""
|
||||
while segundos_inactivo() < TIEMPO_INACTIVIDAD:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def mover_mientras_inactivo():
|
||||
"""
|
||||
Mueve el ratón aleatoriamente hasta detectar actividad real del usuario.
|
||||
|
||||
Los movimientos propios también actualizan GetLastInputInfo, por lo que tras cada
|
||||
movimiento se guarda el dwTime resultante; cualquier cambio posterior indica uso real.
|
||||
"""
|
||||
while True:
|
||||
pyautogui.moveRel(random.randrange(-DESPLAZAMIENTO_MAX, DESPLAZAMIENTO_MAX),
|
||||
random.randrange(-DESPLAZAMIENTO_MAX, DESPLAZAMIENTO_MAX))
|
||||
ultima_propia = obtener_ultima_entrada()
|
||||
fin_pausa = time.monotonic() + random.randrange(2, 5)
|
||||
while time.monotonic() < fin_pausa:
|
||||
time.sleep(INTERVALO_COMPROBACION)
|
||||
if obtener_ultima_entrada() != ultima_propia:
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
"""Bucle principal: espera la inactividad, mueve el ratón y se detiene al detectar uso."""
|
||||
while True:
|
||||
try:
|
||||
esperar_inactividad()
|
||||
mover_mientras_inactivo()
|
||||
except Exception:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user