;;; -*- Mode: common-lisp; Package: xlib; Base: 10; Lowercase: Yes -*-
;;;
;;; CLX -- excldep.cl
;;;
;;; Copyright (c) 1987, 1988, 1989 Franz Inc, Berkeley, Ca.
;;;
;;; Permission is granted to any individual or institution to use, copy,
;;; modify, and distribute this software, provided that this complete
;;; copyright and permission notice is maintained, intact, in all copies and
;;; supporting documentation.
;;;
;;; Franz Incorporated provides this software "as is" without
;;; express or implied warranty.
;;;

(in-package :xlib)

(eval-when (compile load eval)
  (require :foreign)
  (require :process)			; Needed even if scheduler is not
					; running.  (Must be able to make
					; a process-lock.)
  )

(eval-when (load)
  (provide :clx))


#-(or little-endian big-endian)
(eval-when (eval compile load)
  (let ((x '#(1)))
    (if (not (eq 0 (sys::memref x
				#.(comp::mdparam 'comp::md-svector-data0-adj)
				0 :unsigned-byte)))
	(pushnew :little-endian *features*)
      (pushnew :big-endian *features*))))


(defmacro correct-case (string)
  ;; This macro converts the given string to the 
  ;; current preferred case, or leaves it alone in a case-sensitive mode.
  (let ((str (gensym)))
    `(let ((,str ,string))
       (case excl::*current-case-mode*
	 (:case-insensitive-lower
	  (string-downcase ,str))
	 (:case-insensitive-upper
	  (string-upcase ,str))
	 ((:case-sensitive-lower :case-sensitive-upper)
	  ,str)))))


(defconstant type-pred-alist
    '(#-(version>= 4 1 devel 16)
      (card8  . card8p)
      #-(version>= 4 1 devel 16)
      (card16 . card16p)
      #-(version>= 4 1 devel 16)
      (card29 . card29p)
      #-(version>= 4 1 devel 16)
      (card32 . card32p)
      #-(version>= 4 1 devel 16)
      (int8   . int8p)
      #-(version>= 4 1 devel 16)
      (int16  . int16p)
      #-(version>= 4 1 devel 16)
      (int32  . int32p)
      #-(version>= 4 1 devel 16)
      (mask16 . card16p)
      #-(version>= 4 1 devel 16)
      (mask32 . card32p)
      #-(version>= 4 1 devel 16)
      (pixel  . card32p)
      #-(version>= 4 1 devel 16)
      (resource-id . card29p)
      #-(version>= 4 1 devel 16)
      (keysym . card32p)
      (angle  . anglep)
      (color  . color-p)
      (bitmap-format . bitmap-format-p)
      (pixmap-format . pixmap-format-p)
      (display  . display-p)
      (drawable . drawable-p)
      (window   . window-p)
      (pixmap   . pixmap-p)
      (visual-info . visual-info-p)
      (colormap . colormap-p)
      (cursor . cursor-p)
      (gcontext .  gcontext-p)
      (screen . screen-p)
      (font . font-p)
      (image-x . image-x-p)
      (image-xy . image-xy-p)
      (image-z . image-z-p)
      (wm-hints . wm-hints-p)
      (wm-size-hints . wm-size-hints-p)
      ))

;; This (if (and ...) t nil) stuff has a purpose -- it lets the old 
;; sun4 compiler opencode the `and'.

#-(version>= 4 1 devel 16)
(defun card8p (x)
  (declare (optimize (speed 3) (safety 0))
	   (fixnum x))
  (if (and (excl:fixnump x) (> #.(expt 2 8) x) (>= x 0))
      t
    nil))

#-(version>= 4 1 devel 16)
(defun card16p (x)
  (declare (optimize (speed 3) (safety 0))
	   (fixnum x))
  (if (and (excl:fixnump x) (> #.(expt 2 16) x) (>= x 0))
      t
    nil))

#-(version>= 4 1 devel 16)
(defun card29p (x)
  (declare (optimize (speed 3) (safety 0)))
  (if (or (and (excl:fixnump x) (>= (the fixnum x) 0))
	  (and (excl:bignump x) (> #.(expt 2 29) (the bignum x))
	       (>= (the bignum x) 0)))
      t
    nil))

#-(version>= 4 1 devel 16)
(defun card32p (x)
  (declare (optimize (speed 3) (safety 0)))
  (if (or (and (excl:fixnump x) (>= (the fixnum x) 0))
	  (and (excl:bignump x) (> #.(expt 2 32) (the bignum x))
	       (>= (the bignum x) 0)))
      t
    nil))

#-(version>= 4 1 devel 16)
(defun int8p (x)
  (declare (optimize (speed 3) (safety 0))
	   (fixnum x))
  (if (and (excl:fixnump x) (> #.(expt 2 7) x) (>= x #.(expt -2 7)))
      t
    nil))

#-(version>= 4 1 devel 16)
(defun int16p (x)
  (declare (optimize (speed 3) (safety 0))
	   (fixnum x))
  (if (and (excl:fixnump x) (> #.(expt 2 15) x) (>= x #.(expt -2 15)))
      t
    nil))

#-(version>= 4 1 devel 16)
(defun int32p (x)
  (declare (optimize (speed 3) (safety 0)))
  (if (or (excl:fixnump x)
	  (and (excl:bignump x) (> #.(expt 2 31) (the bignum x))
	       (>= (the bignum x) #.(expt -2 31))))
      t
    nil))

;; This one can be handled better by knowing a little about what we're
;; testing for.  Plus this version can handle (single-float pi), which
;; is otherwise larger than pi!
(defun anglep (x)
  (declare (optimize (speed 3) (safety 0)))
  (if (or (and (excl::fixnump x) (>= (the fixnum x) #.(truncate (* -2 pi)))
	       (<= (the fixnum x) #.(truncate (* 2 pi))))
	  (and (excl::single-float-p x)
	       (>= (the single-float x) #.(float (* -2 pi) 0.0s0))
	       (<= (the single-float x) #.(float (* 2 pi) 0.0s0)))
	  (and (excl::double-float-p x)
	       (>= (the double-float x) #.(float (* -2 pi) 0.0d0))
	       (<= (the double-float x) #.(float (* 2 pi) 0.0d0))))
      t
    nil))

(eval-when (load eval)
  #+(version>= 4 1 devel 16)
  (mapcar #'(lambda (elt) (excl:add-typep-transformer (car elt) (cdr elt)))
	  type-pred-alist)
  #-(version>= 4 1 devel 16)
  (nconc excl::type-pred-alist type-pred-alist))


;; Return t if there is a character available for reading or on error,
;; otherwise return nil.
(defun fd-char-avail-p (fd)
  (multiple-value-bind (available-p errcode)
      (comp::.primcall-sargs 'sys::filesys excl::fs-char-avail fd)
    (excl:if* errcode
       then t
       else available-p)))

(defmacro with-interrupt-checking-on (&body body)
  `(locally (declare (optimize (safety 1)))
     ,@body))

;; Read from the given fd into 'vector', which has element type card8.
;; Start storing at index 'start-index' and read exactly 'length' bytes.
;; Return t if an error or eof occurred, nil otherwise.
(defun fd-read-bytes (fd vector start-index length)
  (declare (fixnum fd start-index length)
	   (type (simple-array (unsigned-byte 8) (*)) vector))
  (with-interrupt-checking-on
   (do ((rest length))
       ((eq 0 rest) nil)
     (declare (fixnum rest))
     (multiple-value-bind (numread errcode)
	 (comp::.primcall-sargs 'sys::filesys excl::fs-read-bytes fd vector
				start-index rest)
       (declare (fixnum numread))
       (excl:if* errcode
	  then (if (not (eq errcode
			    excl::*error-code-interrupted-system-call*))
		   (return t))
	elseif (eq 0 numread)
	  then (return t)
	  else (decf rest numread)
	       (incf start-index numread))))))


(when (plusp (ff:get-entry-points
	      (make-array 1 :initial-contents
			  (list (ff:convert-to-lang "fd_wait_for_input")))
	      (make-array 1 :element-type '(unsigned-byte 32))))
  (ff:remove-entry-point (ff:convert-to-lang "fd_wait_for_input"))
  (load "excldep.o"))

(when (plusp (ff:get-entry-points
	      (make-array 1 :initial-contents
			  (list (ff:convert-to-lang "connect_to_server")))
	      (make-array 1 :element-type '(unsigned-byte 32))))
  (ff:remove-entry-point (ff:convert-to-lang "connect_to_server" :language :c))
  (load "socket.o"))

(ff:defforeign-list `((connect-to-server
		       :entry-point
		       ,(ff:convert-to-lang "connect_to_server")
		       :return-type :fixnum
		       :arg-checking nil
		       :arguments (string fixnum))
		      (fd-wait-for-input
		       :entry-point ,(ff:convert-to-lang "fd_wait_for_input")
		       :return-type :fixnum
		       :arg-checking nil
		       :call-direct t
		       :callback nil
		       :allow-other-keys t
		       :arguments (fixnum fixnum))))


;; special patch for CLX (various process fixes)
;; patch1000.2

(eval-when (compile load eval)
  (unless (find-package :patch)
    (make-package :patch :use '(:lisp :excl))))

(in-package :patch)

(defvar *patches* nil)

#+allegro
(eval-when (compile eval load)
  (when (and (= excl::cl-major-version-number 3)
	     (or (= excl::cl-minor-version-number 0)
		 (and (= excl::cl-minor-version-number 1)
		      excl::cl-generation-number
		      (< excl::cl-generation-number 9))))
    (push :clx-r4-process-patches *features*)))

#+clx-r4-process-patches
(push (cons 1000.2 "special patch for CLX (various process fixes)")
      *patches*)


(in-package :mp)

#+clx-r4-process-patches
(export 'wait-for-input-available)


#+clx-r4-process-patches
(defun with-timeout-event (seconds fnc args)
  (unless *scheduler-stack-group* (start-scheduler)) ;[spr670]
  (let ((clock-event (make-clock-event)))
    (when (<= seconds 0) (setq seconds 0))
    (multiple-value-bind (secs msecs) (truncate seconds)
      ;; secs is now a nonegative integer, and msecs is either fixnum zero
      ;; or else something interesting.
      (unless (eq 0 msecs)
	(setq msecs (truncate (* 1000.0 msecs))))
      ;; Now msecs is also a nonnegative fixnum.
      (multiple-value-bind (now mnow) (excl::cl-internal-real-time)
	(incf secs now)
	(incf msecs mnow)
	(when (>= msecs 1000)
	  (decf msecs 1000)
	  (incf secs))
	(unless (excl:fixnump secs) (setq secs most-positive-fixnum))
	(setf (clock-event-secs clock-event) secs
	      (clock-event-msecs clock-event) msecs
	      (clock-event-function clock-event) fnc
	      (clock-event-args clock-event) args)))
    clock-event))


#+clx-r4-process-patches
(defmacro with-timeout ((seconds &body timeout-body) &body body)
  `(let* ((clock-event (with-timeout-event ,seconds
					   #'process-interrupt
					   (cons *current-process*
						 '(with-timeout-internal))))
	  (excl::*without-interrupts* t)
	  ret)
     (unwind-protect
	 ;; Warning: Branch tensioner better not reorder this code!
	 (setq ret (catch 'with-timeout-internal
		     (add-to-clock-queue clock-event)
		     (let ((excl::*without-interrupts* nil))
		       (multiple-value-list (progn ,@body)))))
       (excl:if* (eq ret 'with-timeout-internal)
	  then (let ((excl::*without-interrupts* nil))
		 (setq ret (multiple-value-list (progn ,@timeout-body))))
	  else (remove-from-clock-queue clock-event)))
     (values-list ret)))


#+clx-r4-process-patches
(defun process-lock (lock &optional (lock-value *current-process*)
				    (whostate "Lock") timeout)
  (declare (optimize (speed 3)))
  (unless (process-lock-p lock)
    (error "First argument to PROCESS-LOCK must be a process-lock: ~s" lock))
  (without-interrupts
   (excl:if* (null (process-lock-locker lock))
      then (setf (process-lock-locker lock) lock-value)
      else (excl:if* timeout
	      then (excl:if* (or (eq 0 timeout) ;for speed
				 (zerop timeout))
		      then nil
		      else (with-timeout (timeout)
			     (process-lock-1 lock lock-value whostate)))
	      else (process-lock-1 lock lock-value whostate)))))


#+clx-r4-process-patches
(defun process-lock-1 (lock lock-value whostate)
  (declare (type process-lock lock)
	   (optimize (speed 3)))
  (let ((process *current-process*))
    (declare (type process process))
    (unless process
      (error
       "PROCESS-LOCK may not be called on the scheduler's stack group."))
    (loop (unless (process-lock-locker lock)
	    (return (setf (process-lock-locker lock) lock-value)))
      (push process (process-lock-waiting lock))
      (let ((saved-whostate (process-whostate process)))
	(unwind-protect
	    (progn (setf (process-whostate process) whostate)
		   (process-add-arrest-reason process lock))
	  (setf (process-whostate process) saved-whostate))))))


#+clx-r4-process-patches
(defun process-wait (whostate function &rest args)
  (declare (optimize (speed 3)))
  ;; Run the wait function once here both for efficiency and as a
  ;; first line check for errors in the function.
  (unless (apply function args)
    (process-wait-1 whostate function args)))


#+clx-r4-process-patches
(defun process-wait-1 (whostate function args)
  (declare (optimize (speed 3)))
  (let ((process *current-process*))
    (declare (type process process))
    (unless process
      (error
       "Process-wait may not be called within the scheduler's stack group."))
    (let ((saved-whostate (process-whostate process)))
      (unwind-protect
	  (without-scheduling-internal
	   (without-interrupts
	    (setf (process-whostate process) whostate
		  (process-wait-function process) function
		  (process-wait-args process) args)
	    (chain-rem-q process)
	    (chain-ins-q process *waiting-processes*))
	   (process-resume-scheduler nil))
	(setf (process-whostate process) saved-whostate
	      (process-wait-function process) nil
	      (process-wait-args process) nil)))))


#+clx-r4-process-patches
(defun process-wait-with-timeout (whostate seconds function &rest args)
  ;; Now returns T upon completion, NIL upon timeout. -- 6Jun89 smh
  ;; [spr1135] [rfe939] Timeout won't throw out of interrupt level code.
  ;;  -- 28Feb90 smh
  ;; Run the wait function once here both for efficiency and as a
  ;; first line check for errors in the function.
  (excl:if* (apply function args)
     then t
     else (let ((ret (list nil)))
            (without-interrupts
             (let ((clock-event
                    (with-timeout-event seconds #'identity '(nil))))
               (add-to-clock-queue clock-event)
               (process-wait-1 whostate
                               #'(lambda (clock-event function args ret)
                                   (or (null (chain-next clock-event))
                                       (and (apply function args)
                                            (setf (car ret) 't))))
                               (list clock-event function args ret))))
            (car ret))))


;;
;; Returns nil on timeout, otherwise t.
;;
#+clx-r4-process-patches
(defun wait-for-input-available
    (stream-or-fd &key (wait-function #'listen)
		       (whostate "waiting for input")
		       timeout)
  (let ((fd (excl:if* (excl:fixnump stream-or-fd) then stream-or-fd
	     elseif (streamp stream-or-fd)
	       then (excl::stream-input-fn stream-or-fd)
	       else (error "wait-for-input-available expects a stream or file descriptor: ~s" stream-or-fd))))
    ;; At this point fd could be nil, since stream-input-fn returns nil for
    ;; streams that are output only, or for certain special purpose streams.
    (if fd
	(unwind-protect
	    (progn
	      (mp::mpwatchfor fd)
	      (excl:if* timeout
		 then (mp::process-wait-with-timeout
		       whostate timeout wait-function stream-or-fd)
		 else (mp::process-wait whostate wait-function stream-or-fd)
		      t))
	  (mp::mpunwatchfor fd))
      (excl:if* timeout
	 then (mp::process-wait-with-timeout
	       whostate timeout wait-function stream-or-fd)
	 else (mp::process-wait whostate wait-function stream-or-fd)
	      t))))
