VOICEROID+でSkypeメッセージ喋らせる(2)
昨日の続き。
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); } } }