Skip to content

Commit acaf76b

Browse files
authored
Merge pull request #379 from neilmunday/master
pysdl2 example for issue #324
2 parents 271a6c8 + 279442d commit acaf76b

File tree

2 files changed

+390
-0
lines changed

2 files changed

+390
-0
lines changed

Authors

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ Greg Farrell <gregfarrell.org@@gmail.com>
1313
Finn Hughes <finn.hughes1@@gmail.com>
1414
Marcelo Fernandez <fernandezm@@gmail.com>
1515
Simon Hatt <9hatt2@@gmail.com>
16+
Neil Munday <www.mundayweb.com>

examples/pysdl2.py

+389
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
"""
2+
Simple SDL2 / cefpython3 example.
3+
4+
Requires pysdl2 (and SDL2 library).
5+
6+
Tested configurations:
7+
- SDL2 2.0.5 with PySDL2 0.9.3 on Fedora 25 (x86_64)
8+
- SDL2 with PySDL2 0.9.5 on Ubuntu 14.04
9+
10+
Install instructions.
11+
12+
1. Install SDL libraries for your OS, e.g:
13+
14+
Fedora:
15+
16+
sudo dnf install SDL2 SDL2_ttf SDL2_image SDL2_gfx SDL2_mixer
17+
18+
Ubuntu:
19+
20+
sudo apt-get install libsdl2-dev
21+
22+
2. Install PySDL via PIP:
23+
24+
sudo pip2 install PySDL2
25+
26+
Event handling:
27+
28+
Where possible SDL2 events are mapped to CEF ones. Not all keyboard
29+
modifiers are handled in this example but these could be
30+
added by the reader (if desired). Modifiers that do not work
31+
for example:
32+
33+
- Ctrl
34+
- Mouse dragging
35+
- Marking text inputs with the shift key
36+
37+
Due to SDL2's lack of GUI widgets there are no GUI controls
38+
for the user. However, as an exercise this example could
39+
be extended by create some simple SDL2 widgets. An example of
40+
widgets made using PySDL2 can be found as part of the Pi
41+
Entertainment System at:
42+
https://github.com/neilmunday/pes/blob/master/lib/pes/ui.py
43+
"""
44+
45+
import os
46+
import sys
47+
try:
48+
from cefpython3 import cefpython as cef
49+
except ImportError:
50+
print("cefpython3 module not found - please install")
51+
sys.exit(1)
52+
try:
53+
import sdl2
54+
import sdl2.ext
55+
except ImportError:
56+
print("SDL2 module not found - please install")
57+
sys.exit(1)
58+
try:
59+
from PIL import Image
60+
except ImportError:
61+
print("PIL module not found - please install")
62+
sys.exit(1)
63+
64+
def main():
65+
# The following variables control the dimensions of the window
66+
# and browser display area
67+
width = 1024
68+
height = 768
69+
# headerHeight is useful for leaving space for controls
70+
# at the top of the window (future implementation?)
71+
headerHeight = 0
72+
browserHeight = height - headerHeight
73+
browserWidth = width
74+
# Mouse wheel fudge to enhance scrolling
75+
scrollEnhance = 20
76+
# Initialise CEF for offscreen rendering
77+
WindowUtils = cef.WindowUtils()
78+
sys.excepthook = cef.ExceptHook
79+
cef.Initialize(settings={"windowless_rendering_enabled": True})
80+
window_info = cef.WindowInfo()
81+
window_info.SetAsOffscreen(0)
82+
# Initialise SDL2 for video (add other init constants if you
83+
# require other SDL2 functionality e.g. mixer,
84+
# TTF, joystick etc.
85+
sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
86+
# Create the window
87+
window = sdl2.video.SDL_CreateWindow(
88+
'cefpython3 SDL2 Demo',
89+
sdl2.video.SDL_WINDOWPOS_UNDEFINED,
90+
sdl2.video.SDL_WINDOWPOS_UNDEFINED,
91+
width,
92+
height,
93+
0
94+
)
95+
# Define default background colour (black in this case)
96+
backgroundColour = sdl2.SDL_Color(0, 0, 0)
97+
# Create the renderer using hardware acceleration
98+
renderer = sdl2.SDL_CreateRenderer(window, -1, sdl2.render.SDL_RENDERER_ACCELERATED)
99+
# Set-up the RenderHandler, passing in the SDL2 renderer
100+
renderHandler = RenderHandler(renderer, width, height - headerHeight)
101+
# Create the browser instance
102+
browser = cef.CreateBrowserSync(window_info, url="https://www.google.com/")
103+
browser.SetClientHandler(LoadHandler())
104+
browser.SetClientHandler(renderHandler)
105+
# Must call WasResized at least once to let know CEF that
106+
# viewport size is available and that OnPaint may be called.
107+
browser.SendFocusEvent(True)
108+
browser.WasResized()
109+
# Begin the main rendering loop
110+
shiftDown = False
111+
running = True
112+
while running:
113+
# Convert SDL2 events into CEF events (where appropriate)
114+
events = sdl2.ext.get_events()
115+
for event in events:
116+
if event.type == sdl2.SDL_QUIT or (event.type == sdl2.SDL_KEYDOWN and event.key.keysym.sym == sdl2.SDLK_ESCAPE):
117+
running = False
118+
break
119+
if event.type == sdl2.SDL_MOUSEBUTTONDOWN:
120+
if event.button.button == sdl2.SDL_BUTTON_LEFT:
121+
if event.button.y > headerHeight:
122+
# Mouse click triggered in browser region
123+
browser.SendMouseClickEvent(
124+
event.button.x,
125+
event.button.y - headerHeight,
126+
cef.MOUSEBUTTON_LEFT,
127+
False,
128+
1
129+
)
130+
elif event.type == sdl2.SDL_MOUSEBUTTONUP:
131+
if event.button.button == sdl2.SDL_BUTTON_LEFT:
132+
if event.button.y > headerHeight:
133+
# Mouse click triggered in browser region
134+
browser.SendMouseClickEvent(
135+
event.button.x,
136+
event.button.y - headerHeight,
137+
cef.MOUSEBUTTON_LEFT,
138+
True,
139+
1
140+
)
141+
elif event.type == sdl2.SDL_MOUSEMOTION:
142+
if event.motion.y > headerHeight:
143+
# Mouse move triggered in browser region
144+
browser.SendMouseMoveEvent(event.motion.x, event.motion.y - headerHeight, False)
145+
elif event.type == sdl2.SDL_MOUSEWHEEL:
146+
# Mouse wheel event
147+
x = event.wheel.x
148+
if x < 0:
149+
x -= scrollEnhance
150+
else:
151+
x += scrollEnhance
152+
y = event.wheel.y
153+
if y < 0:
154+
y -= scrollEnhance
155+
else:
156+
y += scrollEnhance
157+
browser.SendMouseWheelEvent(0, 0, x, y)
158+
elif event.type == sdl2.SDL_TEXTINPUT:
159+
# Handle text events to get actual characters typed rather than the key pressed
160+
keycode = ord(event.text.text)
161+
key_event = {
162+
"type": cef.KEYEVENT_CHAR,
163+
"windows_key_code": keycode,
164+
"character": keycode,
165+
"unmodified_character": keycode,
166+
"modifiers": cef.EVENTFLAG_NONE
167+
}
168+
browser.SendKeyEvent(key_event)
169+
key_event = {
170+
"type": cef.KEYEVENT_KEYUP,
171+
"windows_key_code": keycode,
172+
"character": keycode,
173+
"unmodified_character": keycode,
174+
"modifiers": cef.EVENTFLAG_NONE
175+
}
176+
browser.SendKeyEvent(key_event)
177+
elif event.type == sdl2.SDL_KEYDOWN:
178+
# Handle key down events for non-text keys
179+
if event.key.keysym.sym == sdl2.SDLK_RETURN:
180+
keycode = event.key.keysym.sym
181+
key_event = {
182+
"type": cef.KEYEVENT_CHAR,
183+
"windows_key_code": keycode,
184+
"character": keycode,
185+
"unmodified_character": keycode,
186+
"modifiers": cef.EVENTFLAG_NONE
187+
}
188+
browser.SendKeyEvent(key_event)
189+
elif event.key.keysym.sym in [
190+
sdl2.SDLK_BACKSPACE,
191+
sdl2.SDLK_DELETE,
192+
sdl2.SDLK_LEFT,
193+
sdl2.SDLK_RIGHT,
194+
sdl2.SDLK_UP,
195+
sdl2.SDLK_DOWN,
196+
sdl2.SDLK_HOME,
197+
sdl2.SDLK_END
198+
]:
199+
keycode = getKeyCode(event.key.keysym.sym)
200+
if keycode != None:
201+
key_event = {
202+
"type": cef.KEYEVENT_RAWKEYDOWN,
203+
"windows_key_code": keycode,
204+
"character": keycode,
205+
"unmodified_character": keycode,
206+
"modifiers": cef.EVENTFLAG_NONE
207+
}
208+
browser.SendKeyEvent(key_event)
209+
elif event.type == sdl2.SDL_KEYUP:
210+
# Handle key up events for non-text keys
211+
if event.key.keysym.sym in [
212+
sdl2.SDLK_RETURN,
213+
sdl2.SDLK_BACKSPACE,
214+
sdl2.SDLK_DELETE,
215+
sdl2.SDLK_LEFT,
216+
sdl2.SDLK_RIGHT,
217+
sdl2.SDLK_UP,
218+
sdl2.SDLK_DOWN,
219+
sdl2.SDLK_HOME,
220+
sdl2.SDLK_END
221+
]:
222+
keycode = getKeyCode(event.key.keysym.sym)
223+
if keycode != None:
224+
key_event = {
225+
"type": cef.KEYEVENT_KEYUP,
226+
"windows_key_code": keycode,
227+
"character": keycode,
228+
"unmodified_character": keycode,
229+
"modifiers": cef.EVENTFLAG_NONE
230+
}
231+
browser.SendKeyEvent(key_event)
232+
# Clear the renderer
233+
sdl2.SDL_SetRenderDrawColor(
234+
renderer,
235+
backgroundColour.r,
236+
backgroundColour.g,
237+
backgroundColour.b,
238+
255
239+
)
240+
sdl2.SDL_RenderClear(renderer)
241+
# Tell CEF to update which will trigger the OnPaint
242+
# method of the RenderHandler instance
243+
cef.MessageLoopWork()
244+
# Update display
245+
sdl2.SDL_RenderCopy(
246+
renderer,
247+
renderHandler.texture,
248+
None,
249+
sdl2.SDL_Rect(0, headerHeight, browserWidth, browserHeight)
250+
)
251+
sdl2.SDL_RenderPresent(renderer)
252+
# User exited
253+
exit_app()
254+
255+
def getKeyCode(key):
256+
"""Helper function to convert SDL2 key codes to cef ones"""
257+
keyMap = {
258+
sdl2.SDLK_RETURN: 13,
259+
sdl2.SDLK_DELETE: 46,
260+
sdl2.SDLK_BACKSPACE: 8,
261+
sdl2.SDLK_LEFT: 37,
262+
sdl2.SDLK_RIGHT: 39,
263+
sdl2.SDLK_UP: 38,
264+
sdl2.SDLK_DOWN: 40,
265+
sdl2.SDLK_HOME: 36,
266+
sdl2.SDLK_END: 35,
267+
}
268+
if key in keyMap:
269+
return keyMap[key]
270+
# Key not mapped, raise exception
271+
print("Keyboard mapping incomplete: \
272+
unsupported SDL key %d. \
273+
See https://wiki.libsdl.org/SDLKeycodeLookup for mapping."
274+
% key)
275+
return None
276+
277+
class LoadHandler(object):
278+
"""Simple handler for loading URLs."""
279+
280+
def OnLoadingStateChange(self, browser, is_loading, **_):
281+
if not is_loading:
282+
print("loading complete")
283+
284+
def OnLoadError(self, browser, frame, error_code, failed_url, **_):
285+
if not frame.IsMain():
286+
return
287+
print("Failed to load %s" % failed_url)
288+
289+
class RenderHandler(object):
290+
"""
291+
Handler for rendering web pages to the
292+
screen via SDL2.
293+
294+
The object's texture property is exposed
295+
to allow the main rendering loop to access
296+
the SDL2 texture.
297+
"""
298+
299+
def __init__(self, renderer, width, height):
300+
self.__width = width
301+
self.__height = height
302+
self.__renderer = renderer
303+
self.texture = None
304+
305+
def GetViewRect(self, rect_out, **_):
306+
rect_out.extend([0, 0, self.__width, self.__height])
307+
return True
308+
309+
def OnPaint(self, browser, element_type, paint_buffer, **_):
310+
"""
311+
Using the pixel data from CEF's offscreen rendering
312+
the data is converted by PIL into a SDL2 surface
313+
which can then be rendered as a SDL2 texture.
314+
"""
315+
if element_type == cef.PET_VIEW:
316+
image = Image.frombuffer(
317+
'RGBA',
318+
(self.__width, self.__height),
319+
paint_buffer.GetString(mode="rgba", origin="top-left"),
320+
'raw',
321+
'BGRA'
322+
)
323+
# Following PIL to SDL2 surface code from pysdl2 source.
324+
mode = image.mode
325+
rmask = gmask = bmask = amask = 0
326+
if mode == "RGB":
327+
# 3x8-bit, 24bpp
328+
if sdl2.endian.SDL_BYTEORDER == sdl2.endian.SDL_LIL_ENDIAN:
329+
rmask = 0x0000FF
330+
gmask = 0x00FF00
331+
bmask = 0xFF0000
332+
else:
333+
rmask = 0xFF0000
334+
gmask = 0x00FF00
335+
bmask = 0x0000FF
336+
depth = 24
337+
pitch = self.__width * 3
338+
elif mode in ("RGBA", "RGBX"):
339+
# RGBX: 4x8-bit, no alpha
340+
# RGBA: 4x8-bit, alpha
341+
if sdl2.endian.SDL_BYTEORDER == sdl2.endian.SDL_LIL_ENDIAN:
342+
rmask = 0x00000000
343+
gmask = 0x0000FF00
344+
bmask = 0x00FF0000
345+
if mode == "RGBA":
346+
amask = 0xFF000000
347+
else:
348+
rmask = 0xFF000000
349+
gmask = 0x00FF0000
350+
bmask = 0x0000FF00
351+
if mode == "RGBA":
352+
amask = 0x000000FF
353+
depth = 32
354+
pitch = self.__width * 4
355+
else:
356+
print("Unsupported mode: %s" % mode)
357+
exit_app()
358+
359+
pxbuf = image.tobytes()
360+
# Create surface
361+
surface = sdl2.SDL_CreateRGBSurfaceFrom(
362+
pxbuf,
363+
self.__width,
364+
self.__height,
365+
depth,
366+
pitch,
367+
rmask,
368+
gmask,
369+
bmask,
370+
amask
371+
)
372+
if self.texture:
373+
# free memory used by previous texture
374+
sdl2.SDL_DestroyTexture(self.texture)
375+
# Create texture
376+
self.texture = sdl2.SDL_CreateTextureFromSurface(self.__renderer, surface)
377+
# Free the surface
378+
sdl2.SDL_FreeSurface(surface)
379+
else:
380+
print("Unsupport element_type in OnPaint")
381+
382+
def exit_app():
383+
"""Tidy up SDL2 and CEF before exiting."""
384+
sdl2.SDL_Quit()
385+
cef.Shutdown()
386+
print("exited")
387+
388+
if __name__ == "__main__":
389+
main()

0 commit comments

Comments
 (0)