summaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp')
-rw-r--r--lisp/emacs-parallel/README.org147
-rw-r--r--lisp/emacs-parallel/parallel-remote.el81
-rw-r--r--lisp/emacs-parallel/parallel-xwidget.el59
-rw-r--r--lisp/emacs-parallel/parallel.el310
-rw-r--r--lisp/net/browse-url.el13
-rw-r--r--lisp/xwidget.el513
6 files changed, 1121 insertions, 2 deletions
diff --git a/lisp/emacs-parallel/README.org b/lisp/emacs-parallel/README.org
new file mode 100644
index 00000000000..743050518be
--- /dev/null
+++ b/lisp/emacs-parallel/README.org
@@ -0,0 +1,147 @@
+* Emacs Parallel
+
+ Emacs Parallel is yet another library to simulate parallel
+ computations in Emacs (because it lacks threads support in Elisp).
+
+* STARTED HowTo
+
+ You can execute a simple function a retrive the result like this:
+ #+BEGIN_SRC emacs-lisp
+ (parallel-get-result (parallel-start (lambda () (* 42 42))))
+ ⇒ 1764
+ #+END_SRC
+
+ Though you won't benefit from the parallelism because
+ ~parallel-get-result~ is blocking, that is it waits for the function
+ to be executed.
+
+ So you can use define a callback to be called when the function is
+ finished:
+ #+BEGIN_SRC emacs-lisp
+ (parallel-start (lambda () (sleep-for 4.2) "Hello World")
+ :post-exec (lambda (results _status)
+ (message (first results))))
+ ⊣ Hello World
+ #+END_SRC
+
+ Here, why ~(first results)~ and not ~result~? Because you can send
+ data from the remote instance while it's running with
+ ~parallel-remote-send~:
+ #+BEGIN_SRC emacs-lisp
+ (parallel-start (lambda ()
+ (parallel-remote-send "Hello")
+ (sleep-for 4.2)
+ "World")
+ :post-exec (lambda (results _status)
+ (message "%s"
+ (mapconcat #'identity (reverse results) " "))))
+ ⊣ Hello World
+ #+END_SRC
+ As you may have noticed the results are pushed in a list, so the
+ first element is the result returned by the function called, the
+ second is the last piece of data send, and so on...
+
+ And of course you can execute some code when you receive data from
+ the remote instance:
+ #+BEGIN_SRC emacs-lisp
+ (parallel-start (lambda ()
+ (parallel-remote-send 42)
+ (sleep-for 4.2) ; heavy computation to compute PI
+ pi)
+ :on-event (lambda (data)
+ (message "Received %S" data)))
+ ⊣ Received 42
+ ⊣ Received 3.141592653589793
+ #+END_SRC
+
+ Because the function is executed in another Emacs instance (in Batch
+ Mode by default), the environment isn't the same. However you can
+ send some data with the ~env~ parameter:
+ #+BEGIN_SRC emacs-lisp
+ (let ((a 42)
+ (b 12))
+ (parallel-get-result (parallel-start (lambda (a b) (+ a b))
+ :env (list a b))))
+ ⇒ 54
+ #+END_SRC
+
+ By default, the remote Emacs instance is exited when the function is
+ executed, but you can keep it running with the
+ ~:continue-when-executed~ option and send new code to be executed
+ with ~parellel-send~.
+ #+BEGIN_SRC emacs-lisp
+ (let ((task (parallel-start (lambda () 42)
+ :continue-when-executed t)))
+ (sleep-for 4.2)
+ (parallel-send task (lambda () (setq parallel-continue-when-executed nil) 12))
+ (parallel-get-results task))
+ ⇒ (12 42)
+ #+END_SRC
+
+ As you can see, to stop the remote instance you have to set the
+ variable ~parallel-continue-when-executed~ to nil.
+
+* Modules
+
+** Parallel XWidget
+
+ [[http://www.emacswiki.org/emacs/EmacsXWidgets][Emacs XWidget]] is an experimental branch which permits to embed GTK+
+ widget inside Emacs buffers. For instance, it is possible to use it
+ to render an HTML page using the webkit engine within an Emacs
+ buffer.
+
+ With this module, you can configure your "main" Emacs to use
+ another one to render web pages.
+
+ Let's assume that you've cloned [[https://github.com/jave/xwidget-emacs][the Emacs XWidget repository]] in
+ ~$HOME/src/emacs-xwidget/~. Once you've compiled it, an Emacs
+ executable is available ~$HOME/src/emacs-xwidget/src/emacs~.
+
+ Configure ~parallel-xwidget~ to use it:
+ #+BEGIN_SRC emacs-lisp
+ (setq parallel-xwidget-config (list :emacs-path
+ (concat (getenv "HOME")
+ "/src/emacs-xwidget/src/emacs")))
+ #+END_SRC
+
+ Then configure your current Emacs to use it:
+ #+BEGIN_SRC emacs-lisp
+ (setq browse-url-browser-function 'parallel-xwidget-browse-url)
+ #+END_SRC
+
+ You can check it out with M-x browse-url RET google.com RET.
+
+* Tips & Tricks
+
+ If your windows manager is smart enough (like StumpwWM) you can use
+ it to move graphical windows (Emacs frames) in another desktop.
+
+ For example, I use this to move Emacs frames (with the title
+ "emacs-debug") to the group (aka desktop) 9:
+ #+BEGIN_SRC lisp
+ (define-frame-preference "9"
+ (0 nil t :title "emacs-debug"))
+ #+END_SRC
+
+ And this to specify the title of the frame:
+ #+BEGIN_SRC emacs-lisp
+ (parallel-start (lambda () 42)
+ :no-batch t
+ :emacs-args '("-T" "emacs-debug"))
+ #+END_SRC
+
+* TODO How does it work?
+
+* Known limitations
+
+ You can only send data to the remote (with the ~env~ parameter) or
+ from the remote (with ~parallel-send~ and ~parallel-remote-send~)
+ that have a printed representation (see [[info:elisp#Printed%20Representation][info:elisp#Printed
+ Representation]]).
+
+ So you can pass around numbers, symbols, strings, lists, vectors,
+ hash-table but you can't pass buffers, windows, frames...
+
+
+ It lacks documentation, tests and probably a clean API, but I'm
+ working on it!
diff --git a/lisp/emacs-parallel/parallel-remote.el b/lisp/emacs-parallel/parallel-remote.el
new file mode 100644
index 00000000000..54626afc267
--- /dev/null
+++ b/lisp/emacs-parallel/parallel-remote.el
@@ -0,0 +1,81 @@
+;; -*- mode: emacs-lisp; lexical-binding: t; -*-
+;;; parallel-remote.el ---
+
+;; Copyright (C) 2013 Grégoire Jadi
+
+;; Author: Grégoire Jadi <gregoire.jadi@gmail.com>
+
+;; This program is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of
+;; the License, or (at your option) any later version.
+
+;; 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 General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'cl)
+
+(defvar parallel-service nil)
+(defvar parallel-task-id nil)
+(defvar parallel-client nil)
+(defvar parallel--executed nil)
+(defvar parallel-continue-when-executed nil)
+
+(defun parallel-remote-send (data)
+ (process-send-string parallel-client
+ (format "%S " (cons parallel-task-id data))))
+
+(defun parallel-remote--init ()
+ (setq parallel-client (make-network-process :name "emacs-parallel"
+ :buffer nil
+ :server nil
+ :service parallel-service
+ :host "localhost"
+ :family 'ipv4))
+ (set-process-filter parallel-client #'parallel-remote--filter)
+ (parallel-remote-send 'code)
+ (when noninteractive ; Batch Mode
+ ;; The evaluation is done in the `parallel--filter' but in Batch
+ ;; Mode, Emacs doesn't wait for the input, it stops as soon as
+ ;; `parallel--init' has been executed.
+ (while (null parallel--executed)
+ (sleep-for 10)))) ; arbitrary chosen
+
+(defun parallel-remote--filter (_proc output)
+ (dolist (code (parallel--read-output output))
+ (parallel-remote-send
+ (if (or noninteractive
+ (not debug-on-error))
+ (condition-case err
+ (eval code)
+ (error err))
+ (eval code))))
+ (unless parallel-continue-when-executed
+ (setq parallel--executed t)
+ (kill-emacs)))
+
+(defun parallel--read-output (output)
+ "Read lisp forms from output and return them as a list."
+ (loop with output = (replace-regexp-in-string
+ "\\`[ \t\n]*" ""
+ (replace-regexp-in-string "[ \t\n]*\\'" "" output)) ; trim string
+ with start = 0
+ with end = (length output)
+ for ret = (read-from-string output start end)
+ for data = (first ret)
+ do (setq start (rest ret))
+ collect data
+ until (= start end)))
+
+(provide 'parallel-remote)
+
+;;; parallel-remote.el ends here
diff --git a/lisp/emacs-parallel/parallel-xwidget.el b/lisp/emacs-parallel/parallel-xwidget.el
new file mode 100644
index 00000000000..7e23863d6eb
--- /dev/null
+++ b/lisp/emacs-parallel/parallel-xwidget.el
@@ -0,0 +1,59 @@
+;;; parallel-xwidget.el ---
+
+;; Copyright (C) 2013 Grégoire Jadi
+
+;; Author: Grégoire Jadi <gregoire.jadi@gmail.com>
+
+;; This program is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of
+;; the License, or (at your option) any later version.
+
+;; 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 General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'parallel)
+(require 'browse-url)
+
+(defgroup parallel-xwidget nil
+ "Browse the web in another emacs instance with XWidget."
+ :group 'emacs)
+
+(defvar parallel-xwidget--task nil)
+
+(defcustom parallel-xwidget-config nil
+ "Parallel configuration."
+ :type 'alist
+ :group 'parallel-xwidget)
+
+(defun parallel-xwidget--init ()
+ (setq parallel-xwidget--task
+ (parallel-start (lambda ()
+ (require 'xwidget))
+ :graphical t
+ :continue-when-executed t
+ :config parallel-xwidget-config)))
+
+(defun parallel-xwidget-browse-url (url &optional new-session)
+ "Browse URL in another Emacs instance."
+ (interactive (browse-url-interactive-arg "xwidget-webkit URL: "))
+ (unless (and parallel-xwidget--task
+ (eq 'run (parallel-status parallel-xwidget--task)))
+ (parallel-xwidget--init))
+ (parallel-send parallel-xwidget--task
+ (lambda (url new-session)
+ (xwidget-webkit-browse-url url new-session))
+ (url-tidy url) new-session))
+
+(provide 'parallel-xwidget)
+
+;;; parallel-xwidget.el ends here
diff --git a/lisp/emacs-parallel/parallel.el b/lisp/emacs-parallel/parallel.el
new file mode 100644
index 00000000000..3e5eccfd73c
--- /dev/null
+++ b/lisp/emacs-parallel/parallel.el
@@ -0,0 +1,310 @@
+;; -*- lexical-binding: t; -*-
+;;; parallel.el ---
+
+;; Copyright (C) 2013 Grégoire Jadi
+
+;; Author: Grégoire Jadi <gregoire.jadi@gmail.com>
+
+;; This program is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of
+;; the License, or (at your option) any later version.
+
+;; 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 General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'cl)
+(require 'parallel-remote)
+
+(defgroup parallel nil
+ "Execute stuff in parallel"
+ :group 'emacs)
+
+(defcustom parallel-sleep 0.05
+ "How many sec should we wait while polling."
+ :type 'number
+ :group 'parallel)
+
+(defcustom parallel-config nil
+ "Global config setting to use."
+ :type 'plist
+ :group 'parallel)
+
+(defvar parallel--server nil)
+(defvar parallel--tasks nil)
+(defvar parallel--tunnels nil)
+
+;; Declare external function
+(declare-function parallel-send "parallel-remote")
+
+(defun parallel-make-tunnel (username hostname)
+ (parallel--init-server)
+ (let ((tunnel (find-if (lambda (tun)
+ (and (string= username
+ (process-get tun 'username))
+ (string= hostname
+ (process-get tun 'hostname))))
+ parallel--tunnels)))
+ (unless tunnel
+ (setq tunnel (start-process "parallel-ssh" nil "ssh"
+ "-N" "-R" (format "0:localhost:%s"
+ (process-contact parallel--server :service))
+ (format "%s@%s" username hostname)))
+ (process-put tunnel 'username username)
+ (process-put tunnel 'hostname hostname)
+ (set-process-filter tunnel #'parallel--tunnel-filter)
+ (while (null (process-get tunnel 'service))
+ (sleep-for 0.01))
+ (push tunnel parallel--tunnels))
+ tunnel))
+
+(defun parallel-stop-tunnel (tunnel)
+ (setq parallel--tunnels (delq tunnel parallel--tunnels))
+ (delete-process tunnel))
+
+(defun parallel--tunnel-filter (proc output)
+ (if (string-match "\\([0-9]+\\)" output)
+ (process-put proc 'service (match-string 1 output))))
+
+(defmacro parallel--set-option (place config)
+ `(setf ,place (or ,place
+ (plist-get ,config ,(intern (format ":%s" (symbol-name place))))
+ (plist-get parallel-config ,(intern (format ":%s" (symbol-name place)))))))
+
+(defmacro parallel--set-options (config &rest options)
+ `(progn
+ ,@(loop for option in options
+ collect `(parallel--set-option ,option ,config))))
+
+(defun* parallel-start (exec-fun &key post-exec env timeout
+ emacs-path library-path emacs-args
+ graphical debug on-event continue-when-executed
+ username hostname hostport
+ config)
+ (parallel--init-server)
+
+ ;; Initialize parameters
+ (parallel--set-options config
+ post-exec
+ env
+ timeout
+ emacs-args
+ graphical
+ debug
+ on-event
+ continue-when-executed
+ username
+ hostname
+ hostport)
+
+ (setq emacs-path (or emacs-path
+ (plist-get config :emacs-path)
+ (plist-get parallel-config :emacs-path)
+ (expand-file-name invocation-name
+ invocation-directory))
+ library-path (or library-path
+ (plist-get config :library-path)
+ (plist-get parallel-config :library-path)
+ (locate-library "parallel-remote")))
+
+ (let ((task (parallel--new-task))
+ proc tunnel ssh-args)
+ (push task parallel--tasks)
+ (put task 'initialized nil)
+ (put task 'exec-fun exec-fun)
+ (put task 'env env)
+ (when (functionp post-exec)
+ (put task 'post-exec post-exec))
+ (when (functionp on-event)
+ (put task 'on-event on-event))
+ (put task 'results nil)
+ (put task 'status 'run)
+ (put task 'queue nil)
+
+ ;; We need to get the tunnel if it exists so we can send the right
+ ;; `service' to the remote.
+ (when (and username hostname)
+ (if hostport
+ (setq ssh-args (list "-R" (format "%s:localhost:%s" hostport
+ (process-contact parallel--server :service)))
+ tunnel t)
+ (setq tunnel (parallel-make-tunnel username hostname)
+ hostport (process-get tunnel 'service)))
+ (setq ssh-args (append
+ ssh-args
+ (if graphical (list "-X"))
+ (list (format "%s@%s" username hostname)))))
+ (setq emacs-args (remq nil
+ (list* "-Q" "-l" library-path
+ (if graphical nil "-batch")
+ "--eval" (format "(setq parallel-service '%S)"
+ (if tunnel
+ hostport
+ (process-contact parallel--server :service)))
+ "--eval" (format "(setq parallel-task-id '%S)" task)
+ "--eval" (format "(setq debug-on-error '%S)" debug)
+ "--eval" (format "(setq parallel-continue-when-executed '%S)" continue-when-executed)
+ "-f" "parallel-remote--init"
+ emacs-args)))
+
+ ;; Reformat emacs-args if we use a tunnel (escape string)
+ (when tunnel
+ (setq emacs-args (list (mapconcat (lambda (string)
+ (if (find ?' string)
+ (prin1-to-string string)
+ string))
+ emacs-args " "))))
+ (setq proc (apply #'start-process "parallel" nil
+ `(,@(when tunnel
+ (list* "ssh" ssh-args))
+ ,emacs-path
+ ,@emacs-args)))
+ (put task 'proc proc)
+ (set-process-sentinel (get task 'proc) #'parallel--sentinel)
+ (when timeout
+ (run-at-time timeout nil (lambda ()
+ (when (memq (parallel-status task)
+ '(run stop))
+ (parallel-stop task)))))
+ task))
+
+(defun parallel--new-task ()
+ "Generate a new task by enforcing a unique name."
+ (let ((symbol-name (make-temp-name "parallel-task-")))
+ (while (intern-soft symbol-name)
+ (setq symbol-name (make-temp-name "parallel-task-")))
+ (intern symbol-name)))
+
+(defun parallel--init-server ()
+ "Initialize `parallel--server'."
+ (when (or (null parallel--server)
+ (not (eq (process-status parallel--server)
+ 'listen)))
+ (setq parallel--server
+ (make-network-process :name "parallel-server"
+ :buffer nil
+ :server t
+ :host "localhost"
+ :service t
+ :family 'ipv4
+ :filter #'parallel--filter
+ :filter-multibyte t))))
+
+(defun parallel--get-task-process (proc)
+ "Return the task running the given PROC."
+ (find-if (lambda (task)
+ (eq (get task 'proc) proc))
+ parallel--tasks))
+
+(defun parallel--sentinel (proc _event)
+ "Sentinel to watch over the remote process.
+
+This function do the necessary cleanup when the remote process is
+finished."
+ (when (memq (process-status proc) '(exit signal))
+ (let* ((task (parallel--get-task-process proc))
+ (results (get task 'results))
+ (status (process-status proc)))
+ ;; 0 means that the remote process has terminated normally (no
+ ;; SIGNUM 0).
+ (if (zerop (process-exit-status proc))
+ (setq status 'success)
+ ;; on failure, push the exit-code or signal number on the
+ ;; results stack.
+ (push (process-exit-status proc) results))
+ (put task 'results results)
+ (put task 'status status)
+
+ (when (functionp (get task 'post-exec))
+ (funcall (get task 'post-exec)
+ results status))
+ (setq parallel--tasks (delq task parallel--tasks)))))
+
+(defun parallel--call-with-env (fun env)
+ "Return a string which can be READ/EVAL by the remote process
+to `funcall' FUN with ENV as arguments."
+ (format "(funcall (read %S) %s)"
+ (prin1-to-string fun)
+ (mapconcat (lambda (obj)
+ ;; We need to quote it because the remote
+ ;; process will READ/EVAL it.
+ (format "'%S" obj)) env " ")))
+
+(defun parallel--filter (connection output)
+ "Server filter used to retrieve the results send by the remote
+process and send the code to be executed by it."
+ (dolist (data (parallel--read-output output))
+ (parallel--process-output connection (first data) (rest data))))
+
+(defun parallel--process-output (connection task result)
+ (put task 'connection connection)
+ (cond ((and (not (get task 'initialized))
+ (eq result 'code))
+ (apply #'parallel-send
+ task
+ (get task 'exec-fun)
+ (get task 'env))
+ (let ((code nil))
+ (while (setq code (pop (get task 'queue)))
+ (apply #'parallel-send task (car code) (cdr code))))
+ (put task 'initialized t))
+ (t
+ (push result (get task 'results))
+ (if (functionp (get task 'on-event))
+ (funcall (get task 'on-event) result)))))
+
+(defun parallel-ready-p (task)
+ "Determine whether TASK is finished and if the results are
+available."
+ (memq (parallel-status task) '(success exit signal)))
+
+(defun parallel-get-result (task)
+ "Return the last result send by the remote call, that is the
+result returned by exec-fun."
+ (first (parallel-get-results task)))
+
+(defun parallel-get-results (task)
+ "Return all results send during the call of exec-fun."
+ (parallel-wait task)
+ (get task 'results))
+
+(defun parallel-success-p (task)
+ "Determine whether TASK has ended successfully."
+ (parallel-wait task)
+ (eq (parallel-status task) 'success))
+
+(defun parallel-status (task)
+ "Return TASK status."
+ (get task 'status))
+
+(defun parallel-wait (task)
+ "Wait for TASK."
+ (while (not (parallel-ready-p task))
+ (sleep-for parallel-sleep))
+ t) ; for REPL
+
+(defun parallel-stop (task)
+ "Stop TASK."
+ (delete-process (get task 'proc)))
+
+(defun parallel-send (task fun &rest env)
+ "Send FUN to be evaluated by TASK in ENV."
+ (let ((connection (get task 'connection)))
+ (if connection
+ (process-send-string
+ connection
+ (parallel--call-with-env fun env))
+ (push (cons fun env) (get task 'queue)))))
+
+(provide 'parallel)
+
+;;; parallel.el ends here
diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el
index 42fb9549255..57c7b61fc28 100644
--- a/lisp/net/browse-url.el
+++ b/lisp/net/browse-url.el
@@ -668,7 +668,7 @@ regarding its parameter treatment."
;; functions allows them to be stand-alone commands, making it easier
;; to switch between browsers.
-(defun browse-url-interactive-arg (prompt)
+(defun browse-url-interactive-arg (prompt &optional default-url)
"Read a URL from the minibuffer, prompting with PROMPT.
If `transient-mark-mode' is non-nil and the mark is active,
it defaults to the current region, else to the URL at or before
@@ -685,7 +685,8 @@ for use in `interactive'."
"[\t\r\f\n ]+" ""
(buffer-substring-no-properties
(region-beginning) (region-end))))
- (browse-url-url-at-point)))
+ (browse-url-url-at-point)
+ default-url))
(not (eq (null browse-url-new-window-flag)
(null current-prefix-arg)))))
@@ -795,6 +796,13 @@ narrowed."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Browser-independent commands
+(defun url-tidy (url)
+ "Tidy up URL as much as possible."
+ (if (equal 0 (string-match ".*://" url))
+ url
+ (concat "http://" url) ;;TODO guess more url forms, like mailto
+ ))
+
;; A generic command to call the current browse-url-browser-function
;;;###autoload
@@ -807,6 +815,7 @@ first, if that exists."
(interactive (browse-url-interactive-arg "URL: "))
(unless (called-interactively-p 'interactive)
(setq args (or args (list browse-url-new-window-flag))))
+ (setq url (url-tidy url))
(when (and url-handler-mode (not (file-name-absolute-p url)))
(setq url (expand-file-name url)))
(let ((process-environment (copy-sequence process-environment))
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
new file mode 100644
index 00000000000..1f0932ca7dd
--- /dev/null
+++ b/lisp/xwidget.el
@@ -0,0 +1,513 @@
+;;; xwidget.el --- api functions for xwidgets
+;; see xwidget.c for more api functions
+
+
+;;; Commentary:
+;;
+
+;;TODO this breaks compilation when we dont have xwidgets
+;;(require 'xwidget-internal)
+
+;;TODO model after make-text-button instead!
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'reporter)
+
+(defun xwidget-insert (pos type title width height)
+ "Insert an xwidget at POS, given ID, TYPE, TITLE WIDTH and
+HEIGHT in the current buffer.
+
+Return ID
+
+see `make-xwidget' for types suitable for TYPE."
+ (goto-char pos)
+ (let ((id (make-xwidget (point) (point)
+ type title width height nil)))
+ (put-text-property (point) (+ 1 (point))
+ 'display (list 'xwidget ':xwidget id))
+ id))
+
+(defun xwidget-at (pos)
+ "Return xwidget at POS."
+ ;;TODO this function is a bit tedious because the C layer isnt well protected yet and
+ ;;xwidgetp aparently doesnt work yet
+ (let* ((disp (get-text-property pos 'display))
+ (xw (car (cdr (cdr disp)))))
+ ;;(if ( xwidgetp xw) xw nil)
+ (if (equal 'xwidget (car disp)) xw)))
+
+
+;; (defun xwidget-socket-handler ()
+;; "Create plug for socket. TODO."
+;; (interactive)
+;; (message "socket handler xwidget %S" last-input-event)
+;; (let*
+;; ((xwidget-event-type (nth 2 last-input-event))
+;; (xwidget-id (nth 1 last-input-event)))
+;; (cond ( (eq xwidget-event-type 'xembed-ready)
+;; (let*
+;; ((xembed-id (nth 3 last-input-event)))
+;; (message "xembed ready event: %S xw-id:%s" xembed-id xwidget-id)
+;; ;;TODO fetch process data from the xwidget. create it, store process info
+;; ;;will start emacs/uzbl in a xembed socket when its ready
+;; ;; (cond
+;; ;; ((eq 3 xwidget-id)
+;; ;; (start-process "xembed" "*xembed*" (format "%ssrc/emacs" default-directory) "-q" "--parent-id" (number-to-string xembed-id) ) )
+;; ;; ((eq 5 xwidget-id)
+;; ;; (start-process "xembed2" "*xembed2*" "uzbl-core" "-s" (number-to-string xembed-id) "http://www.fsf.org" ) )
+;; )))))
+
+(defun xwidget-display (xwidget)
+ "Force xwidget to be displayed to create a xwidget_view. Return
+the window displaying XWIDGET."
+ (let* ((buffer (xwidget-buffer xwidget))
+ (window (display-buffer buffer))
+ (frame (window-frame window)))
+ (set-frame-visible frame t)
+ (redisplay t)
+ window))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; webkit support
+(require 'browse-url)
+(require 'image-mode);;for some image-mode alike functionality
+(require 'cl-macs);;for flet
+
+;;;###autoload
+(defun xwidget-webkit-browse-url (url &optional new-session)
+ "Ask xwidget-webkit to browse URL.
+NEW-SESSION specifies whether to create a new xwidget-webkit session. URL
+defaults to the string looking like a url around the cursor position."
+ (interactive (progn
+ (require 'browse-url)
+ (browse-url-interactive-arg "xwidget-webkit URL: "
+ ;;( xwidget-webkit-current-url)
+ )))
+ (when (stringp url)
+ (setq url (url-tidy url))
+ (if new-session
+ (xwidget-webkit-new-session url)
+ (xwidget-webkit-goto-url url))))
+
+
+;;shims for adapting image mode code to the webkit browser window
+(defun xwidget-image-display-size (spec &optional pixels frame)
+ "Image code adaptor. SPEC PIXELS FRAME like the corresponding `image-mode' fn."
+ (let ((xwi (xwidget-info (xwidget-at 1))))
+ (cons (aref xwi 2)
+ (aref xwi 3))))
+
+(defadvice image-display-size (around image-display-size-for-xwidget
+ (spec &optional pixels frame)
+ activate)
+ (if (eq (car spec) 'xwidget)
+ (setq ad-return-value (xwidget-image-display-size spec pixels frame))
+ ad-do-it))
+
+;;todo.
+;; - check that the webkit support is compiled in
+(defvar xwidget-webkit-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "g" 'xwidget-webkit-browse-url)
+ (define-key map "a" 'xwidget-webkit-adjust-size-to-content)
+ (define-key map "b" 'xwidget-webkit-back )
+ (define-key map "r" 'xwidget-webkit-reload )
+ (define-key map "t" (lambda () (interactive) (message "o")) )
+ (define-key map "\C-m" 'xwidget-webkit-insert-string)
+ (define-key map "w" 'xwidget-webkit-current-url)
+
+ ;;similar to image mode bindings
+ (define-key map (kbd "SPC") 'image-scroll-up)
+ (define-key map (kbd "DEL") 'image-scroll-down)
+
+ (define-key map [remap scroll-up] 'image-scroll-up)
+ (define-key map [remap scroll-up-command] 'image-scroll-up)
+
+ (define-key map [remap scroll-down] 'image-scroll-down)
+ (define-key map [remap scroll-down-command] 'image-scroll-down)
+
+ (define-key map [remap forward-char] 'image-forward-hscroll)
+ (define-key map [remap backward-char] 'image-backward-hscroll)
+ (define-key map [remap right-char] 'image-forward-hscroll)
+ (define-key map [remap left-char] 'image-backward-hscroll)
+ (define-key map [remap previous-line] 'image-previous-line)
+ (define-key map [remap next-line] 'image-next-line)
+
+ (define-key map [remap move-beginning-of-line] 'image-bol)
+ (define-key map [remap move-end-of-line] 'image-eol)
+ (define-key map [remap beginning-of-buffer] 'image-bob)
+ (define-key map [remap end-of-buffer] 'image-eob)
+ map)
+ "Keymap for `xwidget-webkit-mode'.")
+
+;;the xwidget event needs to go into a higher level handler
+;;since the xwidget can generate an event even if its offscreen
+;;TODO this needs to use callbacks and consider different xw ev types
+(define-key (current-global-map) [xwidget-event] 'xwidget-event-handler)
+(defun xwidget-log ( &rest msg)
+ (let ( (buf (get-buffer-create "*xwidget-log*")))
+ (save-excursion
+ (buffer-disable-undo buf)
+ (set-buffer buf)
+ (insert (apply 'format msg))
+ (insert "\n"))))
+
+(defun xwidget-event-handler ()
+ "Receive xwidget event."
+ (interactive)
+ (xwidget-log "stuff happened to xwidget %S" last-input-event)
+ (let*
+ ((xwidget-event-type (nth 1 last-input-event))
+ (xwidget (nth 2 last-input-event))
+ ;(xwidget-callback (xwidget-get xwidget 'callback));;TODO stopped working for some reason
+ )
+ ;(funcall xwidget-callback xwidget xwidget-event-type)
+ (message "xw callback %s" xwidget)
+ (funcall 'xwidget-webkit-callback xwidget xwidget-event-type)))
+
+(defun xwidget-webkit-callback (xwidget xwidget-event-type)
+ (save-excursion
+ (cond ((buffer-live-p (xwidget-buffer xwidget))
+ (set-buffer (xwidget-buffer xwidget))
+ (let* ((strarg (nth 3 last-input-event)))
+ (cond ((eq xwidget-event-type 'document-load-finished)
+ (xwidget-log "webkit finished loading: '%s'" (xwidget-webkit-get-title xwidget))
+ (xwidget-adjust-size-to-content xwidget)
+ (rename-buffer (format "*xwidget webkit: %s *" (xwidget-webkit-get-title xwidget)))
+ (pop-to-buffer (current-buffer)))
+ ((eq xwidget-event-type 'navigation-policy-decision-requested)
+ (if (string-match ".*#\\(.*\\)" strarg)
+ (xwidget-webkit-show-id-or-named-element xwidget (match-string 1 strarg))))
+ (t (xwidget-log "unhandled event:%s" xwidget-event-type)))))
+ (t (xwidget-log "error: callback called for xwidget with dead buffer")))))
+
+(define-derived-mode xwidget-webkit-mode
+ special-mode "xwidget-webkit" "xwidget webkit view mode"
+ (setq buffer-read-only t)
+ ;; Keep track of [vh]scroll when switching buffers
+ (image-mode-setup-winprops))
+
+(defvar xwidget-webkit-last-session-buffer nil)
+
+(defun xwidget-webkit-last-session ()
+ "Last active webkit, or nil."
+ (if (buffer-live-p xwidget-webkit-last-session-buffer)
+ (with-current-buffer xwidget-webkit-last-session-buffer
+ (xwidget-at 1))
+ nil))
+
+(defun xwidget-webkit-current-session ()
+ "Either the webkit in the current buffer, or the last one used, which might be nil."
+ (if (xwidget-at 1)
+ (xwidget-at 1)
+ (xwidget-webkit-last-session)))
+
+(defun xwidget-adjust-size-to-content (xw)
+ "Resize XW to content."
+ ;;xwidgets doesnt support widgets that have their own opinions about size well yet
+ ;;this reads the desired size and resizes the emacs allocated area accordingly
+ (let ((size (xwidget-size-request xw)))
+ (xwidget-resize xw (car size) (cadr size))))
+
+
+(defvar xwidget-webkit-activeelement-js"
+function findactiveelement(doc){
+//alert(doc.activeElement.value);
+ if(doc.activeElement.value != undefined){
+ return doc.activeElement;
+ }else{
+ // recurse over the child documents:
+ var frames = doc.getElementsByTagName('frame');
+ for (var i = 0; i < frames.length; i++)
+ {
+ var d = frames[i].contentDocument;
+ var rv = findactiveelement(d);
+ if(rv != undefined){
+ return rv;
+ }
+ }
+ }
+ return undefined;
+};
+
+
+"
+
+ "javascript that finds the active element."
+ ;;yes its ugly. because:
+ ;; - there is aparently no way to find the active frame other than recursion
+ ;; - the js "for each" construct missbehaved on the "frames" collection
+ ;; - a window with no frameset still has frames.length == 1, but frames[0].document.activeElement != document.activeElement
+ ;;TODO the activeelement type needs to be examined, for iframe, etc. sucks.
+ )
+
+(defun xwidget-webkit-insert-string (xw str)
+ "Insert string in the active field in the webkit.
+Argument XW webkit.
+Argument STR string."
+ ;;read out the string in the field first and provide for edit
+ (interactive
+ (let* ((xww (xwidget-webkit-current-session))
+
+ (field-value
+ (progn
+ (xwidget-webkit-execute-script xww xwidget-webkit-activeelement-js)
+ (xwidget-webkit-execute-script-rv xww "findactiveelement(document).value;" )))
+ (field-type (xwidget-webkit-execute-script-rv xww "findactiveelement(document).type;" )))
+ (list xww
+ (cond ((equal "text" field-type)
+ (read-string "text:" field-value))
+ ((equal "password" field-type)
+ (read-passwd "password:" nil field-value))
+ ((equal "textarea" field-type)
+ (xwidget-webkit-begin-edit-textarea xww field-value))))))
+ (xwidget-webkit-execute-script xw (format "findactiveelement(document).value='%s'" str)))
+
+
+(defun xwidget-webkit-begin-edit-textarea (xw text)
+ (switch-to-buffer
+ (generate-new-buffer "textarea"))
+
+ (set (make-local-variable 'xwbl) xw)
+ (insert text))
+
+(defun xwidget-webkit-end-edit-textarea ()
+ (interactive)
+ (goto-char (point-min))
+ (while (search-forward "\n" nil t)
+ (replace-match "\\n" nil t))
+ (xwidget-webkit-execute-script xwbl (format "findactiveelement(document).value='%s'"
+ (buffer-substring (point-min) (point-max))))
+ ;;TODO convert linefeed to \n
+ )
+
+(defun xwidget-webkit-show-named-element (xw element-name)
+ "make named-element show. for instance an anchor."
+ (interactive (list (xwidget-webkit-current-session) (read-string "element name:")))
+ ;;TODO
+ ;; since an xwidget is an Emacs object, it is not trivial to do some things that are taken for granted in a normal browser.
+ ;; scrolling an anchor/named-element into view is one such thing.
+ ;; this function implements a proof-of-concept for this.
+ ;; problems remaining:
+ ;; - the selected window is scrolled but this is not always correct
+ ;; - this needs to be interfaced into browse-url somehow. the tricky part is that we need to do this in two steps:
+ ;; A: load the base url, wait for load signal to arrive B: navigate to the anchor when the base url is finished rendering
+
+ ;;this part figures out the Y coordinate of the element
+ (let ((y (string-to-number
+ (xwidget-webkit-execute-script-rv xw
+ (format "document.getElementsByName('%s')[0].getBoundingClientRect().top" element-name)
+ 0))))
+ ;;now we need to tell emacs to scroll the element into view.
+ (xwidget-log "scroll: %d" y)
+ (set-window-vscroll (selected-window) y t)))
+
+(defun xwidget-webkit-show-id-element (xw element-id)
+ "make id-element show. for instance an anchor."
+ (interactive (list (xwidget-webkit-current-session)
+ (read-string "element id:")))
+ (let ((y (string-to-number
+ (xwidget-webkit-execute-script-rv xw
+ (format "document.getElementById('%s').getBoundingClientRect().top" element-id)
+ 0))))
+ ;;now we need to tell emacs to scroll the element into view.
+ (xwidget-log "scroll: %d" y)
+ (set-window-vscroll (selected-window) y t)))
+
+(defun xwidget-webkit-show-id-or-named-element (xw element-id)
+ "make id-element show. for instance an anchor."
+ (interactive (list (xwidget-webkit-current-session)
+ (read-string "element id:")))
+ (let* ((y1 (string-to-number
+ (xwidget-webkit-execute-script-rv xw
+ (format "document.getElementsByName('%s')[0].getBoundingClientRect().top" element-id)
+ "0")))
+ (y2 (string-to-number
+ (xwidget-webkit-execute-script-rv xw
+ (format "document.getElementById('%s').getBoundingClientRect().top" element-id)
+ "0")))
+ (y3 (max y1 y2)))
+ ;;now we need to tell emacs to scroll the element into view.
+ (xwidget-log "scroll: %d" y3)
+ (set-window-vscroll (selected-window) y3 t)))
+
+(defun xwidget-webkit-adjust-size-to-content ()
+ "Adjust webkit to content size."
+ (interactive)
+ (xwidget-adjust-size-to-content (xwidget-webkit-current-session)))
+
+(defun xwidget-webkit-adjust-size (w h)
+ "Manualy set webkit size.
+Argument W width.
+Argument H height."
+ ;;TODO shouldnt be tied to the webkit xwidget
+ (interactive "nWidth:\nnHeight:\n")
+ (xwidget-resize ( xwidget-webkit-current-session) w h))
+
+(defun xwidget-webkit-fit-width ()
+ (interactive)
+ (xwidget-webkit-adjust-size (- (caddr (window-inside-pixel-edges))
+ (car (window-inside-pixel-edges)))
+ 1000))
+
+(defun xwidget-webkit-new-session (url)
+ "Create a new webkit session buffer with URL."
+ (let*
+ ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
+ xw)
+ (setq xwidget-webkit-last-session-buffer (switch-to-buffer (get-buffer-create bufname)))
+ (insert " ")
+ (setq xw (xwidget-insert 1 'webkit-osr bufname 1000 1000))
+ (xwidget-put xw 'callback 'xwidget-webkit-callback)
+ (xwidget-webkit-mode)
+ (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url )))
+
+
+(defun xwidget-webkit-goto-url (url)
+ "Goto URL."
+ (if (xwidget-webkit-current-session)
+ (progn
+ (xwidget-webkit-goto-uri (xwidget-webkit-current-session) url))
+ (xwidget-webkit-new-session url)))
+
+(defun xwidget-webkit-back ()
+ "Back in history."
+ (interactive)
+ (xwidget-webkit-execute-script (xwidget-webkit-current-session) "history.go(-1);"))
+
+(defun xwidget-webkit-reload ()
+ "Reload current url."
+ (interactive)
+ (xwidget-webkit-execute-script (xwidget-webkit-current-session) "history.go(0);"))
+
+(defun xwidget-webkit-current-url ()
+ "Get the webkit url. place it on kill ring."
+ (interactive)
+ (let* ((rv (xwidget-webkit-execute-script-rv (xwidget-webkit-current-session)
+ "document.URL"))
+ (url (kill-new (or rv ""))))
+ (message "url: %s" url )
+ url))
+
+(defun xwidget-webkit-execute-script-rv (xw script &optional default)
+ "same as xwidget-webkit-execute-script but also wraps an ugly hack to return a value"
+ ;;notice the fugly "title" hack. it is needed because the webkit api doesnt support returning values.
+ ;;this is a wrapper for the title hack so its easy to remove should webkit someday support JS return values
+ ;;or we find some other way to access the DOM
+
+ ;;reset webkit title. fugly.
+ (let* ((emptytag "titlecantbewhitespaceohthehorror")
+ title)
+ (xwidget-webkit-execute-script xw (format "document.title=\"%s\";" (or default emptytag)))
+ (xwidget-webkit-execute-script xw (format "document.title=%s;" script))
+ (setq title (xwidget-webkit-get-title xw))
+ (if (equal emptytag title)
+ (setq title ""))
+ (unless title
+ (setq title default))
+ title))
+
+
+;; use declare here?
+;; (declare-function xwidget-resize-internal "xwidget.c" )
+;; check-declare-function?
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun xwidget-webkit-get-selection ()
+ (xwidget-webkit-execute-script-rv (xwidget-webkit-current-session)
+ "window.getSelection().toString();"))
+
+(defun xwidget-webkit-copy-selection-as-kill ()
+ (interactive)
+ (kill-new (xwidget-webkit-get-selection)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; xwidget plist management(similar to the process plist functions)
+
+(defun xwidget-get (xwidget propname)
+ "Return the value of XWIDGET' PROPNAME property.
+This is the last value stored with `(xwidget-put XWIDGET PROPNAME VALUE)'."
+ (plist-get (xwidget-plist xwidget) propname))
+
+(defun xwidget-put (xwidget propname value)
+ "Change XWIDGET' PROPNAME property to VALUE.
+It can be retrieved with `(xwidget-get XWIDGET PROPNAME)'."
+ (set-xwidget-plist xwidget
+ (plist-put (xwidget-plist xwidget) propname value)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun xwidget-delete-zombies ()
+ (dolist (xwidget-view xwidget-view-list)
+ (when (or (not (window-live-p (xwidget-view-window xwidget-view)))
+ (not (memq (xwidget-view-model xwidget-view)
+ xwidget-list)))
+ (delete-xwidget-view xwidget-view))))
+
+(defun xwidget-cleanup ()
+ "Delete zombie xwidgets."
+ ;;its still pretty easy to trigger bugs with xwidgets.
+ ;;this function tries to implement a workaround
+ (interactive)
+ ;; kill xviews who should have been deleted but stull linger
+ (xwidget-delete-zombies)
+ ;; redraw display otherwise ghost of zombies will remain to haunt the screen
+ (redraw-display))
+
+;;this is a workaround because I cant find the right place to put it in C
+;;seems to work well in practice though
+;;(add-hook 'window-configuration-change-hook 'xwidget-cleanup)
+(add-hook 'window-configuration-change-hook 'xwidget-delete-zombies)
+
+(defun xwidget-kill-buffer-query-function ()
+ "Ask beforek illing a buffer that has xwidgets."
+ (let ((xwidgets (get-buffer-xwidgets (current-buffer))))
+ (or (not xwidgets)
+ (not (memq t (mapcar 'xwidget-query-on-exit-flag xwidgets)))
+ (yes-or-no-p
+ (format "Buffer %S has xwidgets; kill it? "
+ (buffer-name (current-buffer)))))))
+
+(add-hook 'kill-buffer-query-functions 'xwidget-kill-buffer-query-function)
+
+;;killflash is sadly not reliable yet.
+(defvar xwidget-webkit-kill-flash-oneshot t)
+(defun xwidget-webkit-kill-flash ()
+ "Disable the flash plugin in webkit.
+This is needed because Flash is non-free and doesnt work reliably
+on 64 bit systems and offscreen rendering. Sadly not reliable
+yet, so deinstall Flash instead for now."
+ ;;you can only call this once or webkit crashes and takes emacs with it. odd.
+ (unless xwidget-webkit-kill-flash-oneshot
+ (xwidget-disable-plugin-for-mime "application/x-shockwave-flash")
+ (setq xwidget-webkit-kill-flash-oneshot t)))
+
+(xwidget-webkit-kill-flash)
+
+(defun report-xwidget-bug ()
+ "Report a bug in GNU Emacs about the XWidget branch.
+Prompts for bug subject. Leaves you in a mail buffer."
+ (interactive)
+ (let ((reporter-prompt-for-summary-p t))
+ (reporter-submit-bug-report "submit@debbugs.gnu.org" nil nil nil nil
+ (format "Package: emacs-xwidgets
+
+Please describee xactly whata ctions triggered the bug, and the
+precise symptoms of the bug. If you can, give a recipe starting
+from `emacs -Q'.
+
+If Emacs crashed, and you have the Emacs process in the gdb
+deubbger, please include the output from the following gdb
+commands:
+ `bt full' and `xbacktrace'.
+
+For information about debugging Emacs, please read the file
+%s" (expand-file-name "DEBUG" data-directory)))))
+
+(provide 'xwidget)
+
+;;; xwidget.el ends here