Update of /project/cells/cvsroot/Celtk In directory clnet:/tmp/cvs-serv11797
Added Files: fileevent.lisp Log Message: New file. Implements Tk's fileevent command via Cells.
--- /project/cells/cvsroot/Celtk/fileevent.lisp 2006/05/25 13:32:45 NONE +++ /project/cells/cvsroot/Celtk/fileevent.lisp 2006/05/25 13:32:45 1.1 ;; -*- mode: Lisp; Syntax: Common-Lisp; Package: celtk; -*- ;;; ;;; Copyright (c) 2006 by Frank Goenninger, Germany. ;;; ;;; Permission is hereby granted, free of charge, to any person obtaining a ;;; copy of this software and associated documentation files (the "Software"), ;;; to deal in the Software without restriction, including without limitation ;;; the rights to use, copy, modify, merge, publish, distribute, sublicense, ;;; and/or sell copies of the Software, and to permit persons to whom the ;;; Software is furnished to do so, subject to the following conditions: ;;; ;;; The above copyright notice and this permission notice shall be included in ;;; all copies or substantial portions of the Software. ;;; ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;;; DEALINGS IN THE SOFTWARE. ;;; ;;; --------------------------------------------------------------------------- ;;; $Header: /project/cells/cvsroot/Celtk/fileevent.lisp,v 1.1 2006/05/25 13:32:45 fgoenninger Exp $ ;;; ---------------------------------------------------------------------------
;;; =========================================================================== ;;; PACKAGE / EXPORTS ;;; ===========================================================================
(in-package :celtk)
(eval-when (:load-toplevel :compile-toplevel) (export '(tk-fileevent iostream read-fn write-fn eof-fn mk-fileevent)))
;;; =========================================================================== ;;; TK-FILEEVENT MODEL ;;; ===========================================================================
(defmodel tk-fileevent (widget)
((.md-name :accessor id :initarg :id :initform (c-in nil) :documentation "ID of the fileevent instance.")
(input-fd :accessor input-fd :initarg :input-fd :initform (c? (if (^iostream) (stream-2-in-fd (^iostream)))) :documentation "The input/read file descriptor - internal use only.")
(output-fd :accessor output-fd :initarg :output-fd :initform (c? (if (^iostream) (stream-2-out-fd (^iostream)))) :documentation "The output/write file descriptor - internal use only.")
(in-tcl-channel :accessor in-tcl-channel :initarg :in-tcl-channel :initform (c? (fd-to-tcl-channel (^tki) (^input-fd))) :documentation "The TCL channel generated from the input file descriptor. - Internal use only.")
(out-tcl-channel :accessor out-tcl-channel :initarg :in-tcl-channel :initform (c? (fd-to-tcl-channel (^tki) (^output-fd))) :documentation "The TCL channel generated from the output file descriptor. - Internal use only.")
(in-tcl-ch-name :accessor in-tcl-ch-name :initarg :in-tcl-ch-name :initform (c? (if (^in-tcl-channel) (Tcl_GetChannelName (^in-tcl-channel)) nil)) :documentation "The input TCL channel's name as passed to the fileevent command. - Internal use only.")
(out-tcl-ch-name :accessor out-tcl-ch-name :initarg :in-tcl-ch-name :initform (c? (if (^out-tcl-channel) (Tcl_GetChannelName (^out-tcl-channel)) nil)) :documentation "The output TCL channel's name as passed to the fileevent command. - Internal use only.")
(iostream :accessor iostream :initarg :iostream :initform (c-in nil) :documentation "The Lisp stream to be monitored - API: initarg,setf.")
(readable-cb :accessor readable-cb :initarg :readable-cb :initform (c-in nil) :documentation "The readable callback. A dispatcher function used to call the function supplied via the read-fn slot. - Internal use only.")
(writeable-cb :accessor writeable-cb :initarg :writeable-cb :initform (c-in nil) :documentation "The writeable callback. A dispatcher function used to call the function supplied via the read-fn slot. - Internal use only.")
(eof-cb :accessor eof-cb :initarg :eof-cb :initform (c-in nil) :documentation "The eof callback. A dispatcher function used to call the function supplied via the eof-fn slot. - Internal use only.")
(tki :accessor tki :initarg :tki :initform (c-in nil) :documentation "The Tcl/Tk Interpreter used. - API: initarg.")
(opcode :accessor opcode :initarg :opcode :initform (file-event-opcode-cell-rule) :documentation "The opcode slot is used to control the operaion of the fileevent instance. - Internal use only.")
(read-fn :accessor read-fn :initarg :read-fn :initform (c-in nil) :documentation "User supplied function, gets called when iostream is ready for reading. Gets iostream as parameter. - API: initarg, setf")
(write-fn :accessor write-fn :initarg :write-fn :initform (c-in nil) :documentation "User supplied function, gets called when iostream is ready for writing. Gets iostream as parameter. - API: initarg, setf")
(eof-fn :accessor eof-fn :initarg :eof-fn :initform (c-in nil) :documentation "User supplied function, gets called when iostream is EOF. Gets iostream as parameter. - API: initarg, setf (Via default-initarg set to fn default-eof-fn which simply closes the stream)."))
(:default-initargs :id (gensym "tk-fileevent-") :eof-fn 'default-eof-fn))
;;; =========================================================================== ;;; CELL RULE: FILE-EVENT/OPCODE ;;; =========================================================================== ;;; ;;; Depending on opcode call the appropriate function to handle the various ;;; cases/combinations of input-fd, output-fd, and the previously executed ;;; update operation.
(defun file-event-opcode-cell-rule () (c? ;; Set the opcode depending on values of input-fd, output-fd, iostream, ;; readable-cb, writeable-cb
(if (and (not (^input-fd)) (not (^output-fd)) (not .cache)) :nop
(if (and (^input-fd) (^iostream) (^readable-cb)) :update-input-tk-fileevent (if (and (^output-fd) (^iostream) (^writeable-cb)) :update-output-tk-fileevent (if (and (not (^iostream)) (not (^input-fd))) :reset-input-tk-fileevent (if (and (not (^iostream)) (not (^output-fd))) :reset-output-tk-fileevent :nop)))))))
;;; =========================================================================== ;;; INIT-TK-FILEEVENT - CALLED UPON INITIALIZATION ;;; ===========================================================================
(defun init-tk-fileevent (tki) (assert tki) ;; Nop - all init done in observers now. )
;;; =========================================================================== ;;; FILEEVENT HELPER METHODS AND FUCTIONS ;;; ===========================================================================
(defmethod set-tk-readable ((self tk-fileevent) ch-name path) (tk-format-now "proc readable {channel path} { if [ eof $channel ] then { eof-cb $path } else { readable-cb $path } }") (tk-format-now "fileevent ~A readable [list readable ~A ~A]" ch-name ch-name path))
(defmethod set-tk-writeable ((self tk-fileevent) ch-name path) (tk-format-now "proc writeable {channel path} { if [ eof $channel ] then { eof-cb $path } else { readable-cb $path } }") (tk-format-now "fileevent ~A writeable [list writeable ~A ~A]" ch-name ch-name path))
;;; =========================================================================== ;;; OBSERVERS - USED TO SEND UPDATES TO TK LAND ;;; ===========================================================================
(defobserver opcode ((self tk-fileevent)) (let ((*tki* (tki self))) (ecase new-value
((:init-tk-fileevent) (init-tk-fileevent (tki self)))
((:update-input-tk-fileevent) (let* ((channel (in-tcl-channel self)) (path (path self)) (ch-name (Tcl_GetChannelName channel))) (set-tk-readable self ch-name path)))
((:update-output-tk-fileevent) (let* ((channel (out-tcl-channel self)) (path (path self)) (ch-name (Tcl_GetChannelName channel))) (set-tk-writeable self ch-name path)))
((:reset-input-tk-fileevent) ;; Do nothing nil)
((:reset-output-tk-fileevent) ;; Do nothing nil)
((:nop) ;; Do nothing nil))))
(defobserver in-tcl-channel ((self tk-fileevent)) (let ((*tki* (tki self))) (if (and new-value (not old-value)) (Tcl_RegisterChannel *tki* new-value)) (if (and old-value (not new-value)) (progn (tk-format-now "fileevent ~A readable {}" (Tcl_GetChannelName old-value)) (Tcl_UnregisterChannel *tki* old-value)))))
(defobserver out-tcl-channel ((self tk-fileevent)) (let ((*tki* (tki self))) (if (and new-value (not old-value)) (Tcl_RegisterChannel *tki* new-value)) (if (and old-value (not new-value)) (progn (tk-format-now "fileevent ~A writeable {}" (Tcl_GetChannelName old-value)) (Tcl_UnregisterChannel *tki* old-value)))))
(defobserver readable-cb ((self tk-fileevent)) (if new-value (Tcl_CreateCommand *tki* "readable-cb" new-value (null-pointer) (null-pointer))))
(defobserver writeable-cb ((self tk-fileevent)) (if new-value (Tcl_CreateCommand *tki* "writeable-cb" new-value (null-pointer) (null-pointer))))
(defobserver eof-cb ((self tk-fileevent)) (if new-value (Tcl_CreateCommand *tki* "eof-cb" new-value (null-pointer) (null-pointer))))
;;; =========================================================================== ;;; HELPER FUNCTIONS - FILE DESCRIPTOR TO STREAM AND CHANNEL ;;; ===========================================================================
(defun fd-to-tcl-channel (interp fd) (assert interp) (if fd (let ((channel (Tcl_MakeFileChannel fd 6))) ;; 6 = READ/WRITE (if channel channel (error "*** Tcl error: ~a" (tcl-get-string-result interp))))))
(defun stream-2-out-fd (stream) ;; FRGO: PORTING...
#+allegro (excl:stream-output-fn stream)
#-allegro (error "STREAM-2-OUT-FD: Not implemented for ~A Version ~A. Sorry." (lisp-implementation-type) (lisp-implementation-version)) )
(defun stream-2-in-fd (stream) ;; FRGO: PORTING...
#+allegro (excl:stream-input-fn stream)
#-allegro (error "STREAM-2-IN-FD: Not implemented for ~A Version ~A. Sorry." (lisp-implementation-type) (lisp-implementation-version)) )
;;; =========================================================================== ;;; CALLBACKS ;;; ===========================================================================
(defcallback readable-cb :int ((clientData :pointer) (interp :pointer) (argc :int) (argv :pointer)) (declare (ignorable clientData argc interp)) (let* ((path (foreign-string-to-lisp (mem-aref argv :pointer 1))) (self (gethash path (dictionary *tkw*)))) (bwhen (fn (^read-fn)) (funcall fn self :read))) (values (foreign-enum-value 'tcl-retcode-values :tcl-ok)))
(defcallback writeable-cb :int ((clientData :pointer) (interp :pointer) (argc :int) (argv :pointer)) (declare (ignorable clientData argc interp)) (let* ((path (foreign-string-to-lisp (mem-aref argv :pointer 1))) (self (gethash path (dictionary *tkw*)))) (bwhen (fn (^write-fn)) (funcall fn self :write))) (values (foreign-enum-value 'tcl-retcode-values :tcl-ok)))
(defcallback eof-cb :int ((clientData :pointer) (interp :pointer) (argc :int) (argv :pointer)) (declare (ignorable clientData interp argc)) (let* ((path (foreign-string-to-lisp (mem-aref argv :pointer 1))) (self (gethash path (dictionary *tkw*)))) (bwhen (fn (^eof-fn)) (funcall fn self))) (values (foreign-enum-value 'tcl-retcode-values :tcl-ok)))
;;; =========================================================================== ;;; MK-FILEEVENT: CONVENIENCE MACRO ;;; ===========================================================================
(defmacro mk-fileevent (&rest inits) `(make-instance 'tk-fileevent :tki *tki* :readable-cb (get-callback 'readable-cb) :writeable-cb (get-callback 'writeable-cb) :eof-cb (get-callback 'eof-cb) :fm-parent *parent* ,@inits))
;;; =========================================================================== ;;; A DEFAULT EOF FUNCTION, USER MAY SUPPLY ANOTHER FUNCTION WHEN MAKING THE ;;; INSTANCE OF TK-FILEEVENT ;;; ===========================================================================
(defmethod default-eof-fn ((self tk-fileevent)) ;; Default action: close stream (bwhen (iostream (^iostream)) (close iostream) (setf (^iostream) nil)))
;;; =========================================================================== ;;; TESTING ;;; =========================================================================== ;;; ;;; With these few lines below we get a simple application with a text widget ;;; that shows data sent to a pipe in that text widget. ;;; ;;; The app does this by opening the named pipe for reading. It then waits ;;; for data on the pipe via the Tcl fileevent command. When establishing ;;; the fileevent a set of callbacks is established. The callbacks call ;;; two Lisp functions, depending on the type of channel (read or write. ;;; ;;; The callback functions look for the file channel's registered read or ;;; write functions. Those functions are set via the write-fn and read-fn ;;; methods of the tk-fileevent object. ;;; ;;; In the test example below we use the read case: the function read-from-pipe ;;; actually reads from the pipe and sends the data to the text widget by ;;; setting the text widgets model value. ;;;
[48 lines skipped]