1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スマホからカーソルを操作してみる

Posted at

はじめに

ふと、「WiiUや3DSからPCを操作できたら面白いんじゃないか」と考えて、ノープランで作成しました。

しかし、この2つはJavaScriptがうまく動作しなかったため、スマホでの操作に切り替えました。

仕組み

簡単に書くと、以下の通りになります。

スマホ(任意の端末)
↑↓
HTMLページ
↑↓
サーバー(C#)

コードと諸設定

サーバー(C#)

ここを参考に、管理者権限でプログラムを実行できるようにしてください。
そうしないとエラーが出ます。
ここ

コードは以下のページを引用、参考にしました。

参考1
参考2

共通部分はかなりコピペになってしまった...

使用したプロジェクトは、Visual Studioの「コンソールアプリケーション(NET.Framework)」

[IPアドレス]は、自身のPCのIPアドレスを入れてください。
例:192.168.12.34

Program.cs
using System;
using System.Net;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sample
{
    internal class Program
    {
        //いろいろインポート
        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        static extern void SetCursorPos(int X, int Y);

        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;
        static void Main(string[] args)
        {
            //Mainを非同期にできないので、適当な関数を挟んで実行
            Init();
            for (int i = 0;i < 10; i++)
            {
                Console.ReadKey();
            }
        }
        static async void Init()
        {
            //非同期関数を実行
            await Run();
        }
        static async Task Run()
        {
            //通信関係
            HttpListener s = new HttpListener();
            s.Prefixes.Add("http://[IPアドレス]:11000/ws/");
            s.Start();
            var hc = await s.GetContextAsync();

            if (!hc.Request.IsWebSocketRequest)
            {
                hc.Response.StatusCode = 400;
                hc.Response.Close();
                return;
            }
            
            var wsc = await hc.AcceptWebSocketAsync(null);
            var ws = wsc.WebSocket;
            //繋がるとここから先が実行
            int lck = 0;
            Console.WriteLine("start");
            //無限ループで受信し続ける
            while(true)
            {
                await Task.Delay(1);
                byte[] buffer = new byte[1024];
                try
                {
                    var receivedBytes = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    if (receivedBytes.Count != 0)
                    {
                        //データがあったら処理
                        string receivedData = Encoding.UTF8.GetString(buffer, 0, receivedBytes.Count);
                        string[] values = receivedData.Split(',');
                        //一応3つ来てるかチェック
                        if (values.Length == 3 && receivedData.Length >= 4)
                        {
                            //(x座標,y座標,クリック情報)を処理
                            Console.WriteLine(receivedData);
                            int x = int.Parse(values[0]);
                            int y = int.Parse(values[1]);
                            int ck = int.Parse(values[2]);
                            SetCursorPos(x, y);
                            //クリック情報の変更
                            if (ck == 1 && lck == 0)
                            {
                                mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                            }
                            else if (ck == 0 && lck == 1)
                            {
                                mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                            }
                            lck = ck;
                        }
                    }
                }
                catch (Exception ex)
                {
                    //エラーになったら無限ループを抜ける
                    break;
                }
            }

            //接続を閉じる
            await ws.CloseAsync(WebSocketCloseStatus.NormalClosure,
              "Done", CancellationToken.None);
        }
    }
}

本来は、オブジェクトをJSONで送受信するのが普通ですが、変数が単純なので、カンマ区切りで送受信しています。(x座標,y座標,クリック情報)

クライアント(HTML/JavaScript)

JavaScriptは前述の「参考1」を参考
こちらも変更点がなかったコピペ部分(connection変数関係)が多く、参考元には申し訳ない...

こちらも[IPアドレス]は、自身のPCのIPアドレスを入れてください。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>Test</title>
</head>
<body>
</body>
<script src="main.js"></script>
</html>
main.js
let keystat = {
    x: 0,
    y: 0,
    click: 0,
}

//画面サイズを入れる
const SCREEN_X = 1360;
const SCREEN_Y = 768;

//相似比
let MX = SCREEN_X / window.innerWidth;
let MY = SCREEN_Y / window.innerHeight;

//横長か
let iswide = true;

window.addEventListener("touchstart", function (e) {
    e.preventDefault();
    keystat.x = e.touches[0].pageX;
    keystat.y = e.touches[0].pageY;
    keystat.click = 1;
    send_keys(false);
}, { passive: false });

window.addEventListener("touchmove", function (e) {
    e.preventDefault();
    keystat.x = e.touches[0].pageX;
    keystat.y = e.touches[0].pageY;
    send_keys(false);
}, { passive: false });

window.addEventListener("touchend", function (e) {
    e.preventDefault();
    keystat.click = 0;
    send_keys(true);
}, { passive: false });

var connection = new WebSocket("ws://[IPアドレス]:11000/ws/");

//接続
connection.onopen = function (event) {
    console.log("ok");
};

//エラー
connection.onerror = function (error) {
    console.log("err");
};

//受信
connection.onmessage = function (event) {
    //特になし
};

//切断
connection.onclose = function () {
    console.log("end");
};

function send_keys(end) {
    //タップ終了以外のときに、相似比に合わせて値を変換
    if (!end) {
        keystat.x = Math.floor(keystat.x * MX);
        keystat.y = Math.floor(keystat.y * MY);
    }
    //送信文字列を作成
    let data = "";
    
    //縦長端末でxy入れ替え
    if (iswide) {
        data = keystat.x + "," + keystat.y;
    } else {
        data = keystat.y + "," + (SCREEN_Y - keystat.x);
    }
    data += "," + keystat.click;

    //送信
    connection.send(data);
}

if (window.innerWidth < window.innerHeight) {
    //縦長の端末は、操作しやすいようにxyを入れ替える(横持ち)
    MY = SCREEN_X / window.innerHeight;
    MX = SCREEN_Y / window.innerWidth;
    iswide = false;
}

実行

  1. index.htmlとmain.jsを、nginxなどのサーバー内に移動
  2. サーバーを実行
  3. C#側のプログラムを実行
  4. スマホで「http://[IPアドレス]:11000/[index.htmlまでのパス]」を検索
  5. スマホをタップすると、PC上のカーソルが動く!

実行例(Twitterの投稿から引用)

最後に

まだまだ操作しにくく改善点はありますが、動作自体がおもしろい。ソケット通信について簡単に学べる。という理由で紹介してみました。
(サーバー側が受信の記事、接続を切らずに何度も送受信を可能にするプログラムの記事が少なかった)

ただ、操作性に関しては、クリックなのかカーソル移動なのかを判別しようがないため、向上させるのは難しいと思います...

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?