commit
a0096184b0
8 changed files with 37 additions and 534 deletions
@ -1,23 +0,0 @@ |
||||
## Run EAF with docker |
||||
|
||||
If you prefer to run linux in a container, you can use below command to build an EAF container. |
||||
|
||||
### Build |
||||
```Shell |
||||
docker build -t eaf --build-arg=_UID=$UID --build-arg=_USER=$USER . |
||||
``` |
||||
|
||||
### Run |
||||
```Shell |
||||
xhost +local:root # WARN: this comes with security issues |
||||
|
||||
docker run -e DISPLAY -e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus --rm -it -v /tmp/.X11-unix/:/tmp/.X11-unix -v ${DBUS_SESSION_BUS_ADDRESS#*=}/:/run/user/1000/bus/ -v ~/.Xauthority:/home/eaf/.Xauthority eaf |
||||
``` |
||||
You can also reuse your own Emacs configuration in the container: |
||||
|
||||
```Shell |
||||
xhost +local:root # WARN: this comes with security issues |
||||
|
||||
# mount the Emacs configuration into the container |
||||
docker run -e DISPLAY -e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus --rm -it -v /tmp/.X11-unix/:/tmp/.X11-unix -v ${DBUS_SESSION_BUS_ADDRESS#*=}/:/run/user/1000/bus/ -v ~/.Xauthority:/home/eaf/.Xauthority -v ~/.emacs.d:/home/eaf/.emacs.d eaf |
||||
``` |
||||
@ -1,281 +0,0 @@ |
||||
## The Framework of EAF |
||||
|
||||
 |
