diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi index 7f91e1c188..1207ab5e9a 100644 --- a/doc/emacs/misc.texi +++ b/doc/emacs/misc.texi @@ -2953,6 +2953,34 @@ Embedded WebKit Widgets reloading it. Type @w{@kbd{C-h b}} in that buffer to see the key bindings. +@findex xwidget-webkit-edit-mode +@cindex xwidget-webkit-edit-mode + By default, typing a self-inserting character inside an xwidget +webkit buffer will do nothing, or trigger some special action. To +make those characters and other common editing keys insert themselves +when pressed, you can enable @code{xwidget-webkit-edit-mode}, which +redefines them to be passed through to the WebKit xwidget. + +You can also enable @code{xwidget-webkit-edit-mode} by typing @kbd{e} +inside the xwidget webkit buffer. + +@findex xwidget-webkit-isearch-mode +@cindex xwidget-webkit-isearch-mode +@cindex searching in webkit buffers + @code{xwidget-webkit-isearch-mode} is a minor mode that behaves +similarly to incremental search (@pxref{Incremental Search}), but +operates on the contents of a WebKit widget instead of the current +buffer. It is bound to @kbd{C-s} and @kbd{C-r} inside xwidget-webkit +buffers. When it is enabled through @kbd{C-r}, the initial search +will be performed in reverse direction. + +Typing any self-inserting character will cause the character to be +inserted into the current search query. Typing @kbd{C-s} will cause +the WebKit widget to display the next search result, while typing +@kbd{C-r} will cause it to display the last. + +To leave incremental search, you can type @kbd{C-g}. + @node Browse-URL @subsection Following URLs @cindex World Wide Web diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index b38a83b4fe..832b570b6a 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1176,6 +1176,7 @@ Input Events * Repeat Events:: Double and triple click (or drag, or down). * Motion Events:: Just moving the mouse, not pushing a button. * Focus Events:: Moving the mouse between frames. +* Xwidget Events:: Events generated by xwidgets. * Misc Events:: Other events the system can generate. * Event Examples:: Examples of the lists for mouse events. * Classifying Events:: Finding the modifier keys in an event symbol. @@ -1871,6 +1872,76 @@ Focus Events so that the focus event comes either before or after the multi-event key sequence, and not within it. +@node Xwidget Events +@subsection Xwidget events + +Xwidgets (@pxref{Xwidgets}) can send events to update Lisp programs on +their status. These events are dubbed @code{xwidget-events}, and +contain various data describing the nature of the change. + +@table @code +@cindex @code{xwidget-event} event +@item (xwidget-event @var{kind} @var{xwidget} @var{arg}) +This event is sent whenever some kind of update occurs in +@var{xwidget}. There are several types of updates, which are +identified by @var{kind}. + +@cindex @code{load-changed} xwidget events +An xwidget event with @var{kind} set to @code{load-changed} indicates +that the @var{xwidget} has reached a particular point of the +page-loading process. When these events are sent, @var{arg} will +contain a string that futher describes the status of the widget. + +@cindex @samp{"load-finished"} in xwidgets +When @var{arg} is @samp{"load-finished"}, it means the xwidget has +finished processing whatever page-loading operation that it was +previously performing. + +@cindex @samp{"load-started"} in xwidgets +Otherwise, if it is @samp{"load-started"}, then the widget has begun a +page-loading operation. + +@cindex @samp{"load-redirected"} in xwidgets +If @var{arg} is @samp{"load-redirected"}, it means the widget has +encountered and followed a redirect during the page-loading operation. + +@cindex @samp{"load-committed"} in xwidgets +If @var{arg} is @samp{"load-committed"}, then the widget has committed +to a given URL during the page-loading operation. This means that the +URL is the final URL that will be rendered by @var{xwidget} during the +current page-loading operation. + +@cindex @code{download-callback} xwidget events +An event with @var{kind} set to @code{download-callback} indicates +that a download of some kind has been completed. + +In these events, there can be arguments after @var{arg}, which itself +indicates the URL that the download file was retrieved from: the first +argument after @var{arg} indicates the MIME type of the download, as a +string, while the second such argument contains the full file path to +the downloaded file. + +@cindex @code{download-started} xwidget events +An event with @var{kind} set to @code{download-started} indicates that +a download has been started. In these events, @var{arg} contains the +URL of the file that is currently being downloaded. + +@cindex @code{javascript-callback} xwidget events +An event with @var{kind} set to @code{javascript-callback} contains +JavaScript callback data. These events are used internally by +@code{xwidget-webkit-execute-script}. + +@cindex @code{xwidget-display-event} event +@item (xwidget-display-event @var{xwidget}) +This event is sent whenever an xwidget requests that another xwidget +be displayed. @var{xwidget} is the xwidget that should be displayed. + +@var{xwidget}'s buffer will be set to a temporary buffer. When +displaying the widget, care should be taken to replace the buffer with +the buffer in which the xwidget will be displayed, using +@code{set-xwidget-buffer} (@pxref{Xwidgets}). +@end table + @node Misc Events @subsection Miscellaneous System Events diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 22528a1b0f..37f07c4f28 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -6784,7 +6784,10 @@ Xwidgets in a @code{display} text or overlay property (@pxref{Display Property}). -@defun make-xwidget type title width height arguments &optional buffer + Embedded widgets can send events notifying Lisp code about changes +occurring within them. (@pxref{Xwidget Events}). + +@defun make-xwidget type title width height arguments &optional buffer related This creates and returns an xwidget object. If @var{buffer} is omitted or @code{nil}, it defaults to the current buffer. If @var{buffer} names a buffer that doesn't exist, it will be @@ -6797,7 +6800,9 @@ Xwidgets @end table The @var{width} and @var{height} arguments specify the widget size in -pixels, and @var{title}, a string, specifies its title. +pixels, and @var{title}, a string, specifies its title. @var{related} +is used internally by the WebKit widget, and is not of interest to the +programmer. @end defun @defun xwidgetp object @@ -6818,6 +6823,10 @@ Xwidgets This function returns the buffer of @var{xwidget}. @end defun +@defun set-xwidget-buffer xwidget buffer +This function sets the buffer of @var{xwidget} to @var{buffer}. +@end defun + @defun get-buffer-xwidgets buffer This function returns a list of xwidget objects associated with the @var{buffer}, which can be specified as a buffer object or a name of @@ -6878,6 +6887,61 @@ Xwidgets query-on-exit flag, either @code{t} or @code{nil}. @end defun +@defun xwidget-perform-lispy-event xwidget event frame +Send an input event @var{event} to @var{xwidget}. The precise action +performed is platform-specific. See @ref{Input Events}. + +You can optionally pass the frame the event was generated from via +@var{frame}. On X11, modifier keys in key events will not be +considered if @var{frame} is @code{nil}, and the selected frame is not +an X-Windows frame. + +On GTK, only keyboard and function key events are implemented. Mouse, +motion, and click events are dispatched to the xwidget without going +through Lisp code, and as such shouldn't require this function to be +sent. +@end defun + +@defun xwidget-webkit-search query xwidget &optional case-insensitive backwards wrap-around +Start an incremental search on the WebKit widget @var{xwidget} with +the string @var{query} as a query. @var{case-insensitive} denotes +whether or not the search is case-insensitive, @var{backwards} +determines if the search is performed backwards towards the start of +the document, and @var{wrap-around} determines whether or not the +search terminates at the end of the document. + +If the function is called while a search query is already present, +then the query specified here will replace the existing query. + +To stop a search query, use @code{xwidget-webkit-finish-search}. +@end defun + +@defun xwidget-webkit-next-result xwidget +Display the next search result in @var{xwidget}. This function will +error unless a search query has already been started in @var{xwidget} +through @code{xwidget-webkit-search}. + +If @code{wrap-around} was non-nil when @code{xwidget-webkit-search} +was called, then the search will restart from the beginning of the +document if the end is reached. +@end defun + +@defun xwidget-webkit-previous-result xwidget +Display the previous search result in @var{xwidget}. This function +will error unless a search query has already been started in +@var{xwidget} through @code{xwidget-webkit-search}. + +If @code{wrap-around} was non-nil when @code{xwidget-webkit-search} +was called, then the search will restart from the end of the +document if the beginning is reached. +@end defun + +@defun xwidget-webkit-finish-search xwidget +Finish a search operation started with @code{xwidget-webkit-search} in +@var{xwidget}. If there is no query currently ongoing, then this +function will error. +@end defun + @node Buttons @section Buttons @cindex buttons in buffers diff --git a/etc/NEWS b/etc/NEWS index a50229916f..0e5caa4825 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -495,6 +495,31 @@ the buffer will take you to that directory. This is a convenience function to extract the field data from 'exif-parse-file' and 'exif-parse-buffer'. +** Xwidgets + ++++ +*** New minor mode `xwidget-webkit-edit-mode'. +When this mode is enabled, self-inserting characters and other common +web browser shotcut keys are redefined to send themselves to the +WebKit widget. + ++++ +*** New minor mode `xwidget-webkit-isearch-mode'. +This mode acts similarly to incremental search, and allows to search +the contents of a WebKit widget. In xwidget-webkit mode, it is bound +to `C-s' and `C-r'. + +--- +*** On X11, the WebKit inspector is now available inside xwidgets. +To access the inspector, right click on the widget and select "Inspect +Element". + +--- +*** "Open in New Window" in a WebKit widget's context menu now works. +The newly created buffer will be displayed via display-buffer, which +can be customized through the usual mechanism of display-buffer-alist +and friends. + * New Modes and Packages in Emacs 29.1 @@ -719,6 +744,33 @@ an exact match, then the lowercased '[menu-bar foo\ bar]' and finally '[menu-bar foo-bar]'. This further improves backwards-compatibility when converting menus to use 'easy-menu-define'. ++++ +** New function `xwidget-perform-lispy-event'. +This function allows you to send events to xwidgets. Usually, some +equivalent of the event will be sent, but there is no guarantee of +what the widget will actually receive. + +On GTK+, only key and function key events are implemented. + ++++ +** New functions for performing searches on WebKit xwidgets. +Some new functions, such as `xwidget-webkit-search', have been added +for performing searches on WebKit xwidgets. + ++++ +** `load-changed' xwidget events are now more detailed. +In particular, they can now have different arguments based on the +state of the WebKit widget. `load-finished' is sent when a load has +completed, `load-started' when a load first starts, `load-redirected' +after a redirect, and `load-committed' when the WebKit widget first +commits to the load. + ++++ +** New event type `xwidget-display-event'. +These events are sent whenever an xwidget requests that Emacs display +another. The only argument to this event is the xwidget that should +be displayed. + * Changes in Emacs 29.1 on Non-Free Operating Systems diff --git a/etc/images/README b/etc/images/README index 9bbe796cc9..561cfff765 100644 --- a/etc/images/README +++ b/etc/images/README @@ -68,6 +68,7 @@ Emacs images and their source in the GNOME icons stock/ directory: bookmark_add.xpm actions/bookmark_add cancel.xpm slightly modified generic/stock_stop connect.xpm net/stock_connect + connect-to-url.xpm net/stock_connect-to-url contact.xpm net/stock_contact data-save.xpm data/stock_data-save delete.xpm generic/stock_delete diff --git a/etc/images/connect-to-url.pbm b/etc/images/connect-to-url.pbm new file mode 100644 index 0000000000..f142349f4a Binary files /dev/null and b/etc/images/connect-to-url.pbm differ diff --git a/etc/images/connect-to-url.xpm b/etc/images/connect-to-url.xpm new file mode 100644 index 0000000000..38fefeaf61 --- /dev/null +++ b/etc/images/connect-to-url.xpm @@ -0,0 +1,281 @@ +/* XPM */ +static char *connect_to_url[] = { +/* columns rows colors chars-per-pixel */ +"24 24 251 2 ", +" c black", +". c #010101", +"X c #000103", +"o c #010204", +"O c #010305", +"+ c #020407", +"@ c #020609", +"# c #03070C", +"$ c #04080D", +"% c #0F0F0D", +"& c #030A10", +"* c #050B10", +"= c #060C11", +"- c #070D13", +"; c #070D14", +": c #060C15", +"> c #070E14", +", c #0B1824", +"< c #0A1B2B", +"1 c #0A1C2E", +"2 c #141A20", +"3 c #161E25", +"4 c #181E23", +"5 c #0D2032", +"6 c #142534", +"7 c #1F2830", +"8 c #1D2933", +"9 c #102438", +"0 c #272622", +"q c #21292F", +"w c #272F36", +"e c #282F33", +"r c #222F3A", +"t c #2E3337", +"y c #2D373E", +"u c #32383C", +"i c #33383C", +"p c #343A3E", +"a c #43423C", +"s c #112941", +"d c #102A44", +"f c #132D47", +"g c #192F46", +"h c #17314B", +"j c #15314F", +"k c #163351", +"l c #163554", +"z c #173554", +"x c #1F3A53", +"c c #1D3955", +"v c #1A3958", +"b c #1C3B5B", +"n c #1F3C58", +"m c #1D3C5C", +"M c #1E3E5D", +"N c #1F3F5F", +"B c #303B44", +"V c #313C44", +"C c #313D47", +"Z c #213C56", +"A c #233E57", +"S c #1F405F", +"D c #374148", +"F c #2D4050", +"G c #25405B", +"H c #25425E", +"J c #214262", +"K c #244565", +"L c #264665", +"P c #254666", +"I c #2A4967", +"U c #284969", +"Y c #2A4C6C", +"T c #2C4F6F", +"R c #33526E", +"E c #385269", +"W c #2D5070", +"Q c #2E5172", +"! c #335473", +"~ c #3F5B75", +"^ c #3D5F7D", +"/ c #41494F", +"( c #646056", +") c #6C685E", +"_ c #505F6C", +"` c #48657C", +"' c #556A7A", +"] c #5B6C78", +"[ c #5F6F7B", +"{ c #5D6F7D", +"} c #706C62", +"| c #726D63", +" . c #78756B", +".. c #7D786E", +"X. c #60727F", +"o. c #807D74", +"O. c #8A857B", +"+. c #8B877E", +"@. c #4E6A83", +"#. c #4A6A86", +"$. c #4A7090", +"%. c #587790", +"&. c #5F7E95", +"*. c #587B98", +"=. c #6F7980", +"-. c #697F8F", +";. c #66839B", +":. c #6A879F", +">. c #708391", +",. c #728A9A", +"<. c #748898", +"1. c #758A99", +"2. c #7B8F9F", +"3. c #708DA4", +"4. c #7990A1", +"5. c #7292AB", +"6. c #7691A8", +"7. c #7693AB", +"8. c #7B98AE", +"9. c #7E98AD", +"0. c #7E9DB3", +"q. c #7F9EB4", +"w. c #8C8981", +"e. c #989389", +"r. c #A6A29B", +"t. c #8093A1", +"y. c #8598A3", +"u. c #8498A7", +"i. c #809AAD", +"p. c #8F9FAA", +"a. c #899FAE", +"s. c #819FB5", +"d. c #86A2B8", +"f. c #87A5BB", +"g. c #88A3B8", +"h. c #89A5BA", +"j. c #8FABBF", +"k. c #97A7B1", +"l. c #90AABE", +"z. c #91ABBF", +"x. c #98ACB9", +"c. c #AAA7A0", +"v. c #B1ADA4", +"b. c #B3B1AA", +"n. c #B7B3AA", +"m. c #A3B1BC", +"M. c #A5B1BC", +"N. c #A9B6BF", +"B. c #BEBBB5", +"V. c #C4C2BD", +"C. c #94AEC1", +"Z. c #96AEC1", +"A. c #94AFC2", +"S. c #95AFC2", +"D. c #96B0C3", +"F. c #98B0C3", +"G. c #9FB5C3", +"H. c #99B3C6", +"J. c #98B3C7", +"K. c #9AB3C6", +"L. c #9BB4C7", +"P. c #9FB8CA", +"I. c #9FB8CB", +"U. c #A2B8C9", +"Y. c #A3B9C9", +"T. c #A0B9CB", +"R. c #A3BACB", +"E. c #A0B9CC", +"W. c #A2BACC", +"Q. c #A4BDCE", +"!. c #A6BECF", +"~. c #B8BEC2", +"^. c #B8C3CA", +"/. c #BCC5CB", +"(. c #BDC8CE", +"). c #A8C0D1", +"_. c #AAC0D0", +"`. c #ABC1D1", +"'. c #ACC2D3", +"]. c #AAC5D7", +"[. c #B4C8D6", +"{. c #BDCBD5", +"}. c #B4C9D8", +"|. c #B6CAD8", +" X c #B8CBD9", +".X c #BBCDDB", +"XX c #B7D0E0", +"oX c #BDD3E2", +"OX c #BCD5E5", +"+X c #CECAC3", +"@X c #C5D2C8", +"#X c #C0D2DE", +"$X c #C4D3DF", +"%X c #CCD7DE", +"&X c #D2D8DC", +"*X c #E1DFDB", +"=X c #E2E1DD", +"-X c #C2D3E0", +";X c #C2D4E1", +":X c #C5D5E1", +">X c #C6D6E1", +",X c #C4D6E2", +". e.o. sXwX}.R.R.`.H.1.- JXJX", +"JX4 a.9.C.h.] a n.V.BXo. p.!.T.l.4.- JXJX", +"JX2 F.d.5.7. =XAXc.BXo. @X@XZX !.C.F.@.> JXJX", +" o.=XAXc.BXo. t.U.z.3.Y $ JXJX", +"BXBXBXBXVXBXBXAXVXO.CXo. P.C.!.I.J.C.;.L * JXJX", +"o.o.o.o.o. . .B.b...*X . $.*.T.J.A.h.Y c @ JXJX", +" .w.r.| +X . 1.C.3.L h JXJX", +"JXJX6 Q ^ 1.% w.r.| +X . @X@XHX h.:.M , JXJX", +"JXJXO x T #.] 0 +.} v.) -.s.H 9 O JXJXJX", +"JXJXJX+ n ! i.X.% % e.( Q Y %.0.&.f O JXJXJX", +"JXJXJXJX& A s.8.E A % % A K J R ` g @ JXJXJXJX", +"JXJXJXJXJX@ C ~ m M J N M b v l < O JXJXJXJXJX", +"JXJXJXJXJXJX : 5 d k z k d 1 & JXJXJXJXJXJX", +"JXJXJXJXJXJXJXJX JXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX" +}; diff --git a/lisp/xwidget.el b/lisp/xwidget.el index 8c593abea8..d427e70233 100644 --- a/lisp/xwidget.el +++ b/lisp/xwidget.el @@ -37,6 +37,7 @@ (declare-function make-xwidget "xwidget.c" (type title width height arguments &optional buffer)) (declare-function xwidget-buffer "xwidget.c" (xwidget)) +(declare-function set-xwidget-buffer "xwidget.c" (xwidget buffer)) (declare-function xwidget-size-request "xwidget.c" (xwidget)) (declare-function xwidget-resize "xwidget.c" (xwidget new-width new-height)) (declare-function xwidget-webkit-execute-script "xwidget.c" @@ -88,6 +89,9 @@ xwidget-at (require 'seq) (require 'url-handlers) +(defvar-local xwidget-webkit--title "" + "The title of the WebKit widget, used for the header line.") + ;;;###autoload (defun xwidget-webkit-browse-url (url &optional new-session) "Ask xwidget-webkit to browse URL. @@ -124,6 +128,14 @@ xwidget-webkit-clone-and-split-right (with-selected-window (split-window-right) (xwidget-webkit-new-session url)))) +(declare-function xwidget-perform-lispy-event "xwidget.c") + +(defun xwidget-webkit-pass-command-event () + "Pass `last-command-event' to the current buffer's WebKit widget." + (interactive) + (xwidget-perform-lispy-event (xwidget-webkit-current-session) + last-command-event)) + ;;todo. ;; - check that the webkit support is compiled in (defvar xwidget-webkit-mode-map @@ -138,6 +150,9 @@ xwidget-webkit-mode-map (define-key map "w" 'xwidget-webkit-current-url) (define-key map "+" 'xwidget-webkit-zoom-in) (define-key map "-" 'xwidget-webkit-zoom-out) + (define-key map "e" 'xwidget-webkit-edit-mode) + (define-key map "\C-r" 'xwidget-webkit-isearch-mode) + (define-key map "\C-s" 'xwidget-webkit-isearch-mode) ;;similar to image mode bindings (define-key map (kbd "SPC") 'xwidget-webkit-scroll-up) @@ -164,6 +179,63 @@ xwidget-webkit-mode-map map) "Keymap for `xwidget-webkit-mode'.") +(easy-menu-define nil xwidget-webkit-mode-map "Xwidget WebKit menu." + (list "Xwidget WebKit" + ["Browse URL" xwidget-webkit-browse-url + :active t + :help "Prompt for a URL, then instruct WebKit to browse it"] + ["Back" xwidget-webkit-back t] + ["Forward" xwidget-webkit-forward t] + ["Reload" xwidget-webkit-reload t] + ["Insert String" xwidget-webkit-insert-string + :active t + :help "Insert a string into the currently active field"] + ["Zoom In" xwidget-webkit-zoom-in t] + ["Zoom Out" xwidget-webkit-zoom-out t] + ["Edit Mode" xwidget-webkit-edit-mode + :active t + :style toggle + :selected xwidget-webkit-edit-mode + :help "Send self inserting characters to the WebKit widget"] + ["Save Selection" xwidget-webkit-copy-selection-as-kill + :active t + :help "Save the browser's selection in the kill ring"] + ["Incremental Search" xwidget-webkit-isearch-mode + :active (not xwidget-webkit-isearch-mode) + :help "Perform incremental search inside the WebKit widget"])) + +(defvar xwidget-webkit-tool-bar-map + (let ((map (make-sparse-keymap))) + (prog1 map + (tool-bar-local-item-from-menu 'xwidget-webkit-back + "left-arrow" + map + xwidget-webkit-mode-map) + (tool-bar-local-item-from-menu 'xwidget-webkit-forward + "right-arrow" + map + xwidget-webkit-mode-map) + (tool-bar-local-item-from-menu 'xwidget-webkit-reload + "refresh" + map + xwidget-webkit-mode-map) + (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-in + "zoom-in" + map + xwidget-webkit-mode-map) + (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-out + "zoom-out" + map + xwidget-webkit-mode-map) + (tool-bar-local-item-from-menu 'xwidget-webkit-browse-url + "connect-to-url" + map + xwidget-webkit-mode-map) + (tool-bar-local-item-from-menu 'xwidget-webkit-isearch-mode + "search" + map + xwidget-webkit-mode-map)))) + (defun xwidget-webkit-zoom-in () "Increase webkit view zoom factor." (interactive nil xwidget-webkit-mode) @@ -276,6 +348,8 @@ xwidget-webkit-callback (with-current-buffer (xwidget-buffer xwidget) (cond ((eq xwidget-event-type 'load-changed) (let ((title (xwidget-webkit-title xwidget))) + (setq xwidget-webkit--title title) + (force-mode-line-update) (xwidget-log "webkit finished loading: %s" title) ;; Do not adjust webkit size to window here, the selected window ;; can be the mini-buffer window unwantedly. @@ -309,8 +383,10 @@ bookmark-make-record-function (define-derived-mode xwidget-webkit-mode special-mode "xwidget-webkit" "Xwidget webkit view mode." (setq buffer-read-only t) + (setq-local tool-bar-map xwidget-webkit-tool-bar-map) (setq-local bookmark-make-record-function #'xwidget-webkit-bookmark-make-record) + (setq-local header-line-format 'xwidget-webkit--title) ;; Keep track of [vh]scroll when switching buffers (image-mode-setup-winprops)) @@ -626,6 +702,29 @@ xwidget-webkit-new-session (xwidget-webkit-mode) (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url))) +(defun xwidget-webkit-import-widget (xwidget) + "Create a new webkit session buffer from XWIDGET, an existing xwidget. +Return the buffer." + (let* ((bufname (generate-new-buffer-name "*xwidget-webkit*")) + (callback #'xwidget-webkit-callback) + (buffer (get-buffer-create bufname))) + (with-current-buffer buffer + (save-excursion + (erase-buffer) + (insert ".") + (put-text-property (point-min) (point-max) + 'display (list 'xwidget :xwidget xwidget))) + (xwidget-put xwidget 'callback callback) + (set-xwidget-buffer xwidget buffer) + (xwidget-webkit-mode)) + buffer)) + +(defun xwidget-webkit-display-event (event) + "Import the xwidget inside EVENT and display it." + (interactive "e") + (display-buffer (xwidget-webkit-import-widget (nth 1 event)))) + +(global-set-key [xwidget-display-event] 'xwidget-webkit-display-event) (defun xwidget-webkit-goto-url (url) "Goto URL with xwidget webkit." @@ -684,6 +783,158 @@ xwidget-put (set-xwidget-plist xwidget (plist-put (xwidget-plist xwidget) propname value))) +(defvar xwidget-webkit-edit-mode-map (make-keymap)) + +(define-key xwidget-webkit-edit-mode-map [backspace] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [tab] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [left] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [right] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [up] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [down] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [return] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [C-left] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [C-right] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [C-up] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [C-down] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [C-return] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [S-left] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [S-right] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [S-up] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [S-down] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [S-return] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [M-left] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [M-right] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [M-up] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [M-down] 'xwidget-webkit-pass-command-event) +(define-key xwidget-webkit-edit-mode-map [M-return] 'xwidget-webkit-pass-command-event) + +(define-minor-mode xwidget-webkit-edit-mode + "Minor mode for editing the content of WebKit buffers. + +This defines most self-inserting characters and some common +keyboard shortcuts to `xwidget-webkit-pass-command-event', which +will pass the key events corresponding to these characters to the +WebKit widget." + :keymap xwidget-webkit-edit-mode-map) + +(substitute-key-definition 'self-insert-command + 'xwidget-webkit-pass-command-event + xwidget-webkit-edit-mode-map + global-map) + +(declare-function xwidget-webkit-search "xwidget.c") +(declare-function xwidget-webkit-next-result "xwidget.c") +(declare-function xwidget-webkit-previous-result "xwidget.c") +(declare-function xwidget-webkit-finish-search "xwidget.c") + +(defvar-local xwidget-webkit-isearch--string "" + "The current search query.") +(defvar-local xwidget-webkit-isearch--is-reverse nil + "Whether or not the current isearch should be reverse.") + +(defun xwidget-webkit-isearch--update () + "Update the current buffer's WebKit widget's search query. +The query will be set to the contents of `xwidget-webkit-isearch--string'." + (xwidget-webkit-search xwidget-webkit-isearch--string + (xwidget-webkit-current-session) + t xwidget-webkit-isearch--is-reverse t) + (message "Search contents: %s" xwidget-webkit-isearch--string)) + +(defun xwidget-webkit-isearch-erasing-char (count) + "Erase the last COUNT characters of the current query." + (interactive (list (prefix-numeric-value current-prefix-arg))) + (when (> (length xwidget-webkit-isearch--string) 0) + (setq xwidget-webkit-isearch--string + (substring xwidget-webkit-isearch--string 0 + (- (length xwidget-webkit-isearch--string) count)))) + (xwidget-webkit-isearch--update)) + +(defun xwidget-webkit-isearch-printing-char (char &optional count) + "Add ordinary character CHAR to the search string and search. +With argument, add COUNT copies of CHAR." + (interactive (list last-command-event + (prefix-numeric-value current-prefix-arg))) + (setq xwidget-webkit-isearch--string (concat xwidget-webkit-isearch--string + (make-string (or count 1) char))) + (xwidget-webkit-isearch--update)) + +(defun xwidget-webkit-isearch-forward (count) + "Move to the next search result COUNT times." + (interactive (list (prefix-numeric-value current-prefix-arg))) + (let ((was-reverse xwidget-webkit-isearch--is-reverse)) + (setq xwidget-webkit-isearch--is-reverse nil) + (when was-reverse + (xwidget-webkit-isearch--update))) + (let ((i 0)) + (while (< i count) + (xwidget-webkit-next-result (xwidget-webkit-current-session)) + (cl-incf i)))) + +(defun xwidget-webkit-isearch-backward (count) + "Move to the previous search result COUNT times." + (interactive (list (prefix-numeric-value current-prefix-arg))) + (let ((was-reverse xwidget-webkit-isearch--is-reverse)) + (setq xwidget-webkit-isearch--is-reverse t) + (unless was-reverse + (xwidget-webkit-isearch--update))) + (let ((i 0)) + (while (< i count) + (xwidget-webkit-next-result (xwidget-webkit-current-session)) + (cl-incf i)))) + +(defun xwidget-webkit-isearch-exit () + "Exit incremental search of a WebKit buffer." + (interactive) + (xwidget-webkit-isearch-mode 0)) + +(defvar xwidget-webkit-isearch-mode-map (make-keymap) + "The keymap used inside xwidget-webkit-isearch-mode.") + +(set-char-table-range (nth 1 xwidget-webkit-isearch-mode-map) + (cons 0 (max-char)) + 'xwidget-webkit-isearch-exit) + +(substitute-key-definition 'self-insert-command + 'xwidget-webkit-isearch-printing-char + xwidget-webkit-isearch-mode-map + global-map) + +(define-key xwidget-webkit-isearch-mode-map (kbd "DEL") + 'xwidget-webkit-isearch-erasing-char) +(define-key xwidget-webkit-isearch-mode-map [return] 'xwidget-webkit-isearch-exit) +(define-key xwidget-webkit-isearch-mode-map "\r" 'xwidget-webkit-isearch-exit) +(define-key xwidget-webkit-isearch-mode-map "\C-g" 'xwidget-webkit-isearch-exit) +(define-key xwidget-webkit-isearch-mode-map "\C-r" 'xwidget-webkit-isearch-backward) +(define-key xwidget-webkit-isearch-mode-map "\C-s" 'xwidget-webkit-isearch-forward) +(define-key xwidget-webkit-isearch-mode-map "\t" 'xwidget-webkit-isearch-printing-char) + +(let ((meta-map (make-keymap))) + (set-char-table-range (nth 1 meta-map) + (cons 0 (max-char)) + 'xwidget-webkit-isearch-exit) + (define-key xwidget-webkit-isearch-mode-map (char-to-string meta-prefix-char) meta-map)) + +(define-minor-mode xwidget-webkit-isearch-mode + "Minor mode for performing incremental search inside WebKit buffers. + +An attempt was made for this to resemble regular incremental +search, but it suffers from several limitations, such as not +supporting recursive edits. + +If this mode is enabled with `C-r', then the search will default +to being performed in reverse direction. + +To navigate around the search results, type +\\[xwidget-webkit-isearch-forward] to move forward, and +\\[xwidget-webkit-isearch-backward] to move backward. + +Press \\[xwidget-webkit-isearch-exit] to exit incremental search." + :keymap xwidget-webkit-isearch-mode-map + (if xwidget-webkit-isearch-mode + (progn + (setq xwidget-webkit-isearch--string "") + (setq xwidget-webkit-isearch--is-reverse (eq last-command-event ?\C-r))) + (xwidget-webkit-finish-search (xwidget-webkit-current-session)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/dispextern.h b/src/dispextern.h index 5b28fe7666..f17f095e0d 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -536,8 +536,8 @@ #define FACE_ID_BITS 20 int img_id; #ifdef HAVE_XWIDGETS - /* Xwidget reference (type == XWIDGET_GLYPH). */ - struct xwidget *xwidget; + /* Xwidget ID. */ + uint32_t xwidget; #endif /* Sub-structure for type == STRETCH_GLYPH. */ diff --git a/src/dispnew.c b/src/dispnew.c index 4a73244c89..632eec2f03 100644 --- a/src/dispnew.c +++ b/src/dispnew.c @@ -4449,16 +4449,6 @@ scrolling_window (struct window *w, int tab_line_p) break; } -#ifdef HAVE_XWIDGETS - /* Currently this seems needed to detect xwidget movement reliably. - This is most probably because an xwidget glyph is represented in - struct glyph's 'union u' by a pointer to a struct, which takes 8 - bytes in 64-bit builds, and thus the comparison of u.val values - done by GLYPH_EQUAL_P doesn't work reliably, since it assumes the - size of the union is 4 bytes. FIXME. */ - return 0; -#endif - /* Can't scroll the display of w32 GUI frames when position of point is indicated by the system caret, because scrolling the display will then "copy" the pixels used by the caret. */ diff --git a/src/keyboard.c b/src/keyboard.c index aa6a4b9e97..c4a5671b10 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -3993,6 +3993,7 @@ kbd_buffer_get_event (KBOARD **kbp, #endif #ifdef HAVE_XWIDGETS case XWIDGET_EVENT: + case XWIDGET_DISPLAY_EVENT: #endif case SAVE_SESSION_EVENT: case NO_EVENT: @@ -4897,7 +4898,7 @@ #define FUNCTION_KEY_OFFSET 0xff00 /* You'll notice that this table is arranged to be conveniently indexed by X Windows keysym values. */ -static const char *const lispy_function_keys[] = +const char *const lispy_function_keys[] = { /* X Keysym value */ @@ -6139,6 +6140,11 @@ make_lispy_event (struct input_event *event) { return Fcons (Qxwidget_event, event->arg); } + + case XWIDGET_DISPLAY_EVENT: + { + return list2 (Qxwidget_display_event, event->arg); + } #endif #ifdef USE_FILE_NOTIFY @@ -11732,6 +11738,7 @@ syms_of_keyboard (void) #ifdef HAVE_XWIDGETS DEFSYM (Qxwidget_event, "xwidget-event"); + DEFSYM (Qxwidget_display_event, "xwidget-display-event"); #endif #ifdef USE_FILE_NOTIFY diff --git a/src/keyboard.h b/src/keyboard.h index 8bdffaa2bf..21c51ec386 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -491,7 +491,7 @@ kbd_buffer_store_event_hold (struct input_event *event, extern struct timespec timer_check (void); extern void mark_kboards (void); -#ifdef HAVE_NTGUI +#if defined HAVE_NTGUI || defined HAVE_X_WINDOWS extern const char *const lispy_function_keys[]; #endif diff --git a/src/print.c b/src/print.c index c13294c8e6..eca389158f 100644 --- a/src/print.c +++ b/src/print.c @@ -1521,8 +1521,20 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag, printchar ('>', printcharfun); break; - case PVEC_XWIDGET: case PVEC_XWIDGET_VIEW: - print_c_string ("#", + XXWIDGET (obj)->xwidget_id, + XXWIDGET (obj)->widget_osr); + strout (buf, len, len, printcharfun); + break; + } +#else + emacs_abort (); +#endif + case PVEC_XWIDGET_VIEW: + print_c_string ("#', printcharfun); break; diff --git a/src/termhooks.h b/src/termhooks.h index 1d3cdc8fe8..e7539bbce2 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -255,6 +255,8 @@ #define EMACS_TERMHOOKS_H #ifdef HAVE_XWIDGETS /* events generated by xwidgets*/ , XWIDGET_EVENT + /* Event generated when WebKit asks us to display another widget. */ + , XWIDGET_DISPLAY_EVENT #endif #ifdef USE_FILE_NOTIFY diff --git a/src/xdisp.c b/src/xdisp.c index 86c4e704d5..d7ad548917 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -28429,7 +28429,7 @@ fill_xwidget_glyph_string (struct glyph_string *s) } s->width = s->first_glyph->pixel_width; s->ybase += s->first_glyph->voffset; - s->xwidget = s->first_glyph->u.xwidget; + s->xwidget = xwidget_from_id (s->first_glyph->u.xwidget); } #endif /* Fill glyph string S from a sequence of stretch glyphs. @@ -29832,7 +29832,7 @@ produce_xwidget_glyph (struct it *it) glyph->padding_p = 0; glyph->glyph_not_available_p = 0; glyph->face_id = it->face_id; - glyph->u.xwidget = it->xwidget; + glyph->u.xwidget = it->xwidget->xwidget_id; glyph->font_type = FONT_TYPE_UNKNOWN; if (it->bidi_p) { diff --git a/src/xterm.c b/src/xterm.c index aa1a1a5eed..9b434bffcc 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -4390,6 +4390,86 @@ x_scroll_run (struct window *w, struct run *run) /* Cursor off. Will be switched on again in gui_update_window_end. */ gui_clear_cursor (w); +#ifdef HAVE_XWIDGETS + /* "Copy" xwidget windows in the area that will be scrolled. */ + Display *dpy = FRAME_X_DISPLAY (f); + Window window = FRAME_X_WINDOW (f); + + Window root, parent, *children; + unsigned int nchildren; + + if (XQueryTree (dpy, window, &root, &parent, &children, &nchildren)) + { + /* Now find xwidget views situated between from_y and to_y, and + attached to w. */ + for (unsigned int i = 0; i < nchildren; ++i) + { + Window child = children[i]; + struct xwidget_view *view = xwidget_view_from_window (child); + + if (view) + { + int window_y = view->y + view->clip_top; + int window_height = view->clip_bottom - view->clip_top; + + Emacs_Rectangle r1, r2, result; + r1.x = w->pixel_left; + r1.y = from_y; + r1.width = w->pixel_width; + r1.height = height; + r2 = r1; + r2.y = window_y; + r2.height = window_height; + + /* The window is offscreen, just unmap it. */ + if (window_height == 0) + { + view->hidden = true; + XUnmapWindow (dpy, child); + continue; + } + + bool intersects_p = + gui_intersect_rectangles (&r1, &r2, &result); + + if (XWINDOW (view->w) == w && intersects_p) + { + int y = view->y + (to_y - from_y); + int text_area_x, text_area_y, text_area_width, text_area_height; + int clip_top, clip_bottom; + + window_box (w, TEXT_AREA, &text_area_x, &text_area_y, + &text_area_width, &text_area_height); + + clip_top = max (0, text_area_y - y); + clip_bottom = max (clip_top, + min (XXWIDGET (view->model)->height, + text_area_y + text_area_height - y)); + + view->y = y; + view->clip_top = clip_top; + view->clip_bottom = clip_bottom; + + /* This means the view has moved offscreen. Unmap + it and hide it here. */ + if ((view->clip_top - view->clip_bottom) <= 0) + { + view->hidden = true; + XUnmapWindow (dpy, child); + } + else + XMoveResizeWindow (dpy, child, view->x + view->clip_left, + view->y + view->clip_top, + view->clip_right - view->clip_left, + view->clip_top - view->clip_bottom); + XFlush (dpy); + } + } + } + XFree (children); + } +#endif + #ifdef USE_CAIRO if (FRAME_CR_CONTEXT (f)) { @@ -4563,8 +4643,9 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra } } -/* Return the Emacs frame-object corresponding to an X window. - It could be the frame's main window or an icon window. */ +/* Return the Emacs frame-object corresponding to an X window. It + could be the frame's main window, an icon window, or an xwidget + window. */ static struct frame * x_window_to_frame (struct x_display_info *dpyinfo, int wdesc) @@ -4575,6 +4656,13 @@ x_window_to_frame (struct x_display_info *dpyinfo, int wdesc) if (wdesc == None) return NULL; +#ifdef HAVE_XWIDGETS + struct xwidget_view *xvw = xwidget_view_from_window (wdesc); + + if (xvw && xvw->frame) + return xvw->frame; +#endif + FOR_EACH_FRAME (tail, frame) { f = XFRAME (frame); @@ -4997,7 +5085,7 @@ x_x_to_emacs_modifiers (struct x_display_info *dpyinfo, int state) | ((state & dpyinfo->hyper_mod_mask) ? mod_hyper : 0)); } -static int +int x_emacs_to_x_modifiers (struct x_display_info *dpyinfo, intmax_t state) { EMACS_INT mod_ctrl = ctrl_modifier; @@ -8211,6 +8299,18 @@ handle_one_xevent (struct x_display_info *dpyinfo, case Expose: f = x_window_to_frame (dpyinfo, event->xexpose.window); +#ifdef HAVE_XWIDGETS + { + struct xwidget_view *xv = + xwidget_view_from_window (event->xexpose.window); + + if (xv) + { + xwidget_expose (xv); + goto OTHER; + } + } +#endif if (f) { if (!FRAME_VISIBLE_P (f)) @@ -8791,6 +8891,31 @@ handle_one_xevent (struct x_display_info *dpyinfo, x_display_set_last_user_time (dpyinfo, event->xcrossing.time); x_detect_focus_change (dpyinfo, any, event, &inev.ie); +#ifdef HAVE_XWIDGETS + { + struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window); + Mouse_HLInfo *hlinfo; + + if (xvw) + { + xwidget_motion_or_crossing (xvw, event); + hlinfo = MOUSE_HL_INFO (xvw->frame); + + if (xvw->frame == hlinfo->mouse_face_mouse_frame) + { + clear_mouse_face (hlinfo); + hlinfo->mouse_face_mouse_frame = 0; + } + + if (any_help_event_p) + { + do_help = -1; + } + goto OTHER; + } + } +#endif + f = any; if (f && x_mouse_click_focus_ignore_position) @@ -8834,6 +8959,17 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case LeaveNotify: +#ifdef HAVE_XWIDGETS + { + struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window); + + if (xvw) + { + xwidget_motion_or_crossing (xvw, event); + goto OTHER; + } + } +#endif x_display_set_last_user_time (dpyinfo, event->xcrossing.time); x_detect_focus_change (dpyinfo, any, event, &inev.ie); @@ -8883,6 +9019,12 @@ handle_one_xevent (struct x_display_info *dpyinfo, #ifdef USE_GTK if (f && xg_event_is_for_scrollbar (f, event)) f = 0; +#endif +#ifdef HAVE_XWIDGETS + struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window); + + if (xvw) + xwidget_motion_or_crossing (xvw, event); #endif if (f) { @@ -9138,6 +9280,24 @@ handle_one_xevent (struct x_display_info *dpyinfo, case ButtonRelease: case ButtonPress: { +#ifdef HAVE_XWIDGETS + struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window); + + if (xvw) + { + xwidget_button (xvw, event->type == ButtonPress, + event->xbutton.x, event->xbutton.y, + event->xbutton.button, event->xbutton.state, + event->xbutton.time); + + if (!EQ (selected_window, xvw->w)) + { + inev.ie.kind = SELECT_WINDOW_EVENT; + inev.ie.frame_or_window = xvw->w; + } + goto OTHER; + } +#endif /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ Lisp_Object tab_bar_arg = Qnil; @@ -12108,6 +12268,10 @@ x_free_frame_resources (struct frame *f) xfree (f->shell_position); #else /* !USE_X_TOOLKIT */ +#ifdef HAVE_XWIDGETS + kill_frame_xwidget_views (f); +#endif + #ifdef USE_GTK xg_free_frame_widgets (f); #endif /* USE_GTK */ diff --git a/src/xterm.h b/src/xterm.h index de6ea50385..9d9534dd62 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1108,6 +1108,7 @@ #define SELECTION_EVENT_TIME(eventp) \ extern int x_dispatch_event (XEvent *, Display *); #endif extern int x_x_to_emacs_modifiers (struct x_display_info *, int); +extern int x_emacs_to_x_modifiers (struct x_display_info *, intmax_t); #ifdef USE_CAIRO extern void x_cr_destroy_frame_context (struct frame *); extern void x_cr_update_surface_desired_size (struct frame *, int, int); diff --git a/src/xwidget.c b/src/xwidget.c index e4b42e6e0c..bf69f262fb 100644 --- a/src/xwidget.c +++ b/src/xwidget.c @@ -19,6 +19,7 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc. #include +#include "buffer.h" #include "xwidget.h" #include "lisp.h" @@ -35,10 +36,22 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc. #ifdef USE_GTK #include #include +#include +#include #elif defined NS_IMPL_COCOA #include "nsxwidget.h" #endif +static Lisp_Object id_to_xwidget_map; +static uint32_t xwidget_counter = 0; + +#ifdef USE_GTK +static Lisp_Object x_window_to_xwv_map; +static gboolean offscreen_damage_event (GtkWidget *, GdkEvent *, gpointer); +static void synthesize_focus_in_event (GtkWidget *); +static GdkDevice *find_suitable_keyboard (struct frame *); +#endif + static struct xwidget * allocate_xwidget (void) { @@ -64,18 +77,32 @@ #define XSETXWIDGET_VIEW(a, b) XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW) GAsyncResult *, gpointer); static gboolean webkit_download_cb (WebKitWebContext *, WebKitDownload *, gpointer); - +static GtkWidget *webkit_create_cb (WebKitWebView *, WebKitNavigationAction *, gpointer); static gboolean webkit_decide_policy_cb (WebKitWebView *, WebKitPolicyDecision *, WebKitPolicyDecisionType, gpointer); +static GtkWidget *find_widget_at_pos (GtkWidget *, int, int, int *, int *); + +struct widget_search_data +{ + int x; + int y; + bool foundp; + bool first; + GtkWidget *data; +}; + +static void find_widget (GtkWidget *t, struct widget_search_data *); +static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint, + gpointer); #endif DEFUN ("make-xwidget", Fmake_xwidget, Smake_xwidget, - 5, 6, 0, + 5, 7, 0, doc: /* Make an xwidget of TYPE. If BUFFER is nil, use the current buffer. If BUFFER is a string and no such buffer exists, create it. @@ -83,10 +110,12 @@ DEFUN ("make-xwidget", - webkit -Returns the newly constructed xwidget, or nil if construction fails. */) +RELATED is nil, or an xwidget. This argument is used internally. +Returns the newly constructed xwidget, or nil if construction +fails. */) (Lisp_Object type, Lisp_Object title, Lisp_Object width, Lisp_Object height, - Lisp_Object arguments, Lisp_Object buffer) + Lisp_Object arguments, Lisp_Object buffer, Lisp_Object related) { #ifdef USE_GTK if (!xg_gtk_initialized) @@ -108,13 +137,19 @@ DEFUN ("make-xwidget", XSETXWIDGET (val, xw); Vxwidget_list = Fcons (val, Vxwidget_list); xw->plist = Qnil; + xw->xwidget_id = ++xwidget_counter; + + Fputhash (make_fixnum (xw->xwidget_id), val, id_to_xwidget_map); #ifdef USE_GTK xw->widgetwindow_osr = NULL; xw->widget_osr = NULL; + xw->hit_result = 0; + xw->find_text = NULL; if (EQ (xw->type, Qwebkit)) { block_input (); + WebKitSettings *settings; WebKitWebContext *webkit_context = webkit_web_context_get_default (); # if WEBKIT_CHECK_VERSION (2, 26, 0) @@ -128,18 +163,33 @@ DEFUN ("make-xwidget", if (EQ (xw->type, Qwebkit)) { - xw->widget_osr = webkit_web_view_new (); - - /* webkitgtk uses GSubprocess which sets sigaction causing - Emacs to not catch SIGCHLD with its usual handle setup in - catch_child_signal(). This resets the SIGCHLD - sigaction. */ - struct sigaction old_action; - sigaction (SIGCHLD, NULL, &old_action); - webkit_web_view_load_uri(WEBKIT_WEB_VIEW (xw->widget_osr), - "about:blank"); - sigaction (SIGCHLD, &old_action, NULL); - } + WebKitWebView *related_view; + + if (NILP (related) + || !XWIDGETP (related) + || !EQ (XXWIDGET (related)->type, Qwebkit)) + { + /* Enable the developer extras */ + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr)); + g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL); + xw->widget_osr = webkit_web_view_new (); + + /* webkitgtk uses GSubprocess which sets sigaction causing + Emacs to not catch SIGCHLD with its usual handle setup in + catch_child_signal(). This resets the SIGCHLD + sigaction. */ + struct sigaction old_action; + sigaction (SIGCHLD, NULL, &old_action); + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), + "about:blank"); + sigaction (SIGCHLD, &old_action, NULL); + } + else + { + related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr); + xw->widget_osr = webkit_web_view_new_with_related_view (related_view); + } + } gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width, xw->height); @@ -157,6 +207,7 @@ DEFUN ("make-xwidget", gtk_widget_show (xw->widget_osr); gtk_widget_show (xw->widgetwindow_osr); + synthesize_focus_in_event (xw->widgetwindow_osr); /* Store some xwidget data in the gtk widgets for convenient retrieval in the event handlers. */ @@ -179,8 +230,20 @@ DEFUN ("make-xwidget", G_CALLBACK (webkit_decide_policy_cb), xw); + + g_signal_connect (G_OBJECT (xw->widget_osr), + "mouse-target-changed", + G_CALLBACK (mouse_target_changed), + xw); + g_signal_connect (G_OBJECT (xw->widget_osr), + "create", + G_CALLBACK (webkit_create_cb), + xw); } + g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event", + G_CALLBACK (offscreen_damage_event), xw); + unblock_input (); } #elif defined NS_IMPL_COCOA @@ -190,6 +253,158 @@ DEFUN ("make-xwidget", return val; } +#ifdef USE_GTK +static void +set_widget_if_text_view (GtkWidget *widget, void *data) +{ + GtkWidget **pointer = data; + + if (GTK_IS_TEXT_VIEW (widget)) + { + *pointer = widget; + } +} +#endif + +DEFUN ("xwidget-perform-lispy-event", + Fxwidget_perform_lispy_event, Sxwidget_perform_lispy_event, + 2, 3, 0, doc: /* Send a lispy event to XWIDGET. +EVENT should be the event that will be sent. FRAME should be the +frame which generated the event, or nil. On X11, modifier keys will +not be processed if FRAME is nil and the selected frame is not an +X-Windows frame. */) + (Lisp_Object xwidget, Lisp_Object event, Lisp_Object frame) +{ + struct xwidget *xw; + struct frame *f = NULL; + int character = -1, keycode = -1; + int modifiers = 0; + +#ifdef USE_GTK + GdkEvent *xg_event; + GtkContainerClass *klass; + GtkWidget *widget; + GtkWidget *temp = NULL; +#endif + + CHECK_XWIDGET (xwidget); + xw = XXWIDGET (xwidget); + + if (!NILP (frame)) + f = decode_window_system_frame (frame); + else if (FRAME_X_P (SELECTED_FRAME ())) + f = SELECTED_FRAME (); + +#ifdef USE_GTK + widget = gtk_window_get_focus (GTK_WINDOW (xw->widgetwindow_osr)); + + if (!widget) + widget = xw->widget_osr; + + if (RANGED_FIXNUMP (0, event, INT_MAX)) + { + character = XFIXNUM (event); + + if (character < 32) + modifiers |= ctrl_modifier; + + modifiers |= character & meta_modifier; + modifiers |= character & hyper_modifier; + modifiers |= character & super_modifier; + modifiers |= character & shift_modifier; + modifiers |= character & ctrl_modifier; + + character = character & ~(1 << 21); + + if (character < 32) + character += '_'; + + if (f) + modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f), modifiers); + else + modifiers = 0; + } + else if (SYMBOLP (event)) + { + Lisp_Object decoded = parse_modifiers (event); + Lisp_Object decoded_name = SYMBOL_NAME (XCAR (decoded)); + + int off = 0; + bool found = false; + + while (off < 256) + { + if (lispy_function_keys[off] + && !strcmp (lispy_function_keys[off], + SSDATA (decoded_name))) + { + found = true; + break; + } + ++off; + } + + if (f) + modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f), + XFIXNUM (XCAR (XCDR (decoded)))); + else + modifiers = 0; + + if (found) + keycode = off + 0xff00; + } + + if (character == -1 && keycode == -1) + return Qnil; + + block_input (); + xg_event = gdk_event_new (GDK_KEY_PRESS); + xg_event->any.window = gtk_widget_get_window (xw->widget_osr); + g_object_ref (xg_event->any.window); + + if (character > -1) + keycode = gdk_unicode_to_keyval (character); + + xg_event->key.keyval = keycode; + xg_event->key.state = modifiers; + + if (keycode > -1) + { + /* WebKitGTK internals abuse follows. */ + if (WEBKIT_IS_WEB_VIEW (widget)) + { + /* WebKitGTK relies on an internal GtkTextView object to + "translate" keys such as backspace. We must find that + widget and activate its binding to this key if any. */ + klass = GTK_CONTAINER_CLASS (G_OBJECT_GET_CLASS (widget)); + + klass->forall (GTK_CONTAINER (xw->widget_osr), TRUE, + set_widget_if_text_view, &temp); + + if (GTK_IS_WIDGET (temp)) + { + if (!gtk_widget_get_realized (temp)) + gtk_widget_realize (temp); + + gtk_bindings_activate (G_OBJECT (temp), keycode, modifiers); + } + } + } + + if (f) + gdk_event_set_device (xg_event, + find_suitable_keyboard (SELECTED_FRAME ())); + + gtk_main_do_event (xg_event); + xg_event->type = GDK_KEY_RELEASE; + gtk_main_do_event (xg_event); + gdk_event_free (xg_event); + unblock_input (); +#endif + + return Qnil; +} + DEFUN ("get-buffer-xwidgets", Fget_buffer_xwidgets, Sget_buffer_xwidgets, 1, 1, 0, doc: /* Return a list of xwidgets associated with BUFFER. @@ -221,16 +436,397 @@ xwidget_hidden (struct xwidget_view *xv) return xv->hidden; } +struct xwidget * +xwidget_from_id (uint32_t id) +{ + Lisp_Object key = make_fixnum (id); + Lisp_Object xwidget = Fgethash (key, id_to_xwidget_map, Qnil); + + if (NILP (xwidget)) + emacs_abort (); + + return XXWIDGET (xwidget); +} + #ifdef USE_GTK + +static GdkDevice * +find_suitable_pointer (struct frame *f) +{ + GdkSeat *seat = gdk_display_get_default_seat + (gtk_widget_get_display (FRAME_GTK_WIDGET (f))); + + if (!seat) + return NULL; + + return gdk_seat_get_pointer (seat); +} + +static GdkDevice * +find_suitable_keyboard (struct frame *f) +{ + GdkSeat *seat = gdk_display_get_default_seat + (gtk_widget_get_display (FRAME_GTK_WIDGET (f))); + + if (!seat) + return NULL; + + return gdk_seat_get_keyboard (seat); +} + +static void +find_widget_cb (GtkWidget *widget, void *user) +{ + find_widget (widget, user); +} + +static void +find_widget (GtkWidget *widget, + struct widget_search_data *data) +{ + GtkAllocation new_allocation; + GdkWindow *window; + int x_offset = 0; + int y_offset = 0; + + gtk_widget_get_allocation (widget, &new_allocation); + + if (gtk_widget_get_has_window (widget)) + { + new_allocation.x = 0; + new_allocation.y = 0; + } + + if (gtk_widget_get_parent (widget) && !data->first) + { + window = gtk_widget_get_window (widget); + while (window != gtk_widget_get_window (gtk_widget_get_parent (widget))) + { + gint tx, ty, twidth, theight; + + if (!window) + return; + + twidth = gdk_window_get_width (window); + theight = gdk_window_get_height (window); + + if (new_allocation.x < 0) + { + new_allocation.width += new_allocation.x; + new_allocation.x = 0; + } + + if (new_allocation.y < 0) + { + new_allocation.height += new_allocation.y; + new_allocation.y = 0; + } + + if (new_allocation.x + new_allocation.width > twidth) + new_allocation.width = twidth - new_allocation.x; + if (new_allocation.y + new_allocation.height > theight) + new_allocation.height = theight - new_allocation.y; + + gdk_window_get_position (window, &tx, &ty); + new_allocation.x += tx; + x_offset += tx; + new_allocation.y += ty; + y_offset += ty; + + window = gdk_window_get_parent (window); + } + } + + if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) && + (data->x < new_allocation.x + new_allocation.width) && + (data->y < new_allocation.y + new_allocation.height)) + { + /* First, check if the drag is in a valid drop site in + * one of our children + */ + if (GTK_IS_CONTAINER (widget)) + { + struct widget_search_data new_data = *data; + + new_data.x -= x_offset; + new_data.y -= y_offset; + new_data.foundp = false; + new_data.first = false; + + gtk_container_forall (GTK_CONTAINER (widget), + find_widget_cb, &new_data); + + data->foundp = new_data.foundp; + if (data->foundp) + data->data = new_data.data; + } + + /* If not, and this widget is registered as a drop site, check to + * emit "drag_motion" to check if we are actually in + * a drop site. + */ + if (!data->foundp) + { + data->foundp = true; + data->data = widget; + } + } +} + +static GtkWidget * +find_widget_at_pos (GtkWidget *w, int x, int y, + int *new_x, int *new_y) +{ + struct widget_search_data data; + + data.x = x; + data.y = y; + data.foundp = false; + data.first = true; + + find_widget (w, &data); + + if (data.foundp) + { + gtk_widget_translate_coordinates (w, data.data, x, + y, new_x, new_y); + return data.data; + } + + *new_x = x; + *new_y = y; + + return NULL; +} + +static Emacs_Cursor +cursor_for_hit (guint result, struct frame *frame) +{ + Emacs_Cursor cursor = FRAME_OUTPUT_DATA (frame)->nontext_cursor; + + if ((result & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) + || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION) + || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)) + cursor = FRAME_X_OUTPUT (frame)->text_cursor; + + if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR) + cursor = FRAME_X_OUTPUT (frame)->vertical_drag_cursor; + + if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) + cursor = FRAME_X_OUTPUT (frame)->hand_cursor; + + return cursor; +} + +static void +define_cursors (struct xwidget *xw, WebKitHitTestResult *res) +{ + struct xwidget_view *xvw; + + xw->hit_result = webkit_hit_test_result_get_context (res); + + for (Lisp_Object tem = Vxwidget_view_list; CONSP (tem); + tem = XCDR (tem)) + { + if (XWIDGET_VIEW_P (XCAR (tem))) + { + xvw = XXWIDGET_VIEW (XCAR (tem)); + + if (XXWIDGET (xvw->model) == xw) + { + xvw->cursor = cursor_for_hit (xw->hit_result, xvw->frame); + if (xvw->wdesc != None) + XDefineCursor (xvw->dpy, xvw->wdesc, xvw->cursor); + } + } + } +} + +static void +mouse_target_changed (WebKitWebView *webview, + WebKitHitTestResult *hitresult, + guint modifiers, gpointer xw) +{ + define_cursors (xw, hitresult); +} + + +static void +xwidget_button_1 (struct xwidget_view *view, + bool down_p, int x, int y, int button, + int modifier_state, Time time) +{ + GdkEvent *xg_event = gdk_event_new (down_p ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE); + struct xwidget *model = XXWIDGET (view->model); + GtkWidget *target; + + /* X and Y should be relative to the origin of view->wdesc. */ + x += view->clip_left; + y += view->clip_top; + + target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y); + + if (!target) + target = model->widget_osr; + + xg_event->any.window = gtk_widget_get_window (target); + g_object_ref (xg_event->any.window); /* The window will be unrefed + later by gdk_event_free. */ + + xg_event->button.x = x; + xg_event->button.x_root = x; + xg_event->button.y = y; + xg_event->button.y_root = y; + xg_event->button.button = button; + xg_event->button.state = modifier_state; + xg_event->button.time = time; + xg_event->button.device = find_suitable_pointer (view->frame); + + gtk_main_do_event (xg_event); + gdk_event_free (xg_event); +} + +void +xwidget_button (struct xwidget_view *view, + bool down_p, int x, int y, int button, + int modifier_state, Time time) +{ + if (button < 4 || button > 8) + xwidget_button_1 (view, down_p, x, y, button, modifier_state, time); + else + { + GdkEvent *xg_event = gdk_event_new (GDK_SCROLL); + struct xwidget *model = XXWIDGET (view->model); + GtkWidget *target; + + x += view->clip_left; + y += view->clip_top; + + target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y); + + if (!target) + target = model->widget_osr; + + xg_event->any.window = gtk_widget_get_window (target); + g_object_ref (xg_event->any.window); /* The window will be unrefed + later by gdk_event_free. */ + if (button == 4) + xg_event->scroll.direction = GDK_SCROLL_UP; + else if (button == 5) + xg_event->scroll.direction = GDK_SCROLL_DOWN; + else if (button == 6) + xg_event->scroll.direction = GDK_SCROLL_LEFT; + else + xg_event->scroll.direction = GDK_SCROLL_RIGHT; + + xg_event->scroll.device = find_suitable_pointer (view->frame); + + xg_event->scroll.x = x; + xg_event->scroll.x_root = x; + xg_event->scroll.y = y; + xg_event->scroll.y_root = y; + xg_event->scroll.state = modifier_state; + xg_event->scroll.time = time; + + xg_event->scroll.delta_x = 0; + xg_event->scroll.delta_y = 0; + + gtk_main_do_event (xg_event); + gdk_event_free (xg_event); + } +} + +void +xwidget_motion_or_crossing (struct xwidget_view *view, const XEvent *event) +{ + GdkEvent *xg_event = gdk_event_new (event->type == MotionNotify ? GDK_MOTION_NOTIFY : + (event->type == LeaveNotify ? GDK_LEAVE_NOTIFY : + GDK_ENTER_NOTIFY)); + struct xwidget *model = XXWIDGET (view->model); + int x; + int y; + GtkWidget *target = find_widget_at_pos (model->widgetwindow_osr, + (event->type == MotionNotify + ? event->xmotion.x + view->clip_left + : event->xmotion.y + view->clip_top), + (event->type == MotionNotify + ? event->xmotion.y + view->clip_left + : event->xcrossing.y + view->clip_top), + &x, &y); + + if (!target) + target = model->widgetwindow_osr; + + xg_event->any.window = gtk_widget_get_window (target); + g_object_ref (xg_event->any.window); /* The window will be unrefed + later by gdk_event_free. */ + + if (event->type == MotionNotify) + { + xg_event->motion.x = x; + xg_event->motion.y = y; + xg_event->motion.x_root = event->xmotion.x_root; + xg_event->motion.y_root = event->xmotion.y_root; + xg_event->motion.time = event->xmotion.time; + xg_event->motion.state = event->xmotion.state; + xg_event->motion.device = find_suitable_pointer (view->frame); + } + else + { + xg_event->crossing.detail = min (5, event->xcrossing.detail); + xg_event->crossing.time = event->xcrossing.time; + xg_event->crossing.x = x; + xg_event->crossing.y = y; + xg_event->crossing.x_root = event->xcrossing.x_root; + xg_event->crossing.y_root = event->xcrossing.y_root; + gdk_event_set_device (xg_event, find_suitable_pointer (view->frame)); + } + + gtk_main_do_event (xg_event); + gdk_event_free (xg_event); +} + +static void +synthesize_focus_in_event (GtkWidget *offscreen_window) +{ + GdkWindow *wnd; + GdkEvent *focus_event; + + if (!gtk_widget_get_realized (offscreen_window)) + gtk_widget_realize (offscreen_window); + + wnd = gtk_widget_get_window (offscreen_window); + + focus_event = gdk_event_new (GDK_FOCUS_CHANGE); + focus_event->any.window = wnd; + focus_event->focus_change.in = TRUE; + g_object_ref (wnd); + + gtk_main_do_event (focus_event); + gdk_event_free (focus_event); +} + +struct xwidget_view * +xwidget_view_from_window (Window wdesc) +{ + Lisp_Object key = make_fixnum (wdesc); + Lisp_Object xwv = Fgethash (key, x_window_to_xwv_map, Qnil); + + if (NILP (xwv)) + return NULL; + + return XXWIDGET_VIEW (xwv); +} + static void xwidget_show_view (struct xwidget_view *xv) { xv->hidden = false; - gtk_widget_show (xv->widgetwindow); - gtk_fixed_move (GTK_FIXED (xv->emacswindow), - xv->widgetwindow, - xv->x + xv->clip_left, - xv->y + xv->clip_top); + XMoveWindow (xv->dpy, xv->wdesc, + xv->x + xv->clip_left, + xv->y + xv->clip_top); + XMapWindow (xv->dpy, xv->wdesc); + XFlush (xv->dpy); } /* Hide an xwidget view. */ @@ -238,28 +834,64 @@ xwidget_show_view (struct xwidget_view *xv) xwidget_hide_view (struct xwidget_view *xv) { xv->hidden = true; - gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow, - 10000, 10000); + XUnmapWindow (xv->dpy, xv->wdesc); + XFlush (xv->dpy); +} + +static void +xv_do_draw (struct xwidget_view *xw, struct xwidget *w) +{ + GtkOffscreenWindow *wnd; + cairo_surface_t *surface; + block_input (); + wnd = GTK_OFFSCREEN_WINDOW (w->widgetwindow_osr); + surface = gtk_offscreen_window_get_surface (wnd); + + cairo_save (xw->cr_context); + if (surface) + { + cairo_set_source_surface (xw->cr_context, surface, xw->clip_left, + xw->clip_top); + cairo_set_operator (xw->cr_context, CAIRO_OPERATOR_SOURCE); + cairo_paint (xw->cr_context); + } + cairo_restore (xw->cr_context); + + unblock_input (); } /* When the off-screen webkit master view changes this signal is called. It copies the bitmap from the off-screen instance. */ static gboolean offscreen_damage_event (GtkWidget *widget, GdkEvent *event, - gpointer xv_widget) -{ - /* Queue a redraw of onscreen widget. - There is a guard against receiving an invalid widget, - which should only happen if we failed to remove the - specific signal handler for the damage event. */ - if (GTK_IS_WIDGET (xv_widget)) - gtk_widget_queue_draw (GTK_WIDGET (xv_widget)); - else - message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n", - xv_widget); + gpointer xwidget) +{ + block_input (); + + for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail); + tail = XCDR (tail)) + { + if (XWIDGET_VIEW_P (XCAR (tail))) + { + struct xwidget_view *view = XXWIDGET_VIEW (XCAR (tail)); + + if (view->wdesc && XXWIDGET (view->model) == xwidget) + xv_do_draw (view, XXWIDGET (view->model)); + } + } + + unblock_input (); return FALSE; } + +void +xwidget_expose (struct xwidget_view *xv) +{ + struct xwidget *xw = XXWIDGET (xv->model); + + xv_do_draw (xv, xw); +} #endif /* USE_GTK */ void @@ -313,22 +945,108 @@ store_xwidget_js_callback_event (struct xwidget *xw, #ifdef USE_GTK +static void +store_xwidget_display_event (struct xwidget *xw) +{ + struct input_event evt; + Lisp_Object val; + + XSETXWIDGET (val, xw); + EVENT_INIT (evt); + evt.kind = XWIDGET_DISPLAY_EVENT; + evt.frame_or_window = Qnil; + evt.arg = val; + kbd_buffer_store_event (&evt); +} + +static void +webkit_ready_to_show (WebKitWebView *new_view, + gpointer user_data) +{ + Lisp_Object tem; + struct xwidget *xw; + + for (tem = Vxwidget_list; CONSP (tem); tem = XCDR (tem)) + { + if (XWIDGETP (XCAR (tem))) + { + xw = XXWIDGET (XCAR (tem)); + + if (EQ (xw->type, Qwebkit) + && WEBKIT_WEB_VIEW (xw->widget_osr) == new_view) + store_xwidget_display_event (xw); + } + } +} + +static GtkWidget * +webkit_create_cb_1 (WebKitWebView *webview, + struct xwidget_view *xv) +{ + Lisp_Object related; + Lisp_Object xwidget; + GtkWidget *widget; + + XSETXWIDGET (related, xv); + xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0), + make_fixnum (0), Qnil, + build_string (" *detached xwidget buffer*"), + related); + + if (NILP (xwidget)) + return NULL; + + widget = XXWIDGET (xwidget)->widget_osr; + + g_signal_connect (G_OBJECT (widget), "ready-to-show", + G_CALLBACK (webkit_ready_to_show), NULL); + + return widget; +} + +static GtkWidget * +webkit_create_cb (WebKitWebView *webview, + WebKitNavigationAction *nav_action, + gpointer user_data) +{ + switch (webkit_navigation_action_get_navigation_type (nav_action)) + { + case WEBKIT_NAVIGATION_TYPE_OTHER: + return webkit_create_cb_1 (webview, user_data); + + case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: + case WEBKIT_NAVIGATION_TYPE_RELOAD: + case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: + case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: + case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: + default: + return NULL; + } +} + void webkit_view_load_changed_cb (WebKitWebView *webkitwebview, WebKitLoadEvent load_event, gpointer data) { - switch (load_event) { - case WEBKIT_LOAD_FINISHED: + struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview), + XG_XWIDGET); + + switch (load_event) { - struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview), - XG_XWIDGET); - store_xwidget_event_string (xw, "load-changed", ""); + case WEBKIT_LOAD_FINISHED: + store_xwidget_event_string (xw, "load-changed", "load-finished"); + break; + case WEBKIT_LOAD_STARTED: + store_xwidget_event_string (xw, "load-changed", "load-started"); + break; + case WEBKIT_LOAD_REDIRECTED: + store_xwidget_event_string (xw, "load-changed", "load-redirected"); + break; + case WEBKIT_LOAD_COMMITTED: + store_xwidget_event_string (xw, "load-changed", "load-committed"); break; } - default: - break; - } } /* Recursively convert a JavaScript value to a Lisp value. */ @@ -498,51 +1216,6 @@ webkit_decide_policy_cb (WebKitWebView *webView, return FALSE; } } - - -/* For gtk3 offscreen rendered widgets. */ -static gboolean -xwidget_osr_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data) -{ - struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET); - struct xwidget_view *xv = g_object_get_data (G_OBJECT (widget), - XG_XWIDGET_VIEW); - - cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom); - cairo_clip (cr); - - gtk_widget_draw (xw->widget_osr, cr); - return FALSE; -} - -static gboolean -xwidget_osr_event_forward (GtkWidget *widget, GdkEvent *event, - gpointer user_data) -{ - /* Copy events that arrive at the outer widget to the offscreen widget. */ - struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET); - GdkEvent *eventcopy = gdk_event_copy (event); - eventcopy->any.window = gtk_widget_get_window (xw->widget_osr); - - /* TODO: This might leak events. They should be deallocated later, - perhaps in xwgir_event_cb. */ - gtk_main_do_event (eventcopy); - - /* Don't propagate this event further. */ - return TRUE; -} - -static gboolean -xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event, - gpointer data) -{ - struct xwidget_view *xv = data; - struct xwidget *xww = XXWIDGET (xv->model); - gdk_offscreen_window_set_embedder (gtk_widget_get_window - (xww->widgetwindow_osr), - gtk_widget_get_window (xv->widget)); - return FALSE; -} #endif /* USE_GTK */ @@ -568,63 +1241,19 @@ xwidget_init_view (struct xwidget *xww, XSETXWIDGET (xv->model, xww); #ifdef USE_GTK - if (EQ (xww->type, Qwebkit)) - { - xv->widget = gtk_drawing_area_new (); - /* Expose event handling. */ - gtk_widget_set_app_paintable (xv->widget, TRUE); - gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK); - - /* Draw the view on damage-event. */ - g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event", - G_CALLBACK (offscreen_damage_event), xv->widget); - - if (EQ (xww->type, Qwebkit)) - { - g_signal_connect (G_OBJECT (xv->widget), "button-press-event", - G_CALLBACK (xwidget_osr_event_forward), NULL); - g_signal_connect (G_OBJECT (xv->widget), "button-release-event", - G_CALLBACK (xwidget_osr_event_forward), NULL); - g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event", - G_CALLBACK (xwidget_osr_event_forward), NULL); - } - else - { - /* xwgir debug, orthogonal to forwarding. */ - g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event", - G_CALLBACK (xwidget_osr_event_set_embedder), xv); - } - g_signal_connect (G_OBJECT (xv->widget), "draw", - G_CALLBACK (xwidget_osr_draw_cb), NULL); - } + xv->dpy = FRAME_X_DISPLAY (s->f); - /* Widget realization. - - Make container widget first, and put the actual widget inside the - container later. Drawing should crop container window if necessary - to handle case where xwidget is partially obscured by other Emacs - windows. Other containers than gtk_fixed where explored, but - gtk_fixed had the most predictable behavior so far. */ - - xv->emacswindow = FRAME_GTK_WIDGET (s->f); - xv->widgetwindow = gtk_fixed_new (); - gtk_widget_set_has_window (xv->widgetwindow, TRUE); - gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget); - - /* Store some xwidget data in the gtk widgets. */ - g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, s->f); - g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, xww); - g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, xv); - g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, xww); - g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW, xv); - - gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width, - xww->height); - gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height); - gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y); xv->x = x; xv->y = y; - gtk_widget_show_all (xv->widgetwindow); + + xv->clip_left = 0; + xv->clip_right = xww->width; + xv->clip_top = 0; + xv->clip_bottom = xww->height; + + xv->wdesc = None; + xv->frame = s->f; + xv->cursor = cursor_for_hit (xww->hit_result, s->f); #elif defined NS_IMPL_COCOA nsxwidget_init_view (xv, xww, s, x, y); nsxwidget_resize_view(xv, xww->width, xww->height); @@ -681,6 +1310,8 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y, &text_area_width, &text_area_height); + /* On X11, this keeps generating expose events. */ +#ifndef USE_GTK /* 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. */ @@ -693,6 +1324,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) make_int (text_area_width), make_int (text_area_height)); } +#endif clip_left = max (0, text_area_x - x); clip_right = max (clip_left, @@ -711,15 +1343,51 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) later. */ bool moved = (xv->x + xv->clip_left != x + clip_left || xv->y + xv->clip_top != y + clip_top); + +#ifdef USE_GTK + bool wdesc_was_none = xv->wdesc == None; +#endif xv->x = x; xv->y = y; +#ifdef USE_GTK + block_input (); + if (xv->wdesc == None) + { + Lisp_Object xvw; + XSETXWIDGET_VIEW (xvw, xv); + XSetWindowAttributes a; + a.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask + | PointerMotionMask | EnterWindowMask | LeaveWindowMask); + + xv->wdesc = XCreateWindow (xv->dpy, FRAME_X_WINDOW (s->f), + x + clip_left, y + clip_top, + clip_right - clip_left, + clip_bottom - clip_top, 0, + CopyFromParent, CopyFromParent, + CopyFromParent, CWEventMask, &a); + XDefineCursor (xv->dpy, xv->wdesc, xv->cursor); + xv->cr_surface = cairo_xlib_surface_create (xv->dpy, + xv->wdesc, + FRAME_DISPLAY_INFO (s->f)->visual, + clip_right - clip_left, + clip_bottom - clip_top); + xv->cr_context = cairo_create (xv->cr_surface); + Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map); + + moved = false; + } +#endif + /* Has it moved? */ if (moved) { #ifdef USE_GTK - gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), - xv->widgetwindow, x + clip_left, y + clip_top); + XMoveResizeWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top, + clip_right - clip_left, clip_bottom - clip_top); + XFlush (xv->dpy); + cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left, + clip_bottom - clip_top); #elif defined NS_IMPL_COCOA nsxwidget_move_view (xv, x + clip_left, y + clip_top); #endif @@ -735,10 +1403,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) || xv->clip_top != clip_top || xv->clip_left != clip_left) { #ifdef 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); + if (!wdesc_was_none && !moved) + { + XResizeWindow (xv->dpy, xv->wdesc, clip_right - clip_left, + clip_bottom - clip_top); + XFlush (xv->dpy); + cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left, + clip_bottom - clip_top); + } #elif defined NS_IMPL_COCOA nsxwidget_resize_view (xv, clip_right - clip_left, clip_bottom - clip_top); @@ -758,12 +1430,15 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) if (!xwidget_hidden (xv)) { #ifdef USE_GTK - gtk_widget_queue_draw (xv->widgetwindow); - gtk_widget_queue_draw (xv->widget); + gtk_widget_queue_draw (xww->widget_osr); #elif defined NS_IMPL_COCOA nsxwidget_set_needsdisplay (xv); #endif } + +#ifdef USE_GTK + unblock_input (); +#endif } static bool @@ -975,16 +1650,13 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0, struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail)); if (XXWIDGET (xv->model) == xw) { -#ifdef 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 + wset_redisplay (XWINDOW (xv->w)); } } } + redisplay (); + return Qnil; } @@ -1084,13 +1756,15 @@ DEFUN ("delete-xwidget-view", CHECK_XWIDGET_VIEW (xwidget_view); struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view); #ifdef 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); + if (xv->wdesc != None) + { + block_input (); + cairo_destroy (xv->cr_context); + cairo_surface_destroy (xv->cr_surface); + XDestroyWindow (xv->dpy, xv->wdesc); + Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map); + unblock_input (); + } #elif defined NS_IMPL_COCOA nsxwidget_delete_view (xv); #endif @@ -1145,6 +1819,19 @@ DEFUN ("xwidget-buffer", return XXWIDGET (xwidget)->buffer; } +DEFUN ("set-xwidget-buffer", + Fset_xwidget_buffer, Sset_xwidget_buffer, + 2, 2, 0, + doc: /* Set XWIDGET's buffer to BUFFER. */) + (Lisp_Object xwidget, Lisp_Object buffer) +{ + CHECK_XWIDGET (xwidget); + CHECK_BUFFER (buffer); + + XXWIDGET (xwidget)->buffer = buffer; + return Qnil; +} + DEFUN ("set-xwidget-plist", Fset_xwidget_plist, Sset_xwidget_plist, 2, 2, 0, @@ -1183,6 +1870,166 @@ DEFUN ("xwidget-query-on-exit-flag", return (XXWIDGET (xwidget)->kill_without_query ? Qnil : Qt); } +DEFUN ("xwidget-webkit-search", Fxwidget_webkit_search, Sxwidget_webkit_search, + 2, 5, 0, + doc: /* Begin an incremental search operation in an xwidget. +QUERY should be a string containing the text to search for. XWIDGET +should be a WebKit xwidget where the search will take place. When the +search operation is complete, callers should also call +`xwidget-webkit-finish-search' to complete the search operation. + +CASE-INSENSITIVE, when non-nil, will cause the search to ignore the +case of characters inside QUERY. BACKWARDS, when non-nil, will cause +the search to proceed towards the beginning of the widget's contents. +WRAP-AROUND, when nil, will cause the search to stop upon hitting the +end of the widget's contents. + +It is OK to call this function even when a search is already in +progress. In that case, the previous search query will be replaced +with QUERY. */) + (Lisp_Object query, Lisp_Object xwidget, Lisp_Object case_insensitive, + Lisp_Object backwards, Lisp_Object wrap_around) +{ +#ifdef USE_GTK + WebKitWebView *webview; + WebKitFindController *controller; + WebKitFindOptions opt; + struct xwidget *xw; + gchar *g_query; +#endif + + CHECK_STRING (query); + CHECK_XWIDGET (xwidget); + +#ifdef USE_GTK + xw = XXWIDGET (xwidget); + webview = WEBKIT_WEB_VIEW (xw->widget_osr); + query = ENCODE_UTF_8 (query); + opt = WEBKIT_FIND_OPTIONS_NONE; + g_query = xstrdup (SSDATA (query)); + + if (!NILP (case_insensitive)) + opt |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; + if (!NILP (backwards)) + opt |= WEBKIT_FIND_OPTIONS_BACKWARDS; + if (!NILP (wrap_around)) + opt |= WEBKIT_FIND_OPTIONS_WRAP_AROUND; + + if (xw->find_text) + xfree (xw->find_text); + xw->find_text = g_query; + + block_input (); + controller = webkit_web_view_get_find_controller (webview); + webkit_find_controller_search (controller, g_query, opt, G_MAXUINT); + unblock_input (); +#endif + + return Qnil; +} + +DEFUN ("xwidget-webkit-next-result", Fxwidget_webkit_next_result, + Sxwidget_webkit_next_result, 1, 1, 0, + doc: /* Show the next result matching the current search query. + +XWIDGET should be an xwidget that currently has a search query. +Before calling this function, you should start a search operation +using `xwidget-webkit-search'. */) + (Lisp_Object xwidget) +{ + struct xwidget *xw; +#ifdef USE_GTK + WebKitWebView *webview; + WebKitFindController *controller; +#endif + + CHECK_XWIDGET (xwidget); + xw = XXWIDGET (xwidget); + + if (!xw->find_text) + error ("Widget has no ongoing search operation"); + +#ifdef USE_GTK + block_input (); + webview = WEBKIT_WEB_VIEW (xw->widget_osr); + controller = webkit_web_view_get_find_controller (webview); + webkit_find_controller_search_next (controller); + unblock_input (); +#endif + + return Qnil; +} + +DEFUN ("xwidget-webkit-previous-result", Fxwidget_webkit_previous_result, + Sxwidget_webkit_previous_result, 1, 1, 0, + doc: /* Show the previous result matching the current search query. + +XWIDGET should be an xwidget that currently has a search query. +Before calling this function, you should start a search operation +using `xwidget-webkit-search'. */) + (Lisp_Object xwidget) +{ + struct xwidget *xw; +#ifdef USE_GTK + WebKitWebView *webview; + WebKitFindController *controller; +#endif + + CHECK_XWIDGET (xwidget); + xw = XXWIDGET (xwidget); + + if (!xw->find_text) + error ("Widget has no ongoing search operation"); + +#ifdef USE_GTK + block_input (); + webview = WEBKIT_WEB_VIEW (xw->widget_osr); + controller = webkit_web_view_get_find_controller (webview); + webkit_find_controller_search_previous (controller); + + if (xw->find_text) + { + xfree (xw->find_text); + xw->find_text = NULL; + } + unblock_input (); +#endif + + return Qnil; +} + +DEFUN ("xwidget-webkit-finish-search", Fxwidget_webkit_finish_search, + Sxwidget_webkit_finish_search, 1, 1, 0, + doc: /* Finish XWIDGET's search operation. + +XWIDGET should be an xwidget that currently has a search query. +Before calling this function, you should start a search operation +using `xwidget-webkit-search'. */) + (Lisp_Object xwidget) +{ + struct xwidget *xw; +#ifdef USE_GTK + WebKitWebView *webview; + WebKitFindController *controller; +#endif + + CHECK_XWIDGET (xwidget); + xw = XXWIDGET (xwidget); + + if (!xw->find_text) + error ("Widget has no ongoing search operation"); + +#ifdef USE_GTK + block_input (); + webview = WEBKIT_WEB_VIEW (xw->widget_osr); + controller = webkit_web_view_get_find_controller (webview); + webkit_find_controller_search_finish (controller); + unblock_input (); +#endif + + return Qnil; +} + void syms_of_xwidget (void) { @@ -1215,6 +2062,12 @@ syms_of_xwidget (void) defsubr (&Sxwidget_plist); defsubr (&Sxwidget_buffer); defsubr (&Sset_xwidget_plist); + defsubr (&Sxwidget_perform_lispy_event); + defsubr (&Sxwidget_webkit_search); + defsubr (&Sxwidget_webkit_finish_search); + defsubr (&Sxwidget_webkit_next_result); + defsubr (&Sxwidget_webkit_previous_result); + defsubr (&Sset_xwidget_buffer); DEFSYM (QCxwidget, ":xwidget"); DEFSYM (QCtitle, ":title"); @@ -1236,6 +2089,15 @@ syms_of_xwidget (void) Vxwidget_view_list = Qnil; Fprovide (intern ("xwidget-internal"), Qnil); + + id_to_xwidget_map = CALLN (Fmake_hash_table, QCtest, Qeq); + staticpro (&id_to_xwidget_map); + +#ifdef USE_GTK + x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq); + + staticpro (&x_window_to_xwv_map); +#endif } @@ -1374,7 +2236,7 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix) /* The only call to xwidget_end_redisplay is in dispnew. xwidget_end_redisplay (w->current_matrix); */ struct xwidget_view *xv - = xwidget_view_lookup (glyph->u.xwidget, w); + = xwidget_view_lookup (xwidget_from_id (glyph->u.xwidget), w); #ifdef USE_GTK /* FIXME: Is it safe to assume xwidget_view_lookup always succeeds here? If so, this comment can be removed. @@ -1424,6 +2286,26 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix) } } +#ifdef USE_GTK +void +kill_frame_xwidget_views (struct frame *f) +{ + Lisp_Object rem = Qnil; + + for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail); + tail = XCDR (tail)) + { + if (XXWIDGET_VIEW (XCAR (tail))->frame == f) + rem = Fcons (XCAR (tail), rem); + } + + for (; CONSP (rem); rem = XCDR (rem)) + { + Fdelete_xwidget_view (XCAR (rem)); + } +} +#endif + /* Kill all xwidget in BUFFER. */ void kill_buffer_xwidgets (Lisp_Object buffer) @@ -1437,12 +2319,15 @@ kill_buffer_xwidgets (Lisp_Object buffer) { CHECK_XWIDGET (xwidget); struct xwidget *xw = XXWIDGET (xwidget); + Fremhash (make_fixnum (xw->xwidget_id), id_to_xwidget_map); #ifdef USE_GTK if (xw->widget_osr && xw->widgetwindow_osr) { gtk_widget_destroy (xw->widget_osr); gtk_widget_destroy (xw->widgetwindow_osr); } + if (xw->find_text) + xfree (xw->find_text); if (!NILP (xw->script_callbacks)) for (ptrdiff_t idx = 0; idx < ASIZE (xw->script_callbacks); idx++) { diff --git a/src/xwidget.h b/src/xwidget.h index 591f23489d..3bab6d5b00 100644 --- a/src/xwidget.h +++ b/src/xwidget.h @@ -32,6 +32,8 @@ #define XWIDGET_H_INCLUDED #if defined (USE_GTK) #include +#include +#include "xterm.h" #elif defined (NS_IMPL_COCOA) && defined (__OBJC__) #import #import "nsxwidget.h" @@ -59,11 +61,14 @@ #define XWIDGET_H_INCLUDED int height; int width; + uint32_t xwidget_id; #if defined (USE_GTK) /* For offscreen widgets, unused if not osr. */ GtkWidget *widget_osr; GtkWidget *widgetwindow_osr; + guint hit_result; + gchar *find_text; #elif defined (NS_IMPL_COCOA) # ifdef __OBJC__ /* For offscreen widgets, unused if not osr. */ @@ -98,9 +103,13 @@ #define XWIDGET_H_INCLUDED bool hidden; #if defined (USE_GTK) - GtkWidget *widget; - GtkWidget *widgetwindow; - GtkWidget *emacswindow; + Display *dpy; + Window wdesc; + Emacs_Cursor cursor; + struct frame *frame; + + cairo_surface_t *cr_surface; + cairo_t *cr_context; #elif defined (NS_IMPL_COCOA) # ifdef __OBJC__ XvWindow *xvWindow; @@ -162,6 +171,15 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view" void store_xwidget_js_callback_event (struct xwidget *xw, Lisp_Object proc, Lisp_Object argument); +struct xwidget_view *xwidget_view_from_window (Window wdesc); +void xwidget_expose (struct xwidget_view *xv); + +extern struct xwidget *xwidget_from_id (uint32_t id); +extern void kill_frame_xwidget_views (struct frame *f); +extern void xwidget_button (struct xwidget_view *, bool, int, + int, int, int, Time); +extern void xwidget_motion_or_crossing (struct xwidget_view *, + const XEvent *); #else INLINE_HEADER_BEGIN INLINE void syms_of_xwidget (void) {}