Merge pull request #172 from MatthewZMD/readme

Remove duplicate information already on Wiki
master
Mingde Zeng 6 years ago committed by GitHub
commit a0096184b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 164
      README.md
  2. 23
      docker/README.md
  3. 281
      docs/HACKING.md
  4. 87
      docs/KEYBINDING.md
  5. 14
      docs/TODOLIST.md
  6. 2
      eaf.el
  7. BIN
      screenshot/framework.png
  8. BIN
      screenshot/hello_world.png

@ -40,74 +40,10 @@ This framework mainly implements three functions:
| <img src="./screenshot/terminal.png" width="400"> |
| |
## Installation
1. Install python dependences:
```Bash
sudo pip3 install dbus-python pymupdf grip qrcode python-xlib pyqt5 pyqtwebengine
```
2. Install QTermWidget's Python binding:
```Bash
git clone https://github.com/lxqt/qtermwidget.git --depth=1
mkdir build && cd build
cmake .. -DQTERMWIDGET_BUILD_PYTHON_BINDING=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr
make
sudo make install
```
3. Clone this repository and add below code in `.emacs`
```Elisp
(require 'eaf)
```
A `use-package` sample configuration
```Elisp
(use-package eaf
:load-path "~/.emacs.d/site-lisp/emacs-application-framework"
:custom
(eaf-find-alternate-file-in-dired t)
:config
(eaf-bind-key scroll_up "RET" eaf-pdf-viewer-keybinding)
(eaf-bind-key scroll_down_page "DEL" eaf-pdf-viewer-keybinding)
(eaf-bind-key scroll_up "C-n" eaf-pdf-viewer-keybinding)
(eaf-bind-key scroll_down "C-p" eaf-pdf-viewer-keybinding)
(eaf-bind-key take_photo "p" eaf-camera-keybinding)
(defun eaf-open-google ()
"Open Google using EAF."
(interactive)
(eaf-open-browser "https://www.google.com")))
```
### Package Description
| Debian Package | Package Repo | Use for |
| :-------- | :-------- | :---- |
| dbus-python | pip3 | DBus IPC for python and elisp |
| pymupdf | pip3 | Render engine required for PDF Viewer |
| grip | pip3 | Markdown render server for Markdown Previewer |
| qrcode | pip3 | Render local file QR code |
| python-xlib | pip3 | Stick app window into emacs frame |
| pyqt5 | pip3 | GUI library required for application development |
| pyqtwebengine | pip3 | QtWebEngine for browser application |
| qtermwidget-git | source code compile | QTermWidget is terminal emulator for PyQt5 |
### You can choose to run EAF with docker@
If you prefer to run linux in a docker, you can read [Run EAF with docker](./docker/README.md)
### Why this awesome framework doesn't works with MacOS or Windows?
There are mainly three obstacles:
1. I can't make dbus/python-dbus works on MacOS High Sierra
2. This framework need use X11 reparent to stick Qt5 window to emacs frame, but I don't know how to make X11 works on MacOS.
3. Qt5 QGraphicsView/QGraphicsScene can't work MacOS, specify QGraphicsVideoItem can't work.
4. If you figure them out, PR always welcome
## Usage
## Getting Started
Please read the [Wiki](https://github.com/manateelazycat/emacs-application-framework/wiki) for instructions on how to install and setup EAF.
## Launch EAF Applications
| Application Name | Launch |
| :-------- | :---- |
| Browser | Type 'eaf-browser' RET https://www.google.com |
@ -124,94 +60,66 @@ There are mainly three obstacles:
| Airshare | Type 'eaf-file-transfer-airshare' |
| Demo | Type 'eaf-open-demo' |
Please check [Key binding](./docs/KEYBINDING.md) to check keybinding of application.
To run `eaf-open` on the current file under the cursor in `dired`, call `eaf-open-this-from-dired`.
```
NOTE:
EAF use DBus' session bus, it must running in general user.
EAF use DBus' session bus, it must run in general user.
Please don't run EAF with root user, root user just can access DBus's system bus.
```
## Settings
## FAQ and Support
### Keybindings
There are default keybindings for each EAF application provided by us. If you want to see them all, execute `(describe-mode)` or `C-h m` within an EAF buffer.
### Read the [Wiki](https://github.com/manateelazycat/emacs-application-framework/wiki) First
For any installation and configuration assistance, please read the [Wiki](https://github.com/manateelazycat/emacs-application-framework/wiki) first!
You can easily customize EAF keybindings, find the corresponding keybinding variable, and add the something like the following to `.emacs`
```Elisp
(eaf-bind-key scroll_up "C-n" eaf-pdf-viewer-keybinding)
(eaf-bind-key scroll_down "C-p" eaf-pdf-viewer-keybinding)
(eaf-bind-key take_photo "p" eaf-camera-keybinding)
```
### How about EXWM? What makes EAF special?
1. EAF gives you control over your program, while satisfying Emacs window design model. [EXWM](https://github.com/ch11ng/exwm) is only a tiling WM, that combines different applications together in an Emacs-like fashion. However, EXWM is unable to split the same application into two different windows while displaying different same application at the same time. For example, EAF is able to display same PDF on two different windows.
2. EAF essentially provides Emacs a secondary scripting language ([this topic had been brought up again in EmacsConf2019](https://media.emacsconf.org/2019/26.html) and [reddit](https://www.reddit.com/r/emacs/comments/e1wfoe/emacs_the_editor_for_the_next_40_years/)). Emacs Lisp doesn't render graphics very well, especially it doesn't play nicely with the browser. This is (an example of) where PyQt5 can come in handy.
3. With DBus IPC, EAF can use Python to control Emacs Lisp, conversely also true that Emacs Lisp can control Qt rendering and Python code.
4. EXWM, as a Windows Manager, does its job very well. Therefore, it doesn't have control and doesn't care at all how other program functions. For example, EXWM cannot control keyboard events of other programs. On the other hand, you can configure them in EAF either using existing features (see above) or write code to contribute to this repository.
5. From a higher point of view, EAF is using Emacs' design principles to extend GUI programs. You have the ability to control good GUI programs using Emacs keybindings. To achieve the ultimate goal: live in Emacs ;)
### Why this awesome framework doesn't works with MacOS or Windows?
There are mainly three obstacles:
1. We don't use MacOS or Windows
2. This framework need use X11 reparent to stick Qt5 window to emacs frame, but had trouble making X11 to work on MacOS.
3. Had trouble making dbus/python-dbus work on MacOS High Sierra
4. Had trouble making Qt5 QGraphicsView/QGraphicsScene work on MacOS, specifically QGraphicsVideoItem cannot work.
5. If you figure them out, PRs always welcome
Currently available keybinding variables are `eaf-browser-keybinding`, `eaf-pdf-viewer-keybinding`, `eaf-video-player-keybinding`, `eaf-image-viewer-keybinding`, `eaf-camera-keybinding`, `eaf-terminal-keybinding`.
### Why not support Wayland?
EAF use X11 XReparent technology to stick Qt5 window on Emacs buffer area, Wayland doesn't not support XReparent.
### Variable Customization
There are certain variables will be shared across Emacs Lisp and Python. You can easily configure then with `eaf-setq`.
We recommend our users to use KDE, it's stable enough and supports X11 XReparent technology.
Check the full list of configurable variables with `C-h v eaf-var-list`.
### Github Personal Access Tokens?
If you use EAF Markdown Previewer, you need the access to a [Personal access token](https://github.com/settings/tokens/new?scopes=), fill something in "Token description" and click button "Generate token" to get your personal token, then set token with code:
#### EAF Browser
To set EAF Browser as your default browser, put the following in your `.emacs`
```Elisp
(setq browse-url-browser-function 'eaf-open-browser)
(defalias 'browse-web 'eaf-open-browser)
(setq eaf-grip-token "yourtokencode")
```
Default browser enable plugin and JavaScript, to disable them, you can add something like the following to `.emacs`
Otherwise, github will popup "times limit" error because so many people use grip. ;)
```Elisp
(eaf-setq eaf-browser-enable-plugin "false")
(eaf-setq eaf-browser-enable-javascript "false")
```
### "undefined symbol" error
If you got "undefined symbol" error after start EAF, and you use Arch Linux, yes, it's a bug of Arch.
#### EAF Camera
The default directory to store images taken by EAF Camera is `~/Downloads`. To modify it, add the something like the following to `.emacs`
You need use pip install all dependences after you upgrade your Arch system, then undefine symbol error will fix.
```Elisp
(eaf-setq eaf-camera-save-path "new/path/")
```
### \*eaf* aborted
If you got ```*eaf* aborted``` error, please check buffer ```*eaf*``` first, mostly because Python library dependencies is not installed successfully.
### Proxy
If you can't access most awesome internet services like me, you perhaps need proxy settings like below:
If you can't access most awesome internet services like me, you can configure the proxy settings.
```Elisp
(setq eaf-http-proxy-host "127.0.0.1")
(setq eaf-http-proxy-port "1080")
```
Then EAF browser is working! ;)
### Markdown Previewer
If you use markdown previewer, you need the access to a [Personal access token](https://github.com/settings/tokens/new?scopes=), fill something in "Token description" and click button "Generate token" to get your personal token, then set token with code:
```Elisp
(setq eaf-grip-token "yourtokencode")
```
Otherwise, github will popup "times limit" error because so many people use grip. ;)
## FAQ
### How about EXWM? What makes EAF special?
1. EAF gives you control over your program, while satisfying Emacs window design model. [EXWM](https://github.com/ch11ng/exwm) is only a tiling WM, that combines different applications together in an Emacs-like fashion. However, EXWM is unable to split the same application into two different windows while displaying different parts of the same application at the same time. For example, EAF is able to display different pages of the same PDF on two different windows.
2. EAF essentially provides Emacs a secondary scripting language ([This topic has been brought up in EmacsConf2019](https://media.emacsconf.org/2019/26.html) and [reddit](https://www.reddit.com/r/emacs/comments/e1wfoe/emacs_the_editor_for_the_next_40_years/)). Emacs Lisp doesn't render graphics very well, especially it doesn't play nicely with the browser. This is (an example of) where PyQt5 can come in handy.
3. With DBus IPC, EAF can use Python to control Emacs Lisp, conversely also true that Emacs Lisp can control Qt rendering and Python code.
4. EXWM, as a Windows Manager, does its job very well. Therefore, it doesn't have control and doesn't care at all how other program functions. For example, EXWM cannot control keyboard events of other programs. On the other hand, you can configure them in EAF either using existing features (see above) or write code to contribute to this repository.
5. From a higher point of view, EAF is using Emacs' design principles to extend GUI programs. You have the ability to control good GUI programs using Emacs keybindings. To achieve the ultimate goal: live in Emacs ;)
### Why not support Wayland?
EAF use X11 XReparent technology to stick Qt5 window on Emacs buffer area, Wayland not support XReparent.
I recommend you use KDE, it's stable enough and support X11 XReparent technology.
### "undefined symbol" error
If you got "undefined symbol" error after start EAF, and you use Arch Linux, yes, it's a bug of Arch.
You need use pip install all dependences after you upgrade your Arch system, then undefine symbol error will fix.
## Report bug
If you got ```*eaf* aborted``` error, please check buffer ```*eaf*``` first, mostly because Python library dependencies is not installed successfully.
If you have any problem with EAF, please use command "emacs -Q" to start Emacs without any customizations.

@ -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
![img](../screenshot/framework.png)
* 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.

@ -7,7 +7,7 @@
;; Copyright (C) 2018, Andy Stewart, all rights reserved.
;; Created: 2018-06-15 14:10:12
;; Version: 0.5
;; Last-Updated: Fri Dec 20 02:42:51 2019 (-0500)
;; Last-Updated: Fri Dec 20 03:26:28 2019 (-0500)
;; By: Mingde (Matthew) Zeng
;; URL: http://www.emacswiki.org/emacs/download/eaf.el
;; Keywords:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Loading…
Cancel
Save