Fix nix run .#clan-app

This commit is contained in:
Qubasa
2025-01-04 01:34:28 +01:00
parent e136e146d5
commit 44a2ce1583
8 changed files with 85 additions and 66 deletions

View File

@@ -5,17 +5,21 @@ from clan_cli.profiler import profile
log = logging.getLogger(__name__) 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 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 @profile
def main(argv: list[str] = sys.argv) -> int: def main(argv: list[str] = sys.argv) -> int:
parser = argparse.ArgumentParser(description="Clan App") 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") parser.add_argument("--debug", action="store_true", help="Enable debug mode")
args = parser.parse_args(argv[1:]) 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" site_index: Path = Path(os.getenv("WEBUI_PATH", ".")).resolve() / "index.html"
content_uri = f"file://{site_index}" content_uri = f"file://{site_index}"
html = """
<html>
<body>
<h1>Hello from Python Webview!</h1>
</body>
</html>
"""
webview = Webview() webview = Webview()
webview.navigate(f"data:text/html,{quote(html)}") webview.navigate(content_uri)
#webview.navigate(content_uri)
webview.run() webview.run()

View File

@@ -1,19 +1,20 @@
import ctypes import ctypes
import sys import ctypes.util
import os import os
import platform import platform
import urllib.request from ctypes import CFUNCTYPE, c_char_p, c_int, c_void_p
from pathlib import Path 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: def _encode_c_string(s: str) -> bytes:
return s.encode("utf-8") return s.encode("utf-8")
def _get_webview_version(): def _get_webview_version():
"""Get webview version from environment variable or use default""" """Get webview version from environment variable or use default"""
return os.getenv("WEBVIEW_VERSION", "0.8.1") return os.getenv("WEBVIEW_VERSION", "0.8.1")
def _get_lib_names(): def _get_lib_names():
"""Get platform-specific library names.""" """Get platform-specific library names."""
system = platform.system().lower() system = platform.system().lower()
@@ -22,22 +23,25 @@ def _get_lib_names():
if system == "windows": if system == "windows":
if machine == "amd64" or machine == "x86_64": if machine == "amd64" or machine == "x86_64":
return ["webview.dll", "WebView2Loader.dll"] return ["webview.dll", "WebView2Loader.dll"]
elif machine == "arm64": if machine == "arm64":
raise Exception("arm64 is not supported on Windows") msg = "arm64 is not supported on Windows"
elif system == "darwin": raise Exception(msg)
return None
if system == "darwin":
if machine == "arm64": if machine == "arm64":
return ["libwebview.aarch64.dylib"] return ["libwebview.aarch64.dylib"]
else:
return ["libwebview.x86_64.dylib"] return ["libwebview.x86_64.dylib"]
else: # linux # linux
return ["libwebview.so"] return ["libwebview.so"]
def _be_sure_libraries(): def _be_sure_libraries():
"""Ensure libraries exist and return paths.""" """Ensure libraries exist and return paths."""
lib_dir = os.environ.get("WEBVIEW_LIB_DIR") lib_dir = os.environ.get("WEBVIEW_LIB_DIR")
if not 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_dir = Path(lib_dir)
lib_names = _get_lib_names() lib_names = _get_lib_names()
lib_paths = [lib_dir / lib_name for lib_name in lib_names] lib_paths = [lib_dir / lib_name for lib_name in lib_names]
@@ -46,10 +50,11 @@ def _be_sure_libraries():
missing_libs = [path for path in lib_paths if not path.exists()] missing_libs = [path for path in lib_paths if not path.exists()]
if not missing_libs: if not missing_libs:
return lib_paths return lib_paths
return None
class _WebviewLibrary: class _WebviewLibrary:
def __init__(self): def __init__(self) -> None:
lib_names = _get_lib_names() lib_names = _get_lib_names()
try: try:
library_path = ctypes.util.find_library(lib_names[0]) library_path = ctypes.util.find_library(lib_names[0])
@@ -99,4 +104,5 @@ class _WebviewLibrary:
self.CFUNCTYPE = CFUNCTYPE self.CFUNCTYPE = CFUNCTYPE
_webview_lib = _WebviewLibrary() _webview_lib = _WebviewLibrary()

View File

