(* Lightweight thread library for Objective Caml * http://www.ocsigen.org/lwt * Program Connect * Copyright (C) 2011 Jérémie Dimino * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, with linking exceptions; * either version 2.1 of the License, or (at your option) any later * version. See COPYING file for details. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. *) (* A simple graphical telnet. *) open Lwt (* +-----------------------------------------------------------------+ | Utils | +-----------------------------------------------------------------+ *) let show_error fmt = Printf.ksprintf (fun message -> let dialog = GWindow.message_dialog ~message_type:`ERROR ~buttons:GWindow.Buttons.ok ~message () in ignore (dialog#connect#response (function | `DELETE_EVENT -> () | `OK -> dialog#destroy ())); dialog#show ()) fmt (* +-----------------------------------------------------------------+ | Connection | +-----------------------------------------------------------------+ *) (* Either [None] if we are not connected, either [Some (ic, oc, thread)] if we are connected. In this last case [thread] is the thread reading data from the connection. *) let connection = ref None (* Read continously data from [ic] and write them to [view]. *) let read ic (view : GText.view) = let rec loop () = match_lwt Lwt_io.read_line_opt ic with | Some line -> view#buffer#insert ~iter:view#buffer#end_iter ~tag_names:["recv"] (line ^ "\n"); loop () | None -> view#buffer#insert ~iter:view#buffer#end_iter "end of connection\n"; Lwt_io.close ic in try_lwt loop () with Unix.Unix_error (error, _, _) -> show_error "reading error: %s" (Unix.error_message error); return () (* Function called when the user active the [connect] menu item. [view] is the text view used to display data received from the connection. *) let connect (view : GText.view) = (* Create a popup for asking the address and port to connect to. *) let dialog = GWindow.dialog ~title:"connection" () in dialog#add_button_stock `OK `OK; dialog#add_button_stock `CANCEL `CANCEL; let hbox = GPack.hbox ~packing:dialog#vbox#add () in ignore (GMisc.label ~packing:hbox#add ~text:"host: " ()); let host = GEdit.entry ~packing:hbox#add ~text:"127.0.0.1" () in ignore (GMisc.label ~packing:hbox#add ~text:" port: " ()); let port = GEdit.spin_button ~digits:0 ~numeric:true ~packing:hbox#add () in port#adjustment#set_bounds ~lower:0. ~upper:(float max_int) ~step_incr:1. (); (* Thread waiting for the popup to be closed. *) let waiter, wakener = wait () in (* Wakeup the thread when the popup is closed. *) ignore (dialog#connect#response (wakeup wakener)); dialog#show (); ignore ( match_lwt waiter with | `DELETE_EVENT -> return () | `CANCEL -> dialog#destroy (); return () | `OK -> let host = host#text and port = int_of_float port#value in dialog#destroy (); try_lwt (* Resolve the address. *) lwt entry = Lwt_unix.gethostbyname host in if Array.length entry.Unix.h_addr_list = 0 then begin show_error "no address found for host %S" host; return () end else begin lwt ic, oc = Lwt_io.open_connection (Unix.ADDR_INET (entry.Unix.h_addr_list.(0), port)) in (* Close the previous connection. *) lwt () = match !connection with | None -> return () | Some (ic, oc, thread) -> cancel thread; try_lwt Lwt_io.close ic <&> Lwt_io.close oc with Unix.Unix_error (error, _, _) -> show_error "cannot close the connection: %s" (Unix.error_message error); return () in (* Clear the buffer. *) view#buffer#delete view#buffer#start_iter view#buffer#end_iter; connection := Some (ic, oc, read ic view); return () end with | Unix.Unix_error (error, _, _) -> show_error "cannot establish the connection: %s" (Unix.error_message error); return () | Not_found -> show_error "host %S not found" host; return () ) (* Send some data. *) let write (view : GText.view) (entry : GEdit.entry) = let text = entry#text in entry#set_text ""; match !connection with | Some (ic, oc, thread) -> view#buffer#insert ~iter:view#buffer#end_iter ~tag_names:["send"] (text ^ "\n"); ignore ( try_lwt Lwt_io.write_line oc text with Unix.Unix_error (error, _, _) -> show_error "cannot send line: %s" (Unix.error_message error); return () ) | None -> show_error "not connected" (* +-----------------------------------------------------------------+ | Entry point | +-----------------------------------------------------------------+ *) lwt () = (* Initializes GTK. *) ignore (GMain.init ~setlocale:false ()); (* Integrate Lwt with Glib. *) Lwt_glib.install (); (* Create the UI. *) let window = GWindow.window ~title:"simple graphical telnet in OCaml with Lwt" ~allow_shrink:true ~width:640 ~height:480 () in let vbox = GPack.vbox ~packing:window#add () in (* Create the menu. *) let menu = GMenu.menu_bar ~packing:(vbox#pack ~expand:false) () in let menu_file = GMenu.menu ~packing:(GMenu.menu_item ~label:"File" ~packing:menu#add ())#set_submenu () in let menu_connect = GMenu.image_menu_item ~label:"Connect" ~packing:menu_file#add ~stock:`CONNECT () in ignore (GMenu.separator_item ~packing:menu_file#add ()); let menu_quit = GMenu.image_menu_item ~label:"Quit" ~packing:menu_file#add ~stock:`QUIT () in (* The text view displaying inputs and outputs. *) let view = GText.view ~editable:false ~packing:(GBin.scrolled_window ~hpolicy:`AUTOMATIC ~vpolicy:`AUTOMATIC ~packing:(GBin.frame ~label:"log" ~packing:vbox#add ())#add ())#add () in ignore (view#buffer#create_tag ~name:"send" [`FOREGROUND "blue"]); ignore (view#buffer#create_tag ~name:"recv" [`FOREGROUND "#007f00"]); let hbox = GPack.hbox ~packing:(GBin.frame ~label:"input" ~packing:(vbox#pack ~expand:false) ())#add () in (* The entry for user input. *) let entry = GEdit.entry ~packing:hbox#add () in let send = GButton.button ~label:"send" ~packing:(hbox#pack ~expand:false) () in (* Try to use a monospace font. *) (try view#misc#modify_font_by_name "Monospace"; entry#misc#modify_font_by_name "Monospace" with _ -> ()); (* Thread waiting for the main window to be closed. *) let waiter, wakener = wait () in (* Setup callbacks. *) ignore (window#connect#destroy (wakeup wakener)); ignore (menu_quit#connect#activate (wakeup wakener)); ignore (menu_connect#connect#activate (fun () -> connect view)); ignore (entry#connect#activate (fun () -> write view entry)); ignore (send#connect#clicked (fun () -> write view entry)); window#show (); (* Wait for the main window to be closed. *) waiter