- 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>
259 lines
11 KiB
PowerShell
259 lines
11 KiB
PowerShell
<#
|
|
.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
|
|
}
|
|
}
|