You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
12 KiB
384 lines
12 KiB
;;; eaf.el --- Emacs application framework |
|
|
|
;; Filename: eaf.el |
|
;; Description: Emacs application framework |
|
;; Author: Andy Stewart <lazycat.manatee@gmail.com> |
|
;; Maintainer: Andy Stewart <lazycat.manatee@gmail.com> |
|
;; Copyright (C) 2018, Andy Stewart, all rights reserved. |
|
;; Created: 2018-06-15 14:10:12 |
|
;; Version: 0.1 |
|
;; Last-Updated: 2018-06-15 14:10:12 |
|
;; By: Andy Stewart |
|
;; URL: http://www.emacswiki.org/emacs/download/eaf.el |
|
;; Keywords: |
|
;; Compatibility: GNU Emacs 27.0.50 |
|
;; |
|
;; Features that might be required by this library: |
|
;; |
|
;; |
|
;; |
|
|
|
;;; This file is NOT part of GNU Emacs |
|
|
|
;;; License |
|
;; |
|
;; This program is free software; you can redistribute it and/or modify |
|
;; it under the terms of the GNU General Public License as published by |
|
;; the Free Software Foundation; either version 3, or (at your option) |
|
;; any later version. |
|
|
|
;; This program is distributed in the hope that it will be useful, |
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
;; GNU General Public License for more details. |
|
|
|
;; You should have received a copy of the GNU General Public License |
|
;; along with this program; see the file COPYING. If not, write to |
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth |
|
;; Floor, Boston, MA 02110-1301, USA. |
|
|
|
;;; Commentary: |
|
;; |
|
;; Emacs application framework |
|
;; |
|
|
|
;;; Installation: |
|
;; |
|
;; Put eaf.el to your load-path. |
|
;; The load-path is usually ~/elisp/. |
|
;; It's set in your ~/.emacs like this: |
|
;; (add-to-list 'load-path (expand-file-name "~/elisp")) |
|
;; |
|
;; And the following to your ~/.emacs startup file. |
|
;; |
|
;; (require 'eaf) |
|
;; |
|
;; No need more. |
|
|
|
;;; Customize: |
|
;; |
|
;; |
|
;; |
|
;; All of the above can customize by: |
|
;; M-x customize-group RET eaf RET |
|
;; |
|
|
|
;;; Change log: |
|
;; |
|
;; 2018/06/15 |
|
;; * First released. |
|
;; |
|
|
|
;;; Acknowledgements: |
|
;; |
|
;; |
|
;; |
|
|
|
;;; TODO |
|
;; |
|
;; |
|
;; |
|
|
|
;;; Require |
|
(require 'dbus) |
|
|
|
;;; Code: |
|
(defcustom eaf-mode-hook '() |
|
"Eaf mode hook." |
|
:type 'hook |
|
:group 'eaf-mode) |
|
|
|
(defvar eaf-mode-map |
|
(let ((map (make-sparse-keymap))) |
|
map) |
|
"Keymap used by `eaf-mode'.") |
|
|
|
(define-derived-mode eaf-mode text-mode "Eaf" |
|
(interactive) |
|
(kill-all-local-variables) |
|
(setq major-mode 'eaf-mode) |
|
(setq mode-name "EAF") |
|
(set (make-local-variable 'buffer-id) (eaf-generate-id)) |
|
(use-local-map eaf-mode-map) |
|
(run-hooks 'eaf-mode-hook)) |
|
|
|
(defvar eaf-python-file (expand-file-name "eaf.py" (concat (file-name-directory load-file-name) "core"))) |
|
|
|
(defvar eaf-process nil) |
|
|
|
(defvar eaf-first-start-url nil) |
|
|
|
(defvar eaf-title-length 30) |
|
|
|
(defcustom eaf-name "*eaf*" |
|
"Name of eaf buffer." |
|
:type 'string |
|
:group 'eaf) |
|
|
|
(defun eaf-call (method &rest args) |
|
(apply 'dbus-call-method |
|
:session ; use the session (not system) bus |
|
"com.lazycat.eaf" ; service name |
|
"/com/lazycat/eaf" ; path name |
|
"com.lazycat.eaf" ; interface name |
|
method args)) |
|
|
|
(defun eaf-get-emacs-xid () |
|
(frame-parameter nil 'window-id)) |
|
|
|
(defun eaf-start-process () |
|
(interactive) |
|
(if (process-live-p eaf-process) |
|
(message "EAF process has started.") |
|
(setq eaf-process |
|
(apply 'start-process |
|
eaf-name |
|
eaf-name |
|
"python" (append (list eaf-python-file (eaf-get-emacs-xid)) (eaf-get-render-size)) |
|
)) |
|
(set-process-query-on-exit-flag eaf-process nil) |
|
(set-process-sentinel |
|
eaf-process |
|
#'(lambda (process event) |
|
(message (format "%s %s" process event)) |
|
)) |
|
(message "EAF process starting..."))) |
|
|
|
(defun eaf-stop-process () |
|
(interactive) |
|
(if (process-live-p eaf-process) |
|
;; Delete eaf server process. |
|
(delete-process eaf-process) |
|
(message "EAF process has dead.")) |
|
(let ((current-buf (current-buffer)) |
|
(count 0)) |
|
(dolist (buffer (buffer-list)) |
|
(set-buffer buffer) |
|
(when (equal major-mode 'eaf-mode) |
|
(incf count) |
|
(kill-buffer buffer))) |
|
;; Just report to me when eaf buffer exists. |
|
(if (> count 1) |
|
(message "Killed EAF %s buffer%s" count (if (> count 1) "s" ""))))) |
|
|
|
(defun eaf-restart-process () |
|
(interactive) |
|
(eaf-stop-process) |
|
(eaf-start-process)) |
|
|
|
(defun eaf-get-render-size () |
|
"Get allocation for render application in backend. |
|
We need calcuate render allocation to make sure no black border around render content." |
|
(let* (;; We use `window-inside-pixel-edges' and `window-absolute-pixel-edges' calcuate height of window header, such as tabbar. |
|
(window-header-height (- (nth 1 (window-inside-pixel-edges)) (nth 1 (window-absolute-pixel-edges)))) |
|
(width (frame-pixel-width)) |
|
;; Render height should minus mode-line height, minibuffer height, header height. |
|
(height (- (frame-pixel-height) (window-mode-line-height) (window-pixel-height (minibuffer-window)) window-header-height))) |
|
(mapcar (lambda (x) (format "%s" x)) (list width height)))) |
|
|
|
(defun eaf-get-window-allocation (&optional window) |
|
(let* ((window-edges (window-inside-pixel-edges window)) |
|
(x (nth 0 window-edges)) |
|
(y (nth 1 window-edges)) |
|
(w (- (nth 2 window-edges) x)) |
|
(h (- (nth 3 window-edges) y)) |
|
) |
|
(list x y w h))) |
|
|
|
(defun eaf-generate-id () |
|
(format "%04x%04x-%04x-%04x-%04x-%06x%06x" |
|
(random (expt 16 4)) |
|
(random (expt 16 4)) |
|
(random (expt 16 4)) |
|
(random (expt 16 4)) |
|
(random (expt 16 4)) |
|
(random (expt 16 6)) |
|
(random (expt 16 6)) )) |
|
|
|
(defun eaf-create-buffer (input-content) |
|
(let ((eaf-buffer (generate-new-buffer (truncate-string-to-width input-content eaf-title-length)))) |
|
(with-current-buffer eaf-buffer |
|
(eaf-mode) |
|
(read-only-mode) |
|
) |
|
eaf-buffer)) |
|
|
|
(defun eaf-is-support (url) |
|
(dbus-call-method |
|
:session "com.lazycat.eaf" |
|
"/com/lazycat/eaf" |
|
"com.lazycat.eaf" |
|
"is_support" |
|
url)) |
|
|
|
(defun eaf-monitor-configuration-change (&rest _) |
|
(ignore-errors |
|
(let (view-infos) |
|
(dolist (window (window-list)) |
|
(let ((buffer (window-buffer window))) |
|
(with-current-buffer buffer |
|
(if (string= "eaf-mode" (format "%s" major-mode)) |
|
(let* ((window-allocation (eaf-get-window-allocation window)) |
|
(x (nth 0 window-allocation)) |
|
(y (nth 1 window-allocation)) |
|
(w (nth 2 window-allocation)) |
|
(h (nth 3 window-allocation)) |
|
) |
|
(add-to-list 'view-infos (format "%s:%s:%s:%s:%s" buffer-id x y w h)) |
|
))))) |
|
;; I don't know how to make emacs send dbus-message with two-dimensional list. |
|
;; So i package two-dimensional list in string, then unpack on server side. ;) |
|
(eaf-call "update_views" (mapconcat 'identity view-infos ",")) |
|
))) |
|
|
|
(defun eaf-monitor-buffer-kill () |
|
(ignore-errors |
|
(with-current-buffer (buffer-name) |
|
(when (string= "eaf-mode" (format "%s" major-mode)) |
|
(eaf-call "kill_buffer" buffer-id) |
|
(message (format "Kill %s" buffer-id)) |
|
)))) |
|
|
|
(defun eaf-monitor-key-event () |
|
(ignore-errors |
|
(with-current-buffer (buffer-name) |
|
(when (string= "eaf-mode" (format "%s" major-mode)) |
|
(let* ((event last-command-event) |
|
(key (make-vector 1 event)) |
|
(key-command (format "%s" (key-binding key))) |
|
(key-desc (key-description key)) |
|
) |
|
(cond |
|
;; Just send event when user insert single character. |
|
;; Don't send event 'M' if user press Ctrl + M. |
|
((and |
|
(or |
|
(equal key-command "self-insert-command") |
|
(equal key-command "completion-select-if-within-overlay") |
|
) |
|
(equal 1 (string-width (this-command-keys)))) |
|
(message (format "Send char: '%s" key-desc)) |
|
(eaf-call "send_key" (format "%s:%s" buffer-id key-desc))) |
|
((or |
|
(equal key-command "nil") |
|
(equal key-desc "RET") |
|
(equal key-desc "DEL") |
|
(equal key-desc "TAB") |
|
(equal key-desc "<home>") |
|
(equal key-desc "<end>") |
|
(equal key-desc "<left>") |
|
(equal key-desc "<right>") |
|
(equal key-desc "<up>") |
|
(equal key-desc "<down>") |
|
(equal key-desc "<prior>") |
|
(equal key-desc "<next>") |
|
) |
|
(message (format "Send: '%s" key-desc)) |
|
(eaf-call "send_key" (format "%s:%s" buffer-id key-desc)) |
|
) |
|
(t |
|
(unless (or |
|
(equal key-command "keyboard-quit") |
|
(equal key-command "kill-this-buffer") |
|
(equal key-command "eaf-open")) |
|
(ignore-errors (call-interactively (key-binding key)))) |
|
(message (format "Got command: %s" key-command))))) |
|
;; Set `last-command-event' with nil, emacs won't notify me buffer is ready-only, |
|
;; because i insert nothing in buffer. |
|
(setq last-command-event nil) |
|
)))) |
|
|
|
(defun eaf-focus-buffer (msg) |
|
(let* ((coordinate-list (split-string msg ",")) |
|
(mouse-press-x (string-to-number (nth 0 coordinate-list))) |
|
(mouse-press-y (string-to-number (nth 1 coordinate-list)))) |
|
(catch 'find-window |
|
(dolist (window (window-list)) |
|
(let ((buffer (window-buffer window))) |
|
(with-current-buffer buffer |
|
(if (string= "eaf-mode" (format "%s" major-mode)) |
|
(let* ((window-allocation (eaf-get-window-allocation window)) |
|
(x (nth 0 window-allocation)) |
|
(y (nth 1 window-allocation)) |
|
(w (nth 2 window-allocation)) |
|
(h (nth 3 window-allocation)) |
|
) |
|
(when (and |
|
(> mouse-press-x x) |
|
(< mouse-press-x (+ x w)) |
|
(> mouse-press-y y) |
|
(< mouse-press-y (+ y h))) |
|
(select-window window) |
|
(throw 'find-window t) |
|
) |
|
)))))))) |
|
|
|
(dbus-register-signal |
|
:session "com.lazycat.eaf" "/com/lazycat/eaf" |
|
"com.lazycat.eaf" "focus_emacs_buffer" |
|
'eaf-focus-buffer) |
|
|
|
(defun eaf-start-finish () |
|
;; Call `eaf-open-internal' after receive `start_finish' signal from server process. |
|
(eaf-open-internal eaf-first-start-url)) |
|
|
|
(dbus-register-signal |
|
:session "com.lazycat.eaf" "/com/lazycat/eaf" |
|
"com.lazycat.eaf" "start_finish" |
|
'eaf-start-finish) |
|
|
|
(defun eaf-update-buffer-title (bid title) |
|
(when (> (length title) 0) |
|
(catch 'find-buffer |
|
(dolist (window (window-list)) |
|
(let ((buffer (window-buffer window))) |
|
(with-current-buffer buffer |
|
(when (and |
|
(string= "eaf-mode" (format "%s" major-mode)) |
|
(equal buffer-id bid)) |
|
(rename-buffer title) |
|
(throw 'find-buffer t) |
|
))))))) |
|
|
|
(dbus-register-signal |
|
:session "com.lazycat.eaf" "/com/lazycat/eaf" |
|
"com.lazycat.eaf" "update_buffer_title" |
|
'eaf-update-buffer-title) |
|
|
|
(defun eaf-open-buffer-url (url) |
|
(eaf-open url)) |
|
|
|
(dbus-register-signal |
|
:session "com.lazycat.eaf" "/com/lazycat/eaf" |
|
"com.lazycat.eaf" "open_buffer_url" |
|
'eaf-open-buffer-url) |
|
|
|
(add-hook 'window-configuration-change-hook #'eaf-monitor-configuration-change) |
|
(add-hook 'pre-command-hook #'eaf-monitor-key-event) |
|
(add-hook 'kill-buffer-hook #'eaf-monitor-buffer-kill) |
|
|
|
(defun eaf-open-internal (url) |
|
(let* ((buffer (eaf-create-buffer url)) |
|
buffer-result) |
|
(with-current-buffer buffer |
|
(setq buffer-result (eaf-call "new_buffer" buffer-id url))) |
|
(if (equal buffer-result "") |
|
;; Switch to new buffer if buffer create successful. |
|
(switch-to-buffer buffer) |
|
;; Kill buffer and show error message from python server. |
|
(kill-buffer buffer) |
|
(message buffer-result)) |
|
)) |
|
|
|
(defun eaf-open (url) |
|
(interactive "FOpen with EAF: ") |
|
(if (process-live-p eaf-process) |
|
;; Call `eaf-open-internal' directly if server process has start. |
|
(eaf-open-internal url) |
|
;; Record user input, and call `eaf-open-internal' after receive `start_finish' signal from server process. |
|
(setq eaf-first-start-url url) |
|
(eaf-start-process))) |
|
|
|
(provide 'eaf) |
|
|
|
;;; eaf.el ends here
|
|
|