||||
|
||||
* QGraphicsScene similar Emacs' buffer, control content and state. |
||||
* QGraphicsView similar Emacs' window, control size and position. |
||||
* Every change in QGraphicsScene will synchronization to QGraphicsView in realtime by GPU composite. |
||||
* We use Xlib XReparent technology stick QGraphicsView to Emacs frame. |
||||
* Destroy QGraphicsView if emacs eaf-mode's window hide, create new QGraphicsView if eaf-mode window show, but QGraphicsScene still live in background until user kill eaf-mode buffer. |
||||
* When user use mouse click on QGraphicsView, QGraphicsView will translate mouse event coordinate and pass mouse event to QGraphicsScene to handle. |
||||
* When user use keyboard input in Emacs buffer, Emacs will key event to QGraphicsScene throught DBus IPC. |
||||
* Elisp call Python function through DBus method. |
||||
* Python call Elisp function through DBus signal. |
||||
|
||||
## Why choose Qt? |
||||
Qt's QGraphicsView and QGraphicsScene is awesome, it's easier to implement window composite than other GUI library (such as GTK+). |
||||
|
||||
If use Gtk+ or other GUI library, I need write many Widget/XComposite code to implement widget like QGraphicsView/QGraphicsScene. |
||||
|
||||
## Why choose Python? |
||||
C/C++ need compile long time for every change, this will interrupt my attention and enthusiasm of development. |
||||
|
||||
Python is a perfect language to develop Qt program and it can call pretty much in every library you need. |
||||
|
||||
## Let me run hello word |
||||
``` |
||||
M-x eaf-open-demo |
||||
``` |
||||
|
||||
This will pop hello world window in emacs like below: |
||||
|
||||
| Demo | |
||||
| :--------: | |
||||
| <img src="../screenshot/hello_world.png" width="600"> | |
||||
|
||||
|
||||
It's a big hello button, try to click it, haha. |
||||
|
||||
## Develop new plugin |
||||
It's very easy if you know how to write PyQt5 code. |
||||
|
||||
Here have awesome tutorial help you study PyQt5: http://zetcode.com/gui/pyqt5/ |
||||
|
||||
Trust me, PyQt5 is pretty easy to study. |
||||
|
||||
After you know how to write PyQt5 code, developing new plugin just needs 3 steps: |
||||
|
||||
1. Open file [buffer.py](app/demo/buffer.py): |
||||
```Python |
||||
from PyQt5.QtGui import QColor |
||||
from PyQt5.QtWidgets import QPushButton |
||||
from core.buffer import Buffer |
||||
|
||||
class AppBuffer(Buffer): |
||||
def __init__(self, buffer_id, url, arguments): |
||||
Buffer.__init__(self, buffer_id, url, arguments, True, QColor(0, 0, 0, 255)) |
||||
|
||||
self.add_widget(QPushButton("Hello, EAF hacker, it's work!!!")) |
||||
self.buffer_widget.setStyleSheet("font-size: 100px") |
||||
``` |
||||
|
||||
Replace QPushButton with your PyQt5 widget. |
||||
|
||||
* buffer_id and url are need by framework, you just need pass those paramaters to Buffer class |
||||
|
||||
* third paramater True mean application content will fit size with emacs window size change, such as image viewer. |
||||
|
||||
* third paramater False mean applicaton content won't fit size with emacs window size change, such as browser. |
||||
|
||||
* fourth paramater is background color to fill application background. |
||||
|
||||
2. Open file [eaf.el](core/eaf.el): |
||||
```Elisp |
||||
... |
||||
|
||||
(defun eaf-open (url &optional app-name) |
||||
(interactive "FOpen with EAF: ") |
||||
(unless app-name |
||||
(cond ((string-equal url "eaf-demo") |
||||
(setq app-name "demo")) |
||||
|
||||
... |
||||
``` |
||||
|
||||
Replace "eaf-demo" to "eaf rocks!" |
||||
|
||||
3. Test |
||||
``` |
||||
Execute command `eaf-stop-process' to kill old python process. |
||||
|
||||
Execute command `eaf-start-process' to start new python process. |
||||
|
||||
Execute command `eaf-open' and type "eaf rocks!". |
||||
``` |
||||
|
||||
|
||||
|
||||
See? It's so easy! |
||||
|
||||
Above are all you need, happy hacking! |
||||
|
||||
## Other APIs |
||||
|
||||
### Show message in emacs' minibuffer. |
||||
|
||||
If you want show message in emacs' minibuffer, you can emit AppBuffer's signal "message_to_emacs" like below: |
||||
|
||||
```Python |
||||
self.message_to_emacs.emit("hello from eaf") |
||||
``` |
||||
|
||||
### Set emacs variable on python side |
||||
|
||||
You can use below code to set emacs variable on python side: |
||||
|
||||
```Python |
||||
self.set_emacs_var.emit("var-name", "var-value") |
||||
``` |
||||
|
||||
### Eval elisp code on python side. |
||||
|
||||
Of course, eval any elisp code can implement by below method: |
||||
|
||||
```Python |
||||
self.eval_in_emacs.emit('''(message "hello")''') |
||||
``` |
||||
|
||||
### Read user's input |
||||
Below is code example from pdfviewer: |
||||
|
||||
```Python |
||||
... |
||||
|
||||
class AppBuffer(Buffer): |
||||
def __init__(self, buffer_id, url, arguments): |
||||
Buffer.__init__(self, buffer_id, url, arguments, False, QColor(0, 0, 0, 255)) |
||||
|
||||
self.add_widget(PdfViewerWidget(url, QColor(0, 0, 0, 255))) |
||||
self.buffer_widget.send_jump_page_message.connect(self.send_jump_page_message) |
||||
|
||||
def send_jump_page_message(self): |
||||
self.send_input_message("Jump to: ", "jump_page") |
||||
|
||||
def handle_input_message(self, result_type, result_content): |
||||
if result_type == "jump_page": |
||||
self.buffer_widget.jump_to_page(int(result_content)) |
||||
|
||||
def cacel_input_message(self, result_type): |
||||
if result_type == "jump_page": |
||||
... |
||||
|
||||
... |
||||
``` |
||||
If you want read input from emacs minibuffer then call back to python. |
||||
|
||||
You can emit buffer signal ```send_input_message```, first argument is prompt string to user, second argument is callback_type for interface ```handle_input_message```. |
||||
|
||||
After emacs read user input, framework will call interface ```handle_input_message```, result_type is callback_type you use in signal ```send_input_message```, result_content is input string from emacs. |
||||
|
||||
Simple logic is send ```send_input_message``` signal to emacs, then handle user input with buffer interface ```handle_input_message``` |
||||
|
||||
If user cancel input, such as press Ctrl + g, you can define your own ```cancel_input_message``` interface, write cancel callback for type. |
||||
|
||||
### Scroll by other window |
||||
In emacs, we usually call command ```scroll-other-window``` to scroll other window's buffer. |
||||
|
||||
If you want eaf application buffer respond scroll event to command "scroll-other-window". |
||||
|
||||
You need implement ```scroll``` interface in AppBuffer, such as like PDF Viewer does: |
||||
|
||||
```Python |
||||
def scroll(self, scroll_direction, scroll_type): |
||||
if scroll_type == "page": |
||||
if scroll_direction == "up": |
||||
self.buffer_widget.scroll_up_page() |
||||
else: |
||||
self.buffer_widget.scroll_down_page() |
||||
else: |
||||
if scroll_direction == "up": |
||||
self.buffer_widget.scroll_up() |
||||
else: |
||||
self.buffer_widget.scroll_down() |
||||
``` |
||||
|
||||
Argument "scroll_direction" is string, "up" mean scroll buffer up, "down" mean scroll buffer down. |
||||
|
||||
Argument "scroll_type" is string, "page" mean scroll buffer by page, "line" mean scroll buffer by line. |
||||
|
||||
### Save/Restore session |
||||
We always need save and restore session for an application, such as, save play position of the video player. |
||||
|
||||
You need implement interfaces ```save_session_data``` and ```restore_session_data```, below is an example of Vide Player does: |
||||
|
||||
|
||||
```Python |
||||
def save_session_data(self): |
||||
return str(self.buffer_widget.media_player.position()) |
||||
|
||||
def restore_session_data(self, session_data): |
||||
position = int(session_data) |
||||
self.buffer_widget.media_player.setPosition(position) |
||||
``` |
||||
|
||||
Argument "session_data" is string, you can put anything in it |
||||
|
||||
All session data save at ~/.emacs.d/eaf/session.json file. |
||||
|
||||
### Update buffer |
||||
If you need to update buffer sometimes, such as update org-file previewer after saving org-file. |
||||
|
||||
You need to implement the interface ```update_with_data```. Below is an example of what Org Previewer does: |
||||
|
||||
```Python |
||||
def update_with_data(self, update_data): |
||||
self.load_org_html_file() |
||||
self.buffer_widget.reload() |
||||
``` |
||||
|
||||
Argument "update_data" is passed from elisp side. |
||||
|
||||
### Update progress. |
||||
|
||||
If your application will do some long-time operation, you can use below use below interfaces of buffer: |
||||
|
||||
```Python |
||||
def start_progress(self): |
||||
def stop_progress(self): |
||||
def update_progress(self, progress): |
||||
``` |
||||
|
||||
### Customize variable at Elisp side. |
||||
You can use below code set the save patch of camera photo: |
||||
|
||||
```Elisp |
||||
(eaf-setq eaf-camera-save-path "~/Downloads") |
||||
``` |
||||
|
||||
On the python side, you can use below code pick up the value of ```eaf-camera-save-path``` |
||||
|
||||
```Python |
||||
self.emacs_var_dict["eaf-camera-save-path"] |
||||
``` |
||||
|
||||
Above is an example of ```eaf-camera-save-path```, you can customize any variable on elisp side actually, don't need modify python code to customize EAF application! |
||||
|
||||
### Call Python method and store function result to temp Elisp variable |
||||
|
||||
In EAF buffer have interface ```get_url``` |
||||
|
||||
```Python |
||||
def get_url(self): |
||||
return "" |
||||
``` |
||||
|
||||
At Elisp side, we can use below code call Python method and store function result to Elisp variable: |
||||
|
||||
```Elisp |
||||
(setq temp-var (eaf-call "call_function" eaf--buffer-id "get_url")) |
||||
``` |
||||
|
||||
Once you understand principle, you can define your own interface function in core/buffer.py , then use ```call_function``` method on Elisp side to fetch Python function result, don't need define temp elisp variable everywhere. |
||||
|
||||
### Update settings at Python side along with customize option change at Elisp side. |
||||
Once you change customize option by ```eaf-setq```, everytime EAF buffer is created, AppBuffer's interface ```update_settings``` will execute. |
||||
|
||||
You can implement your own ```update_settings``` interface, such as, we can write below ```update_settings``` in browser plugin: |
||||
|
||||
```Python |
||||
def update_settings(self): |
||||
settings = QWebEngineSettings.globalSettings() |
||||
try: |
||||
settings.setAttribute(QWebEngineSettings.PluginsEnabled, self.emacs_var_dict["eaf-browser-enable-plugin"] == "true") |
||||
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, self.emacs_var_dict["eaf-browser-enable-javascript"] == "true") |
||||
except Exception: |
||||
pass |
||||
|
||||
``` |
||||
|
||||
|
||||
## Todolist |
||||
[Some works you can hacking ;)](TODOLIST.md) |
||||
@ -1,87 +0,0 @@ |
||||
## All bindings below can be configured with the function eaf-bind-key. See README for more info |
||||
### Browser |
||||
|
||||
| Browser Key | Event | |
||||
| :-----: | :---- | |
||||
| Left Button | Open link in current tab | |
||||
| Ctrl + Left Button | Open link in new tab | |
||||
| Ctrl + Double Click | Use sdcv translate selected text | |
||||
| M-f | Forward page in history | |
||||
| M-b | Backward page in history | |
||||
| M-q | Delete all cookies | |
||||
| C-a | Move cursor to beginning of text | |
||||
| C-e | Move cursor to end of text | |
||||
| C-= | Zoom in | |
||||
| C-- | Zoom out | |
||||
| C-0 | Zoom reset | |
||||
| C-n | Scroll up | |
||||
| C-p | Scroll down | |
||||
| C-v | Scroll up page | |
||||
| C-s | Search forward | |
||||
| C-r | Search backward | |
||||
| M-v | Scroll down page | |
||||
| M-< | Scroll to top | |
||||
| M-> | Scroll to bottom | |
||||
|
||||
You can customize keys in the variable ```eaf-browser-keybinding``` |
||||
|
||||
### PDF Viewer |
||||
|
||||
| PDF Viewer Key | Event | |
||||
| :-----: | :---- | |
||||
| j | Scroll up | |
||||
| k | Scroll down | |
||||
| Space | Scroll up page | |
||||
| b | Scroll down page | |
||||
| , | Scroll to end | |
||||
| . | Scroll to home | |
||||
| t | Switch scale mode | |
||||
| - | Zoom out | |
||||
| = | Zoom in | |
||||
| 0 | Zoom reset | |
||||
| g | Goto page | |
||||
| p | Goto to percent | |
||||
| [ | Remember position | |
||||
| ] | Remember jump | |
||||
| i | Toggle inverted mode | |
||||
| m | Toggle mark link | |
||||
| s | Search text | |
||||
| n | Jump next match of search | |
||||
| N | Jump last match of search | |
||||
| Double Click | Use sdcv translate word under point | |
||||
|
||||
You can customize in the variable ```eaf-pdf-viewer-keybinding``` |
||||
|
||||
### Video Player |
||||
|
||||
| Video Player Key | Event | |
||||
| :-----: | :---- | |
||||
| Space | Play or Pause | |
||||
| h | Seek backward | |
||||
| l | Seek forward | |
||||
|
||||
You can customize keys in the variable ```eaf-video-player-keybinding``` |
||||
|
||||
### Image Viewer |
||||
|
||||
| Image Viewer Key | Event | |
||||
| :-----: | :---- | |
||||
| j | Load next image in current directory | |
||||
| k | Load previous image in current directory | |
||||
|
||||
You can customize keys in the variable ```eaf-image-viewer-keybinding``` |
||||
|
||||
### Terminal |
||||
|
||||
| Terminal Key | Event | |
||||
| :-----: | :---- | |
||||
| C-= | Zoom in | |
||||
| C-- | Zoom out | |
||||
|
||||
### Camera |
||||
|
||||
| Camera Key | Event | |
||||
| :-----: | :---- | |
||||
| j | Take photo | |
||||
|
||||
You can customize keys in the variable ```eaf-camera-keybinding``` |
||||
@ -1,14 +0,0 @@ |
||||
## Todo list |
||||
* Browser: use javascript popup youdao translate instead sdcv solution. |
||||
* Browser: implement js plugin like vimium |
||||
* Browser: study more code from https://github.com/LavaPower/Browthon-Old/blob/c25d5721b40e95131a3c521566a18467f13ecbf0/files/Browthon_elements.py |
||||
* ImageViewer: add zoom support |
||||
* PdfViewer: allow select text. |
||||
* PdfViewer: use self.document.getToC() fetch and render bookmark. |
||||
* Support HiDPI? I haven't HiDPI screen. |
||||
* Aria2 client is awesome, welcome to PR. |
||||
* Markdown Previewer, add margin around preview page. |
||||
* Maybe xterm.js running in browser is cool. ;) |
||||
* RSS Reader: use python feedparser or JavaScript library implement reader like Google Reader. |
||||
* Better error prompt if something goes wrong, maybe in the `*eaf*` buffer |
||||
* Make PDF-Viewer works with Org-Mode, like https://github.com/luhuaei/interleave/tree/add-eaf does. |
||||
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 43 KiB |
Loading…
Reference in new issue