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