Commit e54d2a8e authored by carlosperate's avatar carlosperate

update CEF GUI with Mac OS X compatibility

parent 55765797
#!/usr/bin/env python2 #!/usr/bin/env python2
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Embedding CEF browser in a wxPython window to launch Ardublockly. # Embedding Chromium Embedded Framework browser in a wxPython window to launch
# Ardublockly.
# #
# Copyright (c) 2015 carlosperate https://github.com/carlosperate/ # Based on an example to from the CEF Python repository.
# https://code.google.com/p/cefpython/source/browse/cefpython/cef3/windows/binaries_64bit/wxpython.py
# Copyright (c) 2012-2014 The CEF Python authors. All rights reserved.
# Website: http://code.google.com/p/cefpython/
# New BSD license:
# https://code.google.com/p/cefpython/source/browse/cefpython/LICENSE.txt
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Changes are copyright (c) 2015 carlosperate https://github.com/carlosperate/
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# #
# In Mac cefpython library must be the very first library imported. This is
# because CEF was compiled with the tcmalloc memory allocator which hooks
# globally and replaces the default malloc allocator. If memory was allocated
# using malloc and then freed using tcmalloc then this would result in random
# segmentation faults in an application. See Issue 155 which is to provide CEF
# builds on Mac with tcmalloc disabled:
# https://code.google.com/p/cefpython/issues/detail?id=155
import sys
try:
from cefpython3 import cefpython
import wx
import wx.lib.agw.flatmenu as FM
except ImportError:
print("You need to have ce fpython3, and wx installed!")
sys.exit(1)
import os import os
import re import re
import sys
import time import time
import uuid #import uuid
import struct #import ctypes
#import struct
import codecs import codecs
import inspect import inspect
import platform import platform
import traceback import traceback
from ArdublocklyServer.BlocklyHTTPServer import start_server from ArdublocklyServer.BlocklyHTTPServer import start_server
try:
import wx
import wx.lib.agw.flatmenu as FM
from cefpython3 import cefpython
except ImportError:
print("You need to have cefpython3, and wx installed!")
sys.exit(1)
# Needed for packaging the application on self contained executable
__file__ = sys.argv[0] __file__ = sys.argv[0]
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
...@@ -44,7 +49,9 @@ __file__ = sys.argv[0] ...@@ -44,7 +49,9 @@ __file__ = sys.argv[0]
g_applicationSettings = None g_applicationSettings = None
g_browserSettings = None g_browserSettings = None
g_countWindows = 0
g_ardutag = "[ardublockly] " g_ardutag = "[ardublockly] "
g_ardu_link = "http://localhost:8000/ardublockly/index.html"
g_platform_os = None g_platform_os = None
# Which method to use for message loop processing. # Which method to use for message loop processing.
...@@ -143,7 +150,7 @@ class MainFrame(wx.Frame): ...@@ -143,7 +150,7 @@ class MainFrame(wx.Frame):
size = (1250, 768) size = (1250, 768)
# This is an optional code to enable High DPI support. # This is an optional code to enable High DPI support.
if (g_platform_os == "win") \ if (g_platform_os == "windows") \
and ("auto_zooming" in g_applicationSettings) \ and ("auto_zooming" in g_applicationSettings) \
and (g_applicationSettings["auto_zooming"] == "system_dpi"): and (g_applicationSettings["auto_zooming"] == "system_dpi"):
# This utility function will adjust width/height using # This utility function will adjust width/height using
...@@ -153,9 +160,16 @@ class MainFrame(wx.Frame): ...@@ -153,9 +160,16 @@ class MainFrame(wx.Frame):
self.SetSize(size) self.SetSize(size)
# On mac the cefpython.Shutdown() has to be place in the onClose method.
# So, the number of opened windows has to be tracked/
if g_platform_os == "mac":
global g_countWindows
g_countWindows += 1
if not url: if not url:
url = "http://localhost:8000/ardublockly/index.html" url = g_ardu_link
# Cannot attach browser to the main frame as the menu won't work.
# Also have to set wx.WANTS_CHARS style for all parent panels/controls # Also have to set wx.WANTS_CHARS style for all parent panels/controls
self.mainPanel = wx.Panel(self, style=wx.WANTS_CHARS) self.mainPanel = wx.Panel(self, style=wx.WANTS_CHARS)
...@@ -169,7 +183,9 @@ class MainFrame(wx.Frame): ...@@ -169,7 +183,9 @@ class MainFrame(wx.Frame):
self.clientHandler._OnAfterCreated) self.clientHandler._OnAfterCreated)
windowInfo = cefpython.WindowInfo() windowInfo = cefpython.WindowInfo()
windowInfo.SetAsChild(self.GetHandleForBrowser()) (width, height) = self.mainPanel.GetClientSizeTuple()
windowInfo.SetAsChild(self.GetHandleForBrowser(),
[0, 0, width, height])
self.browser = cefpython.CreateBrowserSync( self.browser = cefpython.CreateBrowserSync(
windowInfo, windowInfo,
browserSettings=g_browserSettings, browserSettings=g_browserSettings,
...@@ -197,7 +213,8 @@ class MainFrame(wx.Frame): ...@@ -197,7 +213,8 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_CLOSE, self.OnClose)
if USE_EVT_IDLE and not popup: if USE_EVT_IDLE and not popup:
# Bind EVT_IDLE only for the main application frame. # Bind EVT_IDLE only for the main application frame.
print(g_ardutag + "Using EVT_IDLE to execute the CEF message loop work") print(g_ardutag + \
"Using EVT_IDLE to execute the CEF message loop work")
self.Bind(wx.EVT_IDLE, self.OnIdle) self.Bind(wx.EVT_IDLE, self.OnIdle)
self.CreateMenu() self.CreateMenu()
...@@ -262,11 +279,11 @@ class MainFrame(wx.Frame): ...@@ -262,11 +279,11 @@ class MainFrame(wx.Frame):
self.menubar.SetBarHeight() self.menubar.SetBarHeight()
def OnSetFocus(self, event): def OnSetFocus(self, event):
if g_platform_os != "linux": if g_platform_os == "windows":
cefpython.WindowUtils.OnSetFocus(self.GetHandleForBrowser(), 0, 0, 0) cefpython.WindowUtils.OnSetFocus(self.GetHandleForBrowser(), 0, 0, 0)
def OnSize(self, event): def OnSize(self, event):
if g_platform_os != "linux": if g_platform_os == "windows":
cefpython.WindowUtils.OnSize(self.GetHandleForBrowser(), 0, 0, 0) cefpython.WindowUtils.OnSize(self.GetHandleForBrowser(), 0, 0, 0)
def OnClose(self, event): def OnClose(self, event):
...@@ -288,12 +305,22 @@ class MainFrame(wx.Frame): ...@@ -288,12 +305,22 @@ class MainFrame(wx.Frame):
# | self.browser.ParentWindowWillClose() # | self.browser.ParentWindowWillClose()
# | event.Skip() # | event.Skip()
# On Win/Linux the call to cefpython.Shutdown() is after app.MainLoop()
# returns, but on Mac it needs to be here.
if g_platform_os == "mac":
global g_countWindows
g_countWindows -= 1
if g_countWindows == 0:
cefpython.Shutdown()
print(g_ardutag + "OnClose: Exiting")
wx.GetApp().Exit()
def OnIdle(self, event): def OnIdle(self, event):
cefpython.MessageLoopWork() cefpython.MessageLoopWork()
def PyPrint(message): def PyPrint(message):
print(g_ardutag + "PyPrint: "+message) print(g_ardutag + "PyPrint: " + message)
class JavascriptExternal: class JavascriptExternal:
...@@ -317,15 +344,16 @@ class JavascriptExternal: ...@@ -317,15 +344,16 @@ class JavascriptExternal:
frame.Show() frame.Show()
def Print(self, message): def Print(self, message):
print(g_ardutag + "Print: "+message) print(g_ardutag + "Print: " + message)
def TestAllTypes(self, *args): def TestAllTypes(self, *args):
print(g_ardutag + "TestAllTypes: "+str(args)) print(g_ardutag + "TestAllTypes: " + str(args))
def ExecuteFunction(self, *args): def ExecuteFunction(self, *args):
if g_platform_os == "linux": if g_platform_os == "windows":
self.mainBrowser.GetMainFrame().ExecuteFunction(*args)
self.mainBrowser.ExecuteFunction(*args) self.mainBrowser.ExecuteFunction(*args)
else:
self.mainBrowser.GetMainFrame().ExecuteFunction(*args)
def TestJSCallback(self, jsCallback): def TestJSCallback(self, jsCallback):
print(g_ardutag + "jsCallback.GetFunctionName() = %s" % print(g_ardutag + "jsCallback.GetFunctionName() = %s" %
...@@ -473,6 +501,8 @@ class ClientHandler: ...@@ -473,6 +501,8 @@ class ClientHandler:
print(" source = %s" % source) print(" source = %s" % source)
print(" line = %s" % line) print(" line = %s" % line)
# onKeyEvent removed to avoid users using common browser key commands
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# RequestHandler # RequestHandler
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
...@@ -615,7 +645,7 @@ class ClientHandler: ...@@ -615,7 +645,7 @@ class ClientHandler:
def _Browser_LoadUrl(self, browser): def _Browser_LoadUrl(self, browser):
if browser.GetUrl() == "data:text/html,Test#Browser.LoadUrl": if browser.GetUrl() == "data:text/html,Test#Browser.LoadUrl":
browser.LoadUrl("http://localhost:8000/ardublockly/index.html") browser.LoadUrl(g_ardu_link)
def OnLoadError(self, browser, frame, errorCode, errorTextList, failedUrl): def OnLoadError(self, browser, frame, errorCode, errorTextList, failedUrl):
print(g_ardutag + "LoadHandler::OnLoadError()") print(g_ardutag + "LoadHandler::OnLoadError()")
...@@ -649,22 +679,20 @@ class ClientHandler: ...@@ -649,22 +679,20 @@ class ClientHandler:
# Set WindowInfo object: # Set WindowInfo object:
# > windowInfo[0] = cefpython.WindowInfo() # > windowInfo[0] = cefpython.WindowInfo()
# On Windows there are keyboard problems in popups, when popup # On Windows there are keyboard problems in popups in wxPython, when
# is created using "window.open" or "target=blank". This issue # popup is created using "window.open" or "target=blank".
# occurs only in wxPython. PyGTK or PyQt do not require this fix.
# The solution is to create window explicitly, and not depend # The solution is to create window explicitly, and not depend
# on CEF to create window internally. See Issue 80 for details: # on CEF to create window internally. See Issue 80 for details:
# https://code.google.com/p/cefpython/issues/detail?id=80 # https://code.google.com/p/cefpython/issues/detail?id=80
#
# If you set allowPopups=True then CEF will create popup window. # If you set allowPopups=True then CEF will create popup window.
# The wx.Frame cannot be created here, as this callback is # The wx.Frame cannot be created here, as this callback is executed on
# executed on the IO thread. Window should be created on the UI # the IO thread. Window should be created on the UI thread.
# thread. One solution is to call cefpython.CreateBrowser() # One solution is to call cefpython.CreateBrowser() which runs
# which runs asynchronously and can be called on any thread. # asynchronously and can be called on any thread.
# The other solution is to post a task on the UI thread, so # The other solution is to post a task on the UI thread, so that
# that cefpython.CreateBrowserSync() can be used. # cefpython.CreateBrowserSync() can be used.
cefpython.PostTask(cefpython.TID_UI, self._CreatePopup, targetUrl) cefpython.PostTask(cefpython.TID_UI, self._CreatePopup, targetUrl)
allowPopups = False allowPopups = False
return not allowPopups return not allowPopups
...@@ -786,17 +814,27 @@ def cef_init(): ...@@ -786,17 +814,27 @@ def cef_init():
"devtools": True}, # Developer Tools "devtools": True}, # Developer Tools
"downloads_enabled": True, "downloads_enabled": True,
"ignore_certificate_errors": True, "ignore_certificate_errors": True,
"locales_dir_path": cefpython.GetModuleDirectory() + os.sep + "locales",
"debug": True, "debug": True,
"log_file": GetApplicationPath("cef_debug.log"), "log_file": GetApplicationPath("cef_debug.log"),
"log_severity": cefpython.LOGSEVERITY_INFO, "log_severity": cefpython.LOGSEVERITY_INFO,
"release_dcheck_enabled": False, "release_dcheck_enabled": False,
"remote_debugging_port": 0, "remote_debugging_port": 0,
"resources_dir_path": cefpython.GetModuleDirectory(),
"unique_request_context_per_browser": True, "unique_request_context_per_browser": True,
"auto_zooming": "system_dpi" "auto_zooming": "system_dpi"
} }
# "resources_dir_path" must be set on Mac, "locales_dir_path" not.
if g_platform_os == "mac":
g_applicationSettings["resources_dir_path"] = \
cefpython.GetModuleDirectory() + os.sep + "Resources"
else:
g_applicationSettings["resources_dir_path"] = \
cefpython.GetModuleDirectory()
g_applicationSettings["locales_dir_path"] = \
cefpython.GetModuleDirectory() + os.sep + "locales"
# High DPI support is available only on Windows. # High DPI support is available only on Windows.
# Example values for auto_zooming are: # Example values for auto_zooming are:
# "system_dpi", "0.0" (96 DPI), "1.0" (120 DPI), # "system_dpi", "0.0" (96 DPI), "1.0" (120 DPI),
...@@ -807,8 +845,8 @@ def cef_init(): ...@@ -807,8 +845,8 @@ def cef_init():
# Medium 125% = 120 DPI = 1.0 zoom level # Medium 125% = 120 DPI = 1.0 zoom level
# Larger 150% = 144 DPI = 2.0 zoom level # Larger 150% = 144 DPI = 2.0 zoom level
# Custom 75% = 72 DPI = -1.0 zoom level # Custom 75% = 72 DPI = -1.0 zoom level
if g_platform_os == "windows":
#g_applicationSettings["auto_zooming"] = "system_dpi" #g_applicationSettings["auto_zooming"] = "system_dpi"
if g_platform_os == "win":
print(g_ardutag + "Calling SetProcessDpiAware") print(g_ardutag + "Calling SetProcessDpiAware")
cefpython.DpiAware.SetProcessDpiAware() cefpython.DpiAware.SetProcessDpiAware()
...@@ -819,6 +857,11 @@ def cef_init(): ...@@ -819,6 +857,11 @@ def cef_init():
"no-proxy-server": "", "no-proxy-server": "",
#"disable-remote-fonts": False, #"disable-remote-fonts": False,
} }
# On Mac it is required to provide path to a specific locale.pak file.
# On Win/Linux you only specify the ApplicationSettings.locales_dir_path.
if g_platform_os == "mac":
g_commandLineSwitches["locale_pak"] = cefpython.GetModuleDirectory() + \
"/Resources/en.lproj/locale.pak"
cefpython.Initialize(g_applicationSettings, g_commandLineSwitches) cefpython.Initialize(g_applicationSettings, g_commandLineSwitches)
...@@ -853,11 +896,12 @@ def detect_os(): ...@@ -853,11 +896,12 @@ def detect_os():
Options for g_platform_os are: win, lin, mac Options for g_platform_os are: win, lin, mac
""" """
global g_platform_os global g_platform_os
if os.name == "nt": os = platform.system()
g_platform_os = "win" if os == "Windows":
elif os.name == "posix": g_platform_os = "windows"
elif os == "Linux":
g_platform_os = "linux" g_platform_os = "linux"
elif os.name == "mac": elif os == "Darwin":
g_platform_os = "mac" g_platform_os = "mac"
...@@ -907,6 +951,9 @@ def main(argv): ...@@ -907,6 +951,9 @@ def main(argv):
# This is to ensure reliable CEF shutdown. # This is to ensure reliable CEF shutdown.
del app del app
# On Mac cefpython.Shutdown() is called in MainFrame.OnClose,
# followed by wx.GetApp.Exit().
if g_platform_os != "mac":
cefpython.Shutdown() cefpython.Shutdown()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment