diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..db6bb26 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +graft webview diff --git a/README.md b/README.md new file mode 100644 index 0000000..81501d0 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# webview + +Python extension that provides API for the [webview] library. + +## Getting started + +Install the bindings: + +```bash +pip install webview +``` + +Try the following example: + +```python +import webview + +w = webview.WebView(width=320, height=240, title="Hello", url="https://google.com", resizable=True, debug=False) +w.run() +``` + +You may use most of the webview APIs: + +```python +# Change window title +w.set_title("New title") +# Make window fullscreen +w.set_fullscreen(True) +# Change initial window background color +w.set_color(255, 0, 0) +# Inject some JS +w.eval("alert('hello')") +# Inject some CSS +w.inject_css('* {background-color: yellow; }') +# Show native OS dialog +file_path = w.dialog(0, 0, "open file", "") +# Post funciton to the UI thread +w.dispatch(some_func) +w.dispatch(lambda: some_func()) +# Control run loop +while w.loop(True): + pass +``` + +Dispatch is currently only a stub and is implemented as direct function call. +Also, proper Python-to-JS object mapping is not implemented yet, but is highly + +## Development + +To build and install the library locally: + +```bash +python setup.py sync install +``` + +To upload a new version: + +```bash +python setup.py sync sdist +twine upload dist/webview-*.tar.gz +``` + +To build and install it locally: + +```bash +python setup.py sync install +``` + +Please, ensure that all sources are formatted using `yapf`. + + +[webview]: https://github.com/zserge/webview diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b27a63d --- /dev/null +++ b/setup.py @@ -0,0 +1,72 @@ +#!/bin/env python + +import os +import commands +import shutil + +from distutils.core import setup +from distutils.extension import Extension +from distutils.cmd import Command + + +class sync_command(Command): + """A custom command to synchronize top-level webview.h implementation + with the one included in the Python bindings source package.""" + description = 'synchronize webview.h' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + shutil.copyfile('../../webview.h', 'webview/webview.h') + pass + + +if hasattr(os, 'uname'): + OSNAME = os.uname()[0] +else: + OSNAME = 'Windows' + +if OSNAME == 'Linux': + + def pkgconfig(flags): + return commands.getoutput( + "pkg-config %s gtk+-3.0 webkit2gtk-4.0" % flags) + + define_macros = [("WEBVIEW_GTK", '1')] + extra_cflags = pkgconfig("--cflags").split() + extra_ldflags = pkgconfig("--libs").split() +elif OSNAME == 'Darwin': + define_macros = [('WEBVIEW_COCOA', '1')] + extra_cflags = "" + extra_ldflags = ['-framework', 'CoreAudio'] +elif OSNAME == 'Windows': + define_macros = [('WEBVIEW_WINAPI', '1')] + extra_cflags = "" + extra_ldflags = ['-framework', 'CoreAudio'] + +webview = Extension( + 'webview', + sources=['webview/webview.c'], + define_macros=define_macros, + extra_compile_args=extra_cflags, + extra_link_args=extra_ldflags, +) + +setup( + name='webview', + version='0.1.4', + description='Python WebView bindings', + author='Serge Zaitsev', + author_email='zaitsev.serge@gmail.com', + url='https://github.com/zserge/webview', + keywords=[], + license='MIT', + classifiers=[], + ext_modules=[webview], + cmdclass={'sync': sync_command}, +) diff --git a/webview/webview.c b/webview/webview.c new file mode 100644 index 0000000..cb26468 --- /dev/null +++ b/webview/webview.c @@ -0,0 +1,234 @@ +#include + +#include "structmember.h" + +#define WEBVIEW_IMPLEMENTATION +#include "webview.h" + +typedef struct { PyObject_HEAD struct webview w; } WebView; + +static void WebView_dealloc(WebView *self) { + webview_exit(&self->w); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *WebView_new(PyTypeObject *type, PyObject *args, + PyObject *kwds) { + WebView *self = (WebView *)type->tp_alloc(type, 0); + if (self == NULL) { + return NULL; + } + memset(&self->w, 0, sizeof(self->w)); + return (PyObject *)self; +} + +static int WebView_init(WebView *self, PyObject *args, PyObject *kwds) { + const char *url = NULL; + const char *title = NULL; + static char *kwlist[] = {"width", "height", "resizable", "debug", + "url", "title", NULL}; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "ii|iiss", kwlist, &self->w.width, &self->w.height, + &self->w.resizable, &self->w.debug, &url, &title)) { + return -1; + } + + self->w.url = url; + self->w.title = title; + + return webview_init(&self->w); +} + +static PyObject *WebView_run(WebView *self) { + while (webview_loop(&self->w, 1) == 0) + ; + Py_RETURN_NONE; +} + +static PyObject *WebView_loop(WebView *self, PyObject *args, PyObject *kwds) { + int blocking = 1; + static char *kwlist[] = {"blocking", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &blocking)) { + return NULL; + } + if (webview_loop(&self->w, blocking) == 0) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +static PyObject *WebView_terminate(WebView *self) { + webview_terminate(&self->w); + Py_RETURN_NONE; +} + +static PyObject *WebView_set_title(WebView *self, PyObject *args) { + const char *title = ""; + if (!PyArg_ParseTuple(args, "s", &title)) { + return NULL; + } + webview_set_title(&self->w, title); + Py_RETURN_NONE; +} + +static PyObject *WebView_set_fullscreen(WebView *self, PyObject *args) { + int fullscreen = 0; + if (!PyArg_ParseTuple(args, "i", &fullscreen)) { + return NULL; + } + webview_set_fullscreen(&self->w, fullscreen); + Py_RETURN_NONE; +} + +static PyObject *WebView_set_color(WebView *self, PyObject *args) { + int r, g, b, a = 255; + if (!PyArg_ParseTuple(args, "iii|i", &r, &g, &b, &a)) { + return NULL; + } + webview_set_color(&self->w, r, g, b, a); + Py_RETURN_NONE; +} + +static PyObject *WebView_eval(WebView *self, PyObject *args) { + const char *js = NULL; + if (!PyArg_ParseTuple(args, "s", &js)) { + return NULL; + } + webview_eval(&self->w, js); + Py_RETURN_NONE; +} + +static PyObject *WebView_inject_css(WebView *self, PyObject *args) { + const char *css = NULL; + if (!PyArg_ParseTuple(args, "s", &css)) { + return NULL; + } + webview_inject_css(&self->w, css); + Py_RETURN_NONE; +} + +static PyObject *WebView_dialog(WebView *self, PyObject *args, PyObject *kwds) { + int type = 0; + int flags = 0; + const char *title = NULL; + const char *arg = NULL; + char result[PATH_MAX]; + static char *kwlist[] = {"type", "flags", "title", "arg", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiss", kwlist, &type, &flags, + &title, &arg)) { + return NULL; + } + webview_dialog(&self->w, type, flags, title, arg, result, sizeof(result)); + return PyUnicode_FromString(result); +} + +static void webview_dispatch_cb(struct webview *w, void *arg) { + PyObject *cb = (PyObject *)arg; + /* TODO */ + PyObject_CallObject(cb, NULL); + Py_XINCREF(cb); +} + +static PyObject *WebView_dispatch(WebView *self, PyObject *args) { + PyObject *tmp; + if (!PyArg_ParseTuple(args, "O:set_callback", &tmp)) { + return NULL; + } + if (!PyCallable_Check(tmp)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return NULL; + } + Py_XINCREF(tmp); + webview_dispatch(&self->w, webview_dispatch_cb, tmp); + Py_RETURN_NONE; +} + +static PyObject *WebView_bind(WebView *self) { + /* TODO, very complex implementation */ + Py_RETURN_NONE; +} + +static PyMemberDef WebView_members[] = { + {NULL} /* Sentinel */ +}; +static PyMethodDef WebView_methods[] = { + {"run", (PyCFunction)WebView_run, METH_NOARGS, "..."}, + {"loop", (PyCFunction)WebView_loop, METH_KEYWORDS, "..."}, + {"terminate", (PyCFunction)WebView_terminate, METH_NOARGS, "..."}, + {"dispatch", (PyCFunction)WebView_dispatch, METH_VARARGS, "..."}, + {"eval", (PyCFunction)WebView_eval, METH_VARARGS, "..."}, + {"inject_css", (PyCFunction)WebView_inject_css, METH_VARARGS, "..."}, + {"dialog", (PyCFunction)WebView_dialog, METH_KEYWORDS, "..."}, + {"set_title", (PyCFunction)WebView_set_title, METH_VARARGS, "..."}, + {"set_fullscreen", (PyCFunction)WebView_set_fullscreen, METH_VARARGS, + "..."}, + {"set_color", (PyCFunction)WebView_set_color, METH_VARARGS, "..."}, + {"bind", (PyCFunction)WebView_bind, METH_VARARGS, "..."}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject WebViewType = { + PyVarObject_HEAD_INIT(NULL, 0) "webview.WebView", /* tp_name */ + sizeof(WebView), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)WebView_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + "WebView objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + WebView_methods, /* tp_methods */ + WebView_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)WebView_init, /* tp_init */ + 0, /* tp_alloc */ + WebView_new, /* tp_new */ +}; + +static PyMethodDef module_methods[] = { + {NULL} /* Sentinel */ +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC initwebview(void) { + PyObject *m; + + if (PyType_Ready(&WebViewType) < 0) { + return; + } + + m = Py_InitModule3("webview", module_methods, + "Example module that creates an extension type."); + if (m == NULL) { + return; + } + + Py_INCREF(&WebViewType); + PyModule_AddObject(m, "WebView", (PyObject *)&WebViewType); +}