|
1 |
| -# Example of embedding CEF browser using the PyWin32 extension. |
2 |
| -# Tested with pywin32 version 219. |
| 1 | +# Example of embedding CEF browser using PyWin32 library. |
| 2 | +# Tested with pywin32 version 221 and CEF Python v57.0+. |
| 3 | +# |
| 4 | +# Usage: |
| 5 | +# python pywin32.py |
| 6 | +# python pywin32.py --multi-threaded |
| 7 | +# |
| 8 | +# By passing --multi-threaded arg CEF will run using multi threaded |
| 9 | +# message loop which has best performance on Windows. However there |
| 10 | +# is one issue with it on exit, see "Known issues" below. See also |
| 11 | +# docs/Tutorial.md and the "Message loop" section. |
| 12 | +# |
| 13 | +# Known issues: |
| 14 | +# - Crash on exit with multi threaded message loop (Issue #380) |
3 | 15 |
|
4 | 16 | from cefpython3 import cefpython as cef
|
5 | 17 |
|
|
13 | 25 | import win32con
|
14 | 26 | import win32gui
|
15 | 27 |
|
| 28 | +# Globals |
16 | 29 | WindowUtils = cef.WindowUtils()
|
| 30 | +g_multi_threaded = False |
17 | 31 |
|
18 |
| -# Platforms (Windows only) |
19 |
| -assert(platform.system() == "Windows") |
20 | 32 |
|
21 |
| -def main(multi_threaded_message_loop): |
22 |
| - |
| 33 | +def main(): |
| 34 | + command_line_args() |
23 | 35 | check_versions()
|
24 | 36 | sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
|
25 | 37 |
|
26 |
| - settings = {"multi_threaded_message_loop": 1 if multi_threaded_message_loop else 0, |
27 |
| - "remote_debugging_port": 2020} |
28 |
| - cef.Initialize(settings) |
| 38 | + settings = { |
| 39 | + "multi_threaded_message_loop": g_multi_threaded, |
| 40 | + } |
| 41 | + cef.Initialize(settings=settings) |
29 | 42 |
|
30 |
| - wndproc = { |
31 |
| - win32con.WM_CLOSE: CloseWindow, |
32 |
| - win32con.WM_DESTROY: QuitApplication, |
| 43 | + window_proc = { |
| 44 | + win32con.WM_CLOSE: close_window, |
| 45 | + win32con.WM_DESTROY: exit_app, |
33 | 46 | win32con.WM_SIZE: WindowUtils.OnSize,
|
34 | 47 | win32con.WM_SETFOCUS: WindowUtils.OnSetFocus,
|
35 | 48 | win32con.WM_ERASEBKGND: WindowUtils.OnEraseBackground
|
36 | 49 | }
|
37 |
| - windowHandle = CreateWindow(title="pywin32 example", className="cefpython3_example", width=1024, height=768, windowProc=wndproc) |
| 50 | + window_handle = create_window(title="PyWin32 example", |
| 51 | + class_name="pywin32.example", |
| 52 | + width=800, |
| 53 | + height=600, |
| 54 | + window_proc=window_proc, |
| 55 | + icon="resources/chromium.ico") |
38 | 56 |
|
39 |
| - windowInfo = cef.WindowInfo() |
40 |
| - windowInfo.SetAsChild(windowHandle) |
| 57 | + window_info = cef.WindowInfo() |
| 58 | + window_info.SetAsChild(window_handle) |
41 | 59 |
|
42 |
| - if(multi_threaded_message_loop): |
43 |
| - # when using multi-threaded message loop, CEF's UI thread is no more application's main thread |
44 |
| - cef.PostTask(cef.TID_UI, _createBrowserInUiThread, windowInfo, {}, "https://www.google.com/") |
| 60 | + if g_multi_threaded: |
| 61 | + # When using multi-threaded message loop, CEF's UI thread |
| 62 | + # is no more application's main thread. In such case browser |
| 63 | + # must be created using cef.PostTask function and CEF message |
| 64 | + # loop must not be run explicitilly. |
| 65 | + cef.PostTask(cef.TID_UI, |
| 66 | + create_browser, |
| 67 | + window_info, |
| 68 | + {}, |
| 69 | + "https://www.google.com/") |
45 | 70 | win32gui.PumpMessages()
|
46 | 71 |
|
47 | 72 | else:
|
48 |
| - browser = _createBrowserInUiThread(windowInfo, {}, "https://www.google.com/") |
| 73 | + create_browser(window_info=window_info, |
| 74 | + settings={}, |
| 75 | + url="https://www.google.com/") |
49 | 76 | cef.MessageLoop()
|
50 | 77 |
|
51 | 78 | cef.Shutdown()
|
52 | 79 |
|
53 | 80 |
|
54 |
| -def check_versions(): |
55 |
| - print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) |
56 |
| - print("[pywin32.py] Python {ver} {arch}".format(ver=platform.python_version(), arch=platform.architecture()[0])) |
57 |
| - print("[pywin32.py] pywin32 {ver}".format(ver=GetPywin32Version())) |
58 |
| - assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" |
| 81 | +def command_line_args(): |
| 82 | + global g_multi_threaded |
| 83 | + if "--multi-threaded" in sys.argv: |
| 84 | + sys.argv.remove("--multi-threaded") |
| 85 | + print("[pywin32.py] Message loop mode: CEF multi-threaded" |
| 86 | + " (best performance)") |
| 87 | + g_multi_threaded = True |
| 88 | + else: |
| 89 | + print("[pywin32.py] Message loop mode: CEF single-threaded") |
| 90 | + if len(sys.argv) > 1: |
| 91 | + print("ERROR: Invalid args passed." |
| 92 | + " For usage see top comments in pywin32.py.") |
| 93 | + sys.exit(1) |
59 | 94 |
|
60 | 95 |
|
61 |
| -def _createBrowserInUiThread(windowInfo, settings, url): |
62 |
| - |
63 |
| - assert(cef.IsThread(cef.TID_UI)) |
64 |
| - browser = cef.CreateBrowserSync(windowInfo, settings, url) |
| 96 | +def check_versions(): |
| 97 | + if platform.system() != "Windows": |
| 98 | + print("ERROR: This example is for Windows platform only") |
| 99 | + sys.exit(1) |
65 | 100 |
|
| 101 | + print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) |
| 102 | + print("[pywin32.py] Python {ver} {arch}".format( |
| 103 | + ver=platform.python_version(), arch=platform.architecture()[0])) |
66 | 104 |
|
67 |
| -def CloseWindow(windowHandle, message, wparam, lparam): |
68 |
| - browser = cef.GetBrowserByWindowHandle(windowHandle) |
69 |
| - browser.CloseBrowser() |
70 |
| - return win32gui.DefWindowProc(windowHandle, message, wparam, lparam) |
| 105 | + # PyWin32 version |
| 106 | + python_lib = distutils.sysconfig.get_python_lib(plat_specific=1) |
| 107 | + with open(os.path.join(python_lib, "pywin32.version.txt")) as fp: |
| 108 | + pywin32_version = fp.read().strip() |
| 109 | + print("[pywin32.py] pywin32 {ver}".format(ver=pywin32_version)) |
71 | 110 |
|
| 111 | + assert cef.__version__ >= "57.0", "CEF Python v57.0 required to run this" |
72 | 112 |
|
73 |
| -def QuitApplication(windowHandle, message, wparam, lparam): |
74 |
| - win32gui.PostQuitMessage(0) |
75 |
| - return 0 |
76 | 113 |
|
| 114 | +def create_browser(window_info, settings, url): |
| 115 | + assert(cef.IsThread(cef.TID_UI)) |
| 116 | + cef.CreateBrowserSync(window_info=window_info, |
| 117 | + settings=settings, |
| 118 | + url=url) |
77 | 119 |
|
78 |
| -def CreateWindow(title, className, width, height, windowProc): |
79 |
| - |
| 120 | + |
| 121 | +def create_window(title, class_name, width, height, window_proc, icon): |
| 122 | + # Register window class |
80 | 123 | wndclass = win32gui.WNDCLASS()
|
81 | 124 | wndclass.hInstance = win32api.GetModuleHandle(None)
|
82 |
| - wndclass.lpszClassName = className |
| 125 | + wndclass.lpszClassName = class_name |
83 | 126 | wndclass.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
|
84 |
| - # win32con.CS_GLOBALCLASS |
85 | 127 | wndclass.hbrBackground = win32con.COLOR_WINDOW
|
86 | 128 | wndclass.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
|
87 |
| - wndclass.lpfnWndProc = windowProc |
88 |
| - atomClass = win32gui.RegisterClass(wndclass) |
89 |
| - assert(atomClass != 0) |
90 |
| - |
91 |
| - # Center window on the screen. |
| 129 | + wndclass.lpfnWndProc = window_proc |
| 130 | + atom_class = win32gui.RegisterClass(wndclass) |
| 131 | + assert(atom_class != 0) |
| 132 | + |
| 133 | + # Center window on screen. |
92 | 134 | screenx = win32api.GetSystemMetrics(win32con.SM_CXSCREEN)
|
93 | 135 | screeny = win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
|
94 | 136 | xpos = int(math.floor((screenx - width) / 2))
|
95 | 137 | ypos = int(math.floor((screeny - height) / 2))
|
96 |
| - if xpos < 0: xpos = 0 |
97 |
| - if ypos < 0: ypos = 0 |
98 |
| - |
99 |
| - windowHandle = win32gui.CreateWindow(className, title, |
100 |
| - win32con.WS_OVERLAPPEDWINDOW | win32con.WS_CLIPCHILDREN | win32con.WS_VISIBLE, |
101 |
| - xpos, ypos, width, height, # xpos, ypos, width, height |
102 |
| - 0, 0, wndclass.hInstance, None) |
103 |
| - |
104 |
| - assert(windowHandle != 0) |
105 |
| - return windowHandle |
106 |
| - |
107 |
| - |
108 |
| -def GetPywin32Version(): |
109 |
| - pth = distutils.sysconfig.get_python_lib(plat_specific=1) |
110 |
| - ver = open(os.path.join(pth, "pywin32.version.txt")).read().strip() |
111 |
| - return ver |
| 138 | + if xpos < 0: |
| 139 | + xpos = 0 |
| 140 | + if ypos < 0: |
| 141 | + ypos = 0 |
| 142 | + |
| 143 | + # Create window |
| 144 | + window_style = (win32con.WS_OVERLAPPEDWINDOW | win32con.WS_CLIPCHILDREN |
| 145 | + | win32con.WS_VISIBLE) |
| 146 | + window_handle = win32gui.CreateWindow(class_name, title, window_style, |
| 147 | + xpos, ypos, width, height, |
| 148 | + 0, 0, wndclass.hInstance, None) |
| 149 | + assert(window_handle != 0) |
| 150 | + |
| 151 | + # Window icon |
| 152 | + icon = os.path.abspath(icon) |
| 153 | + if not os.path.isfile(icon): |
| 154 | + icon = None |
| 155 | + if icon: |
| 156 | + # Load small and big icon. |
| 157 | + # WNDCLASSEX (along with hIconSm) is not supported by pywin32, |
| 158 | + # we need to use WM_SETICON message after window creation. |
| 159 | + # Ref: |
| 160 | + # 1. http://stackoverflow.com/questions/2234988 |
| 161 | + # 2. http://blog.barthe.ph/2009/07/17/wmseticon/ |
| 162 | + bigx = win32api.GetSystemMetrics(win32con.SM_CXICON) |
| 163 | + bigy = win32api.GetSystemMetrics(win32con.SM_CYICON) |
| 164 | + big_icon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, |
| 165 | + bigx, bigy, |
| 166 | + win32con.LR_LOADFROMFILE) |
| 167 | + smallx = win32api.GetSystemMetrics(win32con.SM_CXSMICON) |
| 168 | + smally = win32api.GetSystemMetrics(win32con.SM_CYSMICON) |
| 169 | + small_icon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, |
| 170 | + smallx, smally, |
| 171 | + win32con.LR_LOADFROMFILE) |
| 172 | + win32api.SendMessage(window_handle, win32con.WM_SETICON, |
| 173 | + win32con.ICON_BIG, big_icon) |
| 174 | + win32api.SendMessage(window_handle, win32con.WM_SETICON, |
| 175 | + win32con.ICON_SMALL, small_icon) |
| 176 | + |
| 177 | + return window_handle |
| 178 | + |
| 179 | + |
| 180 | +def close_window(window_handle, message, wparam, lparam): |
| 181 | + browser = cef.GetBrowserByWindowHandle(window_handle) |
| 182 | + browser.CloseBrowser(True) |
| 183 | + # OFF: win32gui.DestroyWindow(window_handle) |
| 184 | + return win32gui.DefWindowProc(window_handle, message, wparam, lparam) |
| 185 | + |
| 186 | + |
| 187 | +def exit_app(*_): |
| 188 | + win32gui.PostQuitMessage(0) |
| 189 | + return 0 |
112 | 190 |
|
113 | 191 |
|
114 | 192 | if __name__ == '__main__':
|
115 |
| - |
116 |
| - if "--multi_threaded_message_loop" in sys.argv: |
117 |
| - print("[pywin32.py] Message loop mode: CEF multi-threaded (best performance)") |
118 |
| - multi_threaded_message_loop = True |
119 |
| - else: |
120 |
| - print("[pywin32.py] Message loop mode: CEF single-threaded") |
121 |
| - multi_threaded_message_loop = False |
122 |
| - |
123 |
| - main(multi_threaded_message_loop) |
| 193 | + main() |
0 commit comments