diff --git a/pkgs/clan-app/clan_app/__init__.py b/pkgs/clan-app/clan_app/__init__.py index 457e151c8..b5801ff6a 100644 --- a/pkgs/clan-app/clan_app/__init__.py +++ b/pkgs/clan-app/clan_app/__init__.py @@ -5,17 +5,21 @@ from clan_cli.profiler import profile log = logging.getLogger(__name__) -from clan_cli.custom_logger import setup_logging -from clan_app.deps.webview.webview import Webview -from pathlib import Path -import os import argparse -from urllib.parse import quote +import os +from pathlib import Path + +from clan_cli.custom_logger import setup_logging + +from clan_app.deps.webview.webview import Webview + @profile def main(argv: list[str] = sys.argv) -> int: parser = argparse.ArgumentParser(description="Clan App") - parser.add_argument("--content-uri", type=str, help="The URI of the content to display") + parser.add_argument( + "--content-uri", type=str, help="The URI of the content to display" + ) parser.add_argument("--debug", action="store_true", help="Enable debug mode") args = parser.parse_args(argv[1:]) @@ -36,16 +40,6 @@ def main(argv: list[str] = sys.argv) -> int: site_index: Path = Path(os.getenv("WEBUI_PATH", ".")).resolve() / "index.html" content_uri = f"file://{site_index}" - html = """ - - -

Hello from Python Webview!

- - - """ - webview = Webview() - webview.navigate(f"data:text/html,{quote(html)}") - #webview.navigate(content_uri) + webview.navigate(content_uri) webview.run() - diff --git a/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py b/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py index 529ff1686..25bd83453 100644 --- a/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py +++ b/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py @@ -1,43 +1,47 @@ import ctypes -import sys +import ctypes.util import os import platform -import urllib.request +from ctypes import CFUNCTYPE, c_char_p, c_int, c_void_p from pathlib import Path -from ctypes import c_int, c_char_p, c_void_p, CFUNCTYPE -import ctypes.util + def _encode_c_string(s: str) -> bytes: return s.encode("utf-8") + def _get_webview_version(): """Get webview version from environment variable or use default""" return os.getenv("WEBVIEW_VERSION", "0.8.1") + def _get_lib_names(): """Get platform-specific library names.""" system = platform.system().lower() machine = platform.machine().lower() - + if system == "windows": if machine == "amd64" or machine == "x86_64": return ["webview.dll", "WebView2Loader.dll"] - elif machine == "arm64": - raise Exception("arm64 is not supported on Windows") - elif system == "darwin": + if machine == "arm64": + msg = "arm64 is not supported on Windows" + raise Exception(msg) + return None + if system == "darwin": if machine == "arm64": return ["libwebview.aarch64.dylib"] - else: - return ["libwebview.x86_64.dylib"] - else: # linux - return ["libwebview.so"] + return ["libwebview.x86_64.dylib"] + # linux + return ["libwebview.so"] + def _be_sure_libraries(): """Ensure libraries exist and return paths.""" lib_dir = os.environ.get("WEBVIEW_LIB_DIR") if not lib_dir: - raise RuntimeError("WEBVIEW_LIB_DIR environment variable is not set") + msg = "WEBVIEW_LIB_DIR environment variable is not set" + raise RuntimeError(msg) lib_dir = Path(lib_dir) lib_names = _get_lib_names() lib_paths = [lib_dir / lib_name for lib_name in lib_names] @@ -46,11 +50,12 @@ def _be_sure_libraries(): missing_libs = [path for path in lib_paths if not path.exists()] if not missing_libs: return lib_paths + return None class _WebviewLibrary: - def __init__(self): - lib_names=_get_lib_names() + def __init__(self) -> None: + lib_names = _get_lib_names() try: library_path = ctypes.util.find_library(lib_names[0]) if not library_path: @@ -99,4 +104,5 @@ class _WebviewLibrary: self.CFUNCTYPE = CFUNCTYPE + _webview_lib = _WebviewLibrary() diff --git a/pkgs/clan-app/clan_app/deps/webview/webview.py b/pkgs/clan-app/clan_app/deps/webview/webview.py index 2cebd315e..ca0a761d8 100644 --- a/pkgs/clan-app/clan_app/deps/webview/webview.py +++ b/pkgs/clan-app/clan_app/deps/webview/webview.py @@ -1,8 +1,11 @@ -from enum import IntEnum -from typing import Optional, Callable, Any -import json import ctypes -from ._webview_ffi import _webview_lib, _encode_c_string +import json +from collections.abc import Callable +from enum import IntEnum +from typing import Any + +from ._webview_ffi import _encode_c_string, _webview_lib + class SizeHint(IntEnum): NONE = 0 @@ -10,14 +13,18 @@ class SizeHint(IntEnum): MAX = 2 FIXED = 3 + class Size: - def __init__(self, width: int, height: int, hint: SizeHint): + def __init__(self, width: int, height: int, hint: SizeHint) -> None: self.width = width self.height = height self.hint = hint + class Webview: - def __init__(self, debug: bool = False, size: Optional[Size] = None, window: Optional[int] = None): + def __init__( + self, debug: bool = False, size: Size | None = None, window: int | None = None + ) -> None: self._handle = _webview_lib.webview_create(int(debug), window) self._callbacks = {} @@ -29,8 +36,10 @@ class Webview: return self._size @size.setter - def size(self, value: Size): - _webview_lib.webview_set_size(self._handle, value.width, value.height, value.hint) + def size(self, value: Size) -> None: + _webview_lib.webview_set_size( + self._handle, value.width, value.height, value.hint + ) self._size = value @property @@ -38,26 +47,26 @@ class Webview: return self._title @title.setter - def title(self, value: str): + def title(self, value: str) -> None: _webview_lib.webview_set_title(self._handle, _encode_c_string(value)) self._title = value - def destroy(self): + def destroy(self) -> None: for name in list(self._callbacks.keys()): self.unbind(name) _webview_lib.webview_terminate(self._handle) _webview_lib.webview_destroy(self._handle) self._handle = None - def navigate(self, url: str): + def navigate(self, url: str) -> None: _webview_lib.webview_navigate(self._handle, _encode_c_string(url)) - def run(self): + def run(self) -> None: _webview_lib.webview_run(self._handle) self.destroy() - def bind(self, name: str, callback: Callable[..., Any]): - def wrapper(seq: bytes, req: bytes, arg: int): + def bind(self, name: str, callback: Callable[..., Any]) -> None: + def wrapper(seq: bytes, req: bytes, arg: int) -> None: args = json.loads(req.decode()) try: result = callback(*args) @@ -67,24 +76,31 @@ class Webview: success = False self.return_(seq.decode(), 0 if success else 1, json.dumps(result)) - c_callback = _webview_lib.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p)(wrapper) + c_callback = _webview_lib.CFUNCTYPE( + None, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p + )(wrapper) self._callbacks[name] = c_callback - _webview_lib.webview_bind(self._handle, _encode_c_string(name), c_callback, None) + _webview_lib.webview_bind( + self._handle, _encode_c_string(name), c_callback, None + ) - def unbind(self, name: str): + def unbind(self, name: str) -> None: if name in self._callbacks: _webview_lib.webview_unbind(self._handle, _encode_c_string(name)) del self._callbacks[name] - def return_(self, seq: str, status: int, result: str): - _webview_lib.webview_return(self._handle, _encode_c_string(seq), status, _encode_c_string(result)) + def return_(self, seq: str, status: int, result: str) -> None: + _webview_lib.webview_return( + self._handle, _encode_c_string(seq), status, _encode_c_string(result) + ) - def eval(self, source: str): + def eval(self, source: str) -> None: _webview_lib.webview_eval(self._handle, _encode_c_string(source)) - def init(self, source: str): + def init(self, source: str) -> None: _webview_lib.webview_init(self._handle, _encode_c_string(source)) + if __name__ == "__main__": wv = Webview() wv.title = "Hello, World!" diff --git a/pkgs/clan-app/default.nix b/pkgs/clan-app/default.nix index 944ab36fd..8f09df6e1 100644 --- a/pkgs/clan-app/default.nix +++ b/pkgs/clan-app/default.nix @@ -19,6 +19,7 @@ pytest-xdist, # Run tests in parallel on multiple cores pytest-timeout, # Add timeouts to your tests webview-ui, + webview-lib, fontconfig, }: let @@ -48,7 +49,7 @@ let # Runtime binary dependencies required by the application runtimeDependencies = [ - + webview-lib ]; # Dependencies required for running tests @@ -77,10 +78,9 @@ python3.pkgs.buildPythonApplication rec { dontWrapGApps = true; preFixup = '' makeWrapperArgs+=( - # Use software rendering for webkit, mesa causes random crashes with css. - --set WEBKIT_DISABLE_COMPOSITING_MODE 1 --set FONTCONFIG_FILE ${fontconfig.out}/etc/fonts/fonts.conf --set WEBUI_PATH "$out/${python3.sitePackages}/clan_app/.webui" + --set WEBVIEW_LIB_DIR "${webview-lib}/lib" # This prevents problems with mixed glibc versions that might occur when the # cli is called through a browser built against another glibc --unset LD_LIBRARY_PATH diff --git a/pkgs/clan-app/flake-module.nix b/pkgs/clan-app/flake-module.nix index 4ed9d50fb..72c098ce7 100644 --- a/pkgs/clan-app/flake-module.nix +++ b/pkgs/clan-app/flake-module.nix @@ -14,11 +14,11 @@ else { devShells.clan-app = pkgs.callPackage ./shell.nix { - inherit (config.packages) clan-app webview-wrapper; + inherit (config.packages) clan-app webview-lib; inherit self'; }; packages.clan-app = pkgs.python3.pkgs.callPackage ./default.nix { - inherit (config.packages) clan-cli webview-ui; + inherit (config.packages) clan-cli webview-ui webview-lib; }; checks = config.packages.clan-app.tests; diff --git a/pkgs/clan-app/shell.nix b/pkgs/clan-app/shell.nix index 9d91c7a3a..3904bbb8e 100644 --- a/pkgs/clan-app/shell.nix +++ b/pkgs/clan-app/shell.nix @@ -12,7 +12,7 @@ python3, gtk4, libadwaita, - webview-wrapper, + webview-lib, clang, self', }: @@ -39,8 +39,8 @@ mkShell { ruff gtk4 clang - webview-wrapper.dev - webview-wrapper + webview-lib.dev + webview-lib gtk4.dev # has the demo called 'gtk4-widget-factory' libadwaita.devdoc # has the demo called 'adwaita-1-demo' ] @@ -68,6 +68,6 @@ mkShell { export XDG_DATA_DIRS=${gtk4}/share/gsettings-schemas/gtk4-4.14.4:$XDG_DATA_DIRS export XDG_DATA_DIRS=${gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas-46.0:$XDG_DATA_DIRS - export WEBVIEW_LIB_DIR=${webview-wrapper}/lib + export WEBVIEW_LIB_DIR=${webview-lib}/lib ''; } diff --git a/pkgs/flake-module.nix b/pkgs/flake-module.nix index 85704a7cf..b608c9728 100644 --- a/pkgs/flake-module.nix +++ b/pkgs/flake-module.nix @@ -33,7 +33,7 @@ editor = pkgs.callPackage ./editor/clan-edit-codium.nix { }; classgen = pkgs.callPackage ./classgen { }; zerotierone = pkgs.callPackage ./zerotierone { }; - webview-wrapper = pkgs.callPackage ./webview-wrapper { }; + webview-lib = pkgs.callPackage ./webview-lib { }; }; }; } diff --git a/pkgs/webview-wrapper/default.nix b/pkgs/webview-lib/default.nix similarity index 87% rename from pkgs/webview-wrapper/default.nix rename to pkgs/webview-lib/default.nix index 96d8fd2d9..5dd78de97 100644 --- a/pkgs/webview-wrapper/default.nix +++ b/pkgs/webview-lib/default.nix @@ -7,11 +7,14 @@ pkgs.stdenv.mkDerivation { src = pkgs.fetchFromGitHub { owner = "webview"; repo = "webview"; - rev = "83a4b4a5bbcb4b0ba2ca3ee226c2da1414719106"; + rev = "83a4b4a5bbcb4b0ba2ca3ee226c2da1414719106"; sha256 = "sha256-5R8kllvP2EBuDANIl07fxv/EcbPpYgeav8Wfz7Kt13c="; }; - outputs = [ "out" "dev" ]; + outputs = [ + "out" + "dev" + ]; # Dependencies used during the build process, if any buildInputs = with pkgs; [ @@ -28,4 +31,4 @@ pkgs.stdenv.mkDerivation { license = licenses.mit; platforms = platforms.linux ++ platforms.darwin; }; -} \ No newline at end of file +}