@@ -1,8 +1,11 @@
from enum import IntEnum
from typing import Optional, Callable, Any
import json
import ctypes 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): class SizeHint(IntEnum):
NONE = 0 NONE = 0
@@ -10,14 +13,18 @@ class SizeHint(IntEnum):
MAX = 2 MAX = 2
FIXED = 3 FIXED = 3
class Size: 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.width = width
self.height = height self.height = height
self.hint = hint self.hint = hint
class Webview: 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._handle = _webview_lib.webview_create(int(debug), window)
self._callbacks = {} self._callbacks = {}
@@ -29,8 +36,10 @@ class Webview:
return self._size return self._size
@size.setter @size.setter
def size(self, value: Size): def size(self, value: Size) -> None:
_webview_lib.webview_set_size(self._handle, value.width, value.height, value.hint) _webview_lib.webview_set_size(
self._handle, value.width, value.height, value.hint
)
self._size = value self._size = value
@property @property
@@ -38,26 +47,26 @@ class Webview:
return self._title return self._title
@title.setter @title.setter
def title(self, value: str): def title(self, value: str) -> None:
_webview_lib.webview_set_title(self._handle, _encode_c_string(value)) _webview_lib.webview_set_title(self._handle, _encode_c_string(value))
self._title = value self._title = value
def destroy(self): def destroy(self) -> None:
for name in list(self._callbacks.keys()): for name in list(self._callbacks.keys()):
self.unbind(name) self.unbind(name)
_webview_lib.webview_terminate(self._handle) _webview_lib.webview_terminate(self._handle)
_webview_lib.webview_destroy(self._handle) _webview_lib.webview_destroy(self._handle)
self._handle = None self._handle = None
def navigate(self, url: str): def navigate(self, url: str) -> None:
_webview_lib.webview_navigate(self._handle, _encode_c_string(url)) _webview_lib.webview_navigate(self._handle, _encode_c_string(url))
def run(self): def run(self) -> None:
_webview_lib.webview_run(self._handle) _webview_lib.webview_run(self._handle)
self.destroy() self.destroy()
def bind(self, name: str, callback: Callable[..., Any]): def bind(self, name: str, callback: Callable[..., Any]) -> None:
def wrapper(seq: bytes, req: bytes, arg: int): def wrapper(seq: bytes, req: bytes, arg: int) -> None:
args = json.loads(req.decode()) args = json.loads(req.decode())
try: try:
result = callback(*args) result = callback(*args)
@@ -67,24 +76,31 @@ class Webview:
success = False success = False
self.return_(seq.decode(), 0 if success else 1, json.dumps(result)) 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 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: if name in self._callbacks:
_webview_lib.webview_unbind(self._handle, _encode_c_string(name)) _webview_lib.webview_unbind(self._handle, _encode_c_string(name))
del self._callbacks[name] del self._callbacks[name]
def return_(self, seq: str, status: int, result: str): 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)) _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)) _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)) _webview_lib.webview_init(self._handle, _encode_c_string(source))
if __name__ == "__main__": if __name__ == "__main__":
wv = Webview() wv = Webview()
wv.title = "Hello, World!" wv.title = "Hello, World!"

View File

@@ -19,6 +19,7 @@
pytest-xdist, # Run tests in parallel on multiple cores pytest-xdist, # Run tests in parallel on multiple cores
pytest-timeout, # Add timeouts to your tests pytest-timeout, # Add timeouts to your tests
webview-ui, webview-ui,
webview-lib,
fontconfig, fontconfig,
}: }:
let let
@@ -48,7 +49,7 @@ let
# Runtime binary dependencies required by the application # Runtime binary dependencies required by the application
runtimeDependencies = [ runtimeDependencies = [
webview-lib
]; ];
# Dependencies required for running tests # Dependencies required for running tests
@@ -77,10 +78,9 @@ python3.pkgs.buildPythonApplication rec {
dontWrapGApps = true; dontWrapGApps = true;
preFixup = '' preFixup = ''
makeWrapperArgs+=( 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 FONTCONFIG_FILE ${fontconfig.out}/etc/fonts/fonts.conf
--set WEBUI_PATH "$out/${python3.sitePackages}/clan_app/.webui" --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 # This prevents problems with mixed glibc versions that might occur when the
# cli is called through a browser built against another glibc # cli is called through a browser built against another glibc
--unset LD_LIBRARY_PATH --unset LD_LIBRARY_PATH

View File

@@ -14,11 +14,11 @@
else else
{ {
devShells.clan-app = pkgs.callPackage ./shell.nix { devShells.clan-app = pkgs.callPackage ./shell.nix {
inherit (config.packages) clan-app webview-wrapper; inherit (config.packages) clan-app webview-lib;
inherit self'; inherit self';
}; };
packages.clan-app = pkgs.python3.pkgs.callPackage ./default.nix { 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; checks = config.packages.clan-app.tests;

View File

@@ -12,7 +12,7 @@
python3, python3,
gtk4, gtk4,
libadwaita, libadwaita,
webview-wrapper, webview-lib,
clang, clang,
self', self',
}: }:
@@ -39,8 +39,8 @@ mkShell {
ruff ruff
gtk4 gtk4
clang clang
webview-wrapper.dev webview-lib.dev
webview-wrapper webview-lib
gtk4.dev # has the demo called 'gtk4-widget-factory' gtk4.dev # has the demo called 'gtk4-widget-factory'
libadwaita.devdoc # has the demo called 'adwaita-1-demo' 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=${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 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
''; '';
} }

View File

@@ -33,7 +33,7 @@
editor = pkgs.callPackage ./editor/clan-edit-codium.nix { }; editor = pkgs.callPackage ./editor/clan-edit-codium.nix { };
classgen = pkgs.callPackage ./classgen { }; classgen = pkgs.callPackage ./classgen { };
zerotierone = pkgs.callPackage ./zerotierone { }; zerotierone = pkgs.callPackage ./zerotierone { };
webview-wrapper = pkgs.callPackage ./webview-wrapper { }; webview-lib = pkgs.callPackage ./webview-lib { };
}; };
}; };
} }

View File

@@ -11,7 +11,10 @@ pkgs.stdenv.mkDerivation {
sha256 = "sha256-5R8kllvP2EBuDANIl07fxv/EcbPpYgeav8Wfz7Kt13c="; sha256 = "sha256-5R8kllvP2EBuDANIl07fxv/EcbPpYgeav8Wfz7Kt13c=";
}; };
outputs = [ "out" "dev" ]; outputs = [
"out"
"dev"
];
# Dependencies used during the build process, if any # Dependencies used during the build process, if any
buildInputs = with pkgs; [ buildInputs = with pkgs; [