マウスを自動で移動させてスリープさせないアプリ（SoulMouse）

##離席中でもWindowsをスリープさせたくない
体は離席しても、魂だけは在席してマウス操作しててほしい。
というわけで、プログラムでマウス操作させてみました。
##作ったアプリ
・カーソル移動 → スクロール → 待ち、を繰り返すアプリ
・それぞれの時間はランダム（配布版では「待ち」時間はもう少し長め）
・人がいなくても、人のぬくもりを感じるアプリになりました
![GhostMouse-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2467542/9fc16a0d-c752-c886-33f1-4377aa445284.gif)
##ソースコード
[マウスをぷるぷる動かしてスクリーンセーバを抑制するアプリ](https://qiita.com/ms22912/items/90ca200305d1fea656a4) を参考にさせていただきました。ありがとうございます。

上記ページのソースコードに、以下を追加しています。
（Wait, Move, Wheel のステート追加）

```csharp:Form1.cs
const int MOUSEEVENTF_WHEEL = 0x800;
INPUT[] mInput = new INPUT[1];
Stopwatch mStopwatch = new Stopwatch();
enum EState { Wait, Move, Wheel }
EState mState = EState.Wait;
Random mRandom = new Random();
int mTargetX = 0, mTargetY = 0;

private async void autoMouseRunStart()
{
    isAppRun = true;
    int screen_width = Screen.PrimaryScreen.Bounds.Width;
    int screen_height = Screen.PrimaryScreen.Bounds.Height;
    mStopwatch.Restart();
    mInput[0].mi.dx = Cursor.Position.X * (65535 / screen_width);
    mInput[0].mi.dy = Cursor.Position.Y * (65535 / screen_height);
    mInput[0].mi.dwFlags = MOUSEEVENTF_MOVED | MOUSEEVENTF_ABSOLUTE;

    // 開始時点でマウスがPrimaryScreenに無い場合、PrimaryScreenに強制移動させる
    if (Cursor.Position.X > screen_width || Cursor.Position.Y > screen_height)
    {
        mInput[0].mi.dx = (screen_width / 2) * (65535 / screen_width);
        mInput[0].mi.dy = (screen_height / 2) * (65535 / screen_height);
        SendInput(1, mInput, Marshal.SizeOf(mInput[0]));
    }

    await Task.Run(() =>
    {
        while (isAppRun)
        {
            switch (mState)
            {
            case EState.Wait:
                // 一定時間経過したら EState.Move に移行
                if (mStopwatch.ElapsedMilliseconds > mWaitTime)
                {
                    mState = EState.Move;
                    mMoveTime = mRandom.Next(1000, 3000); // 1～3秒でマウス移動
                    mTargetX = mRandom.Next(0, screen_width);  // ランダムなX座標
                    mTargetY = mRandom.Next(0, screen_height); // ランダムなY座標
                    mInput[0].mi.dwFlags = MOUSEEVENTF_MOVED | MOUSEEVENTF_ABSOLUTE;
                    mStopwatch.Restart();
                }

                // 適当にスリープさせる
                // さもないと、CPU100%で処理してしまう
                System.Threading.Thread.Sleep(5);
                break;

            case EState.Move:
                // 一定時間経過したら EState.Wheel に移行
                if (mStopwatch.ElapsedMilliseconds > mMoveTime)
                {
                    mState = EState.Wheel;
                    mWheelTime = mRandom.Next(100, 800); // 0.1～0.8秒でマウスホイール
                    mInput[0].mi.dwFlags = MOUSEEVENTF_WHEEL;
                    mInput[0].mi.mouseData = mRandom.Next(-1, 2);
                    mStopwatch.Restart();
                }

                // カーソルを目標に追従させる
                mInput[0].mi.dx += (int)((mTargetX - Cursor.Position.X) * 0.07) * (65535 / screen_width);
                mInput[0].mi.dy += (int)((mTargetY - Cursor.Position.Y) * 0.07) * (65535 / screen_height);

                // 適当にスリープさせる
                // さもないと、カーソル移動処理が実行されすぎて移動が速くなりすぎてしまう
                System.Threading.Thread.Sleep(5);

                break;

            case EState.Wheel:
                // 一定時間経過したら EState.Wait に移行
                if (mStopwatch.ElapsedMilliseconds > mWheelTime)
                {
                    mState = EState.Wait;
                    mWaitTime = mRandom.Next(10000, 30000); // 10～300秒でマウス停止
                    mInput[0].mi.dwFlags = 0;
                    mStopwatch.Restart();
                }

                // マウスホイールではスリープさせない
                // スリープさせると滑らかなスクロールにならなかった
                //System.Threading.Thread.Sleep(1);

                break;
            }

            // イベントの実行
            // Cursor.Position への代入や、SetCursorPos での座標セットではスリープ状態になってしまうので SendInput を使用
            SendInput(1, mInput, Marshal.SizeOf(mInput[0]));
        }
    });
}

// 機能停止
private void autoMouseRunStop()
{
    isAppRun = false;
    mState = EState.Wait;
    mTargetX = mTargetY = 0;
    mWaitTime = mMoveTime = mWheelTime = 2000;
}
```
マウスホイール時に Thread.Sleep するとスクロールがガクガクしていました。
よって、EState.Wheel では Thread.Sleep していません。
CPU速度に依存するコードになっているので、高性能PCでは Thread.Sleep する必要があるかもしれません。

プロジェクトは以下にあります。

https://github.com/kondeneenen/SoulMouse

##アプリを使用してみる
1. [こちら](https://kondeneenen.com/soulmouse/) の「Download SoulMouse.zip」からダウンロード
2. zip を展開
3. SoulMouse.exe を実行するとタスクトレイに登場（灰色矢印アイコン）
4. 「CTRL + SHIT + S」で開始（カーソルが自動で動き出す）
5. 「CTRL + SHIT + D」で停止
6. タスクトレイのアイコンを右クリック「終了」でタスクトレイから削除

:::note info
・よくPCスリープになってしまう人は是非
・テレワーク監視はスリープ時間チェックから行われることが多い
・Webカメラを定期送信するなどの本気の監視には対応できないですが
・いずれにせよ、用法・用量を守って正しくお使いください
:::