Skip to content

Commit

Permalink
Use QGraphicsView and QGraphicsSecene implement window compositer, ap…
Browse files Browse the repository at this point in the history
…proaching hardware performance. Yow!
  • Loading branch information
manateelazycat committed Jun 25, 2018
1 parent 363d4ab commit 3ceda23
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 146 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -115,7 +115,7 @@ from app.foo.buffer import FooBuffer
* Browser: support pop window, such as emacs-china.org
* Browser: add progressbar
* ImageViewer: add zoom support
* VideoPlayer: use ffmpeg implement video player, mpv/vlc can't work with QWidget.render and slow, XComposite must need main window is show before composite to other window.
* VideoPlayer: use ffmpeg implement QGraphicsItem

## Contact me

Expand Down
5 changes: 4 additions & 1 deletion app/browser/buffer.py
Expand Up @@ -31,13 +31,16 @@ class BrowserBuffer(Buffer):
def __init__(self, buffer_id, url, width, height):
Buffer.__init__(self, buffer_id, url, width, height, QColor(255, 255, 255, 255))

self.buffer_widget = BrowserWidget()
self.add_widget(BrowserWidget())
# self.buffer_widget = BrowserWidget()
self.buffer_widget.resize(self.width, self.height)
self.buffer_widget.setUrl(QUrl(url))

self.buffer_widget.titleChanged.connect(self.change_title)
self.buffer_widget.web_page.open_url_in_new_tab.connect(self.open_url)

self.fit_to_view = False

print("Create buffer: %s" % buffer_id)

def resize_buffer(self, width, height):
Expand Down
5 changes: 4 additions & 1 deletion app/imageviewer/buffer.py
Expand Up @@ -30,11 +30,14 @@ class ImageViewerBuffer(Buffer):
def __init__(self, buffer_id, url, width, height):
Buffer.__init__(self, buffer_id, url, width, height, QColor(0, 0, 0, 255))

self.buffer_widget = ImageViewerWidget(url, QColor(0, 0, 0, 255))
self.add_widget(ImageViewerWidget(url, QColor(0, 0, 0, 255)))
# self.buffer_widget = ImageViewerWidget(url, QColor(0, 0, 0, 255))
self.buffer_widget.resize(self.width, self.height)

self.buffer_widget.render_image.connect(self.change_title)

self.fit_to_view = True

def resize_buffer(self, width, height):
self.width = width
self.height = height
Expand Down
7 changes: 5 additions & 2 deletions app/videoplayer/buffer.py
Expand Up @@ -58,12 +58,15 @@

class VideoPlayerBuffer(Buffer):
def __init__(self, buffer_id, url, width, height):
Buffer.__init__(self, buffer_id, url, width, height, QColor(255, 255, 255, 255))
Buffer.__init__(self, buffer_id, url, width, height, QColor(0, 0, 0, 255))

self.buffer_widget = QWebView()
self.add_widget(QWebView())
# self.buffer_widget = QWebView()
self.buffer_widget.resize(self.width, self.height)
self.buffer_widget.setHtml(html_file.replace("**********", url))

self.fit_to_view = True

print("Create buffer: %s" % buffer_id)

def resize_buffer(self, width, height):
Expand Down
26 changes: 12 additions & 14 deletions core/buffer.py
Expand Up @@ -20,7 +20,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from PyQt5 import QtCore
from PyQt5.QtGui import QImage
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import QGraphicsScene
import functools
import abc

Expand Down Expand Up @@ -51,26 +52,33 @@ def on_signal_received(self, args, kwargs):
else:
self._func(*args, **kwargs)

class Buffer(QtCore.QObject):
class Buffer(QGraphicsScene):
__metaclass__ = abc.ABCMeta

update_title = QtCore.pyqtSignal(str, str)
open_url = QtCore.pyqtSignal(str)
before_destroy_hook = QtCore.pyqtSignal()

def __init__(self, buffer_id, url, width, height, background_color):
super(Buffer, self).__init__()
super(QGraphicsScene, self).__init__()

self.width = width
self.height = height

self.buffer_id = buffer_id
self.url = url

self.qimage = None
self.buffer_widget = None
self.background_color = background_color

self.setBackgroundBrush(QBrush(self.background_color))

self.fit_to_view = True

def add_widget(self, widget):
self.buffer_widget = widget
self.addWidget(self.buffer_widget)

def resize_buffer(self, width, height):
pass

Expand All @@ -80,17 +88,7 @@ def handle_destroy(self):
if self.buffer_widget != None:
self.buffer_widget.destroy()

if self.qimage != None:
del self.qimage

print("Destroy buffer: %s" % self.buffer_id)

@PostGui()
def update_content(self):
if self.buffer_widget != None:
qimage = QImage(self.width, self.height, QImage.Format_ARGB32)
self.buffer_widget.render(qimage)
self.qimage = qimage

def change_title(self, title):
self.update_title.emit(self.buffer_id, title)
121 changes: 23 additions & 98 deletions core/eaf.py
Expand Up @@ -19,16 +19,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from PyQt5.QtCore import QEvent, QPointF
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from dbus.mainloop.glib import DBusGMainLoop
from fake_key_event import fake_key_event
from view import View
import dbus
import dbus.service
import threading
import time

import os,sys,inspect
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
Expand Down Expand Up @@ -58,6 +54,7 @@ def __init__(self, args):

