マウスを自動で移動してスクリーンセイバーの起動を抑制する


# 概要
PowerShell の Class の手習いに「マウスふるふる」的なスクリプトを作成してみました。
実用向きというよりはお遊びのスクリプトですが、もし利用される場合は[こちらの記事のスクリプト](https://qiita.com/earthdiver1/items/a0a016f636de0ba3bbc7)と組み合わせることをお薦めします。

# 特長

- 手でマウスを動かした最後の位置を中心として、自動でマウスが移動します。
- 連続した自動移動では慣性（前回移動の勢いみないなもの）を維持して曲線的に移動します。
- 自動移動の大きさは正規分布に従います（たまに大きく動く）。

# コード

```powershell:mousejiggler.ps1
# by earthdiver1
##################
$linear   = $false  # マウスを常に直線的に移動する場合は $true に変更 
$std      =  100.0  # 最後に手でマウスを止めた場所を中心とした自動移動距離（縦・横）の標準偏差[pixel]
$int_minS =   1000  # マウスを手で動かしていない場合の、自動移動までの最小待機時間[msec]
$int_maxS =  10000  # マウスを手で動かしていない場合の、自動移動までの最大待機時間[msec]
$int_minL =  60000  # マウスを手で動かした場合の、自動移動までの最小待機時間[msec]
$int_maxL = 180000  # マウスを手で動かした場合の、自動移動までの最大待機時間[msec]
##################

Add-Type -A System.Drawing
Add-Type -A System.Windows.Forms
Add-Type -T @'
using System.Runtime.InteropServices;
public static class Win32 {
   [DllImport("user32.dll")]public extern static void mouse_event(int dwFlags,int dx,int dy,int dwData,int dwExtraInfo);
}
'@

iex @'
class myPoint {
   [double]$x
   [double]$y
   myPoint ([double]$x, [double]$y) { $this.x = $x      ; $this.y = $y      }
   myPoint ([double]$scalar)        { $this.x = $scalar ; $this.y = $scalar }
   myPoint ([Drawing.Point]$p)      { $this.x = $p.x    ; $this.y = $p.y    }
   [myPoint] Round()                { return [myPoint]::new([Math]::Round($this.x), [Math]::Round($this.y)) }
   [Drawing.Point] ToPoint()        { return [Drawing.Point]::new([int][Math]::Round($this.x), [int][Math]::Round($this.y)) }
   [bool] Equals([object]$other)    { return $this.x -eq $other.x -and $this.y -eq $other.y }
   static [myPoint] op_Addition    ([myPoint]$left, [myPoint]$right) { return [myPoint]::new($left.x + $right.x, $left.y + $right.y) }
   static [myPoint] op_Subtraction ([myPoint]$left, [myPoint]$right) { return [myPoint]::new($left.x - $right.x, $left.y - $right.y) }
   static [myPoint] op_Multiply    ([myPoint]$left, [myPoint]$right) { return [myPoint]::new($left.x * $right.x, $left.y * $right.y) }
   static [myPoint] op_Multiply    ([myPoint]$left, [double]$right)  { return [myPoint]::new($left.x * $right  , $left.y * $right  ) }
   static [myPoint] op_Implicit    ([double]$scalar)                 { return [double]$scalar }
}
'@

function Normal ($mu, $sigma) { 
   # 平均 $mu, 標準偏差 $sigma の（近似的な）正規分布に従う乱数を返す。
   return ((1..12 | %{ Get-Random -Minimum 0.0 -Maximum 1.0 } | Measure-Object -Sum).Sum - 6.0) * $sigma + $mu
}

function SmoothMove ($start, $end) {
   if ($inertia.x -eq -9999.0) {
      $bezier = ($start + $end) * 0.5
   } else {
      $bezier = $start + $inertia
   }
   for ( $i = 1; $i -le 50; $i++ ) {
      $t = $i/50
      $p = $start*(1-$t)*(1-$t) + $bezier*2*(1-$t)*$t + $end*$t*$t
      [Windows.Forms.Cursor]::Position = $p.ToPoint()
      [Win32]::mouse_event(0x0001, 0, 0, 0, 0)
      $before = [Windows.Forms.Cursor]::Position
      Microsoft.PowerShell.Utility\Start-Sleep -MilliSeconds 10
      $after = [Windows.Forms.Cursor]::Position
      if ($after -ne $before) { break }
      if ($i -eq 40) { $script:inertia = $end - $p }
   }
}

$epsilon = 5  # 許容誤差（振動などによってマウスが移動することを考慮）
$p_init  = [myPoint]::new(-9999.0, -9999.0)
$p_last  = $p_init

while ( $true ) {
   $p_now = [myPoint]::new([Windows.Forms.Cursor]::Position)
   if ([Math]::Abs($p_now.x - $p_last.x) -le $epsilon -and `
       [Math]::Abs($p_now.y - $p_last.y) -le $epsilon      ) {
      $p_new = ([myPoint]::new((Normal $p_base.x $std), (Normal $p_base.y $std))).Round()
      SmoothMove $p_now $p_new
      $p_last = [myPoint]::new([Windows.Forms.Cursor]::Position)
      if ($linear -or $p_last -ne $p_new) { $inertia = $p_init }
      $interval = Get-Random -Minimum $int_minS -Maximum $int_maxS
   } else {
      $p_base = $p_now
      $p_last = $p_now
      $inertia = $p_init
      $interval = Get-Random -Minimum $int_minL -Maximum $int_maxL
   }
   Start-Sleep -MilliSeconds $interval
}
```

# 備考
- スクリプトの構文解析の時点（`Add-Type` コマンドレットの実行前）で Class定義が検証されて 「`[Drawing.Point]` の型がみつからない」というエラーが発生してしまうのを回避するために Class定義を `iex`（`Invoke-Expression`）で実行しています[^1]。
- 暗黙の型変換が邪魔をして、（両側の型が異なる場合の）演算子のオーバーローディングがうまく働かなかったのですが、op_Implicit メソッドの定義で暗黙の型変換を半ば強引に無効化することで回避できました（裏技？）。



[^1]: 他のバージョンでは検証していませんが、少なくとも PowerShell 5.1では `using assembly` を使っても駄目なようです。

　
クリエイティブ・コモンズ 表示 - 継承 4.0 国際