open Sys open Unix module Ms = Map.Make(String) module Mc = Map.Make(Char) type 'a or_error = Ok of 'a | Error of string let identity x = x let try_finally body cleanup = let res = try body () with exn -> cleanup (); raise exn in cleanup (); res let exn_to_opt body = try Some (body ()) with exn -> None let exec cmd = execvp cmd.(0) cmd let launch cmd = if fork () = 0 then exec cmd let print_usage () = List.iter print_endline ["Usage: " ^ Sys.argv.(0) ^ " <console>\n" ^ " Consoles: [gb gba]"]; exit 0 let die msg = print_endline ("ERROR! (" ^ msg ^ ")"); exit (-1) let get_or_die = function | Ok x -> x | Error msg -> die msg let launch_get_output cmd = let input_lines ic = let rec aux lines = try aux (input_line ic :: lines) with End_of_file -> lines in aux [] in let (ifd,ofd) = pipe () in match fork () with | 0 -> dup2 ofd stdout; dup2 ofd stderr; close stdin; close ifd; close ofd; exec cmd | n -> close ofd; let lns = input_lines (in_channel_of_descr ifd) in close ifd; ignore (waitpid [] n); lns module Net = struct let tcp_server s_handler addr = let setup_tcp_socket addr = let s = socket PF_INET SOCK_STREAM 0 in try bind s addr; listen s 10; s with z -> close s; raise z in ignore (signal sigpipe Signal_ignore); let ss = setup_tcp_socket addr in while true do let client = accept ss in s_handler ss client done let double_fork server service (cfd, _ as client) = let treat () = match fork () with | 0 -> if fork () <> 0 then exit 0; close server; service client; exit 0 | n -> ignore (waitpid [] n) in try_finally treat (fun () -> close cfd) let s_handler c_handler sock (_, caddr as client) = print_endline (match caddr with | ADDR_INET (a,_) -> "Connected to " ^ string_of_inet_addr a | ADDR_UNIX _ -> "Local connection (dafuq?)"); double_fork sock c_handler client end module type WM = sig type wid val pp_wid : wid -> string val search_by_title : string -> wid list val title : wid -> string val focus : wid -> unit val send : string -> unit end module Xdotool : WM = struct type wid = string let cmdname = "xdotool" let delay = "50" let pp_wid = identity let search_by_title name = launch_get_output [|cmdname; "search"; "--name"; name|] let title wid = List.hd (launch_get_output [|cmdname; "getwindowname"; wid|]) let focus wid = launch [|cmdname; "windowfocus"; wid|] let send keys = launch [|cmdname; "type"; "--delay"; delay; keys|] end module Console : sig type t = Gb | Gba type key val of_string : string -> t or_error val pp : t -> string val keys : t -> key list end = struct type t = Gb | Gba type key = | Kup | Kdown | Kleft | Kright | Ka | Kb | Kl | Kr | Kstart | Kselect let of_string c = match c with | "gb" -> Ok Gb | "gba" -> Ok Gba | _ -> Error ("console " ^ c ^ " not supported") let pp = function | Gb -> "gb (Game Boy)" | Gba -> "gba (Game Boy Advanced)" let keys = function | Gb -> [Kup;Kdown;Kleft;Kright;Ka;Kb;Kstart] | Gba -> [Kup;Kdown;Kleft;Kright;Ka;Kb;Kl;Kr;Kstart] end module Comm (Wm : WM) = struct type mode = Cmd | Key let pp_mode = function | Cmd -> "cmd" | Key -> "key" type state = { wid : Wm.wid; mode : mode; (* bds : char Bindings.t; *) console : Console.t } let mk_state wid mode (* bds *) console = { wid = wid; mode = mode; (* bds = bds; *) console = console } let switch_mode st = let mode' = match st.mode with Cmd -> Key | Key -> Cmd in print_endline ("Mode switched to " ^ pp_mode mode'); { st with mode = mode' } let cmdmap = let press k st = Wm.send st.wid k; st in let default_cmds = [("up","w"); ("down","s"); ("left","a"); ("right","d"); ("a","u"); ("b","h"); ("l","i"); ("r","o"); ("start","j")] in let keys = List.fold_left (fun m (k,v) -> Ms.add k (press v) m) Ms.empty default_cmds in let cmdbds = [("switch", switch_mode)] in let cmds = List.fold_left (fun m (k,f) -> Ms.add k f m) Ms.empty cmdbds in Ms.fold Ms.add keys cmds let find_window name = let wids = Wm.search_by_title name in let falses = [Sys.argv.(0); "mednafen"] in (* May have the same title *) let falsewids = List.concat (List.map Wm.search_by_title falses) in match List.filter (fun wid -> not (List.mem wid falsewids)) wids with | [] -> Error "window not found" | [x] -> Ok (x, Wm.title x) | _ -> Error "multiple windows found ()" (* TODO print titles found *) let c_handler st0 (cfd, _) = dup2 cfd stdin; dup2 cfd stdout; dup2 cfd stderr; close cfd; Wm.focus st0.wid; let rec loop st = match st.mode with | Cmd -> print_string "> "; flush_all (); let cmd = read_line () in let f = match exn_to_opt (fun () -> Ms.find cmd cmdmap) with | None -> print_endline ("Unknown cmd '" ^ cmd ^ "'"); identity | Some f -> f in loop (f st) | Key -> print_string "> "; in loop st0 end module MyWm = Xdotool module MyComm = Comm(MyWm) let () = if Array.length Sys.argv <> 3 then print_usage (); let addr = inet_addr_any in let port = 8080 in let inaddr = ADDR_INET (addr, port) in let title = Sys.argv.(1) in let console = get_or_die (Console.of_string Sys.argv.(2)) in print_endline ("Using " ^ Console.pp console ^ " settings"); print_string ("Searching window with title '" ^ title ^ "'... "); let (wid, title) = get_or_die (MyComm.find_window title) in print_endline ("found '" ^ title ^ "' (WID " ^ MyWm.pp_wid wid ^ ")"); let st0 = MyComm.mk_state wid MyComm.Cmd console in print_endline ("Starting server in port " ^ string_of_int port ^ "..."); Net.tcp_server (Net.s_handler (MyComm.c_handler st0)) inaddr