@dbus.service.method(EAF_DBUS_NAME, in_signature="ss", out_signature="s")
def new_buffer(self, buffer_id, url):

global emacs_width, emacs_height

if url.startswith("/"):
Expand Down Expand Up @@ -97,12 +94,32 @@ def update_views(self, args):
if view_infos != ['']:
for view_info in view_infos:
if view_info not in self.view_dict:
view = View(emacs_xid, view_info)
(buffer_id, _, _, _, _) = view_info.split(":")
view = View(emacs_xid, self.buffer_dict[buffer_id], view_info)
self.view_dict[view_info] = view

view.trigger_mouse_event.connect(self.send_mouse_event_to_buffer)
view.trigger_focus_event.connect(self.focus_emacs_buffer)

# Update buffer size.
for buffer in list(self.buffer_dict.values()):
# Get match views.
match_views = list(filter(lambda v: view.py.buffer_id == buffer.buffer_id, self.view_dict.values()))

# Get size list of buffer's views.
view_sizes = list(map(lambda v: (v.width, v.height), match_views))

# Init buffer size with emacs' size.
buffer_width = emacs_width
buffer_height = emacs_height

# Update buffer size with max area view's size,
# to make each view has the same rendering area after user do split operation in emacs.
if len(view_sizes) > 0:
buffer_width, buffer_height = max(view_sizes, key=lambda size: size[0] * size[1])

# Resize buffer.
buffer.resize_buffer(buffer_width, buffer_height)

@dbus.service.method(EAF_DBUS_NAME, in_signature="s", out_signature="")
def kill_buffer(self, buffer_id):
# Kill all view base on buffer_id.
Expand Down Expand Up @@ -148,96 +165,6 @@ def create_buffer(self, buffer_id, app_buffer):

app_buffer.open_url.connect(self.open_buffer_url)

def send_mouse_event_to_buffer(self, buffer_id, view_width, view_height, view_image_width, view_image_height, event):
print("Send mouse: %s %s" % (buffer_id, event))

global emacs_xid

if buffer_id in self.buffer_dict:
if event.type() in [QEvent.MouseButtonPress, QEvent.MouseButtonRelease,
QEvent.MouseMove, QEvent.MouseButtonDblClick]:
# Get view render coordinate.
view_render_x = (view_width - view_image_width) / 2
view_render_y = (view_height - view_image_height) / 2

# Just send event if response in view image area.
if (event.x() >= view_render_x) and (event.x() <= view_render_x + view_image_width) and (event.y() >= view_render_y) and (event.y() <= view_render_y + view_image_height):
view_sizes = list(map(lambda v: (v.width, v.height), self.view_dict.values()))

buffer_width = emacs_width
buffer_height = emacs_height

if len(view_sizes) > 0:
buffer_width, buffer_height = max(view_sizes, key=lambda size: size[0] * size[1])

width_scale = view_width * 1.0 / buffer_width
height_scale = view_height * 1.0 / buffer_height
image_scale = 1.0
if width_scale < height_scale:
image_scale = width_scale
else:
image_scale = height_scale

new_event_x = (event.x() - view_render_x) / image_scale
new_event_y = (event.y() - view_render_y) / image_scale
new_event_pos = QPointF(new_event_x, new_event_y)

new_event = QMouseEvent(event.type(),
new_event_pos,
event.button(),
event.buttons(),
event.modifiers())

QApplication.sendEvent(self.buffer_dict[buffer_id].buffer_widget, new_event)
else:
print("Do not send event, because event out of view's response area")
else:
QApplication.sendEvent(self.buffer_dict[buffer_id].buffer_widget, event)

def update_buffers(self):
global emacs_width, emacs_height

while True:
for buffer in list(self.buffer_dict.values()):
# Get size list of buffer's views.
view_sizes = list(map(lambda v: (v.width, v.height), self.view_dict.values()))

# Init buffer size with emacs' size.
buffer_width = emacs_width
buffer_height = emacs_height

# Update buffer size with max area view's size,
# to make each view has the same rendering area after user do split operation in emacs.
if len(view_sizes) > 0:
buffer_width, buffer_height = max(view_sizes, key=lambda size: size[0] * size[1])

# Resize buffer.
buffer.resize_buffer(buffer_width, buffer_height)

# Update buffer image.
buffer.update_content()

if hasattr(buffer, "qimage") and buffer.qimage != None:
# Render views.
for view in list(self.view_dict.values()):
if view.buffer_id == buffer.buffer_id:
# Scale image to view size.
width_scale = view.width * 1.0 / buffer_width
height_scale = view.height * 1.0 / buffer_height
image_scale = 1.0
if width_scale < height_scale:
image_scale = width_scale
else:
image_scale = height_scale

view.qimage = buffer.qimage.scaled(buffer_width * image_scale, buffer_height * image_scale)
view.background_color = buffer.background_color

# Update view.
view.update()

time.sleep(0.04)

if __name__ == "__main__":
import sys
import signal
Expand All @@ -255,8 +182,6 @@ def update_buffers(self):

eaf = EAF(sys.argv[1:])

threading.Thread(target=eaf.update_buffers).start()

print("EAF process start.")

signal.signal(signal.SIGINT, signal.SIG_DFL)
Expand Down

0 comments on commit 3ceda23

Please sign in to comment.