From: Jaesup Kwak <veshboo@gmail.com>
To: 29565@debbugs.gnu.org
Cc: Alan Third <alan@idiocy.org>
Subject: bug#29565: [PATCH] Support xwidget webkit for macOS X
Date: Tue, 5 Dec 2017 16:55:56 +0900 [thread overview]
Message-ID: <CAADX8xKL+VJQRR0q2XqGXt2ORSes9xrCKjmzEvmoGagEUdZXLg@mail.gmail.com> (raw)
In-Reply-To: <CAADX8xLuN56bOnA9U7JaRT=oKJJXOEGUneJLnfR_Anh4KRaVaw@mail.gmail.com>
[-- Attachment #1.1: Type: text/plain, Size: 324 bytes --]
On Tue, Dec 5, 2017 at 3:01 PM, Jaesup Kwak <veshboo@gmail.com> wrote:
>
> I will soon post a patch accompanying above changes.
>
>
I made and attached a new patch, which includes changes for comments
from Alan Third and previous patch. So, please ignore the patch file
from previous post and use this patch file.
Thanks.
[-- Attachment #1.2: Type: text/html, Size: 871 bytes --]
[-- Attachment #2: 0001-Support-xwidget-webkit-for-macOS-X.20171205.patch --]
[-- Type: application/octet-stream, Size: 61206 bytes --]
From 3be38a63735e911b7322591af5e38d1ac90a3125 Mon Sep 17 00:00:00 2001
From: Jaesup Kwak <veshboo@gmail.com>
Date: Mon, 4 Dec 2017 21:23:19 +0900
Subject: [PATCH] Support xwidget webkit for macOS X
Add xwidget webkit support for macOS X / NS Cocoa and accompanying
changes.
Squash changes for comments from Alan Third in Bug#29565.
* configure.ac: Allow '--with-xwidgets' for "${NS_IMPL_COCOA}".
* lisp/xwidget.el
(xwidget-webkit-cx2-cb, xwidget-webkit-cx3-cb, xwidget-webkit-cx2)
(xwidget-webkit-cx3, xwidget-webkit-mode-map): New webkit session when
window split and key map for 'nextstep'.
(xwidget-log): Fix typo in buffer name.
(xwidget-event-handler): Remove a 'message'.
(xwidget-webkit-callback, xwidget-webkit-url-title-cb): Replace lambda
with defun for GC safe, remember URL for bookmark integration, and
support vector result from javascript.
(xwidget-webkit-mode): No cursor and integration for 'isearch'.
(xwidget-webkit-bookmark-jump-new-session): Add new variable.
(xwidget-webkit-bookmark-make-record): Changes to save URL in
'filename' attribute of a bookmark record, the URL obtained when the
page was loaded, and 'switch-to-buffer' when a bookmark selected.
(xwidget-webkit-isearch-last-length, xwidget-webkit-search-js)
(xwidget-webkit-search-fun-function): Integration for 'isearch'.
(xwidget-webkit-insert-string, xwidget-webkit-insert-string-cb): GC
safe, FIELD is now a list, and fix for a javascript exception.
(xwidget-window-inside-pixel-width)
(xwidget-window-inside-pixel-height)
(xwidget-webkit-adjust-size-to-window)
(xwidget-webkit-new-session): New functions to get window inside width
and height in pixel and insert invisible URL instead of ' '.
(xwidget-webkit-current-url, xwidget-webkit-current-url-cb): GC safe.
(xwidget-webkit-copy-selection-as-kill): GC safe.
* nextstep/templates/Info.plist.in: Add 'NSAppTransportSecurity'.
* src/Makefile.in: Add nsxwidget.o for compilation.
* src/emacs.c (main): Simplify conditional call to 'syms_of_xwidget'.
* src/nsterm.m (ns_draw_glyph_string): Add case for 'XWIDGET_GLYPH'.
(note_mouse_movement mouseMoved): Make it easy to resize window by
dragging mode-line or vertical separator adjacent to large glyph.
* src/nsxwidget.h src/nsxwidget.m: Newly added files, xwidget webkit
backend for macOS Cocoa.
* src/xwidget.c src/xwidget.h (xwidget xwidget_view): Add macOS Cocoa
specific fields with 'NS_IMPL_COCOA', guard GTK specific fields with
'USE_GTK' in the structures, and implement accordingly.
(x_draw_xwidget_glyph_string): Change for top alignment of xwidgets in
a glyph string instead of vertically middle alignment.
---
configure.ac | 35 ++-
lisp/xwidget.el | 235 ++++++++++++++----
nextstep/templates/Info.plist.in | 8 +
src/Makefile.in | 1 +
src/emacs.c | 5 +-
src/nsterm.m | 20 +-
src/nsxwidget.h | 77 ++++++
src/nsxwidget.m | 509 +++++++++++++++++++++++++++++++++++++++
src/xwidget.c | 182 ++++++++++++--
src/xwidget.h | 35 ++-
10 files changed, 1029 insertions(+), 78 deletions(-)
create mode 100644 src/nsxwidget.h
create mode 100644 src/nsxwidget.m
diff --git a/configure.ac b/configure.ac
index 61455a4b0f..3d521fcfb1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -399,7 +399,7 @@ AC_DEFUN
[with_file_notification=$with_features])
OPTION_DEFAULT_OFF([xwidgets],
- [enable use of some gtk widgets in Emacs buffers (requires gtk3)])
+ [enable use of some gtk widgets in Emacs buffers (requires gtk3 or macOS Cocoa)])
## For the times when you want to build Emacs but don't have
## a suitable makeinfo, and can live without the manuals.
@@ -2715,20 +2715,34 @@ AC_DEFUN
dnl Enable xwidgets if GTK3 and WebKitGTK+ are available.
+dnl Enable xwidgets if macOS Cocoa and WebKit framework are available.
HAVE_XWIDGETS=no
XWIDGETS_OBJ=
if test "$with_xwidgets" != "no"; then
- test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none" ||
- AC_MSG_ERROR([xwidgets requested but gtk3 not used.])
+ if test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none"; then
+ WEBKIT_REQUIRED=2.12
+ WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED"
+ EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES])
+ HAVE_XWIDGETS=$HAVE_WEBKIT
+ XWIDGETS_OBJ="xwidget.o"
+ elif test "${NS_IMPL_COCOA}" = "yes"; then
+ dnl FIXME: Check framework WebKit2
+ dnl WEBKIT_REQUIRED=M.m.p
+ WEBKIT_LIBS="-Wl,-framework -Wl,WebKit"
+ WEBKIT_CFLAGS="-D_REENTRANT -I/System/Library/Frameworks/WebKit.framework/Headers"
+ HAVE_WEBKIT="yes"
+ HAVE_XWIDGETS=$HAVE_WEBKIT
+ XWIDGETS_OBJ="xwidget.o"
+ NS_OBJC_OBJ="$NS_OBJC_OBJ nsxwidget.o"
+ dnl Update NS_OBJC_OBJ with added nsxwidget.o
+ AC_SUBST(NS_OBJC_OBJ)
+ else
+ AC_MSG_ERROR([xwidgets requested, it requires GTK3 as X window toolkit or macOS Cocoa as window systeml])
+ fi
- WEBKIT_REQUIRED=2.12
- WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED"
- EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES])
- HAVE_XWIDGETS=$HAVE_WEBKIT
test $HAVE_XWIDGETS = yes ||
- AC_MSG_ERROR([xwidgets requested but WebKitGTK+ not found.])
+ AC_MSG_ERROR([xwidgets requested but WebKitGTK+ or WebKit framework not found.])
- XWIDGETS_OBJ=xwidget.o
AC_DEFINE([HAVE_XWIDGETS], 1, [Define to 1 if you have xwidgets support.])
fi
AC_SUBST(XWIDGETS_OBJ)
@@ -5419,7 +5433,8 @@ AC_DEFUN
Does Emacs directly use zlib? ${HAVE_ZLIB}
Does Emacs have dynamic modules support? ${HAVE_MODULES}
Does Emacs use toolkit scroll bars? ${USE_TOOLKIT_SCROLL_BARS}
- Does Emacs support Xwidgets (requires gtk3)? ${HAVE_XWIDGETS}
+ Does Emacs support Xwidgets? ${HAVE_XWIDGETS}
+ (requires gtk3 or macOS Cocoa)
Does Emacs have threading support in lisp? ${threads_enabled}
"])
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 5e37209cc2..03183c19d5 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -78,6 +78,7 @@ xwidget-at
;;; webkit support
(require 'browse-url)
(require 'image-mode);;for some image-mode alike functionality
+(require 'seq)
;;;###autoload
(defun xwidget-webkit-browse-url (url &optional new-session)
@@ -96,6 +97,38 @@ xwidget-webkit-browse-url
(xwidget-webkit-new-session url)
(xwidget-webkit-goto-url url))))
+;; NOTE: @javascript-callback - prefer defun to lambda.
+;; Lambda seems to be more easily garbage collected in flight from
+;; `xwidget-webkit-execute-script' to its execution via event.
+
+;; @javascript-callback
+(defun xwidget-webkit-cx2-cb (url)
+ "New xwidget webkit session and buffer with URL in split window below."
+ (with-selected-window (split-window-below)
+ (xwidget-webkit-new-session url)))
+
+;; @javascript-callback
+(defun xwidget-webkit-cx3-cb (url)
+ "New xwidget webkit session and buffer with URL in split window right."
+ (with-selected-window (split-window-right)
+ (xwidget-webkit-new-session url)))
+
+(defun xwidget-webkit-cx2 ()
+ "Get the URL of current session, then `xwidget-webkit-cx2-cb'."
+ (interactive)
+ (xwidget-webkit-execute-script
+ (xwidget-webkit-current-session)
+ "document.URL"
+ 'xwidget-webkit-cx2-cb))
+
+(defun xwidget-webkit-cx3 ()
+ "Get the URL of current session, then `xwidget-webkit-cx3-cb'."
+ (interactive)
+ (xwidget-webkit-execute-script
+ (xwidget-webkit-current-session)
+ "document.URL"
+ 'xwidget-webkit-cx3-cb))
+
;;todo.
;; - check that the webkit support is compiled in
(defvar xwidget-webkit-mode-map
@@ -131,6 +164,12 @@ xwidget-webkit-mode-map
;; (define-key map [remap move-end-of-line] 'image-eol)
(define-key map [remap beginning-of-buffer] 'xwidget-webkit-scroll-top)
(define-key map [remap end-of-buffer] 'xwidget-webkit-scroll-bottom)
+
+ ;; For macOS xwidget webkit, we don't support multiple views for a
+ ;; model, instead, create a new session and model behind the scene.
+ (when (memq window-system '(mac ns))
+ (define-key map (kbd "C-x 2") 'xwidget-webkit-cx2)
+ (define-key map (kbd "C-x 3") 'xwidget-webkit-cx3))
map)
"Keymap for `xwidget-webkit-mode'.")
@@ -192,7 +231,7 @@ xwidget-webkit-scroll-bottom
(define-key (current-global-map) [xwidget-event] #'xwidget-event-handler)
(defun xwidget-log (&rest msg)
"Log MSG to a buffer."
- (let ((buf (get-buffer-create " *xwidget-log*")))
+ (let ((buf (get-buffer-create "*xwidget-log*")))
(with-current-buffer buf
(insert (apply #'format msg))
(insert "\n"))))
@@ -208,7 +247,6 @@ xwidget-event-handler
;;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)
@@ -218,16 +256,25 @@ xwidget-webkit-callback
(xwidget-log
"error: callback called for xwidget with dead buffer")
(with-current-buffer (xwidget-buffer xwidget)
+
+ ;; @javascript-callback
+ ;; We do not change selected window due to getting to knowing
+ ;; URL and title. And also do not adjust webkit size to window
+ ;; here, the window can be the mini-buffer window unwantedly.
+ (defun xwidget-webkit-url-title-cb (url-title)
+ "Put URL as text property and change buffer name using TITLE."
+ (let ((url (car url-title))
+ (title (car (cdr url-title))))
+ (xwidget-log "webkit finished loading: '%s' from '%s'" title url)
+ (setq buffer-read-only nil)
+ (put-text-property 2 3 'URL url)
+ (setq buffer-read-only t)
+ (rename-buffer (format "*xwidget webkit: %s *" title))))
+
(cond ((eq xwidget-event-type 'load-changed)
(xwidget-webkit-execute-script
- xwidget "document.title"
- (lambda (title)
- (xwidget-log "webkit finished loading: '%s'" title)
- ;;TODO - check the native/internal scroll
- ;;(xwidget-adjust-size-to-content xwidget)
- (xwidget-webkit-adjust-size-to-window xwidget)
- (rename-buffer (format "*xwidget webkit: %s *" title))))
- (pop-to-buffer (current-buffer)))
+ xwidget "[document.URL, document.title]"
+ 'xwidget-webkit-url-title-cb))
((eq xwidget-event-type 'decide-policy)
(let ((strarg (nth 3 last-input-event)))
(if (string-match ".*#\\(.*\\)" strarg)
@@ -237,25 +284,108 @@ xwidget-webkit-callback
((eq xwidget-event-type 'javascript-callback)
(let ((proc (nth 3 last-input-event))
(arg (nth 4 last-input-event)))
- (funcall proc arg)))
+ ;; Some javascript return vector as result
+ (if (vectorp arg)
+ (funcall proc (seq-into arg 'list))
+ (funcall proc arg))))
(t (xwidget-log "unhandled event:%s" xwidget-event-type))))))
(defvar bookmark-make-record-function)
+(defvar isearch-search-fun-function)
(define-derived-mode xwidget-webkit-mode
special-mode "xwidget-webkit" "Xwidget webkit view mode."
(setq buffer-read-only t)
+ (setq cursor-type nil)
(setq-local bookmark-make-record-function
#'xwidget-webkit-bookmark-make-record)
+ (setq-local isearch-search-fun-function
+ #'xwidget-webkit-search-fun-function)
+ (setq-local isearch-lazy-highlight nil)
;; Keep track of [vh]scroll when switching buffers
(image-mode-setup-winprops))
+;;; Bookmarks integration
+
+(defvar xwidget-webkit-bookmark-jump-new-session nil
+ "Control bookmark jump to use new session or not.
+If non-nil, it will use a new session. Otherwise, it will use
+`xwidget-webkit-last-session'. When you set this variable to
+nil, consider further customization with
+`xwidget-webkit-last-session-buffer'.")
+
+;; We avoid using async `xwidget-webkit-current-url', instead use URL
+;; kept in xwidget webkit as property
(defun xwidget-webkit-bookmark-make-record ()
"Integrate Emacs bookmarks with the webkit xwidget."
(nconc (bookmark-make-record-default t t)
- `((page . ,(xwidget-webkit-current-url))
- (handler . (lambda (bmk) (browse-url
- (bookmark-prop-get bmk 'page)))))))
-
+ `((filename . ,(get-text-property 2 'URL))
+ (handler . (lambda (bmk)
+ (browse-url
+ (bookmark-prop-get bmk 'filename)
+ xwidget-webkit-bookmark-jump-new-session)
+ (switch-to-buffer
+ (xwidget-buffer (xwidget-webkit-last-session))))))))
+
+;;; Search text in page
+
+;; Initialize last search text length variable when isearch starts
+(defvar xwidget-webkit-isearch-last-length 0)
+(add-hook 'isearch-mode-hook
+ (lambda ()
+ (setq xwidget-webkit-isearch-last-length 0)))
+
+;; This is minimal. Regex and incremental search will be nice
+(defvar xwidget-webkit-search-js "
+var xwSearchForward = %s;
+var xwSearchRepeat = %s;
+var xwSearchString = '%s';
+if (window.getSelection() && !window.getSelection().isCollapsed) {
+ if (xwSearchRepeat) {
+ if (xwSearchForward)
+ window.getSelection().collapseToEnd();
+ else
+ window.getSelection().collapseToStart();
+ } else {
+ if (xwSearchForward)
+ window.getSelection().collapseToStart();
+ else {
+ var sel = window.getSelection();
+ window.getSelection().collapse(sel.focusNode, sel.focusOffset + 1);
+ }
+ }
+}
+window.find(xwSearchString, false, !xwSearchForward, true, false, true);
+")
+
+(defun xwidget-webkit-search-fun-function ()
+ "Return the function which perform the search in xwidget webkit."
+ (lambda (string &optional bound noerror count)
+ (ignore bound noerror count)
+ (let ((current-length (length string))
+ search-forward
+ search-repeat)
+ ;; Forward or backward
+ (if (eq isearch-forward nil)
+ (setq search-forward "false")
+ (setq search-forward "true"))
+ ;; Repeat if search string length not changed
+ (if (eq current-length xwidget-webkit-isearch-last-length)
+ (setq search-repeat "true")
+ (setq search-repeat "false"))
+ (setq xwidget-webkit-isearch-last-length current-length)
+ (xwidget-webkit-execute-script
+ (xwidget-webkit-current-session)
+ (format xwidget-webkit-search-js
+ search-forward
+ search-repeat
+ (regexp-quote string)))
+ ;; Unconditionally avoid 'Failing I-search ...'
+ (if (eq isearch-forward nil)
+ (goto-char (point-max))
+ (goto-char (point-min)))
+ )))
+
+;;; xwidget webkit session
(defvar xwidget-webkit-last-session-buffer nil)
@@ -303,7 +433,7 @@ xwidget-webkit-activeelement-js"
"
- "javascript that finds the active element."
+ "Javascript that finds the active element."
;; Yes it's ugly, because:
;; - there is apparently no way to find the active frame other than recursion
;; - the js "for each" construct misbehaved on the "frames" collection
@@ -313,29 +443,35 @@ xwidget-webkit-activeelement-js"
)
(defun xwidget-webkit-insert-string ()
- "Prompt for a string and insert it in the active field in the
+ "Prompt for a string and insert it in the active field in the \
current webkit widget."
;; Read out the string in the field first and provide for edit.
(interactive)
(let ((xww (xwidget-webkit-current-session)))
+
+ ;; @javascript-callback
+ (defun xwidget-webkit-insert-string-cb (field)
+ "Prompt a string for the FIELD and insert in the active input."
+ (let ((str (pcase field
+ (`(,val "text")
+ (read-string "Text: " val))
+ (`(,val "password")
+ (read-passwd "Password: " nil val))
+ (`(,val "textarea")
+ (xwidget-webkit-begin-edit-textarea xww val)))))
+ (xwidget-webkit-execute-script
+ xww
+ (format "findactiveelement(document).value='%s'" str))))
+
(xwidget-webkit-execute-script
xww
(concat xwidget-webkit-activeelement-js "
(function () {
var res = findactiveelement(document);
- return [res.value, res.type];
+ if (res)
+ return [res.value, res.type];
})();")
- (lambda (field)
- (let ((str (pcase field
- (`[,val "text"]
- (read-string "Text: " val))
- (`[,val "password"]
- (read-passwd "Password: " nil val))
- (`[,val "textarea"]
- (xwidget-webkit-begin-edit-textarea xww val)))))
- (xwidget-webkit-execute-script
- xww
- (format "findactiveelement(document).value='%s'" str)))))))
+ 'xwidget-webkit-insert-string-cb)))
(defvar xwidget-xwbl)
(defun xwidget-webkit-begin-edit-textarea (xw text)
@@ -444,11 +580,23 @@ xwidget-webkit-adjust-size-dispatch
(ignore-errors
(recenter-top-bottom)))
+;; Utility functions, wanted in `window.el'
+
+(defun xwidget-window-inside-pixel-width (window)
+ "Return Emacs WINDOW body width in pixel."
+ (let ((edges (window-inside-pixel-edges window)))
+ (- (nth 2 edges) (nth 0 edges))))
+
+(defun xwidget-window-inside-pixel-height (window)
+ "Return Emacs WINDOW body height in pixel."
+ (let ((edges (window-inside-pixel-edges window)))
+ (- (nth 3 edges) (nth 1 edges))))
+
(defun xwidget-webkit-adjust-size-to-window (xwidget &optional window)
"Adjust the size of the webkit XWIDGET to fit the WINDOW."
(xwidget-resize xwidget
- (window-pixel-width window)
- (window-pixel-height window)))
+ (xwidget-window-inside-pixel-width window)
+ (xwidget-window-inside-pixel-height window)))
(defun xwidget-webkit-adjust-size (w h)
"Manually set webkit size to width W, height H."
@@ -487,10 +635,12 @@ xwidget-webkit-new-session
(get-buffer-create bufname)))
;; The xwidget id is stored in a text property, so we need to have
;; at least character in this buffer.
- (insert " ")
+ ;; Insert invisible url, good default for next `g' to browse url.
+ (insert url)
+ (put-text-property 1 (+ 1 (length url)) 'invisible t)
(setq xw (xwidget-insert 1 'webkit bufname
- (window-pixel-width)
- (window-pixel-height)))
+ (xwidget-window-inside-pixel-width (selected-window))
+ (xwidget-window-inside-pixel-height (selected-window))))
(xwidget-put xw 'callback 'xwidget-webkit-callback)
(xwidget-webkit-mode)
(xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
@@ -515,14 +665,18 @@ xwidget-webkit-reload
(xwidget-webkit-execute-script (xwidget-webkit-current-session)
"history.go(0);"))
+;; @javascript-callback
+(defun xwidget-webkit-current-url-cb (result)
+ "Callback for `xwidget-webkit-current-url', message and kill the RESULT."
+ (let ((url (kill-new (or result ""))))
+ (message "url: %s" url)))
+
(defun xwidget-webkit-current-url ()
- "Get the webkit url and place it on the kill-ring."
+ "Get the webkit url and place it on the `kill-ring'."
(interactive)
(xwidget-webkit-execute-script
(xwidget-webkit-current-session)
- "document.URL" (lambda (rv)
- (let ((url (kill-new (or rv ""))))
- (message "url: %s" url)))))
+ "document.URL" 'xwidget-webkit-current-url-cb))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun xwidget-webkit-get-selection (proc)
@@ -533,10 +687,9 @@ xwidget-webkit-get-selection
proc))
(defun xwidget-webkit-copy-selection-as-kill ()
- "Get the webkit selection and put it on the kill-ring."
+ "Get the webkit selection and put it on the `kill-ring'."
(interactive)
- (xwidget-webkit-get-selection (lambda (selection) (kill-new selection))))
-
+ (xwidget-webkit-get-selection #'kill-new))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Xwidget plist management (similar to the process plist functions)
diff --git a/nextstep/templates/Info.plist.in b/nextstep/templates/Info.plist.in
index 5d2eb7def3..54544fb7fa 100644
--- a/nextstep/templates/Info.plist.in
+++ b/nextstep/templates/Info.plist.in
@@ -675,5 +675,13 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
</array>
<key>NSAppleScriptEnabled</key>
<string>YES</string>
+ <!-- For xwidget webkit to browse remote url,
+ but this set no restriction at all. Consult apple's documentation
+ for detail information about `NSApplicationDefinedMask'. -->
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
</dict>
</plist>
diff --git a/src/Makefile.in b/src/Makefile.in
index 9a8c9c85f0..5cadb71c95 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -408,6 +408,7 @@ SOME_MACHINE_OBJECTS =
xterm.o xfns.o xmenu.o xselect.o xrdb.o xsmfns.o fringe.o image.o \
fontset.o dbusbind.o cygw32.o \
nsterm.o nsfns.o nsmenu.o nsselect.o nsimage.o nsfont.o macfont.o \
+ nsxwidget.o \
w32.o w32console.o w32fns.o w32heap.o w32inevt.o w32notify.o \
w32menu.o w32proc.o w32reg.o w32select.o w32term.o w32xfns.o \
w16select.o widget.o xfont.o ftfont.o xftfont.o ftxfont.o gtkutil.o \
diff --git a/src/emacs.c b/src/emacs.c
index 808abcd9aa..8e740381e1 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1532,7 +1532,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
syms_of_xfns ();
syms_of_xmenu ();
syms_of_fontset ();
- syms_of_xwidget ();
syms_of_xsettings ();
#ifdef HAVE_X_SM
syms_of_xsmfns ();
@@ -1605,6 +1604,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
#endif /* HAVE_W32NOTIFY */
#endif /* WINDOWSNT */
+#ifdef HAVE_XWIDGETS
+ syms_of_xwidget ();
+#endif /* HAVE_XWIDGETS */
+
syms_of_threads ();
syms_of_profiler ();
diff --git a/src/nsterm.m b/src/nsterm.m
index 50e06c94d4..3d85fdc0d5 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -48,6 +48,7 @@ Updated by Christian Limpach (chris@nice.ch)
#include "nsterm.h"
#include "systime.h"
#include "character.h"
+#include "xwidget.h"
#include "fontset.h"
#include "composite.h"
#include "ccl.h"
@@ -2429,7 +2430,7 @@ so some key presses (TAB) are swallowed by the system. */
}
static int
-note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y)
+note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y, BOOL dragging)
/* ------------------------------------------------------------------------
Called by EmacsView on mouseMovement events. Passes on
to emacs mainstream code if we moved off of a rect of interest
@@ -2438,17 +2439,24 @@ so some key presses (TAB) are swallowed by the system. */
{
struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
NSRect *r;
+ BOOL force_update = NO;
// NSTRACE ("note_mouse_movement");
dpyinfo->last_mouse_motion_frame = frame;
r = &dpyinfo->last_mouse_glyph;
+ /* If the last rect is too large (ex, xwidget webkit), update at
+ every move, or resizing by dragging modeline or vertical split is
+ very hard to make its way. */
+ if (dragging && (r->size.width > 32 || r->size.height > 32))
+ force_update = YES;
+
/* Note, this doesn't get called for enter/leave, since we don't have a
position. Those are taken care of in the corresponding NSView methods. */
/* has movement gone beyond last rect we were tracking? */
- if (x < r->origin.x || x >= r->origin.x + r->size.width
+ if (force_update || x < r->origin.x || x >= r->origin.x + r->size.width
|| y < r->origin.y || y >= r->origin.y + r->size.height)
{
ns_update_begin (frame);
@@ -4066,6 +4074,10 @@ overwriting cursor (usually when cursor on a tab) */
ns_unfocus (s->f);
break;
+ case XWIDGET_GLYPH:
+ x_draw_xwidget_glyph_string (s);
+ break;
+
case STRETCH_GLYPH:
ns_dumpglyphs_stretch (s);
break;
@@ -6724,6 +6736,7 @@ - (void)mouseMoved: (NSEvent *)e
struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
Lisp_Object frame;
NSPoint pt;
+ BOOL dragging;
NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "[EmacsView mouseMoved:]");
@@ -6766,7 +6779,8 @@ - (void)mouseMoved: (NSEvent *)e
last_mouse_window = window;
}
- if (!note_mouse_movement (emacsframe, pt.x, pt.y))
+ dragging = (e.type == NSEventTypeLeftMouseDragged);
+ if (!note_mouse_movement (emacsframe, pt.x, pt.y, dragging))
help_echo_string = previous_help_echo_string;
XSETFRAME (frame, emacsframe);
diff --git a/src/nsxwidget.h b/src/nsxwidget.h
new file mode 100644
index 0000000000..a617a0d527
--- /dev/null
+++ b/src/nsxwidget.h
@@ -0,0 +1,77 @@
+/* Header for NS Cocoa part of xwidget and webkit widget.
+
+Copyright (C) 2011-2017 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef NSXWIDGET_H_INCLUDED
+#define NSXWIDGET_H_INCLUDED
+
+/* This file can be included from non-objc files through 'xwidget.h'. */
+#ifdef __OBJC__
+#import <AppKit/NSView.h>
+#endif
+
+#include "dispextern.h"
+#include "lisp.h"
+#include "xwidget.h"
+
+/* Functions for xwidget webkit. */
+
+bool nsxwidget_is_web_view (struct xwidget *xw);
+void nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri);
+void nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change);
+void nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
+ Lisp_Object fun);
+
+/* Functions for xwidget model. */
+
+#ifdef __OBJC__
+@interface XwWindow : NSView
+@property struct xwidget *xw;
+@end
+#endif
+
+void nsxwidget_init (struct xwidget *xw);
+void nsxwidget_kill (struct xwidget *xw);
+void nsxwidget_resize (struct xwidget *xw);
+Lisp_Object nsxwidget_get_size (struct xwidget *xw);
+
+/* Functions for xwidget view. */
+
+#ifdef __OBJC__
+@interface XvWindow : NSView
+@property struct xwidget *xw;
+@property struct xwidget_view *xv;
+@end
+#endif
+
+void nsxwidget_init_view (struct xwidget_view *xv,
+ struct xwidget *xww,
+ struct glyph_string *s,
+ int x, int y);
+void nsxwidget_delete_view (struct xwidget_view *xv);
+
+void nsxwidget_show_view (struct xwidget_view *xv);
+void nsxwidget_hide_view (struct xwidget_view *xv);
+void nsxwidget_resize_view (struct xwidget_view *xv,
+ int widget, int height);
+
+void nsxwidget_move_view (struct xwidget_view *xv, int x, int y);
+void nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y);
+void nsxwidget_set_needsdisplay (struct xwidget_view *xv);
+
+#endif /* NSXWIDGET_H_INCLUDED */
diff --git a/src/nsxwidget.m b/src/nsxwidget.m
new file mode 100644
index 0000000000..04f1472c96
--- /dev/null
+++ b/src/nsxwidget.m
@@ -0,0 +1,509 @@
+/* NS Cocoa part implementation of xwidget and webkit widget.
+
+Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2017 Free Software
+Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "lisp.h"
+#include "blockinput.h"
+#include "dispextern.h"
+#include "buffer.h"
+#include "frame.h"
+#include "nsterm.h"
+#include "xwidget.h"
+
+/* Defined in 'xwidget.c'. */
+void store_xwidget_event_string (struct xwidget *xw,
+ const char *eventname,
+ const char *eventstr);
+
+void store_xwidget_js_callback_event (struct xwidget *xw,
+ Lisp_Object proc,
+ Lisp_Object argument);
+
+#import <AppKit/AppKit.h>
+#import <WebKit/WebKit.h>
+
+/* Thoughts on NS Cocoa xwidget and webkit2:
+
+ Webkit2 process architecture seems to be very hostile for offscreen
+ rendering techniques, which is used by GTK xwiget implementation;
+ Specifically NSView level view sharing / copying is not working.
+
+ *** So only one view can be associcated with a model. ***
+
+ With this decision, implementation is plain and can expect best out
+ of webkit2's rationale. But process and session structures will
+ diverge from GTK xwiget. Though, cosmetically similar usages can
+ be presented and will be preferred, if agreeable.
+
+ For other widget types, OSR seems possible, but will not care for a
+ while. */
+
+/* Xwidget webkit. */
+
+@interface XwWebView : WKWebView
+<WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>
+@property struct xwidget *xw;
+@end
+@implementation XwWebView : WKWebView
+
+- (id)initWithFrame:(CGRect)frame
+ configuration:(WKWebViewConfiguration *)configuration
+ xwidget:(struct xwidget *)xw
+{
+ /* Script controller to add script message handler and user script. */
+ WKUserContentController *scriptor = [[WKUserContentController alloc] init];
+ configuration.userContentController = scriptor;
+
+ self = [super initWithFrame:frame configuration:configuration];
+ if (self)
+ {
+ self.xw = xw;
+ self.navigationDelegate = self;
+ self.UIDelegate = self;
+ self.customUserAgent =
+ @"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)"
+ @" AppleWebKit/603.3.8 (KHTML, like Gecko)"
+ @" Version/11.0.1 Safari/603.3.8";
+ [scriptor addScriptMessageHandler:self name:@"keyDown"];
+ [scriptor addUserScript:[[WKUserScript alloc]
+ initWithSource:xwScript
+ injectionTime:
+ WKUserScriptInjectionTimeAtDocumentEnd
+ forMainFrameOnly:NO]];
+ }
+ return self;
+}
+
+#if 0
+/* Non ARC - just to check lifecycle. */
+- (void)dealloc
+{
+ NSLog (@"XwWebView dealloc");
+ [super dealloc];
+}
+#endif
+
+- (void)webView:(WKWebView *)webView
+didFinishNavigation:(WKNavigation *)navigation
+{
+ store_xwidget_event_string (self.xw, "load-changed", "");
+}
+
+- (void)webView:(WKWebView *)webView
+decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
+decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ switch (navigationAction.navigationType) {
+ case WKNavigationTypeLinkActivated:
+ decisionHandler (WKNavigationActionPolicyAllow);
+ break;
+ default:
+ // decisionHandler (WKNavigationActionPolicyCancel);
+ decisionHandler (WKNavigationActionPolicyAllow);
+ break;
+ }
+}
+
+- (void)webView:(WKWebView *)webView
+decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
+decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
+{
+ if (!navigationResponse.canShowMIMEType)
+ {
+ /* TODO: download using NSURLxxx? */
+ }
+ decisionHandler (WKNavigationResponsePolicyAllow);
+}
+
+/* No additional new webview or emacs window will be created
+ for <a ... target="_blank">. */
+- (WKWebView *)webView:(WKWebView *)webView
+createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
+ forNavigationAction:(WKNavigationAction *)navigationAction
+ windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+ if (!navigationAction.targetFrame.isMainFrame)
+ [webView loadRequest:navigationAction.request];
+ return nil;
+}
+
+/* By forwarding mouse events to emacs view (frame)
+ - Mouse click in webview selects the window contains the webview.
+ - Correct mouse hand/arrow/I-beam is displayed (TODO: not perfect yet).
+*/
+
+- (void)mouseDown:(NSEvent *)event
+{
+ [self.xw->xv->emacswindow mouseDown:event];
+ [super mouseDown:event];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+ [self.xw->xv->emacswindow mouseUp:event];
+ [super mouseUp:event];
+}
+
+/* Basically we want keyboard events handled by emacs unless an input
+ element has focus. Especially, while incremental search, we set
+ emacs as first responder to avoid focus held in an input element
+ with matching text. */
+
+- (void)keyDown:(NSEvent *)event
+{
+ Lisp_Object var = Fintern (build_string ("isearch-mode"), Qnil);
+ Lisp_Object val = buffer_local_value (var, Fcurrent_buffer ());
+ if (!EQ (val, Qunbound) && !EQ (val, Qnil))
+ {
+ [self.window makeFirstResponder:self.xw->xv->emacswindow];
+ [self.xw->xv->emacswindow keyDown:event];
+ return;
+ }
+
+ [self evaluateJavaScript:@"xwHasFocus()"
+ completionHandler:^(id result, NSError *error) {
+ if (error)
+ NSLog (@"xwHasFocus: %@", error.localizedDescription);
+ else if (result)
+ {
+ NSNumber *hasFocus = result; /* __NSCFBoolean */
+ if (!hasFocus.boolValue)
+ [self.xw->xv->emacswindow keyDown:event];
+ else
+ [super keyDown:event];
+ }
+ }];
+}
+
+- (void)interpretKeyEvents:(NSArray<NSEvent *> *)eventArray
+{
+ /* We should do nothing and do not forward (default implementation
+ if we not override here) to let emacs collect key events and ask
+ interpretKeyEvents to its superclass. */
+}
+
+static NSString *xwScript;
++ (void)initialize
+{
+ /* Find out if an input element has focus.
+ Message to script message handler when 'C-g' key down. */
+ if (!xwScript)
+ xwScript =
+ @"function xwHasFocus() {"
+ @" var ae = document.activeElement;"
+ @" if (ae) {"
+ @" var name = ae.nodeName;"
+ @" return name == 'INPUT' || name == 'TEXTAREA';"
+ @" } else {"
+ @" return false;"
+ @" }"
+ @"}"
+ @"function xwKeyDown(event) {"
+ @" if (event.ctrlKey && event.key == 'g') {"
+ @" window.webkit.messageHandlers.keyDown.postMessage('C-g');"
+ @" }"
+ @"}"
+ @"document.addEventListener('keydown', xwKeyDown);"
+ ;
+}
+
+/* Confirming to WKScriptMessageHandler, listens concerning keyDown in
+ webkit. Currently 'C-g'. */
+- (void)userContentController:(WKUserContentController *)userContentController
+ didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ if ([message.body isEqualToString:@"C-g"])
+ {
+ /* Just give up focus, no relay "C-g" to emacs, another "C-g"
+ follows will be handled by emacs. */
+ [self.window makeFirstResponder:self.xw->xv->emacswindow];
+ }
+}
+
+@end
+
+/* Xwidget webkit commands. */
+
+bool
+nsxwidget_is_web_view (struct xwidget *xw)
+{
+ return xw->xwWidget != NULL &&
+ [xw->xwWidget isKindOfClass:WKWebView.class];
+}
+
+/* @Note ATS - Need application transport security in 'Info.plist' or
+ remote pages will not loaded. */
+void
+nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri)
+{
+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+ NSString *urlString = [NSString stringWithUTF8String:uri];
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
+ [xwWebView loadRequest:urlRequest];
+}
+
+void
+nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change)
+{
+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+ xwWebView.magnification += zoom_change;
+ /* TODO: setMagnification:centeredAtPoint. */
+}
+
+/* Build lisp string */
+static Lisp_Object
+build_string_with_nsstr (NSString *nsstr)
+{
+ const char *utfstr = [nsstr UTF8String];
+ NSUInteger bytes = [nsstr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ return make_string (utfstr, bytes);
+}
+
+/* Recursively convert an objc native type JavaScript value to a Lisp
+ value. Mostly copied from GTK xwidget 'webkit_js_to_lisp'. */
+static Lisp_Object
+js_to_lisp (id value)
+{
+ if (value == nil || [value isKindOfClass:NSNull.class])
+ return Qnil;
+ else if ([value isKindOfClass:NSString.class])
+ return build_string_with_nsstr ((NSString *) value);
+ else if ([value isKindOfClass:NSNumber.class])
+ {
+ NSNumber *nsnum = (NSNumber *) value;
+ char type = nsnum.objCType[0];
+ if (type == 'c') /* __NSCFBoolean has type character 'c'. */
+ return nsnum.boolValue? Qt : Qnil;
+ else
+ {
+ if (type == 'i' || type == 'l')
+ return make_number (nsnum.longValue);
+ else if (type == 'f' || type == 'd')
+ return make_float (nsnum.doubleValue);
+ /* else fall through. */
+ }
+ }
+ else if ([value isKindOfClass:NSArray.class])
+ {
+ NSArray *nsarr = (NSArray *) value;
+ EMACS_INT n = nsarr.count;
+ Lisp_Object obj;
+ struct Lisp_Vector *p = allocate_vector (n);
+
+ for (ptrdiff_t i = 0; i < n; ++i)
+ p->contents[i] = js_to_lisp ([nsarr objectAtIndex:i]);
+ XSETVECTOR (obj, p);
+ return obj;
+ }
+ else if ([value isKindOfClass:NSDictionary.class])
+ {
+ NSDictionary *nsdict = (NSDictionary *) value;
+ NSArray *keys = nsdict.allKeys;
+ ptrdiff_t n = keys.count;
+ Lisp_Object obj;
+ struct Lisp_Vector *p = allocate_vector (n);
+
+ for (ptrdiff_t i = 0; i < n; ++i)
+ {
+ NSString *prop_key = (NSString *) [keys objectAtIndex:i];
+ id prop_value = [nsdict valueForKey:prop_key];
+ p->contents[i] = Fcons (build_string_with_nsstr (prop_key),
+ js_to_lisp (prop_value));
+ }
+ XSETVECTOR (obj, p);
+ return obj;
+ }
+ NSLog (@"Unhandled type in javascript result");
+ return Qnil;
+}
+
+void
+nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
+ Lisp_Object fun)
+{
+ NSString *javascriptString = [NSString stringWithUTF8String:script];
+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+
+ [xwWebView evaluateJavaScript:javascriptString
+ completionHandler:^(id result, NSError *error) {
+ if (error)
+ {
+ NSLog (@"evaluateJavaScript error : %@", error.localizedDescription);
+ NSLog (@"error script=%@", javascriptString);
+ }
+ else if (result && FUNCTIONP (fun))
+ {
+ // NSLog (@"result=%@, type=%@", result, [result class]);
+ Lisp_Object lisp_value = js_to_lisp (result);
+ store_xwidget_js_callback_event (xw, fun, lisp_value);
+ }
+ }];
+}
+
+/* Window containing an xwidget. */
+
+@implementation XwWindow
+- (BOOL)isFlipped { return YES; }
+@end
+
+/* Xwidget model, macOS Cocoa part. */
+
+void
+nsxwidget_init(struct xwidget *xw)
+{
+ block_input ();
+ NSRect rect = NSMakeRect (0, 0, xw->width, xw->height);
+ xw->xwWidget = [[XwWebView alloc]
+ initWithFrame:rect
+ configuration:[[WKWebViewConfiguration alloc] init]
+ xwidget:xw];
+ xw->xwWindow = [[XwWindow alloc]
+ initWithFrame:rect];
+ [xw->xwWindow addSubview:xw->xwWidget];
+ xw->xv = NULL; /* for 1 to 1 relationship of webkit2. */
+ unblock_input ();
+}
+
+void
+nsxwidget_kill (struct xwidget *xw)
+{
+ if (xw)
+ {
+ WKUserContentController *scriptor =
+ ((XwWebView *) xw->xwWidget).configuration.userContentController;
+ [scriptor removeAllUserScripts];
+ [scriptor removeScriptMessageHandlerForName:@"keyDown"];
+ [scriptor release];
+ if (xw->xv)
+ xw->xv->model = Qnil; /* Make sure related view stale. */
+ [xw->xwWidget removeFromSuperviewWithoutNeedingDisplay];
+ [xw->xwWidget release];
+ [xw->xwWindow removeFromSuperviewWithoutNeedingDisplay];
+ [xw->xwWindow release];
+ xw->xwWidget = nil;
+ }
+}
+
+void
+nsxwidget_resize (struct xwidget *xw)
+{
+ if (xw->xwWidget)
+ {
+ [xw->xwWindow setFrameSize:NSMakeSize(xw->width, xw->height)];
+ [xw->xwWidget setFrameSize:NSMakeSize(xw->width, xw->height)];
+ }
+}
+
+Lisp_Object
+nsxwidget_get_size (struct xwidget *xw)
+{
+ return list2 (make_number (xw->xwWidget.frame.size.width),
+ make_number (xw->xwWidget.frame.size.height));
+}
+
+/* Xwidget view, macOS Cocoa part. */
+
+@implementation XvWindow : NSView
+- (BOOL)isFlipped { return YES; }
+@end
+
+void
+nsxwidget_init_view (struct xwidget_view *xv,
+ struct xwidget *xw,
+ struct glyph_string *s,
+ int x, int y)
+{
+ /* 'x_draw_xwidget_glyph_string' will calculate correct position and
+ size of clip to draw in emacs buffer window. Thus, just begin at
+ origin with no crop. */
+ xv->x = x;
+ xv->y = y;
+ xv->clip_left = 0;
+ xv->clip_right = xw->width;
+ xv->clip_top = 0;
+ xv->clip_bottom = xw->height;
+
+ xv->xvWindow = [[XvWindow alloc]
+ initWithFrame:NSMakeRect (x, y, xw->width, xw->height)];
+ xv->xvWindow.xw = xw;
+ xv->xvWindow.xv = xv;
+
+ xw->xv = xv; /* For 1 to 1 relationship of webkit2. */
+ [xv->xvWindow addSubview:xw->xwWindow];
+
+ xv->emacswindow = FRAME_NS_VIEW (s->f);
+ [xv->emacswindow addSubview:xv->xvWindow];
+}
+
+void
+nsxwidget_delete_view (struct xwidget_view *xv)
+{
+ if (!EQ (xv->model, Qnil))
+ {
+ struct xwidget *xw = XXWIDGET (xv->model);
+ [xw->xwWindow removeFromSuperviewWithoutNeedingDisplay];
+ xw->xv = NULL; /* Now model has no view. */
+ }
+ [xv->xvWindow removeFromSuperviewWithoutNeedingDisplay];
+ [xv->xvWindow release];
+}
+
+void
+nsxwidget_show_view (struct xwidget_view *xv)
+{
+ xv->hidden = NO;
+ [xv->xvWindow setFrameOrigin:NSMakePoint(xv->x + xv->clip_left,
+ xv->y + xv->clip_top)];
+}
+
+void
+nsxwidget_hide_view (struct xwidget_view *xv)
+{
+ xv->hidden = YES;
+ [xv->xvWindow setFrameOrigin:NSMakePoint(10000, 10000)];
+}
+
+void
+nsxwidget_resize_view (struct xwidget_view *xv, int width, int height)
+{
+ [xv->xvWindow setFrameSize:NSMakeSize(width, height)];
+}
+
+void
+nsxwidget_move_view (struct xwidget_view *xv, int x, int y)
+{
+ [xv->xvWindow setFrameOrigin:NSMakePoint (x, y)];
+}
+
+/* Move model window in container (view window). */
+void
+nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y)
+{
+ struct xwidget *xww = xv->xvWindow.xw;
+ [xww->xwWindow setFrameOrigin:NSMakePoint (x, y)];
+}
+
+void
+nsxwidget_set_needsdisplay (struct xwidget_view *xv)
+{
+ xv->xvWindow.needsDisplay = YES;
+}
diff --git a/src/xwidget.c b/src/xwidget.c
index a67dc0ecf4..17e1e6428a 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -18,17 +18,26 @@ You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <config.h>
+#include <stdio.h> /* FIXME: Emacs error? message? instead of printf. */
#include "xwidget.h"
#include "lisp.h"
#include "blockinput.h"
+#include "dispextern.h"
#include "frame.h"
#include "keyboard.h"
#include "gtkutil.h"
+#include "termhooks.h"
+#include "window.h"
+/* Include xwidget bottom end headers. */
+#if defined (USE_GTK)
#include <webkit2/webkit2.h>
#include <JavaScriptCore/JavaScript.h>
+#elif defined (NS_IMPL_COCOA)
+#include "nsxwidget.h"
+#endif
static struct xwidget *
allocate_xwidget (void)
@@ -48,6 +57,7 @@ allocate_xwidget_view (void)
static struct xwidget_view *xwidget_view_lookup (struct xwidget *,
struct window *);
+#if defined (USE_GTK)
static void webkit_view_load_changed_cb (WebKitWebView *,
WebKitLoadEvent,
gpointer);
@@ -61,6 +71,7 @@ webkit_decide_policy_cb (WebKitWebView *,
WebKitPolicyDecision *,
WebKitPolicyDecisionType,
gpointer);
+#endif
DEFUN ("make-xwidget",
@@ -92,10 +103,11 @@ Returns the newly constructed xwidget, or nil if construction fails. */)
xw->kill_without_query = false;
XSETXWIDGET (val, xw);
Vxwidget_list = Fcons (val, Vxwidget_list);
- xw->widgetwindow_osr = NULL;
- xw->widget_osr = NULL;
xw->plist = Qnil;
+#if defined (USE_GTK)
+ xw->widgetwindow_osr = NULL;
+ xw->widget_osr = NULL;
if (EQ (xw->type, Qwebkit))
{
block_input ();
@@ -150,6 +162,9 @@ Returns the newly constructed xwidget, or nil if construction fails. */)
unblock_input ();
}
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_init (xw);
+#endif
return val;
}
@@ -185,6 +200,7 @@ xwidget_hidden (struct xwidget_view *xv)
return xv->hidden;
}
+#if defined (USE_GTK)
static void
xwidget_show_view (struct xwidget_view *xv)
{
@@ -223,8 +239,9 @@ offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
return FALSE;
}
+#endif /* USE_GTK */
-static void
+void
store_xwidget_event_string (struct xwidget *xw, const char *eventname,
const char *eventstr)
{
@@ -238,7 +255,7 @@ store_xwidget_event_string (struct xwidget *xw, const char *eventname,
kbd_buffer_store_event (&event);
}
-static void
+void
store_xwidget_js_callback_event (struct xwidget *xw,
Lisp_Object proc,
Lisp_Object argument)
@@ -254,6 +271,7 @@ store_xwidget_js_callback_event (struct xwidget *xw,
}
+#if defined (USE_GTK)
void
webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
WebKitLoadEvent load_event,
@@ -389,9 +407,8 @@ webkit_javascript_finished_cb (GObject *webview,
/* Register an xwidget event here, which then runs the callback.
This ensures that the callback runs in sync with the Emacs
event loop. */
- /* FIXME: This might lead to disaster if LISP_CALLBACK’s object
- was garbage collected before now. See the FIXME in
- Fxwidget_webkit_execute_script. */
+ /* LISP_CALLBACK must not be garbage collected up to here. See
+ comments in Fxwidget_webkit_execute_script. */
store_xwidget_js_callback_event (xw, XIL ((intptr_t) lisp_callback),
lisp_value);
}
@@ -500,6 +517,7 @@ xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
gtk_widget_get_window (xv->widget));
return FALSE;
}
+#endif /* USE_GTK */
/* Initializes and does initial placement of an xwidget view on screen. */
@@ -517,6 +535,7 @@ xwidget_init_view (struct xwidget *xww,
XSETWINDOW (xv->w, s->w);
XSETXWIDGET (xv->model, xww);
+#if defined (USE_GTK)
if (EQ (xww->type, Qwebkit))
{
xv->widget = gtk_drawing_area_new ();
@@ -574,6 +593,9 @@ xwidget_init_view (struct xwidget *xww,
xv->x = x;
xv->y = y;
gtk_widget_show_all (xv->widgetwindow);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_init_view (xv, xww, s, x, y);
+#endif
return xv;
}
@@ -586,24 +608,59 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
initialization. */
struct xwidget *xww = s->xwidget;
struct xwidget_view *xv = xwidget_view_lookup (xww, s->w);
+ int text_area_x, text_area_y, text_area_width, text_area_height;
int clip_right;
int clip_bottom;
int clip_top;
int clip_left;
int x = s->x;
- int y = s->y + (s->height / 2) - (xww->height / 2);
+ int y = s->y;
/* Do initialization here in the display loop because there is no
other time to know things like window placement etc. Do not
create a new view if we have found one that is usable. */
+#if defined (USE_GTK)
if (!xv)
xv = xwidget_init_view (xww, s, x, y);
-
- int text_area_x, text_area_y, text_area_width, text_area_height;
+#elif defined (NS_IMPL_COCOA)
+ if (!xv)
+ {
+ /* Enforce 1 to 1, model and view for macOS Cocoa webkit2. */
+ if (xww->xv)
+ {
+ if (xwidget_hidden (xww->xv))
+ {
+ Lisp_Object xvl;
+ XSETXWIDGET_VIEW (xvl, xww->xv);
+ Fdelete_xwidget_view (xvl);
+ }
+ else
+ {
+ message ("You can't share an xwidget (webkit2) among windows.");
+ return;
+ }
+ }
+ xv = xwidget_init_view (xww, s, x, y);
+ }
+#endif
window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
&text_area_width, &text_area_height);
+
+ /* Resize xwidget webkit if its container window size is changed in
+ some ways, for example, a buffer became hidden in small split
+ window, then it can appear front in merged whole window. */
+ if (EQ (xww->type, Qwebkit)
+ && (xww->width != text_area_width || xww->height != text_area_height))
+ {
+ Lisp_Object xwl;
+ XSETXWIDGET (xwl, xww);
+ Fxwidget_resize (xwl,
+ make_number (text_area_width),
+ make_number (text_area_height));
+ }
+
clip_left = max (0, text_area_x - x);
clip_right = max (clip_left,
min (xww->width, text_area_x + text_area_width - x));
@@ -626,8 +683,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
/* Has it moved? */
if (moved)
- gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
- xv->widgetwindow, x + clip_left, y + clip_top);
+ {
+#if defined (USE_GTK)
+ gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
+ xv->widgetwindow, x + clip_left, y + clip_top);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_move_view (xv, x + clip_left, y + clip_top);
+#endif
+ }
/* Clip the widget window if some parts happen to be outside
drawable area. An Emacs window is not a gtk window. A gtk window
@@ -638,10 +701,16 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
|| xv->clip_bottom != clip_bottom
|| xv->clip_top != clip_top || xv->clip_left != clip_left)
{
+#if defined (USE_GTK)
gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
clip_bottom - clip_top);
gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
-clip_top);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_resize_view (xv, clip_right - clip_left,
+ clip_bottom - clip_top);
+ nsxwidget_move_widget_in_view (xv, -clip_left, -clip_top);
+#endif
xv->clip_right = clip_right;
xv->clip_bottom = clip_bottom;
@@ -655,18 +724,32 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
xwidgets background. It's just a visual glitch though. */
if (!xwidget_hidden (xv))
{
+#if defined (USE_GTK)
gtk_widget_queue_draw (xv->widgetwindow);
gtk_widget_queue_draw (xv->widget);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_set_needsdisplay (xv);
+#endif
}
}
-/* Macro that checks WEBKIT_IS_WEB_VIEW (xw->widget_osr) first. */
+static bool
+xwidget_is_web_view (struct xwidget *xw)
+{
+#if defined (USE_GTK)
+ return xw->widget_osr != NULL && WEBKIT_IS_WEB_VIEW (xw->widget_osr);
+#elif defined (NS_IMPL_COCOA)
+ return nsxwidget_is_web_view (xw);
+#endif
+}
+
+/* Macro that checks xwidget hold webkit web view first. */
#define WEBKIT_FN_INIT() \
CHECK_XWIDGET (xwidget); \
struct xwidget *xw = XXWIDGET (xwidget); \
- if (!xw->widget_osr || !WEBKIT_IS_WEB_VIEW (xw->widget_osr)) \
+ if (!xwidget_is_web_view (xw)) \
{ \
- printf ("ERROR xw->widget_osr does not hold a webkit instance\n"); \
+ printf ("ERROR xwidget does not hold a webkit instance\n"); \
return Qnil; \
}
@@ -678,7 +761,11 @@ DEFUN ("xwidget-webkit-goto-uri",
{
WEBKIT_FN_INIT ();
CHECK_STRING (uri);
+#if defined (USE_GTK)
webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), SSDATA (uri));
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_webkit_goto_uri (xw, SSDATA (uri));
+#endif
return Qnil;
}
@@ -693,10 +780,14 @@ referenced by XWIDGET. */)
if (FLOATP (factor))
{
double zoom_change = XFLOAT_DATA (factor);
+#if defined (USE_GTK)
webkit_web_view_set_zoom_level
(WEBKIT_WEB_VIEW (xw->widget_osr),
webkit_web_view_get_zoom_level
(WEBKIT_WEB_VIEW (xw->widget_osr)) + zoom_change);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_webkit_zoom (xw, zoom_change);
+#endif
}
return Qnil;
}
@@ -712,17 +803,18 @@ argument procedure FUN.*/)
{
WEBKIT_FN_INIT ();
CHECK_STRING (script);
- if (!NILP (fun) && !FUNCTIONP (fun))
+ /* FUN will not be garbage collected if it is defined with `defun'
+ instead of `lambda'. If it is garbage collected even though it
+ is `defun', we can counter by pinning the FUN's symbol. */
+ if (!NILP (fun) && !SYMBOLP (fun) && !NILP (Ffboundp (fun)))
wrong_type_argument (Qinvalid_function, fun);
+#if defined (USE_GTK)
GAsyncReadyCallback callback
= FUNCTIONP (fun) ? webkit_javascript_finished_cb : NULL;
/* FIXME: The following hack assumes USE_LSB_TAG. */
verify (USE_LSB_TAG);
- /* FIXME: This hack might lead to disaster if FUN is garbage
- collected before store_xwidget_js_callback_event makes it visible
- to Lisp again. See the FIXME in webkit_javascript_finished_cb. */
gpointer callback_arg = (gpointer) (intptr_t) XLI (fun);
/* JavaScript execution happens asynchronously. If an elisp
@@ -732,6 +824,9 @@ argument procedure FUN.*/)
SSDATA (script),
NULL, /* cancelable */
callback, callback_arg);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_webkit_execute_script (xw, SSDATA (script), fun);
+#endif
return Qnil;
}
@@ -750,6 +845,7 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
xw->height = h;
/* If there is an offscreen widget resize it first. */
+#if defined (USE_GTK)
if (xw->widget_osr)
{
gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
@@ -758,6 +854,9 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
xw->height);
}
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_resize (xw);
+#endif
for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail); tail = XCDR (tail))
{
@@ -765,8 +864,14 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
{
struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
if (XXWIDGET (xv->model) == xw)
+ {
+#if defined (USE_GTK)
gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
xw->height);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_resize_view(xv, xw->width, xw->height);
+#endif
+ }
}
}
@@ -785,10 +890,14 @@ Emacs allocated area accordingly. */)
(Lisp_Object xwidget)
{
CHECK_XWIDGET (xwidget);
+#if defined (USE_GTK)
GtkRequisition requisition;
gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition);
return list2 (make_number (requisition.width),
make_number (requisition.height));
+#elif defined (NS_IMPL_COCOA)
+ return nsxwidget_get_size(XXWIDGET (xwidget));
+#endif
}
DEFUN ("xwidgetp",
@@ -865,14 +974,19 @@ DEFUN ("delete-xwidget-view",
{
CHECK_XWIDGET_VIEW (xwidget_view);
struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
- gtk_widget_destroy (xv->widgetwindow);
Vxwidget_view_list = Fdelq (xwidget_view, Vxwidget_view_list);
+#if defined (USE_GTK)
+ gtk_widget_destroy (xv->widgetwindow);
/* xv->model still has signals pointing to the view. There can be
several views. Find the matching signals and delete them all. */
g_signal_handlers_disconnect_matched (XXWIDGET (xv->model)->widgetwindow_osr,
G_SIGNAL_MATCH_DATA,
0, 0, 0, 0,
xv->widget);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_delete_view (xv);
+#endif
+
return Qnil;
}
@@ -1151,11 +1265,19 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
xwidget_end_redisplay (w->current_matrix); */
struct xwidget_view *xv
= xwidget_view_lookup (glyph->u.xwidget, w);
+#if defined (USE_GTK)
/* FIXME: Is it safe to assume xwidget_view_lookup
always succeeds here? If so, this comment can be removed.
If not, the code probably needs fixing. */
eassume (xv);
xwidget_touch (xv);
+#elif defined (NS_IMPL_COCOA)
+ /* In NS xwidget, xv can be NULL for the second or
+ later views for a model, the result of 1 to 1
+ model view relation enforcement. */
+ if (xv)
+ xwidget_touch (xv);
+#endif
}
}
}
@@ -1172,9 +1294,21 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
if (XWINDOW (xv->w) == w)
{
if (xwidget_touched (xv))
- xwidget_show_view (xv);
+ {
+#if defined (USE_GTK)
+ xwidget_show_view (xv);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_show_view (xv);
+#endif
+ }
else
- xwidget_hide_view (xv);
+ {
+#if defined (USE_GTK)
+ xwidget_hide_view (xv);
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_hide_view (xv);
+#endif
+ }
}
}
}
@@ -1193,11 +1327,15 @@ kill_buffer_xwidgets (Lisp_Object buffer)
{
CHECK_XWIDGET (xwidget);
struct xwidget *xw = XXWIDGET (xwidget);
+#if defined (USE_GTK)
if (xw->widget_osr && xw->widgetwindow_osr)
{
gtk_widget_destroy (xw->widget_osr);
gtk_widget_destroy (xw->widgetwindow_osr);
}
+#elif defined (NS_IMPL_COCOA)
+ nsxwidget_kill (xw);
+#endif
}
}
}
diff --git a/src/xwidget.h b/src/xwidget.h
index 02a0453dab..eee9654a6f 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -29,7 +29,13 @@ struct xwidget_view;
struct window;
#ifdef HAVE_XWIDGETS
-# include <gtk/gtk.h>
+
+#if defined (USE_GTK)
+#include <gtk/gtk.h>
+#elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
+#import <AppKit/NSView.h>
+#import "nsxwidget.h"
+#endif
struct xwidget
{
@@ -52,9 +58,25 @@ struct xwidget
int height;
int width;
+#if defined (USE_GTK)
/* For offscreen widgets, unused if not osr. */
GtkWidget *widget_osr;
GtkWidget *widgetwindow_osr;
+#elif defined (NS_IMPL_COCOA)
+# ifdef __OBJC__
+ /* For offscreen widgets, unused if not osr. */
+ NSView *xwWidget;
+ XwWindow *xwWindow;
+
+ /* Used only for xwidget types (such as webkit2) enforcing 1 to 1
+ relationship between model and view. */
+ struct xwidget_view *xv;
+# else
+ void *xwWidget;
+ void *xwWindow;
+ struct xwidget_view *xv;
+# endif
+#endif
/* Kill silently if Emacs is exited. */
bool_bf kill_without_query : 1;
@@ -74,9 +96,20 @@ struct xwidget_view
/* The "live" instance isn't drawn. */
bool hidden;
+#if defined (USE_GTK)
GtkWidget *widget;
GtkWidget *widgetwindow;
GtkWidget *emacswindow;
+#elif defined (NS_IMPL_COCOA)
+# ifdef __OBJC__
+ XvWindow *xvWindow;
+ NSView *emacswindow;
+# else
+ void *xvWindow;
+ void *emacswindow;
+# endif
+#endif
+
int x;
int y;
int clip_right;
--
2.15.0
next prev parent reply other threads:[~2017-12-05 7:55 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-12-04 16:44 bug#29565: [PATCH] Support xwidget webkit for macOS X Jaesup Kwak
2017-12-04 20:59 ` Alan Third
2017-12-05 6:01 ` Jaesup Kwak
2017-12-05 7:55 ` Jaesup Kwak [this message]
2017-12-05 20:00 ` Alan Third
2017-12-06 5:59 ` Jaesup Kwak
2017-12-06 6:20 ` Jaesup Kwak
2017-12-13 11:15 ` Jaesup Kwak
2017-12-13 11:27 ` Jaesup Kwak
2017-12-13 16:13 ` Jaesup Kwak
2017-12-15 2:01 ` Jaesup Kwak
2017-12-15 2:27 ` Jaesup Kwak
2017-12-15 16:06 ` Jaesup Kwak
2017-12-20 2:39 ` bug#29565: [PATCH] Fix compile failure for GTK xwidget (Bug#29565) Jaesup Kwak
2017-12-20 8:25 ` bug#29565: [PATCH] Enable plugins for ns xwidget webkit (Bug#29565) Jaesup Kwak
2017-12-21 4:12 ` bug#29565: [PATCH] Support file download and upload (Bug#29565) Jaesup Kwak
2018-03-30 11:48 ` bug#29565: [PATCH] Support xwidget webkit for macOS X Alan Third
2018-03-30 12:19 ` Jaesup Kwak
2019-09-28 23:52 ` Stefan Kangas
2020-08-10 13:55 ` Lars Ingebrigtsen
2020-08-10 19:06 ` Alan Third
2020-08-11 11:04 ` Lars Ingebrigtsen
2020-08-11 15:22 ` Eli Zaretskii
2020-08-11 16:26 ` Lars Ingebrigtsen
2020-08-11 18:28 ` Eli Zaretskii
2020-08-11 19:26 ` Lars Ingebrigtsen
2020-08-11 19:33 ` Eli Zaretskii
2020-08-12 10:44 ` Lars Ingebrigtsen
2020-08-11 19:56 ` Alan Third
2020-08-11 19:58 ` Lars Ingebrigtsen
2020-08-11 20:18 ` Alan Third
2020-08-11 20:29 ` Lars Ingebrigtsen
2020-08-11 20:35 ` Alan Third
2020-08-11 20:50 ` Lars Ingebrigtsen
2020-08-12 3:46 ` Unknown
2020-08-12 10:05 ` Lars Ingebrigtsen
2020-08-12 16:34 ` Alan Third
2020-08-12 2:26 ` Richard Stallman
2020-08-12 4:13 ` Eli Zaretskii
2020-08-12 4:27 ` Eli Zaretskii
2020-08-13 3:43 ` Richard Stallman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CAADX8xKL+VJQRR0q2XqGXt2ORSes9xrCKjmzEvmoGagEUdZXLg@mail.gmail.com \
--to=veshboo@gmail.com \
--cc=29565@debbugs.gnu.org \
--cc=alan@idiocy.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.