読者です 読者をやめる 読者になる 読者になる

謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

VOICEROID+でSkypeメッセージ喋らせる(2)

日記 Tips

昨日の続き。

SendMessage 先までは問題なくできたのだが、送るメッセージの問題で、273(WM_COMMAND)でコピペしかできなかった件。
キーボードイベントで操作しようとすると、ウィンドウをアクティブ化した上でイベント送信せざるを得ない、、、、とはいえ、バックグラウンドで動いて欲しいものなので、それはNOだ。
メニューの「新規テキスト」では新しいダイアログが開いてしまうので、それもNG、はてさてどうするか?

COM でもあれば話は早いんだけどなぁと思いつつ詰まった。

結局いい手が思い浮かばなかった、、、音声として喋らせるエンジンはきっと別DLLだから起動時に関数フックして、、、、とか考えたけど、やろうと思ったら逆アセする必要が出てしまう。
それをやりだすと大変なのでやりたくない。そんな得意でもないので出来れば避けたい。そもそも利用規約的にアウトだろ、、、、Web寄りエンジニアではこれが限度か。

結局

  • アクティブ化するのを許容する
  • クリップボードの内容が書き換わるのを許容する

どっちかしかなさげ。
後者を選択しました。そもそも全画面でゲームやってる最中ぶフォーカス持っていかれるとか正気の沙汰じゃねぇ!

できたコードをペタリ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace net.azworks.VoiceroidAccess
{
    class Win32
    {
        public static string GetWindowClassName(IntPtr handle)
        {
            const int nChars = 256;
            StringBuilder Buff = new StringBuilder(nChars);

            if (GetClassName(handle, Buff, nChars) > 0)
            {
                return Buff.ToString();
            }
            return null;
        }

        public static List<IntPtr> GetChildWindows(IntPtr parent)
        {
            List<IntPtr> result = new List<IntPtr>();
            GCHandle listHandle = GCHandle.Alloc(result);
            try
            {
                EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
                EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
            }
            finally
            {
                if (listHandle.IsAllocated)
                {
                    listHandle.Free();
                }
            }
            return result;
        }

        public const int WM_CHAR = 0x102;
        public const int WM_CLOSE = 0x10;
        public const int WM_KEYDOWN = 0x100;
        public const int WM_KEYUP = 0x101;
        public const int WM_IME_CHAR = 0x286;

        private delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);

        [DllImport("User32.Dll", CharSet = CharSet.Unicode)]
        private static extern int GetClassName(IntPtr hWnd, StringBuilder s, int nMaxCount);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool IsChild(IntPtr hwndParent, IntPtr hwndTarget);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr GetParent(IntPtr scrollbar);

        public const int GW_HWNDFIRST = 0;
        public const int GW_HWNDLAST = 1;
        public const int GW_HWNDNEXT = 2;
        public const int GW_HWNDPREV = 3;
        public const int GW_OWNER = 4;
        public const int GW_CHILD = 5;

        [DllImport("user32.dll")]
        public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

        private static bool EnumWindow(IntPtr handle, IntPtr pointer)
        {
            GCHandle gch = GCHandle.FromIntPtr(pointer);
            List<IntPtr> list = gch.Target as List<IntPtr>;
            if (list == null)
            {
                throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
            }
            list.Add(handle);
            //  You can modify this to check to see if you want to cancel the operation, then return a null here
            return true;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace net.azworks.VoiceroidAccess
{
    class Yukari
    {

        private Process getVoiceroidProcess()
        {
            Process[] proceses = Process.GetProcessesByName("VOICEROID");

            if (proceses.Length == 0)
                throw new ApplicationInirializeException(
                    "VOCALOID.exe プロセスが存在していません。");

            foreach (Process process in proceses)
            {
                string processName = process.ProcessName;
                string windowTitle = process.MainWindowTitle;
                if (process.MainWindowTitle.Equals("VOICEROID+ 結月ゆかり"))
                    return process;
            }
            return null;
        }

        private Process process = null;

        private IntPtr getVoiceroidWindwHandle()
        {
            try
            {
                return process.MainWindowHandle;
            }
            catch(Exception e)
            {
                textCache = IntPtr.Zero;
                talkButton = IntPtr.Zero;
                process = getVoiceroidProcess();
                if (process == null)
                    throw new ApplicationInirializeException(
                        "VOICEROID+ 結月ゆかり が起動してないっぽいです。");
                return process.MainWindowHandle;
            }
        }

        private IntPtr FindScroll(List<IntPtr> tgts)
        {
            foreach (var p in tgts)
                if (Win32.GetWindowClassName(p).Equals("ScrollBar"))
                    return p;
            return IntPtr.Zero;
        }

        private IntPtr textCache = IntPtr.Zero;

        private IntPtr FindTextBox()
        {
            if (textCache == IntPtr.Zero)
            {
                List<IntPtr> targets = Win32.GetChildWindows(getVoiceroidWindwHandle());

                // 親子関係からScrollBarの親を検索する
                var scrollbar = FindScroll(targets);

                // scrollbar の兄弟が対象
                textCache = Win32.GetWindow(scrollbar, Win32.GW_HWNDNEXT);
            }
            return textCache;
        }

        IntPtr talkButton = IntPtr.Zero;

        private IntPtr TalkButton()
        {
            if (talkButton == IntPtr.Zero)
            {
                var windowHandle = getVoiceroidWindwHandle();
                var granfa = Win32.GetWindow(windowHandle, Win32.GW_CHILD);
                var charrac = Win32.GetWindow(granfa, Win32.GW_CHILD);
                var buttongroup = Win32.GetWindow(charrac, Win32.GW_HWNDNEXT);
                var topbutton = Win32.GetWindow(buttongroup, Win32.GW_CHILD);
                IntPtr current = Win32.GetWindow(topbutton, Win32.GW_HWNDNEXT);
                current = Win32.GetWindow(current, Win32.GW_HWNDNEXT);
                current = Win32.GetWindow(current, Win32.GW_HWNDNEXT);
                talkButton = Win32.GetWindow(current, Win32.GW_HWNDNEXT);
            }
            return talkButton;
        }

        IntPtr UNDO = new IntPtr(46);
        IntPtr PASTE = new IntPtr(56);
        IntPtr ALLSELECT = new IntPtr(60);
        IntPtr CUT = new IntPtr(52);

        public void talk(String message, int delay=100)
        {
            // 削除(全部選んでカット)
            Win32.SendMessage(getVoiceroidWindwHandle(), 273, ALLSELECT, IntPtr.Zero);
            Win32.SendMessage(getVoiceroidWindwHandle(), 273, CUT, IntPtr.Zero);

            // コピペ
            Clipboard.SetText(message);
            System.Threading.Thread.Sleep(delay);
            Win32.SendMessage(getVoiceroidWindwHandle(), 273, PASTE, IntPtr.Zero);
        }

        public void Play()
        {
            Win32.SendMessage(TalkButton(), 0, IntPtr.Zero, IntPtr.Zero);
        }
    }
}