feat(Anaconda): Local Repo
This commit is contained in:
@@ -0,0 +1,898 @@
|
||||
format = 2
|
||||
pkgname = python-simpleline
|
||||
pkgbase = python-simpleline
|
||||
pkgver = 1.8-1
|
||||
pkgarch = any
|
||||
pkgbuild_sha256sum = 7502d64c9ab7b8658cf85b6919f95cec8bbdc1febca98f91b01de326450080ea
|
||||
packager = Unknown Packager
|
||||
builddate = 1781307804
|
||||
builddir = /home/nocode/Files/FWS-ISO/pkgbuilds/python-simpleline
|
||||
startdir = /home/nocode/Files/FWS-ISO/pkgbuilds/python-simpleline
|
||||
buildtool = makepkg
|
||||
buildtoolver = 7.1.0
|
||||
buildenv = !distcc
|
||||
buildenv = color
|
||||
buildenv = !ccache
|
||||
buildenv = check
|
||||
buildenv = !sign
|
||||
options = strip
|
||||
options = docs
|
||||
options = !libtool
|
||||
options = !staticlibs
|
||||
options = emptydirs
|
||||
options = zipman
|
||||
options = purge
|
||||
options = debug
|
||||
options = lto
|
||||
installed = aardvark-dns-1.17.1-1-x86_64
|
||||
installed = abseil-cpp-20260107.1-1-x86_64
|
||||
installed = accountsservice-26.13.3-1-x86_64
|
||||
installed = acl-2.3.2-2-x86_64
|
||||
installed = adwaita-cursors-50.0-1-any
|
||||
installed = adwaita-fonts-50.0-1-any
|
||||
installed = adwaita-icon-theme-50.0-1-any
|
||||
installed = adwaita-icon-theme-legacy-46.2-3-any
|
||||
installed = alsa-card-profiles-1:1.6.6-1-x86_64
|
||||
installed = alsa-lib-1.2.16-1-x86_64
|
||||
installed = alsa-plugins-1:1.2.12-5-x86_64
|
||||
installed = alsa-topology-conf-1.2.5.1-4-any
|
||||
installed = alsa-ucm-conf-1.2.16-2-any
|
||||
installed = aom-3.14.1-1-x86_64
|
||||
installed = appstream-1.1.2-1-x86_64
|
||||
installed = aquamarine-0.12.1-1-x86_64
|
||||
installed = arch-install-scripts-31-1-any
|
||||
installed = archiso-88-1-any
|
||||
installed = archlinux-keyring-20260420-1-any
|
||||
installed = argon2-20190702-6-x86_64
|
||||
installed = at-spi2-core-2.60.4-1-x86_64
|
||||
installed = atkmm-2.28.5-2-x86_64
|
||||
installed = attr-2.5.2-2-x86_64
|
||||
installed = audit-4.1.4-2-x86_64
|
||||
installed = autoconf-2.73-1-any
|
||||
installed = automake-1.18.1-1-any
|
||||
installed = avahi-1:0.9rc4-1-x86_64
|
||||
installed = base-3-3-any
|
||||
installed = base-devel-1-2-any
|
||||
installed = bash-5.3.15-1-x86_64
|
||||
installed = bemoji-0.4.0-1-any
|
||||
installed = binutils-2.46+r70+g155188ea10a7-1-x86_64
|
||||
installed = bison-3.8.2-8-x86_64
|
||||
installed = blas-3.12.1-2-x86_64
|
||||
installed = bluez-libs-5.86-6-x86_64
|
||||
installed = boost-libs-1.91.0-1-x86_64
|
||||
installed = brightnessctl-0.5.1-3-x86_64
|
||||
installed = brotli-1.2.0-1-x86_64
|
||||
installed = bubblewrap-0.11.2-1-x86_64
|
||||
installed = bzip2-1.0.8-6-x86_64
|
||||
installed = c-ares-1.34.6-1-x86_64
|
||||
installed = ca-certificates-20240618-1-any
|
||||
installed = ca-certificates-mozilla-3.124-1-x86_64
|
||||
installed = ca-certificates-utils-20240618-1-any
|
||||
installed = cabextract-1.11-2-x86_64
|
||||
installed = cairo-1.18.4-1-x86_64
|
||||
installed = cairomm-1.14.6-1-x86_64
|
||||
installed = cairomm-1.16-1.18.1-1-x86_64
|
||||
installed = catatonit-0.2.1-2-x86_64
|
||||
installed = cblas-3.12.1-2-x86_64
|
||||
installed = cliphist-1:0.7.0-2-x86_64
|
||||
installed = clucene-2.3.3.4-17-x86_64
|
||||
installed = cmake-4.3.3-1-x86_64
|
||||
installed = compiler-rt-22.1.6-1-x86_64
|
||||
installed = composefs-1.0.8-1-x86_64
|
||||
installed = conmon-1:2.2.1-1-x86_64
|
||||
installed = containers-common-1:0.67.1-3-any
|
||||
installed = coreutils-9.11-1-x86_64
|
||||
installed = cpio-2.15-3-x86_64
|
||||
installed = cppdap-1.58.0-3-x86_64
|
||||
installed = cracklib-2.10.3-1-x86_64
|
||||
installed = criu-4.2-3-x86_64
|
||||
installed = crun-1.28-1-x86_64
|
||||
installed = cryptsetup-2.8.6-1-x86_64
|
||||
installed = curl-8.20.0-7-x86_64
|
||||
installed = dav1d-1.5.3-1-x86_64
|
||||
installed = db5.3-5.3.28-7-x86_64
|
||||
installed = dbus-1.16.2-1-x86_64
|
||||
installed = dbus-broker-37-3-x86_64
|
||||
installed = dbus-broker-units-37-3-x86_64
|
||||
installed = dbus-units-37-3-x86_64
|
||||
installed = dconf-0.49.0-1-x86_64
|
||||
installed = debugedit-5.3-1-x86_64
|
||||
installed = default-cursors-3-1-any
|
||||
installed = desktop-file-utils-0.28-1-x86_64
|
||||
installed = device-mapper-2.03.41-1-x86_64
|
||||
installed = diffutils-3.12-2-x86_64
|
||||
installed = discord-1:1.0.142-1-x86_64
|
||||
installed = dkms-3.4.1-1-any
|
||||
installed = dos2unix-7.5.6-1-x86_64
|
||||
installed = dosfstools-4.2-5-x86_64
|
||||
installed = double-conversion-3.4.0-1-x86_64
|
||||
installed = duktape-2.7.0-7-x86_64
|
||||
installed = e2fsprogs-1.47.4-1-x86_64
|
||||
installed = efibootmgr-18-4-x86_64
|
||||
installed = efivar-39-2-x86_64
|
||||
installed = egl-gbm-1.1.3-1-x86_64
|
||||
installed = egl-wayland-4:1.1.21-1-x86_64
|
||||
installed = egl-wayland2-1.0.1-1-x86_64
|
||||
installed = egl-x11-1.0.5-1-x86_64
|
||||
installed = eglexternalplatform-1.2.1-1-any
|
||||
installed = electron41-41.7.2-1-x86_64
|
||||
installed = elfutils-0.195-1-x86_64
|
||||
installed = erofs-utils-1.9.1-1-x86_64
|
||||
installed = exo-4.20.0-2-x86_64
|
||||
installed = expat-2.8.1-1-x86_64
|
||||
installed = extra-cmake-modules-6.26.0-1-any
|
||||
installed = fakeroot-1:1.37.2-1-x86_64
|
||||
installed = fcft-3.3.3-1-x86_64
|
||||
installed = ffmpeg-2:8.1.1-2-x86_64
|
||||
installed = fftw-3.3.11-1-x86_64
|
||||
installed = file-5.48-1-x86_64
|
||||
installed = filesystem-2025.10.12-1-any
|
||||
installed = findutils-4.10.0-3-x86_64
|
||||
installed = firefox-151.0.4-1-x86_64
|
||||
installed = flac-1.5.0-1-x86_64
|
||||
installed = flatpak-1:1.18.0-1-x86_64
|
||||
installed = flex-2.6.4-6-x86_64
|
||||
installed = fmt-12.1.0-2-x86_64
|
||||
installed = fontconfig-2:2.18.1-1-x86_64
|
||||
installed = freerdp-2:3.26.0-1-x86_64
|
||||
installed = freetype2-2.14.3-1-x86_64
|
||||
installed = fribidi-1.0.16-2-x86_64
|
||||
installed = fuse-common-3.18.2-1-x86_64
|
||||
installed = fuse2-2.9.9-5-x86_64
|
||||
installed = fuse3-3.18.2-1-x86_64
|
||||
installed = fuzzel-1.14.1-1-x86_64
|
||||
installed = gawk-5.4.0-1-x86_64
|
||||
installed = gc-8.2.12-1-x86_64
|
||||
installed = gcc-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = gcr-3.41.2-2-x86_64
|
||||
installed = gcr-4-4.4.0.1-1-x86_64
|
||||
installed = gdb-17.2-1-x86_64
|
||||
installed = gdb-common-17.2-1-x86_64
|
||||
installed = gdbm-1.26-2-x86_64
|
||||
installed = gdk-pixbuf2-2.44.6-2-x86_64
|
||||
installed = gettext-1.0-2-x86_64
|
||||
installed = giflib-6.1.3-1-x86_64
|
||||
installed = git-2.54.0-1-x86_64
|
||||
installed = glib-networking-1:2.80.1-1-x86_64
|
||||
installed = glib2-2.88.1-1-x86_64
|
||||
installed = glibc-2.43+r22+g8362e8ce10b2-2-x86_64
|
||||
installed = glibmm-2.66.8-2-x86_64
|
||||
installed = glibmm-2.68-2.88.0-2-x86_64
|
||||
installed = glslang-1:1.4.350.0-1-x86_64
|
||||
installed = glycin-2.1.1-1-x86_64
|
||||
installed = gmp-6.3.0-3-x86_64
|
||||
installed = gnome-themes-extra-1:3.28-1-any
|
||||
installed = gnulib-l10n-20241231-1-any
|
||||
installed = gnupg-2.4.9-1-x86_64
|
||||
installed = gnutls-3.8.13-2-x86_64
|
||||
installed = go-2:1.26.4-1-x86_64
|
||||
installed = gobject-introspection-1.86.0-2-x86_64
|
||||
installed = gobject-introspection-runtime-1.86.0-2-x86_64
|
||||
installed = gparted-1.8.1-2-x86_64
|
||||
installed = gperftools-2.18.1-1-x86_64
|
||||
installed = gpgme-2.1.0-1-x86_64
|
||||
installed = gpgmepp-2.1.0-1-x86_64
|
||||
installed = gpm-1.20.7.r38.ge82d1a6-6-x86_64
|
||||
installed = gpsd-3.27.5-1-x86_64
|
||||
installed = gptfdisk-1.0.10-2-x86_64
|
||||
installed = graphene-1.10.8-2-x86_64
|
||||
installed = graphite-1:1.3.15-1-x86_64
|
||||
installed = grep-3.12-2-x86_64
|
||||
installed = grim-1.5.0-2-x86_64
|
||||
installed = groff-1.24.1-1-x86_64
|
||||
installed = grub-2:2.14-1-x86_64
|
||||
installed = gsettings-desktop-schemas-50.1-1-any
|
||||
installed = gsettings-system-schemas-50.1-1-any
|
||||
installed = gsm-1.0.24-1-x86_64
|
||||
installed = gssdp-1.6.5-1-x86_64
|
||||
installed = gst-plugins-bad-libs-1.28.3-1-x86_64
|
||||
installed = gst-plugins-base-libs-1.28.3-1-x86_64
|
||||
installed = gstreamer-1.28.3-1-x86_64
|
||||
installed = gtest-1.17.0-2-x86_64
|
||||
installed = gtk-layer-shell-0.10.1-1-x86_64
|
||||
installed = gtk-update-icon-cache-1:4.22.4-1-x86_64
|
||||
installed = gtk3-1:3.24.52-1-x86_64
|
||||
installed = gtk4-1:4.22.4-1-x86_64
|
||||
installed = gtkmm-4.0-4.22.0-2-x86_64
|
||||
installed = gtkmm3-3.24.11-1-x86_64
|
||||
installed = guile-3.0.11-1-x86_64
|
||||
installed = gupnp-1:1.6.10-1-x86_64
|
||||
installed = gupnp-igd-1.6.0-2-x86_64
|
||||
installed = gzip-1.14-2-x86_64
|
||||
installed = harfbuzz-14.2.1-1-x86_64
|
||||
installed = harfbuzz-icu-14.2.1-1-x86_64
|
||||
installed = hicolor-icon-theme-0.18-1-any
|
||||
installed = hidapi-0.15.0-1-x86_64
|
||||
installed = highway-1.4.0-1-x86_64
|
||||
installed = hunspell-1.7.3-1-x86_64
|
||||
installed = hwdata-0.408-1-any
|
||||
installed = hwloc-2.13.0-1-x86_64
|
||||
installed = hyphen-2.8.9-1-x86_64
|
||||
installed = hyprcursor-0.1.13-6-x86_64
|
||||
installed = hyprgraphics-0.5.1-2-x86_64
|
||||
installed = hypridle-0.1.7-9-x86_64
|
||||
installed = hyprland-0.55.4-1-x86_64
|
||||
installed = hyprland-guiutils-0.2.1-6-x86_64
|
||||
installed = hyprland-qt-support-0.1.0-12-x86_64
|
||||
installed = hyprlang-0.6.8-4-x86_64
|
||||
installed = hyprlock-0.9.5-4-x86_64
|
||||
installed = hyprpaper-0.8.4-2-x86_64
|
||||
installed = hyprpicker-0.4.7-1-x86_64
|
||||
installed = hyprpolkitagent-0.1.3-7-x86_64
|
||||
installed = hyprshot-1.3.0-4-any
|
||||
installed = hyprtoolkit-0.5.4-2-x86_64
|
||||
installed = hyprutils-0.13.1-1-x86_64
|
||||
installed = hyprwayland-scanner-0.4.6-1-x86_64
|
||||
installed = hyprwire-0.3.1-2-x86_64
|
||||
installed = iana-etc-20260530-1-any
|
||||
installed = icu-78.3-1-x86_64
|
||||
installed = imagemagick-7.1.2.25-1-x86_64
|
||||
installed = iniparser-4.2.6-2-x86_64
|
||||
installed = iproute2-7.0.0-1-x86_64
|
||||
installed = iptables-1:1.8.13-1-x86_64
|
||||
installed = iputils-20250605-1-x86_64
|
||||
installed = iso-codes-4.20.1-1-any
|
||||
installed = jack2-1.9.22-2-x86_64
|
||||
installed = jansson-2.15.0-1-x86_64
|
||||
installed = jbigkit-2.1-8-x86_64
|
||||
installed = jq-1.8.1-3-x86_64
|
||||
installed = json-c-0.18-2-x86_64
|
||||
installed = json-glib-1.10.8-1-x86_64
|
||||
installed = jsoncpp-1.9.6-3-x86_64
|
||||
installed = kbd-2.10.0-1-x86_64
|
||||
installed = kcoreaddons-6.26.0-1-x86_64
|
||||
installed = keyutils-1.6.3-4-x86_64
|
||||
installed = ki18n-6.26.0-1-x86_64
|
||||
installed = kitty-0.47.1-1-x86_64
|
||||
installed = kitty-shell-integration-0.47.1-1-x86_64
|
||||
installed = kitty-terminfo-0.47.1-1-x86_64
|
||||
installed = kmod-34.2-1-x86_64
|
||||
installed = kpmcore-26.04.2-1-x86_64
|
||||
installed = krb5-1.22.2-1-x86_64
|
||||
installed = kwidgetsaddons-6.26.0-1-x86_64
|
||||
installed = l-smash-2.14.5-4-x86_64
|
||||
installed = lame-3.101.r6531-1-x86_64
|
||||
installed = lapack-3.12.1-2-x86_64
|
||||
installed = lcms2-2.19.1-1-x86_64
|
||||
installed = leancrypto-1.7.2-1-x86_64
|
||||
installed = lib32-alsa-lib-1.2.16-1-x86_64
|
||||
installed = lib32-alsa-plugins-1.2.12-1-x86_64
|
||||
installed = lib32-brotli-1.1.0-1-x86_64
|
||||
installed = lib32-bzip2-1.0.8-4-x86_64
|
||||
installed = lib32-curl-8.20.0-7-x86_64
|
||||
installed = lib32-dbus-1.16.2-1-x86_64
|
||||
installed = lib32-e2fsprogs-1.47.4-1-x86_64
|
||||
installed = lib32-expat-2.8.1-1-x86_64
|
||||
installed = lib32-fontconfig-2:2.18.1-1-x86_64
|
||||
installed = lib32-freetype2-2.14.3-1-x86_64
|
||||
installed = lib32-gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = lib32-glib2-2.88.1-1-x86_64
|
||||
installed = lib32-glibc-2.43+r22+g8362e8ce10b2-2-x86_64
|
||||
installed = lib32-gmp-6.3.0-2-x86_64
|
||||
installed = lib32-gnutls-3.8.13-3-x86_64
|
||||
installed = lib32-icu-78.3-1-x86_64
|
||||
installed = lib32-json-c-0.18-2-x86_64
|
||||
installed = lib32-keyutils-1.6.3-4-x86_64
|
||||
installed = lib32-krb5-1.22.2-1-x86_64
|
||||
installed = lib32-libdrm-2.4.134-1-x86_64
|
||||
installed = lib32-libelf-0.195-1-x86_64
|
||||
installed = lib32-libffi-3.5.2-1-x86_64
|
||||
installed = lib32-libgcrypt-1.12.2-1-x86_64
|
||||
installed = lib32-libglvnd-1.7.0-1-x86_64
|
||||
installed = lib32-libgpg-error-1.61-1-x86_64
|
||||
installed = lib32-libidn2-2.3.8-1-x86_64
|
||||
installed = lib32-libldap-2.6.13-1-x86_64
|
||||
installed = lib32-libnghttp2-1.69.0-1-x86_64
|
||||
installed = lib32-libnghttp3-1.16.0-1-x86_64
|
||||
installed = lib32-libngtcp2-1.23.0-1-x86_64
|
||||
installed = lib32-libnm-1.56.1-1-x86_64
|
||||
installed = lib32-libpciaccess-0.19-1-x86_64
|
||||
installed = lib32-libpipewire-1:1.6.6-1-x86_64
|
||||
installed = lib32-libpng-1.6.58-1-x86_64
|
||||
installed = lib32-libpsl-0.21.5-1-x86_64
|
||||
installed = lib32-libssh2-1.11.1-1-x86_64
|
||||
installed = lib32-libtasn1-4.21.0-1-x86_64
|
||||
installed = lib32-libunistring-1.4.2-1-x86_64
|
||||
installed = lib32-libva-2.22.0-1-x86_64
|
||||
installed = lib32-libx11-1.8.13-1-x86_64
|
||||
installed = lib32-libxau-1.0.12-1-x86_64
|
||||
installed = lib32-libxcb-1.17.0-1-x86_64
|
||||
installed = lib32-libxcrypt-4.5.2-1-x86_64
|
||||
installed = lib32-libxcrypt-compat-4.5.2-1-x86_64
|
||||
installed = lib32-libxdmcp-1.1.5-1-x86_64
|
||||
installed = lib32-libxext-1.3.7-1-x86_64
|
||||
installed = lib32-libxfixes-6.0.1-2-x86_64
|
||||
installed = lib32-libxinerama-1.1.6-1-x86_64
|
||||
installed = lib32-libxml2-2.15.3-1-x86_64
|
||||
installed = lib32-libxshmfence-1.3.3-1-x86_64
|
||||
installed = lib32-libxss-1.2.5-1-x86_64
|
||||
installed = lib32-libxxf86vm-1.1.5-2-x86_64
|
||||
installed = lib32-llvm-libs-1:22.1.6-1-x86_64
|
||||
installed = lib32-lm_sensors-1:3.6.2-2-x86_64
|
||||
installed = lib32-mesa-1:26.1.2-1-x86_64
|
||||
installed = lib32-ncurses-6.6-2-x86_64
|
||||
installed = lib32-nettle-4.0-2-x86_64
|
||||
installed = lib32-nspr-4.39-1-x86_64
|
||||
installed = lib32-nss-3.124-1-x86_64
|
||||
installed = lib32-nvidia-utils-610.43.02-1-x86_64
|
||||
installed = lib32-openssl-1:3.6.3-1-x86_64
|
||||
installed = lib32-p11-kit-0.26.2-1-x86_64
|
||||
installed = lib32-pcre2-10.47-1-x86_64
|
||||
installed = lib32-pipewire-1:1.6.6-1-x86_64
|
||||
installed = lib32-spirv-tools-1:1.4.350.0-1-x86_64
|
||||
installed = lib32-sqlite-3.53.2-1-x86_64
|
||||
installed = lib32-systemd-260.2-1-x86_64
|
||||
installed = lib32-util-linux-2.42.1-1-x86_64
|
||||
installed = lib32-vulkan-icd-loader-1.4.350.0-1-x86_64
|
||||
installed = lib32-wayland-1.25.0-1-x86_64
|
||||
installed = lib32-xz-5.8.3-1-x86_64
|
||||
installed = lib32-zlib-1.3.2-1-x86_64
|
||||
installed = lib32-zstd-1.5.7-2-x86_64
|
||||
installed = libabw-0.1.4-1-x86_64
|
||||
installed = libadwaita-1:1.9.1-1-x86_64
|
||||
installed = libaio-0.3.113-4-x86_64
|
||||
installed = libappindicator-12.10.1-1-x86_64
|
||||
installed = libarchive-3.8.7-1-x86_64
|
||||
installed = libasan-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libass-0.17.4-2-x86_64
|
||||
installed = libassuan-3.0.0-1-x86_64
|
||||
installed = libasyncns-1:0.8+r3+g68cd5af-3-x86_64
|
||||
installed = libatomic-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libatomic_ops-7.8.2-2-x86_64
|
||||
installed = libavc1394-0.5.4-7-x86_64
|
||||
installed = libavif-1.4.2-1-x86_64
|
||||
installed = libb2-0.98.1-3-x86_64
|
||||
installed = libblake3-1.8.4-1-x86_64
|
||||
installed = libbluray-1.4.1-1-x86_64
|
||||
installed = libbpf-1.7.0-1-x86_64
|
||||
installed = libbs2b-3.1.0-10-x86_64
|
||||
installed = libbsd-0.12.2-2-x86_64
|
||||
installed = libburn-1.5.8-1-x86_64
|
||||
installed = libcanberra-1:0.30+r2+gc0620e4-6-x86_64
|
||||
installed = libcap-2.78-1-x86_64
|
||||
installed = libcap-ng-0.9.3-1-x86_64
|
||||
installed = libcbor-0.14.0-1-x86_64
|
||||
installed = libcdio-2.3.0-1-x86_64
|
||||
installed = libcdio-paranoia-10.2+2.0.2-2-x86_64
|
||||
installed = libcdr-0.1.9-1-x86_64
|
||||
installed = libcloudproviders-0.4.0-1-x86_64
|
||||
installed = libcmis-0.6.3-1-x86_64
|
||||
installed = libcolord-1.4.8-1-x86_64
|
||||
installed = libcups-2:2.4.19-1-x86_64
|
||||
installed = libdaemon-0.14-6-x86_64
|
||||
installed = libdatrie-0.2.14-1-x86_64
|
||||
installed = libdbusmenu-glib-18.10.20180917-1-x86_64
|
||||
installed = libdbusmenu-gtk3-18.10.20180917-1-x86_64
|
||||
installed = libdecor-0.2.5-1-x86_64
|
||||
installed = libdeflate-1.25-1-x86_64
|
||||
installed = libdisplay-info-0.3.0-1-x86_64
|
||||
installed = libdovi-3.3.2-1-x86_64
|
||||
installed = libdrm-2.4.134-1-x86_64
|
||||
installed = libdvdnav-7.0.0-1-x86_64
|
||||
installed = libdvdread-7.0.1-1-x86_64
|
||||
installed = libe-book-0.1.3-20-x86_64
|
||||
installed = libebur128-1.2.6-2-x86_64
|
||||
installed = libedit-20260512_3.1-1-x86_64
|
||||
installed = libei-1.6.0-1-x86_64
|
||||
installed = libelf-0.195-1-x86_64
|
||||
installed = libepoxy-1.5.10-3-x86_64
|
||||
installed = libepubgen-0.1.1-6-x86_64
|
||||
installed = libetonyek-0.1.13-2-x86_64
|
||||
installed = libevdev-1.13.6-1-x86_64
|
||||
installed = libevent-2.1.12-5-x86_64
|
||||
installed = libexif-0.6.26-1-x86_64
|
||||
installed = libexttextcat-3.4.8-1-x86_64
|
||||
installed = libfdk-aac-2.0.3-2-x86_64
|
||||
installed = libffi-3.5.2-1-x86_64
|
||||
installed = libfido2-1.17.0-1-x86_64
|
||||
installed = libfontenc-1.1.9-1-x86_64
|
||||
installed = libfreeaptx-0.2.2-1-x86_64
|
||||
installed = libfreehand-0.1.3-1-x86_64
|
||||
installed = libfyaml-0.9.6-2-x86_64
|
||||
installed = libgcc-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libgcrypt-1.12.2-1-x86_64
|
||||
installed = libgfortran-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libgirepository-1.86.0-2-x86_64
|
||||
installed = libgit2-1:1.9.4-1-x86_64
|
||||
installed = libglvnd-1.7.0-3-x86_64
|
||||
installed = libgomp-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libgpg-error-1.61-1-x86_64
|
||||
installed = libgtop-2.41.3-2-x86_64
|
||||
installed = libgudev-238-3-x86_64
|
||||
installed = libice-1.1.2-1-x86_64
|
||||
installed = libidn2-2.3.8-1-x86_64
|
||||
installed = libiec61883-1.2.0-9-x86_64
|
||||
installed = libimobiledevice-1.4.0-2-x86_64
|
||||
installed = libimobiledevice-glue-1.3.2-1-x86_64
|
||||
installed = libinih-62-2-x86_64
|
||||
installed = libinput-1.31.3-1-x86_64
|
||||
installed = libisl-0.27-1-x86_64
|
||||
installed = libisoburn-1.5.8.2-1-x86_64
|
||||
installed = libisofs-1.5.8.2-1-x86_64
|
||||
installed = libixion-0.20.0-7-x86_64
|
||||
installed = libjpeg-turbo-3.1.4.1-1-x86_64
|
||||
installed = libjxl-0.11.2-2-x86_64
|
||||
installed = libksba-1.8.0-1-x86_64
|
||||
installed = liblangtag-0.6.8-1-x86_64
|
||||
installed = liblc3-1.1.3-2-x86_64
|
||||
installed = libldac-2.0.2.3-3-x86_64
|
||||
installed = libldap-2.6.13-1-x86_64
|
||||
installed = liblqr-0.4.3-1-x86_64
|
||||
installed = liblsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libmakepkg-dropins-20-1-any
|
||||
installed = libmalcontent-0.14.0-4-x86_64
|
||||
installed = libmd-1.2.0-1-x86_64
|
||||
installed = libmm-glib-1.24.2-1-x86_64
|
||||
installed = libmnl-1.0.5-2-x86_64
|
||||
installed = libmodplug-0.8.9.0-7-x86_64
|
||||
installed = libmpc-1.4.1-1-x86_64
|
||||
installed = libmpdclient-2.23-1-x86_64
|
||||
installed = libmspub-0.1.5-1-x86_64
|
||||
installed = libmwaw-0.3.22-4-x86_64
|
||||
installed = libmysofa-1.3.4-1-x86_64
|
||||
installed = libndp-1.9-1-x86_64
|
||||
installed = libnet-2:1.3-2-x86_64
|
||||
installed = libnetfilter_conntrack-1.1.1-1-x86_64
|
||||
installed = libnewt-0.52.25-2-x86_64
|
||||
installed = libnfnetlink-1.0.2-2-x86_64
|
||||
installed = libnftnl-1.3.1-1-x86_64
|
||||
installed = libnghttp2-1.69.0-1-x86_64
|
||||
installed = libnghttp3-1.16.0-1-x86_64
|
||||
installed = libngtcp2-1.23.0-1-x86_64
|
||||
installed = libnice-0.1.23-1-x86_64
|
||||
installed = libnl-3.12.0-1-x86_64
|
||||
installed = libnm-1.56.1-1-x86_64
|
||||
installed = libnma-1.10.6-3-x86_64
|
||||
installed = libnma-common-1.10.6-3-x86_64
|
||||
installed = libnotify-0.8.8-1-x86_64
|
||||
installed = libnsl-2.0.1-2-x86_64
|
||||
installed = libnumbertext-1.0.11-3-x86_64
|
||||
installed = libobjc-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libodfgen-0.1.8-5-x86_64
|
||||
installed = libogg-1.3.6-1-x86_64
|
||||
installed = libopenmpt-0.8.7-1-x86_64
|
||||
installed = libopenrazer-0.4.0-1-x86_64
|
||||
installed = liborcus-0.21.0-6-x86_64
|
||||
installed = libp11-kit-0.26.2-1-x86_64
|
||||
installed = libpagemaker-0.0.4-5-x86_64
|
||||
installed = libpcap-1.10.6-1-x86_64
|
||||
installed = libpciaccess-0.19-1-x86_64
|
||||
installed = libpgm-5.3.128-4-x86_64
|
||||
installed = libpipewire-1:1.6.6-1-x86_64
|
||||
installed = libplacebo-7.360.1-2-x86_64
|
||||
installed = libplist-2.7.0-3-x86_64
|
||||
installed = libpng-1.6.58-1-x86_64
|
||||
installed = libproxy-0.5.12-1-x86_64
|
||||
installed = libpsl-0.21.5-2-x86_64
|
||||
installed = libpulse-17.0+r98+gb096704c0-1-x86_64
|
||||
installed = libpwquality-1.4.5-7-x86_64
|
||||
installed = libquadmath-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libqxp-0.0.3-1-x86_64
|
||||
installed = libraqm-0.10.5-1-x86_64
|
||||
installed = libraw1394-2.1.2-4-x86_64
|
||||
installed = libreoffice-fresh-26.2.4-1-x86_64
|
||||
installed = librevenge-0.0.5-4-x86_64
|
||||
installed = librsvg-2:2.62.3-1-x86_64
|
||||
installed = librsync-1:2.3.4-2-x86_64
|
||||
installed = libsamplerate-0.2.2-3-x86_64
|
||||
installed = libsasl-2.1.28-5-x86_64
|
||||
installed = libseccomp-2.6.0-1-x86_64
|
||||
installed = libsecret-0.21.7-1-x86_64
|
||||
installed = libsigc++-2.12.2-1-x86_64
|
||||
installed = libsigc++-3.0-3.8.1-1-x86_64
|
||||
installed = libsixel-1.10.5-1-x86_64
|
||||
installed = libsm-1.2.6-1-x86_64
|
||||
installed = libsndfile-1.2.2-4-x86_64
|
||||
installed = libsodium-1.0.22-1-x86_64
|
||||
installed = libsoup3-3.6.6-2-x86_64
|
||||
installed = libsoxr-0.1.3-4-x86_64
|
||||
installed = libssh-0.12.0-1-x86_64
|
||||
installed = libssh2-1.11.1-1-x86_64
|
||||
installed = libstaroffice-0.0.7-5-x86_64
|
||||
installed = libstdc++-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libstemmer-3.1.1-1-x86_64
|
||||
installed = libsysprof-capture-50.0-2-x86_64
|
||||
installed = libtasn1-4.21.0-1-x86_64
|
||||
installed = libtatsu-1.0.5-1-x86_64
|
||||
installed = libteam-1.32-3-x86_64
|
||||
installed = libthai-0.1.30-1-x86_64
|
||||
installed = libtheora-1.2.0-1-x86_64
|
||||
installed = libtiff-4.7.1-2-x86_64
|
||||
installed = libtirpc-1.3.7-1-x86_64
|
||||
installed = libtommath-1.3.0-2-x86_64
|
||||
installed = libtool-2.6.1-1-x86_64
|
||||
installed = libtsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libubsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
|
||||
installed = libunibreak-7.0-1-x86_64
|
||||
installed = libunistring-1.4.2-1-x86_64
|
||||
installed = libunwind-1.8.2-1-x86_64
|
||||
installed = liburing-2.14-1-x86_64
|
||||
installed = libusb-1.0.30-1-x86_64
|
||||
installed = libusbmuxd-2.1.1-2-x86_64
|
||||
installed = libutf8proc-2.11.3-1-x86_64
|
||||
installed = libuv-1.52.1-1-x86_64
|
||||
installed = libva-2.23.0-1-x86_64
|
||||
installed = libva-nvidia-driver-0.0.17-1-x86_64
|
||||
installed = libvdpau-1.5-4-x86_64
|
||||
installed = libverto-0.3.2-6-x86_64
|
||||
installed = libvisio-0.1.11-1-x86_64
|
||||
installed = libvorbis-1.3.7-4-x86_64
|
||||
installed = libvpl-2.16.0-2-x86_64
|
||||
installed = libvpx-1.16.0-3-x86_64
|
||||
installed = libwacom-2.19.0-1-x86_64
|
||||
installed = libwebp-1.6.0-2-x86_64
|
||||
installed = libwireplumber-0.5.14-1-x86_64
|
||||
installed = libwpd-0.10.3-6-x86_64
|
||||
installed = libwps-0.4.14-4-x86_64
|
||||
installed = libx11-1.8.13-1-x86_64
|
||||
installed = libxau-1.0.12-1-x86_64
|
||||
installed = libxcb-1.17.0-1-x86_64
|
||||
installed = libxcomposite-0.4.7-1-x86_64
|
||||
installed = libxcrypt-4.5.2-1-x86_64
|
||||
installed = libxcrypt-compat-4.5.2-1-x86_64
|
||||
installed = libxcursor-1.2.3-1-x86_64
|
||||
installed = libxcvt-0.1.3-1-x86_64
|
||||
installed = libxdamage-1.1.7-1-x86_64
|
||||
installed = libxdmcp-1.1.5-2-x86_64
|
||||
installed = libxext-1.3.7-1-x86_64
|
||||
installed = libxfce4ui-4.20.2-1-x86_64
|
||||
installed = libxfce4util-4.20.1-1-x86_64
|
||||
installed = libxfixes-6.0.2-1-x86_64
|
||||
installed = libxfont2-2.0.7-1-x86_64
|
||||
installed = libxft-2.3.9-1-x86_64
|
||||
installed = libxi-1.8.3-1-x86_64
|
||||
installed = libxinerama-1.1.6-1-x86_64
|
||||
installed = libxkbcommon-1.13.2-1-x86_64
|
||||
installed = libxkbcommon-x11-1.13.2-1-x86_64
|
||||
installed = libxkbfile-1.2.0-1-x86_64
|
||||
installed = libxml2-2.15.3-1-x86_64
|
||||
installed = libxml2-legacy-2.13.9-2-x86_64
|
||||
installed = libxmlb-0.3.27-1-x86_64
|
||||
installed = libxmp-4.7.0-1-x86_64
|
||||
installed = libxmu-1.3.1-1-x86_64
|
||||
installed = libxpresent-1.0.2-1-x86_64
|
||||
installed = libxrandr-1.5.5-1-x86_64
|
||||
installed = libxrender-0.9.12-1-x86_64
|
||||
installed = libxshmfence-1.3.3-1-x86_64
|
||||
installed = libxslt-1.1.45-2-x86_64
|
||||
installed = libxss-1.2.5-1-x86_64
|
||||
installed = libxt-1.3.1-1-x86_64
|
||||
installed = libxtst-1.2.5-1-x86_64
|
||||
installed = libxv-1.0.13-1-x86_64
|
||||
installed = libxxf86vm-1.1.7-1-x86_64
|
||||
installed = libyaml-0.2.5-3-x86_64
|
||||
installed = libyuv-r2426+464c51a03-1-x86_64
|
||||
installed = libzip-1.11.4-1-x86_64
|
||||
installed = libzmf-0.0.2-20-x86_64
|
||||
installed = licenses-20240728-1-any
|
||||
installed = lilv-0.26.4-1-x86_64
|
||||
installed = linux-7.0.11.arch1-1-x86_64
|
||||
installed = linux-api-headers-7.0-1-x86_64
|
||||
installed = linux-firmware-20260519-1-any
|
||||
installed = linux-firmware-amdgpu-20260519-1-any
|
||||
installed = linux-firmware-atheros-20260519-1-any
|
||||
installed = linux-firmware-broadcom-20260519-1-any
|
||||
installed = linux-firmware-cirrus-20260519-1-any
|
||||
installed = linux-firmware-intel-20260519-1-any
|
||||
installed = linux-firmware-mediatek-20260519-1-any
|
||||
installed = linux-firmware-nvidia-20260519-1-any
|
||||
installed = linux-firmware-other-20260519-1-any
|
||||
installed = linux-firmware-radeon-20260519-1-any
|
||||
installed = linux-firmware-realtek-20260519-1-any
|
||||
installed = linux-firmware-whence-20260519-1-any
|
||||
installed = linux-headers-7.0.11.arch1-1-x86_64
|
||||
installed = lld-22.1.6-1-x86_64
|
||||
installed = llhttp-9.3.1-1-x86_64
|
||||
installed = llvm-libs-22.1.6-1-x86_64
|
||||
installed = lm_sensors-1:3.6.2-1-x86_64
|
||||
installed = lmdb-0.9.35-1-x86_64
|
||||
installed = lpsolve-5.5.2.14-1-x86_64
|
||||
installed = lsb-release-2.0.r55.a25a4fc-1-any
|
||||
installed = lsof-4.99.6-1-x86_64
|
||||
installed = lua-5.5.0-2-x86_64
|
||||
installed = lua54-5.4.8-6-x86_64
|
||||
installed = luajit-2.1.1780076327+b925b3e-1-x86_64
|
||||
installed = lug-helper-1:4.13-1-any
|
||||
installed = lv2-1.18.10-2-x86_64
|
||||
installed = lz4-1:1.10.0-2-x86_64
|
||||
installed = lzo-2.10-5-x86_64
|
||||
installed = m4-1.4.21-2-x86_64
|
||||
installed = mailcap-2.1.54-2-any
|
||||
installed = make-4.4.1-3-x86_64
|
||||
installed = mako-1.11.0-1-x86_64
|
||||
installed = md4c-0.5.3-1-x86_64
|
||||
installed = mesa-1:26.1.2-1-x86_64
|
||||
installed = meson-1.11.1-3-any
|
||||
installed = mingw-w64-binutils-2.46.0-1-x86_64
|
||||
installed = mingw-w64-crt-14.0.0-1-any
|
||||
installed = mingw-w64-gcc-16.1.0-1-x86_64
|
||||
installed = mingw-w64-headers-14.0.0-1-any
|
||||
installed = mingw-w64-winpthreads-14.0.0-1-any
|
||||
installed = minizip-1:1.3.2-3-x86_64
|
||||
installed = mkinitcpio-41-4-any
|
||||
installed = mkinitcpio-busybox-1.36.1-1-x86_64
|
||||
installed = mobile-broadband-provider-info-20251101-1-any
|
||||
installed = mpdecimal-4.0.1-3-x86_64
|
||||
installed = mpfr-4.2.2-1-x86_64
|
||||
installed = mpg123-1.33.5-1-x86_64
|
||||
installed = mpv-1:0.41.0-3-x86_64
|
||||
installed = mpvpaper-1.8-1-x86_64
|
||||
installed = mtdev-1.1.7-1-x86_64
|
||||
installed = mtools-1:4.0.49-1-x86_64
|
||||
installed = mujs-1.3.9-1-x86_64
|
||||
installed = muparser-2.3.5-2-x86_64
|
||||
installed = nano-9.0-1-x86_64
|
||||
installed = ncurses-6.6-2-x86_64
|
||||
installed = neon-0.37.1-1-x86_64
|
||||
installed = netavark-1.17.2-2-x86_64
|
||||
installed = nettle-4.0-1-x86_64
|
||||
installed = network-manager-applet-1.36.0-2-x86_64
|
||||
installed = networkmanager-1.56.1-1-x86_64
|
||||
installed = nftables-1:1.1.6-3-x86_64
|
||||
installed = ninja-1.13.2-3-x86_64
|
||||
installed = nm-connection-editor-1.36.0-2-x86_64
|
||||
installed = noto-fonts-1:2026.06.01-1-any
|
||||
installed = noto-fonts-cjk-20240730-1-any
|
||||
installed = noto-fonts-emoji-1:2.051-1-any
|
||||
installed = noto-fonts-extra-1:2026.06.01-1-any
|
||||
installed = npth-1.8-1-x86_64
|
||||
installed = nspr-4.39-1-x86_64
|
||||
installed = nss-3.124-1-x86_64
|
||||
installed = nvidia-open-610.43.02-2-x86_64
|
||||
installed = nvidia-utils-610.43.02-2-x86_64
|
||||
installed = ocl-icd-2.3.4-1-x86_64
|
||||
installed = onetbb-2023.0.0-1-x86_64
|
||||
installed = oniguruma-6.9.10-1-x86_64
|
||||
installed = openal-1.25.2-1-x86_64
|
||||
installed = opencore-amr-0.1.6-2-x86_64
|
||||
installed = openjpeg2-2.5.4-1-x86_64
|
||||
installed = openrazer-daemon-3.12.2-2-any
|
||||
installed = openrazer-driver-dkms-3.12.2-2-any
|
||||
installed = openssh-10.3p1-1-x86_64
|
||||
installed = openssl-3.6.3-1-x86_64
|
||||
installed = opus-1.6.1-1-x86_64
|
||||
installed = opusfile-0.12-4-x86_64
|
||||
installed = orc-0.4.42-1-x86_64
|
||||
installed = os-prober-1.84-1-x86_64
|
||||
installed = ostree-2025.7-3-x86_64
|
||||
installed = p11-kit-0.26.2-1-x86_64
|
||||
installed = pacman-7.1.0.r9.g54d9411-2-x86_64
|
||||
installed = pacman-mirrorlist-20260610-1-any
|
||||
installed = pahole-1:1.31-2-x86_64
|
||||
installed = pam-1.7.2-2-x86_64
|
||||
installed = pambase-20250719-1-any
|
||||
installed = pango-1:1.57.1-1-x86_64
|
||||
installed = pangomm-2.46.4-3-x86_64
|
||||
installed = pangomm-2.48-2.56.1-2-x86_64
|
||||
installed = papirus-icon-theme-20250501-1-any
|
||||
installed = parted-3.7-1-x86_64
|
||||
installed = paru-2.1.0-2-x86_64
|
||||
installed = paru-debug-2.1.0-2-x86_64
|
||||
installed = passt-2026_05_26.038c51e-1-x86_64
|
||||
installed = patch-2.8-1-x86_64
|
||||
installed = pavucontrol-1:6.2-1-x86_64
|
||||
installed = pciutils-3.15.0-1-x86_64
|
||||
installed = pcre-8.45-4-x86_64
|
||||
installed = pcre2-10.47-1-x86_64
|
||||
installed = pcsclite-2.5.1-1-x86_64
|
||||
installed = perl-5.42.2-1-x86_64
|
||||
installed = perl-error-0.17030-3-any
|
||||
installed = perl-mailtools-2.22-3-any
|
||||
installed = perl-timedate-2.35-1-any
|
||||
installed = pinentry-1.3.2-2-x86_64
|
||||
installed = pipewire-1:1.6.6-1-x86_64
|
||||
installed = pipewire-audio-1:1.6.6-1-x86_64
|
||||
installed = pipewire-pulse-1:1.6.6-1-x86_64
|
||||
installed = pixman-0.46.4-1-x86_64
|
||||
installed = pkgconf-2.5.1-1-x86_64
|
||||
installed = playerctl-2.4.1-5-x86_64
|
||||
installed = podman-5.8.3-1-x86_64
|
||||
installed = podman-compose-1.6.0-1-any
|
||||
installed = podman-desktop-1.27.2-2-x86_64
|
||||
installed = polkit-127-3-x86_64
|
||||
installed = polkit-qt6-0.201.1-1-x86_64
|
||||
installed = poppler-26.05.0-1-x86_64
|
||||
installed = popt-1.19-2-x86_64
|
||||
installed = portaudio-1:19.7.0-4-x86_64
|
||||
installed = portmidi-1:2.0.8-1-x86_64
|
||||
installed = pps-tools-1.0.3-2-x86_64
|
||||
installed = procps-ng-4.0.6-1-x86_64
|
||||
installed = protobuf-35.0-1-x86_64
|
||||
installed = protobuf-c-1.5.2-10-x86_64
|
||||
installed = psmisc-23.7-2-x86_64
|
||||
installed = pugixml-1.15-3-x86_64
|
||||
installed = python-3.14.5-1-x86_64
|
||||
installed = python-attrs-26.1.0-1-any
|
||||
installed = python-autocommand-2.2.2-9-any
|
||||
installed = python-cffi-2.0.0-2-x86_64
|
||||
installed = python-click-8.3.3-1-any
|
||||
installed = python-cryptography-48.0.1-1-x86_64
|
||||
installed = python-daemonize-2.5.0-10-any
|
||||
installed = python-dbus-1.4.0-2-x86_64
|
||||
installed = python-dotenv-1.2.2-1-any
|
||||
installed = python-filelock-3.29.0-1-any
|
||||
installed = python-gobject-3.56.3-1-x86_64
|
||||
installed = python-jaraco.collections-5.1.0-3-any
|
||||
installed = python-jaraco.context-6.1.2-1-any
|
||||
installed = python-jaraco.functools-4.1.0-3-any
|
||||
installed = python-jaraco.text-4.0.0-4-any
|
||||
installed = python-mako-1.3.11-1-any
|
||||
installed = python-markdown-3.10.2-1-any
|
||||
installed = python-markupsafe-3.0.3-1-x86_64
|
||||
installed = python-more-itertools-11.1.0-1-any
|
||||
installed = python-numpy-2.4.6-1-x86_64
|
||||
installed = python-openrazer-3.12.2-2-any
|
||||
installed = python-packaging-26.2-1-any
|
||||
installed = python-pip-26.1.2-1-any
|
||||
installed = python-pkg_resources-81.0.0-1-any
|
||||
installed = python-platformdirs-4.10.0-1-any
|
||||
installed = python-protobuf-35.0-1-x86_64
|
||||
installed = python-pycparser-3.00-1-any
|
||||
installed = python-pygame-2.6.1-5-x86_64
|
||||
installed = python-pyudev-0.24.4-1-any
|
||||
installed = python-setproctitle-1.3.7-2-x86_64
|
||||
installed = python-setuptools-1:82.0.1-1-any
|
||||
installed = python-tqdm-4.68.1-1-any
|
||||
installed = python-typing_extensions-4.15.0-3-any
|
||||
installed = python-wheel-0.47.0-1-any
|
||||
installed = python-yaml-6.0.3-2-x86_64
|
||||
installed = qt5-base-5.15.19+kde+r96-1-x86_64
|
||||
installed = qt5-declarative-5.15.19+kde+r23-1-x86_64
|
||||
installed = qt5-graphicaleffects-5.15.19-1-x86_64
|
||||
installed = qt5-quickcontrols2-5.15.19+kde+r5-1-x86_64
|
||||
installed = qt5-svg-5.15.19+kde+r5-1-x86_64
|
||||
installed = qt5-translations-5.15.19-1-any
|
||||
installed = qt5-wayland-5.15.19+kde+r55-1-x86_64
|
||||
installed = qt6-5compat-6.11.1-1-x86_64
|
||||
installed = qt6-base-6.11.1-1-x86_64
|
||||
installed = qt6-declarative-6.11.1-3-x86_64
|
||||
installed = qt6-shadertools-6.11.1-1-x86_64
|
||||
installed = qt6-svg-6.11.1-1-x86_64
|
||||
installed = qt6-tools-6.11.1-1-x86_64
|
||||
installed = qt6-translations-6.11.1-1-any
|
||||
installed = qt6-wayland-6.11.1-1-x86_64
|
||||
installed = raptor-2.0.16-9-x86_64
|
||||
installed = rasqal-1:0.9.33-9-x86_64
|
||||
installed = rav1e-0.8.1-2-x86_64
|
||||
installed = razergenie-1.3.0-1-x86_64
|
||||
installed = re2-2:2025.11.05-4-x86_64
|
||||
installed = readline-8.3.003-1-x86_64
|
||||
installed = redland-1:1.0.17-10-x86_64
|
||||
installed = reflector-2023-5-any
|
||||
installed = resvg-0.47.0-2-x86_64
|
||||
installed = rhash-1.4.6-1-x86_64
|
||||
installed = rsync-3.4.4-1-x86_64
|
||||
installed = rtkit-0.14-1-x86_64
|
||||
installed = rubberband-4.0.0-2-x86_64
|
||||
installed = rustup-1.29.0-2-x86_64
|
||||
installed = sbc-2.2-1-x86_64
|
||||
installed = scdoc-1.11.4-1-x86_64
|
||||
installed = sdbus-cpp-2.3.1-1-x86_64
|
||||
installed = sddm-0.21.0-7-x86_64
|
||||
installed = sddm-catppuccin-git-r42.10831de-1-any
|
||||
installed = sdl2-compat-2.32.70-1-x86_64
|
||||
installed = sdl2_image-2.8.12-1-x86_64
|
||||
installed = sdl2_mixer-2.8.2-1-x86_64
|
||||
installed = sdl2_ttf-2.24.0-2-x86_64
|
||||
installed = sdl3-3.4.10-1-x86_64
|
||||
installed = sdl3_ttf-3.2.2-3-x86_64
|
||||
installed = seatd-0.9.3-1-x86_64
|
||||
installed = sed-4.10-1-x86_64
|
||||
installed = serd-0.32.10-1-x86_64
|
||||
installed = shaderc-2026.2-1-x86_64
|
||||
installed = shadow-4.18.0-1-x86_64
|
||||
installed = shared-mime-info-2.4-3-x86_64
|
||||
installed = slang-2.3.3-4-x86_64
|
||||
installed = slurp-1.5.0-2-x86_64
|
||||
installed = smartmontools-7.5-1-x86_64
|
||||
installed = snappy-1.2.2-3-x86_64
|
||||
installed = sndio-1.10.0-1-x86_64
|
||||
installed = sord-0.16.22-1-x86_64
|
||||
installed = sound-theme-freedesktop-0.8-6-any
|
||||
installed = source-highlight-3.1.9-18-x86_64
|
||||
installed = spandsp-0.0.6-7-x86_64
|
||||
installed = spdlog-1.17.0-2-x86_64
|
||||
installed = speex-1.2.1-2-x86_64
|
||||
installed = speexdsp-1.2.1-2-x86_64
|
||||
installed = spirv-tools-1:1.4.350.0-1-x86_64
|
||||
installed = sqlite-3.53.2-1-x86_64
|
||||
installed = squashfs-tools-4.7.5-1-x86_64
|
||||
installed = sratom-0.6.22-1-x86_64
|
||||
installed = srt-1.5.5-1-x86_64
|
||||
installed = startup-notification-0.12-9-x86_64
|
||||
installed = steam-1.0.0.85-7-x86_64
|
||||
installed = steam-devices-1.0.0.85-7-x86_64
|
||||
installed = sudo-1.9.17.p2-2-x86_64
|
||||
installed = svt-av1-4.1.0-1-x86_64
|
||||
installed = systemd-260.2-2-x86_64
|
||||
installed = systemd-libs-260.2-2-x86_64
|
||||
installed = systemd-sysvcompat-260.2-2-x86_64
|
||||
installed = tar-1.35-2-x86_64
|
||||
installed = tcl-8.6.16-1-x86_64
|
||||
installed = tdb-1.4.15-1-x86_64
|
||||
installed = texinfo-7.3-1-x86_64
|
||||
installed = thunar-4.20.8-3-x86_64
|
||||
installed = tinysparql-3.11.1-1-x86_64
|
||||
installed = tk-8.6.16-1-x86_64
|
||||
installed = tomlplusplus-3.4.0-2-x86_64
|
||||
installed = tpm2-tss-4.1.3-1-x86_64
|
||||
installed = tslib-1.24-1-x86_64
|
||||
installed = ttf-jetbrains-mono-nerd-3.4.0-2-any
|
||||
installed = tzdata-2026b-1-x86_64
|
||||
installed = uchardet-0.0.8-4-x86_64
|
||||
installed = uefitool-debug-1:0.28.0-3-x86_64
|
||||
installed = unzip-6.0-23-x86_64
|
||||
installed = upower-1.91.2-1-x86_64
|
||||
installed = usbutils-019-1-x86_64
|
||||
installed = util-linux-2.42.1-1-x86_64
|
||||
installed = util-linux-libs-2.42.1-1-x86_64
|
||||
installed = v4l-utils-1.32.0-2-x86_64
|
||||
installed = vapoursynth-76-1-x86_64
|
||||
installed = vid.stab-1.1.1-2-x86_64
|
||||
installed = vim-9.2.0623-1-x86_64
|
||||
installed = vim-runtime-9.2.0623-1-x86_64
|
||||
installed = vmaf-3.1.0-1-x86_64
|
||||
installed = vmware-keymaps-1.0-3-any
|
||||
installed = vmware-workstation-26H1-3-x86_64
|
||||
installed = vscodium-bin-1.121.03429-1-x86_64
|
||||
installed = vulkan-icd-loader-1.4.350.0-1-x86_64
|
||||
installed = vulkan-tools-1.4.350.0-1-x86_64
|
||||
installed = waybar-0.15.0-2-x86_64
|
||||
installed = wayland-1.25.0-1-x86_64
|
||||
installed = wayland-protocols-1.49-1-any
|
||||
installed = webrtc-audio-processing-1-1.3-5-x86_64
|
||||
installed = which-2.25-1-x86_64
|
||||
installed = winboat-bin-0.9.0-1-x86_64
|
||||
installed = wine-staging-11.10-1-x86_64
|
||||
installed = wireplumber-0.5.14-1-x86_64
|
||||
installed = wl-clipboard-1:2.3.0-1-x86_64
|
||||
installed = wlogout-1.2.2-0-x86_64
|
||||
installed = woff2-font-awesome-7.2.0-1-any
|
||||
installed = wpa_supplicant-2:2.11-5-x86_64
|
||||
installed = x264-3:0.165.r3222.b35605a-2-x86_64
|
||||
installed = x265-4.1-1-x86_64
|
||||
installed = xautomation-1.09-6-x86_64
|
||||
installed = xcb-proto-1.17.0-4-any
|
||||
installed = xcb-util-0.4.1-2-x86_64
|
||||
installed = xcb-util-cursor-0.1.6-1-x86_64
|
||||
installed = xcb-util-errors-1.0.1-2-x86_64
|
||||
installed = xcb-util-image-0.4.1-3-x86_64
|
||||
installed = xcb-util-keysyms-0.4.1-5-x86_64
|
||||
installed = xcb-util-renderutil-0.3.10-2-x86_64
|
||||
installed = xcb-util-wm-0.4.2-2-x86_64
|
||||
installed = xdg-dbus-proxy-0.1.7-1-x86_64
|
||||
installed = xdg-desktop-portal-1.22.0-1-x86_64
|
||||
installed = xdg-desktop-portal-gtk-1.15.3-1-x86_64
|
||||
installed = xdg-desktop-portal-hyprland-1.3.12-2-x86_64
|
||||
installed = xdg-user-dirs-0.20-1-x86_64
|
||||
installed = xdg-utils-1.2.1-2-any
|
||||
installed = xf86-input-libinput-1.5.0-1-x86_64
|
||||
installed = xfconf-4.20.0-2-x86_64
|
||||
installed = xkeyboard-config-2.47-1-any
|
||||
installed = xmlsec-1.3.11-1-x86_64
|
||||
installed = xorg-fonts-encodings-1.1.0-2-any
|
||||
installed = xorg-server-21.1.23-1-x86_64
|
||||
installed = xorg-server-common-21.1.23-1-x86_64
|
||||
installed = xorg-setxkbmap-1.3.5-1-x86_64
|
||||
installed = xorg-xauth-1.1.5-1-x86_64
|
||||
installed = xorg-xkbcomp-1.5.0-1-x86_64
|
||||
installed = xorg-xprop-1.2.8-1-x86_64
|
||||
installed = xorg-xrandr-1.5.4-1-x86_64
|
||||
installed = xorg-xwayland-24.1.12-1-x86_64
|
||||
installed = xorgproto-2025.1-1-any
|
||||
installed = xvidcore-1.3.7-3-x86_64
|
||||
installed = xxhash-0.8.3-1-x86_64
|
||||
installed = xz-5.8.3-1-x86_64
|
||||
installed = yaml-cpp-0.9.0-1-x86_64
|
||||
installed = yay-12.6.0-1-x86_64
|
||||
installed = yay-debug-12.6.0-1-x86_64
|
||||
installed = zenity-4.2.2-1-x86_64
|
||||
installed = zeromq-4.3.5-3-x86_64
|
||||
installed = zimg-3.0.6-1-x86_64
|
||||
installed = zint-2.16.0-2-x86_64
|
||||
installed = zix-0.8.0-1-x86_64
|
||||
installed = zlib-1:1.3.2-3-x86_64
|
||||
installed = zlib-ng-2.3.3-1-x86_64
|
||||
installed = zstd-1.5.7-3-x86_64
|
||||
installed = zxing-cpp-3.0.2-1-x86_64
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
# Generated by makepkg 7.1.0
|
||||
# using fakeroot version 1.37.2
|
||||
pkgname = python-simpleline
|
||||
pkgbase = python-simpleline
|
||||
xdata = pkgtype=pkg
|
||||
pkgver = 1.8-1
|
||||
pkgdesc = Python3 text UI framework (dépendance Anaconda)
|
||||
url = https://github.com/rhinstaller/python-simpleline
|
||||
builddate = 1781307804
|
||||
packager = Unknown Packager
|
||||
size = 346552
|
||||
arch = any
|
||||
license = LGPL-3.0-or-later
|
||||
depend = python
|
||||
depend = python-gobject
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# Base class for Simpleline Text UI framework.
|
||||
#
|
||||
# Library containing the Text UI framework.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
__all__ = ["App"]
|
||||
|
||||
|
||||
from simpleline.logging import setup_logging
|
||||
from simpleline.errors import NothingScheduledError
|
||||
|
||||
setup_logging()
|
||||
|
||||
|
||||
class App():
|
||||
"""This is the main class for Simpleline library.
|
||||
|
||||
Do not create instance of this class. Use this class as static!
|
||||
The `initialize()` method must be called before use.
|
||||
|
||||
It is giving you access to the scheduler and event loop. You can have only one instance of this
|
||||
class in your application.
|
||||
|
||||
To create this instance call `App.initialize()` method. This method can also be used to
|
||||
reset settings in the App class to start with new event loop or scheduler.
|
||||
"""
|
||||
__app = None
|
||||
|
||||
class AppPimpl():
|
||||
|
||||
def __init__(self, scheduler, event_loop, configuration):
|
||||
self.event_loop = event_loop
|
||||
self.scheduler = scheduler
|
||||
self.configuration = configuration
|
||||
|
||||
@classmethod
|
||||
def initialize(cls, scheduler=None, event_loop=None, global_configuration=None):
|
||||
"""Create app instance inside of this class.
|
||||
|
||||
This method can be called multiple times to reset App settings.
|
||||
|
||||
:param scheduler: scheduler used for rendering screens; if not specified use
|
||||
`simpleline.render.screen_scheduler.ScreenScheduler`.
|
||||
:type scheduler: instance of `simpleline.render.screen_scheduler.ScreenScheduler`.
|
||||
|
||||
:param event_loop: event loop used for asynchronous tasks;
|
||||
if not specified use `simpleline.event_loop.main_loop.MainLoop`.
|
||||
:type event_loop: object based on class `simpleline.event_loop.AbstractEventLoop`.
|
||||
|
||||
:param global_configuration: instance of the global configuration object; if not specified
|
||||
use `simpleline.global_configuration.GlobalConfiguration`
|
||||
:type global_configuration: object based on class
|
||||
`simpleline.global_configuration.GlobalConfiguration`
|
||||
"""
|
||||
from simpleline.event_loop.main_loop import MainLoop # pylint: disable=import-outside-toplevel
|
||||
from simpleline.render.screen_scheduler import ScreenScheduler # pylint: disable=import-outside-toplevel
|
||||
from simpleline.global_configuration import GlobalConfiguration # pylint: disable=import-outside-toplevel
|
||||
|
||||
if event_loop is None:
|
||||
event_loop = MainLoop()
|
||||
if scheduler is None:
|
||||
scheduler = ScreenScheduler(event_loop)
|
||||
if global_configuration is None:
|
||||
global_configuration = GlobalConfiguration()
|
||||
|
||||
cls.__app = cls.AppPimpl(scheduler, event_loop, global_configuration)
|
||||
|
||||
cls._post_init()
|
||||
|
||||
@classmethod
|
||||
def _post_init(cls):
|
||||
from simpleline.input.input_threading import InputThreadManager # pylint: disable=import-outside-toplevel
|
||||
# FIXME: This should be done by more general way not by calling exact class here.
|
||||
# Create new instance of InputThreadManager because it needs new event loop
|
||||
InputThreadManager.create_new_instance()
|
||||
|
||||
@classmethod
|
||||
def is_initialized(cls):
|
||||
"""Is the App already initialized?
|
||||
|
||||
:returns: True if the `App.initialized()` method was called, False otherwise.
|
||||
"""
|
||||
if cls.__app is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_scheduler(cls):
|
||||
"""Get instance of class responsible for rendering of the screen."""
|
||||
return cls.__app.scheduler
|
||||
|
||||
@classmethod
|
||||
def get_event_loop(cls):
|
||||
"""Get instance of class responsible for processing asynchronous events."""
|
||||
return cls.__app.event_loop
|
||||
|
||||
@classmethod
|
||||
def get_configuration(cls):
|
||||
"""Get application defaults configuration object."""
|
||||
return cls.__app.configuration
|
||||
|
||||
@classmethod
|
||||
def run(cls):
|
||||
"""Run event loop.
|
||||
|
||||
Raise an exception if no screen is scheduled. This behavior can be changed by
|
||||
`should_run_with_empty_stack` global configuration option.
|
||||
|
||||
This is shortcut to `App.event_loop().run()`.
|
||||
:raises NothingScheduledError: when there is no screen scheduled
|
||||
"""
|
||||
if not cls.__app.configuration.should_run_with_empty_stack:
|
||||
# Check if the screen stack is not empty
|
||||
if cls.__app.scheduler.nothing_to_render:
|
||||
raise NothingScheduledError("Can't run application with the empty screen stack! "
|
||||
"To avoid this please see should_run_with_empty_stack "
|
||||
"global configuration option.")
|
||||
App.get_event_loop().run()
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+31
@@ -0,0 +1,31 @@
|
||||
# Base exceptions for the Simpleline application.
|
||||
#
|
||||
# Base class for Simpleline Text UI framework.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This can't be moved to __init__.py because of cyclic imports error.
|
||||
#
|
||||
|
||||
|
||||
class SimplelineError(Exception):
|
||||
"""Base exception for all other exceptions."""
|
||||
|
||||
|
||||
class NothingScheduledError(SimplelineError):
|
||||
"""Exception when running the loop with no screens scheduled."""
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
# Abstract base class for Simpleline Event Loop.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# This class can be overridden to use any existing event loop of your program.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import namedtuple
|
||||
|
||||
from simpleline.errors import SimplelineError
|
||||
from simpleline.event_loop.ticket_machine import TicketMachine
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
__all__ = ["AbstractEventLoop", "AbstractSignal", "ExitMainLoop"]
|
||||
|
||||
QuitCallback = namedtuple("QuitCallback", ["callback", "args"])
|
||||
|
||||
|
||||
class ExitMainLoop(SimplelineError):
|
||||
"""This exception ends the whole event loop."""
|
||||
|
||||
|
||||
class AbstractEventLoop(metaclass=ABCMeta):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._handlers = {}
|
||||
self._processed_signals = TicketMachine()
|
||||
self._quit_callback = None
|
||||
# end most inner loop politely by setting to False
|
||||
self._run_loop = True
|
||||
self._force_quit = False
|
||||
|
||||
def register_signal_handler(self, signal, callback, data=None):
|
||||
"""Register a callback which will be called when message "event"
|
||||
is encountered during process_events.
|
||||
|
||||
The callback has to accept two arguments:
|
||||
- the received message in the form of (type, [arguments])
|
||||
- the data registered with the handler
|
||||
|
||||
:param signal: Signal class we want to react on.
|
||||
:type signal: Class based on the simpleline.event_loop.AbstractSignal class.
|
||||
|
||||
:param callback: The callback function.
|
||||
:type callback: func(event_message, data)
|
||||
|
||||
:param data: Optional data to pass to callback.
|
||||
:type data: Anything.
|
||||
"""
|
||||
if signal not in self._handlers:
|
||||
self._handlers[signal] = []
|
||||
|
||||
event_handler = self._create_event_handler(callback, data)
|
||||
self._handlers[signal].append(event_handler)
|
||||
|
||||
@abstractmethod
|
||||
def register_signal_source(self, signal_source):
|
||||
"""Register source of signal for actual event queue.
|
||||
|
||||
:param signal_source: Source for future signals.
|
||||
:type signal_source: `simpleline.render.ui_screen.UIScreen`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def enqueue_signal(self, signal):
|
||||
"""Enqueue new event for processing.
|
||||
|
||||
:param signal: Signal which you want to add to the event queue for processing.
|
||||
:type signal: Instance based on AbstractEvent class.
|
||||
"""
|
||||
log.debug("New signal %s enqueued with source %s",
|
||||
signal,
|
||||
signal.source.__class__.__name__)
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
"""Starts the event loop."""
|
||||
log.debug("Starting main loop")
|
||||
self._force_quit = False
|
||||
|
||||
def force_quit(self):
|
||||
"""Force quit all running event loops.
|
||||
|
||||
Kill all loop including inner loops (modal window).
|
||||
None of the Simpleline events will be processed anymore.
|
||||
"""
|
||||
log.debug("Force quit called. Killing all loops!")
|
||||
self._force_quit = True
|
||||
|
||||
@abstractmethod
|
||||
def execute_new_loop(self, signal):
|
||||
"""Starts the new event loop and pass `signal` in it.
|
||||
|
||||
This is required for processing a modal screens.
|
||||
|
||||
:param signal: Signal passed to the new event loop.
|
||||
:type signal: The `AbstractSignal` based class.
|
||||
"""
|
||||
log.debug("Executing inner loop")
|
||||
|
||||
@abstractmethod
|
||||
def close_loop(self):
|
||||
"""Close active event loop.
|
||||
|
||||
Close an event loop created by the `execute_new_loop()` method.
|
||||
"""
|
||||
log.debug("Closing inner loop")
|
||||
|
||||
@abstractmethod
|
||||
def process_signals(self, return_after=None):
|
||||
"""This method processes incoming async messages.
|
||||
|
||||
Process signals enqueued by the `self.enqueue_signal()` method. Call handlers
|
||||
registered to the signals by the `self.register_signal_handler()` method.
|
||||
|
||||
When `return_after` is specified then wait to the point when this signal is processed.
|
||||
NO warranty that this method will return immediately after the signal was processed!
|
||||
|
||||
Without `return_after` parameter this method will return after all queued signals
|
||||
with the highest priority will be processed.
|
||||
|
||||
The method is NOT thread safe!
|
||||
|
||||
:param return_after: Wait on this signal to be processed.
|
||||
:type return_after: Class of the signal.
|
||||
"""
|
||||
|
||||
def set_quit_callback(self, callback, args=None):
|
||||
"""Call this callback when event loop quits.
|
||||
|
||||
:param callback: Call this callback when event loops ends (application quit).
|
||||
:type callback: Function with one parameter data `func(data)`.
|
||||
|
||||
:param args: Arguments passed to the quit callback.
|
||||
:type args: Anything.
|
||||
"""
|
||||
self._quit_callback = QuitCallback(callback, args)
|
||||
|
||||
def kill_app_with_traceback(self, exception_signal, data=None):
|
||||
"""Print exception and screen stack and kill the application.
|
||||
|
||||
:param exception_signal: ExceptionSignal encapsulating the original exception which
|
||||
will be passed to the sys.excepthook method.
|
||||
:type exception_signal: Instance of `simpleline.event_loop.signals.ExceptionSignal` class.
|
||||
|
||||
:param data: To be usable as signal handler.
|
||||
:type data: Anything will be ignored.
|
||||
"""
|
||||
log.debug("Unhandled error in handler raised:")
|
||||
sys.excepthook(*exception_signal.exception_info)
|
||||
|
||||
from simpleline import App # pylint: disable=import-outside-toplevel
|
||||
stack_dump = App.get_scheduler().dump_stack()
|
||||
print("")
|
||||
print(stack_dump)
|
||||
log.error(stack_dump)
|
||||
|
||||
log.debug("Killing application!")
|
||||
sys.exit(1)
|
||||
|
||||
@staticmethod
|
||||
def _create_event_handler(callback, data):
|
||||
"""Create event handler data object and return it."""
|
||||
return EventHandler(callback=callback, data=data)
|
||||
|
||||
def _register_wait_on_signal(self, wait_on_signal):
|
||||
"""Register process waiting on signal `wait_on_signal` and return id for later checking.
|
||||
|
||||
ID is returned which is then used in the `self._check_if_signal_processed()` method.
|
||||
|
||||
:param wait_on_signal: Signal we are waiting for.
|
||||
:type wait_on_signal: Class based on `simpleline.event_loop.AbstractSignal`.
|
||||
"""
|
||||
return self._processed_signals.take_ticket(wait_on_signal.__name__)
|
||||
|
||||
def _mark_signal_processed(self, signal):
|
||||
"""Mark that processes waiting on this signal that they are able to go.
|
||||
|
||||
:param signal: Signal which was processed.
|
||||
:type signal: Class based on `simpleline.event_loop.AbstractSignal`.
|
||||
"""
|
||||
self._processed_signals.mark_line_to_go(signal.__class__.__name__)
|
||||
|
||||
def _check_if_signal_processed(self, wait_on_signal, unique_id):
|
||||
"""Check if the signal was processed.
|
||||
|
||||
:param wait_on_signal: Signal the process is waiting for.
|
||||
:type wait_on_signal: Class based on `simpleline.event_loop.AbstractSignal`.
|
||||
|
||||
:param unique_id: Unique id returned by the `self._register_wait_on_signal()` method.
|
||||
:type unique_id: int
|
||||
"""
|
||||
return self._processed_signals.check_ticket(wait_on_signal.__name__, unique_id)
|
||||
|
||||
|
||||
class EventHandler():
|
||||
"""Data class to save event handlers."""
|
||||
|
||||
def __init__(self, callback, data):
|
||||
self.callback = callback
|
||||
self.data = data
|
||||
|
||||
|
||||
class AbstractSignal(metaclass=ABCMeta):
|
||||
"""This class is base class for signals.
|
||||
|
||||
.. NOTE:
|
||||
Ordering and equality is based on priority.
|
||||
"""
|
||||
|
||||
def __init__(self, source, priority=0):
|
||||
self._source = source
|
||||
self._priority = priority
|
||||
|
||||
def __lt__(self, other):
|
||||
"""Order Signal classes by priority."""
|
||||
return self._priority < other.priority
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Order Signal classes by priority."""
|
||||
return self._priority == other.priority
|
||||
|
||||
def __str__(self):
|
||||
"""For easier logging."""
|
||||
return self.__class__.__name__
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
"""Priority of this event.
|
||||
|
||||
Values less than 0 denote higher priorities. Values greater than 0 denote lower priorities.
|
||||
Events from high priority sources are always processed before events from lower priority
|
||||
sources.
|
||||
"""
|
||||
return self._priority
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Source which emitted this event."""
|
||||
return self._source
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+153
@@ -0,0 +1,153 @@
|
||||
# Default event queue for Simpleline application.
|
||||
#
|
||||
# This class is thread safe.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
|
||||
from queue import PriorityQueue
|
||||
from threading import Lock
|
||||
|
||||
from simpleline.errors import SimplelineError
|
||||
|
||||
|
||||
class EventQueueError(SimplelineError):
|
||||
"""Main exception for `EventQueue` class.
|
||||
|
||||
Inherits from `simpleline.SimplelineError`.
|
||||
"""
|
||||
|
||||
|
||||
class EventQueue():
|
||||
"""Class for managing signal queue.
|
||||
|
||||
Responsibilities of this class are:
|
||||
* sorting by priority of signals
|
||||
* managing sources for this event queue
|
||||
* enqueuing signals
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._queue = PriorityQueue()
|
||||
self._contained_screens = set()
|
||||
self._lock = Lock()
|
||||
|
||||
def empty(self):
|
||||
"""Return true if Queue is empty.
|
||||
|
||||
:return: True if empty, False otherwise.
|
||||
"""
|
||||
return self._queue.empty()
|
||||
|
||||
def enqueue(self, signal):
|
||||
"""Enqueue signal to this queue.
|
||||
|
||||
:param signal: Signal which should be enqueued to this queue.
|
||||
:type signal: Signal class based on `simpleline.event_loop.signals.AbstractSignal`.
|
||||
"""
|
||||
self._queue.put(signal)
|
||||
|
||||
def enqueue_if_source_belongs(self, signal, source):
|
||||
"""Enqueue signal to this queue if the signal source belongs to this queue.
|
||||
|
||||
Enqueue the `signal` only if the `source` belongs to this queue.
|
||||
See the `add_source()` method.
|
||||
|
||||
:param signal: Signal which should be enqueued to this queue.
|
||||
:type signal: Signal class based on `simpleline.event_loop.signals.AbstractSignal`.
|
||||
:param source: Source of this signal.
|
||||
:type source: Anything.
|
||||
:return: True if the source belongs to this queue and signal was queued, False otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.contains_source(source):
|
||||
self._queue.put(signal)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get(self):
|
||||
"""Return enqueued signal with the highest priority.
|
||||
|
||||
This is FIFO implementation for the same priority.
|
||||
If the queue is empty this method will wait for the input signal.
|
||||
|
||||
:return: Queued signal.
|
||||
:rtype: Signal based on class `simpleline.event_loop.signals.AbstractSignal`.
|
||||
"""
|
||||
return self._queue.get()
|
||||
|
||||
def get_top_event_if_priority(self, priority):
|
||||
"""Return top enqueued signal if priority is equal to `priority`. Otherwise `None`.
|
||||
|
||||
:param priority: Requested event priority.
|
||||
:type priority: int
|
||||
|
||||
:return: Queued signal if it has requested priority. Otherwise `None`.
|
||||
:rtype: Signal based on class `simpleline.event_loop.signals.AbstractSignal` or `None`.
|
||||
"""
|
||||
event = self._queue.get()
|
||||
if event.priority == priority:
|
||||
return event
|
||||
|
||||
self._queue.put(event)
|
||||
return None
|
||||
|
||||
def add_source(self, signal_source):
|
||||
"""Add new source of signals to this queue.
|
||||
|
||||
This method is mandatory for `enqueue_if_source_belongs()` method.
|
||||
The same source will be added only once.
|
||||
|
||||
:param signal_source: Source of future signals.
|
||||
:type signal_source: Anything which will emit signals in future.
|
||||
"""
|
||||
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
|
||||
# pylint: disable=not-context-manager
|
||||
with self._lock:
|
||||
self._contained_screens.add(signal_source)
|
||||
|
||||
def remove_source(self, signal_source):
|
||||
"""Remove signal source from this queue.
|
||||
|
||||
:param signal_source: Source of future signals.
|
||||
:type signal_source: Anything.
|
||||
:raise: EventQueueError"""
|
||||
try:
|
||||
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
|
||||
# pylint: disable=not-context-manager
|
||||
with self._lock:
|
||||
self._contained_screens.remove(signal_source)
|
||||
except KeyError as e:
|
||||
raise EventQueueError("Can't remove non-existing event source!") from e
|
||||
|
||||
def contains_source(self, signal_source):
|
||||
"""Test if `signal_source` belongs to this queue.
|
||||
|
||||
:param signal_source: Source of signals.
|
||||
:type signal_source: Anything.
|
||||
:return: True if signal source belongs to this queue.
|
||||
:rtype: bool
|
||||
"""
|
||||
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
|
||||
# pylint: disable=not-context-manager
|
||||
with self._lock:
|
||||
return signal_source in self._contained_screens
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
# Glib event queue used by Simpleline application.
|
||||
#
|
||||
# This class is thread safe.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
from collections import namedtuple
|
||||
|
||||
import gi
|
||||
|
||||
from simpleline.event_loop import AbstractEventLoop, ExitMainLoop
|
||||
from simpleline.event_loop.signals import ExceptionSignal
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
|
||||
gi.require_version("GLib", "2.0")
|
||||
|
||||
from gi.repository import GLib # pylint: disable=wrong-import-order, wrong-import-position
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
CallbackArgs = namedtuple("CallbackArgs", ["signal", "source", "handlers"])
|
||||
|
||||
|
||||
__all__ = ["GLibEventLoop"]
|
||||
|
||||
|
||||
class GLibEventLoop(AbstractEventLoop):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Create first loop
|
||||
loop = GLib.MainLoop()
|
||||
self._event_loops = [EventLoopData(loop)]
|
||||
log.debug("GLib event loop is used!")
|
||||
|
||||
@property
|
||||
def active_main_loop(self):
|
||||
"""Return GLib mainloop object."""
|
||||
return self._event_loops[-1].loop
|
||||
|
||||
def register_signal_source(self, signal_source):
|
||||
"""Register source of signal for actual event queue.
|
||||
|
||||
:param signal_source: Source for future signals.
|
||||
:type signal_source: `simpleline.render.ui_screen.UIScreen`
|
||||
"""
|
||||
super().register_signal_source(signal_source)
|
||||
loop_data = self._event_loops[-1]
|
||||
loop_data.sources.add(signal_source)
|
||||
|
||||
def enqueue_signal(self, signal):
|
||||
"""Enqueue new event for processing.
|
||||
|
||||
:param signal: signal which you want to add to the event queue for processing
|
||||
:type signal: instance based on AbstractEvent class
|
||||
"""
|
||||
if self._force_quit:
|
||||
return
|
||||
|
||||
super().enqueue_signal(signal)
|
||||
|
||||
loop_data = self._find_loop_data_for_source(signal.source)
|
||||
self._register_handlers_to_loop(loop_data.loop, signal)
|
||||
|
||||
def _find_loop_data_for_source(self, source):
|
||||
"""Find event loop belonging to this signal source."""
|
||||
for loop_data in reversed(self._event_loops):
|
||||
if source in loop_data.sources:
|
||||
return loop_data
|
||||
|
||||
return self._event_loops[-1]
|
||||
|
||||
def _register_handlers_to_loop(self, event_loop, signal):
|
||||
"""Register handlers to the event loop."""
|
||||
context = event_loop.get_context()
|
||||
handlers = []
|
||||
|
||||
if type(signal) in self._handlers: # pylint: disable=unidiomatic-typecheck
|
||||
handlers = self._handlers[type(signal)]
|
||||
elif isinstance(signal, ExceptionSignal):
|
||||
handler_data = self._create_event_handler(self.kill_app_with_traceback, None)
|
||||
handlers = [handler_data]
|
||||
|
||||
# GLib event source which contains handler callback
|
||||
# Every source can hold only one callback
|
||||
source = GLib.idle_source_new()
|
||||
source.set_priority(signal.priority)
|
||||
data = CallbackArgs(signal, source, handlers)
|
||||
|
||||
source.set_callback(self._run_handlers, data)
|
||||
# attach source to the event loop
|
||||
source.attach(context)
|
||||
|
||||
def _run_handlers(self, data):
|
||||
"""Run handlers attached to this signal and clean source afterwards."""
|
||||
signal = data.signal
|
||||
source = data.source
|
||||
handlers = data.handlers
|
||||
|
||||
if not self._force_quit:
|
||||
try:
|
||||
for handler in handlers:
|
||||
handler.callback(signal, handler.data)
|
||||
except ExitMainLoop:
|
||||
self._quit_all_loops()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
self.enqueue_signal(ExceptionSignal(self))
|
||||
|
||||
# based on GLib documentation we should clean source
|
||||
# source will be removed from event loop context this way
|
||||
source.destroy()
|
||||
|
||||
self._mark_signal_processed(signal)
|
||||
|
||||
def _quit_all_loops(self):
|
||||
for loop_data in reversed(self._event_loops):
|
||||
loop_data.loop.quit()
|
||||
|
||||
def run(self):
|
||||
"""Starts the event loop."""
|
||||
super().run()
|
||||
if len(self._event_loops) != 1:
|
||||
raise ValueError("Can't run event loop multiple times.")
|
||||
|
||||
self._event_loops[0].loop.run()
|
||||
log.debug("Main loop ended. Running callback if set.")
|
||||
|
||||
if self._quit_callback:
|
||||
cb = self._quit_callback.callback
|
||||
cb(self._quit_callback.args)
|
||||
|
||||
def force_quit(self):
|
||||
"""Force quit all running event loops.
|
||||
|
||||
Kill all loop including inner loops (modal window).
|
||||
None of the Simpleline events will be processed anymore.
|
||||
"""
|
||||
super().force_quit()
|
||||
self._quit_all_loops()
|
||||
|
||||
def execute_new_loop(self, signal):
|
||||
"""Starts the new event loop and pass `signal` in it.
|
||||
|
||||
This is required for processing a modal screens.
|
||||
|
||||
:param signal: signal passed to the new event loop
|
||||
:type signal: `AbstractSignal` based class
|
||||
"""
|
||||
super().execute_new_loop(signal)
|
||||
|
||||
if self._force_quit:
|
||||
return
|
||||
|
||||
new_context = GLib.MainContext()
|
||||
new_loop = GLib.MainLoop(new_context)
|
||||
loop_data = EventLoopData(new_loop)
|
||||
self._event_loops.append(loop_data)
|
||||
|
||||
self.enqueue_signal(signal)
|
||||
new_loop.run()
|
||||
|
||||
def close_loop(self):
|
||||
"""Close active event loop.
|
||||
|
||||
Close an event loop created by the `execute_new_loop()` method.
|
||||
"""
|
||||
super().close_loop()
|
||||
old_loop_data = self._event_loops.pop()
|
||||
old_loop_data.loop.quit()
|
||||
|
||||
def process_signals(self, return_after=None):
|
||||
"""This method processes incoming async messages.
|
||||
|
||||
Process signals en-queued by the `self.enqueue_signal()` method. Call handlers registered
|
||||
to the signals by the `self.register_signal_handler()` method.
|
||||
|
||||
When `return_after` is specified then wait to the point when this signal is processed.
|
||||
NO warranty that this method will return immediately after the signal was processed!
|
||||
|
||||
Without `return_after` parameter this method will return after all queued signals with
|
||||
the highest priority will be processed.
|
||||
|
||||
The method is NOT thread safe!
|
||||
|
||||
:param return_after: Wait on this signal to be processed.
|
||||
:type return_after: Class of the signal.
|
||||
"""
|
||||
super().process_signals(return_after)
|
||||
loop_data = self._event_loops[-1]
|
||||
|
||||
if return_after is not None:
|
||||
ticket_id = self._register_wait_on_signal(return_after)
|
||||
|
||||
while not self._check_if_signal_processed(return_after, ticket_id) and \
|
||||
not self._force_quit:
|
||||
self._iterate_event_loop(loop_data.loop)
|
||||
else:
|
||||
self._iterate_event_loop(loop_data.loop)
|
||||
|
||||
@staticmethod
|
||||
def _iterate_event_loop(event_loop):
|
||||
context = event_loop.get_context()
|
||||
# This is useful for tests
|
||||
wait_on_timeout = False
|
||||
context.iteration(wait_on_timeout)
|
||||
|
||||
|
||||
class EventLoopData():
|
||||
|
||||
def __init__(self, loop):
|
||||
super().__init__()
|
||||
self.loop = loop
|
||||
self.sources = set()
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
# Default event loop for Simpleline application.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from threading import Lock
|
||||
|
||||
from simpleline.event_loop import AbstractEventLoop, ExitMainLoop
|
||||
from simpleline.event_loop.event_queue import EventQueue
|
||||
from simpleline.event_loop.signals import ExceptionSignal
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
__all__ = ["MainLoop"]
|
||||
|
||||
|
||||
class MainLoop(AbstractEventLoop):
|
||||
"""Default main event loop for the Simpleline.
|
||||
|
||||
This event loop can be replaced by your event loop by implementing
|
||||
`simpleline.event_loop.AbstractEventLoop` class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._active_queue = EventQueue()
|
||||
self._event_queues = [self._active_queue]
|
||||
self._lock = Lock()
|
||||
|
||||
def register_signal_source(self, signal_source):
|
||||
"""Register source of signal for actual event queue.
|
||||
|
||||
:param signal_source: Source for future signals.
|
||||
:type signal_source: `simpleline.render.ui_screen.UIScreen`.
|
||||
"""
|
||||
super().register_signal_source(signal_source)
|
||||
self._active_queue.add_source(signal_source)
|
||||
|
||||
def run(self):
|
||||
"""This methods starts the application.
|
||||
|
||||
Do not use self.mainloop() directly as run() handles all the required exceptions
|
||||
needed to keep nested mainloop working.
|
||||
"""
|
||||
super().run()
|
||||
self._run_loop = True
|
||||
|
||||
try:
|
||||
self._mainloop()
|
||||
except ExitMainLoop:
|
||||
pass
|
||||
|
||||
log.debug("Main loop ended. Running callback if set.")
|
||||
|
||||
if self._quit_callback:
|
||||
cb = self._quit_callback.callback
|
||||
cb(self._quit_callback.args)
|
||||
|
||||
def force_quit(self):
|
||||
"""Force quit all running event loops.
|
||||
|
||||
Kill all loop including inner loops (modal window).
|
||||
None of the Simpleline events will be processed anymore.
|
||||
"""
|
||||
super().force_quit()
|
||||
self._event_queues.clear()
|
||||
self._run_loop = False
|
||||
|
||||
def execute_new_loop(self, signal):
|
||||
"""Starts the new event loop and pass `signal` in it.
|
||||
|
||||
This is required for processing a modal screens.
|
||||
|
||||
:param signal: Signal passed to the new event loop.
|
||||
:type signal: The `AbstractSignal` based class.
|
||||
"""
|
||||
super().execute_new_loop(signal)
|
||||
|
||||
if self._force_quit:
|
||||
return
|
||||
|
||||
self._active_queue = EventQueue()
|
||||
|
||||
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
|
||||
# pylint: disable=not-context-manager
|
||||
with self._lock:
|
||||
self._event_queues.append(self._active_queue)
|
||||
|
||||
self.enqueue_signal(signal)
|
||||
self._mainloop()
|
||||
log.debug("Inner loop is closed")
|
||||
|
||||
def close_loop(self):
|
||||
"""Close active event loop.
|
||||
|
||||
Close an event loop created by the `execute_new_loop()` method.
|
||||
"""
|
||||
super().close_loop()
|
||||
self.process_signals()
|
||||
|
||||
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
|
||||
# pylint: disable=not-context-manager
|
||||
with self._lock:
|
||||
self._event_queues.pop()
|
||||
try:
|
||||
self._active_queue = self._event_queues[-1]
|
||||
except IndexError:
|
||||
log.error("No more event queues to work with!")
|
||||
raise ExitMainLoop() # pylint: disable=raise-missing-from
|
||||
|
||||
self._run_loop = False
|
||||
|
||||
def enqueue_signal(self, signal):
|
||||
"""Enqueue new event for processing.
|
||||
|
||||
Enqueue signal to the most inner queue (nearest to the active queue) where
|
||||
the `signal.source` belongs.
|
||||
If it belongs nowhere enqueue it to the active one.
|
||||
|
||||
This method is thread safe.
|
||||
|
||||
:param signal: Event which you want to add to the event queue for processing.
|
||||
:type signal: Instance based on AbstractEvent class.
|
||||
"""
|
||||
if self._force_quit:
|
||||
return
|
||||
|
||||
super().enqueue_signal(signal)
|
||||
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
|
||||
# pylint: disable=not-context-manager
|
||||
with self._lock:
|
||||
for queue in reversed(self._event_queues):
|
||||
if queue.enqueue_if_source_belongs(signal, signal.source):
|
||||
return
|
||||
|
||||
self._active_queue.enqueue(signal)
|
||||
|
||||
def _mainloop(self):
|
||||
"""Single mainloop. Do not use directly, start the application using run()."""
|
||||
# run infinite loop
|
||||
# this will always wait on input processing or similar so it should not busy waiting
|
||||
while self._run_loop:
|
||||
self._process_signals_loop()
|
||||
|
||||
if not self._force_quit:
|
||||
# set back to True to leave outer loop working
|
||||
self._run_loop = True
|
||||
|
||||
def process_signals(self, return_after=None):
|
||||
"""This method processes incoming async messages.
|
||||
|
||||
Process signals en-queued by the `self.enqueue_signal()` method. Call handlers
|
||||
registered to the signals by the `self.register_signal_handler()` method.
|
||||
|
||||
When `return_after` is specified then wait to the point when this signal is processed.
|
||||
NO warranty that this method will return immediately after the signal was processed!
|
||||
|
||||
Without `return_after` parameter this method will return after all queued signals
|
||||
with the highest priority will be processed.
|
||||
|
||||
The method is NOT thread safe!
|
||||
|
||||
:param return_after: Wait on this signal to be processed.
|
||||
:type return_after: Class of the signal.
|
||||
"""
|
||||
super().process_signals(return_after)
|
||||
if return_after is not None:
|
||||
self._process_signals_with_return(return_after)
|
||||
else:
|
||||
self._process_signals_iteration()
|
||||
|
||||
def _process_signals_with_return(self, return_after):
|
||||
"""Process signals until the return_after signal was processed.
|
||||
|
||||
Or the loop quited.
|
||||
"""
|
||||
# get unique ID when waiting for the signal
|
||||
unique_id = self._register_wait_on_signal(return_after)
|
||||
|
||||
while self._run_loop:
|
||||
signal = self._active_queue.get()
|
||||
|
||||
# do the signal processing (call handlers)
|
||||
self._process_signal(signal)
|
||||
|
||||
# was our signal processed if yes, return this method
|
||||
if self._check_if_signal_processed(return_after, unique_id):
|
||||
return
|
||||
|
||||
def _process_signals_iteration(self):
|
||||
"""Process queued signal and then return."""
|
||||
priority = None
|
||||
|
||||
while not self._active_queue.empty() and self._run_loop:
|
||||
if priority is None:
|
||||
# take first signal to find out the highest priority in queue
|
||||
signal = self._active_queue.get()
|
||||
priority = signal.priority
|
||||
else:
|
||||
# get signal with this priority only
|
||||
signal = self._active_queue.get_top_event_if_priority(priority)
|
||||
|
||||
# Signal with this priority is not available anymore
|
||||
if signal is None:
|
||||
return
|
||||
|
||||
self._process_signal(signal)
|
||||
|
||||
def _process_signals_loop(self):
|
||||
"""Process signal until the event loop quited."""
|
||||
while self._run_loop:
|
||||
signal = self._active_queue.get()
|
||||
self._process_signal(signal)
|
||||
|
||||
def _process_signal(self, signal):
|
||||
log.debug("Processing signal %s", signal)
|
||||
|
||||
self._mark_signal_processed(signal)
|
||||
|
||||
if type(signal) in self._handlers: # pylint: disable=unidiomatic-typecheck
|
||||
for handler_data in self._handlers[type(signal)]:
|
||||
try:
|
||||
handler_data.callback(signal, handler_data.data)
|
||||
except ExitMainLoop: # pylint: disable=try-except-raise
|
||||
raise
|
||||
except Exception: # pylint: disable=broad-except
|
||||
self.enqueue_signal(ExceptionSignal(self))
|
||||
elif isinstance(signal, ExceptionSignal):
|
||||
self.kill_app_with_traceback(signal)
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Set of default signals used inside of widgets.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from sys import exc_info
|
||||
from simpleline.event_loop import AbstractSignal
|
||||
|
||||
__all__ = ["ExceptionSignal", "InputReadySignal", "RenderScreenSignal", "CloseScreenSignal",
|
||||
"InputReceivedSignal"]
|
||||
|
||||
|
||||
class ExceptionSignal(AbstractSignal):
|
||||
"""Emit this signal when exception is raised.
|
||||
|
||||
This class must be created inside of exception handler or `exception_info` must be specified
|
||||
in creation process.
|
||||
|
||||
If you register handler for this exception then the Simpleline's exception handling
|
||||
is disabled!
|
||||
"""
|
||||
|
||||
def __init__(self, source, exception_info=None):
|
||||
"""Create exception signal with higher priority (-20) than other signals.
|
||||
|
||||
:param source: source of this signal
|
||||
:type source: class which emits this signal
|
||||
|
||||
:param exception_info: if specified raise your exception, otherwise create exception here;
|
||||
to create exception here it needs to be created inside of exception
|
||||
handler
|
||||
:type exception_info: output of `sys.exc_info()` method
|
||||
"""
|
||||
super().__init__(source, priority=-20)
|
||||
if exception_info:
|
||||
self.exception_info = exception_info
|
||||
else:
|
||||
self.exception_info = exc_info()
|
||||
|
||||
|
||||
class InputReadySignal(AbstractSignal):
|
||||
"""Input from user is ready for processing."""
|
||||
def __init__(self, source, input_handler_source, data, priority=0, success=True):
|
||||
"""Store user input inside of this signal
|
||||
|
||||
Read the data from user input in `data` attribute.
|
||||
|
||||
The only way how a user should ask for input is to use InputHandler and inherited classes.
|
||||
The input_handler_source param must be set but this signal instance can be attached to
|
||||
another source object which is registered to a specific event loop.
|
||||
|
||||
If no requester (object who uses InputHandler) is specified then source and
|
||||
input_handler_source will both point to InputHandler instance.
|
||||
|
||||
:param source: Source of this signal.
|
||||
:type source: Any object.
|
||||
|
||||
:param input_handler_source: InputHandler who is asking for input.
|
||||
:type input_handler_source: The `simpleline.input.input_handler.InputHandler` based
|
||||
instance.
|
||||
|
||||
:param data: User input data.
|
||||
:type data: str
|
||||
|
||||
:param priority: Priority of this event.
|
||||
:type priority: Int greater than 0.
|
||||
|
||||
:param success: Was the input successful? True on successful input False otherwise.
|
||||
:type success: bool
|
||||
"""
|
||||
super().__init__(source, priority=priority)
|
||||
self.input_handler_source = input_handler_source
|
||||
self.data = data
|
||||
self.success = success
|
||||
|
||||
|
||||
class InputReceivedSignal(AbstractSignal):
|
||||
"""Raw input received.
|
||||
|
||||
This signal will be further processed and InputReadySignal should be enqueued soon.
|
||||
Most probably you are looking for InputReadySignal instead.
|
||||
"""
|
||||
def __init__(self, source, data, priority=0):
|
||||
super().__init__(source, priority=priority)
|
||||
self.data = data
|
||||
|
||||
|
||||
class RenderScreenSignal(AbstractSignal):
|
||||
"""Render UIScreen to terminal."""
|
||||
|
||||
|
||||
class CloseScreenSignal(AbstractSignal):
|
||||
"""Close current screen."""
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
# Ticket machine synchronization class.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
|
||||
class TicketMachine():
|
||||
"""Hold signals processed by the event loop if someone wait on them.
|
||||
|
||||
This is useful when recursive process events will skip required signal.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._lines = {}
|
||||
self._counter = 0
|
||||
|
||||
def take_ticket(self, line_id):
|
||||
"""Take ticket (id) and go line (processing events).
|
||||
|
||||
Use `check_ticket` if you are ready to go.
|
||||
|
||||
:param line_id: Line where you are waiting.
|
||||
:type line_id: Anything.
|
||||
"""
|
||||
obj_id = self._counter
|
||||
if line_id not in self._lines:
|
||||
self._lines[line_id] = {obj_id: False}
|
||||
else:
|
||||
self._lines[line_id][obj_id] = False
|
||||
|
||||
self._counter += 1
|
||||
return obj_id
|
||||
|
||||
def check_ticket(self, line, unique_id):
|
||||
"""Check if you are ready to go.
|
||||
|
||||
If True the unique_id is not valid anymore.
|
||||
|
||||
:param unique_id: Your id used to identify you in the line.
|
||||
:type unique_id: int
|
||||
|
||||
:param line: Line where you are waiting.
|
||||
:type line: Anything.
|
||||
|
||||
:return: True if the ticket was already marked, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
if self._lines[line][unique_id]:
|
||||
return self._lines[line].pop(unique_id)
|
||||
|
||||
return False
|
||||
|
||||
def mark_line_to_go(self, line):
|
||||
"""All in the `line` are ready to go.
|
||||
|
||||
Mark all tickets in the line as True.
|
||||
|
||||
:param line: Line which should processed.
|
||||
:type line: Anything.
|
||||
"""
|
||||
if line in self._lines:
|
||||
our_line = self._lines[line]
|
||||
for key in our_line:
|
||||
our_line[key] = True
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
# Global configuration for the whole application.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from getpass import getpass
|
||||
|
||||
__all__ = ["GlobalConfiguration"]
|
||||
|
||||
DEFAULT_WIDTH = 80
|
||||
DEFAULT_PASSWORD_FUNC = getpass
|
||||
|
||||
|
||||
class GlobalConfiguration():
|
||||
"""Class for global configuration of application defaults.
|
||||
|
||||
All stored data are persistent between App.initialize() calls and can be used before this call.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._width = DEFAULT_WIDTH
|
||||
self._getpass = DEFAULT_PASSWORD_FUNC
|
||||
self._run_with_empty_stack = False
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""Get width of the application.
|
||||
|
||||
:returns: int
|
||||
"""
|
||||
return self._width
|
||||
|
||||
@width.setter
|
||||
def width(self, width):
|
||||
"""Set width of the application.
|
||||
|
||||
:param width: Number of characters which can be printed to one line.
|
||||
:type width: int
|
||||
"""
|
||||
self._width = width
|
||||
|
||||
def clear_width(self):
|
||||
"""Clear user defined width and set the default.
|
||||
|
||||
Default: 80 characters
|
||||
"""
|
||||
self._width = DEFAULT_WIDTH
|
||||
|
||||
@property
|
||||
def password_function(self):
|
||||
"""Get function to get user passwords from a console.
|
||||
|
||||
:returns: Function with one argument which is text representation of prompt.
|
||||
"""
|
||||
return self._getpass
|
||||
|
||||
@password_function.setter
|
||||
def password_function(self, password_func):
|
||||
"""Set function to get user passwords from a console.
|
||||
|
||||
:param password_func: Function to get password from a command line.
|
||||
:type password_func: Function with one argument which is text representation of prompt.
|
||||
"""
|
||||
self._getpass = password_func
|
||||
|
||||
def clear_password_function(self):
|
||||
"""Clear user defined password function and set the default.
|
||||
|
||||
Default: getpass.getpass function
|
||||
"""
|
||||
self._getpass = getpass
|
||||
|
||||
@property
|
||||
def should_run_with_empty_stack(self):
|
||||
"""Should test on empty screen stack when starting event loop.
|
||||
|
||||
:returns: If False the App.run() call will end with an exception (default), True otherwise.
|
||||
"""
|
||||
return self._run_with_empty_stack
|
||||
|
||||
@should_run_with_empty_stack.setter
|
||||
def should_run_with_empty_stack(self, value):
|
||||
"""Set if the App.run() call should end with an exception when screen stack is empty.
|
||||
|
||||
This can be valuable when you want to schedule a screen later by an other thread.
|
||||
|
||||
:param value: If False the App.run() call will end with an exception, if True it will
|
||||
run with nothing displayed.
|
||||
"""
|
||||
self._run_with_empty_stack = value
|
||||
|
||||
def clear_should_run_with_empty_stack(self):
|
||||
"""Clear user defined test to run with an empty screen stack.
|
||||
|
||||
Default: False
|
||||
"""
|
||||
self._run_with_empty_stack = False
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from simpleline.input.input_handler import InputHandler, PasswordInputHandler
|
||||
|
||||
__all__ = ["InputHandler", "PasswordInputHandler"]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+292
@@ -0,0 +1,292 @@
|
||||
# Handle user input
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
from simpleline import App
|
||||
from simpleline.event_loop.signals import InputReadySignal
|
||||
from simpleline.render.widgets import TextWidget
|
||||
from simpleline.input.input_threading import InputThreadManager, InputRequest
|
||||
|
||||
__all__ = ["InputHandler", "PasswordInputHandler"]
|
||||
|
||||
|
||||
class InputHandler():
|
||||
|
||||
def __init__(self, callback=None, source=None):
|
||||
"""Class to handle input from the terminal.
|
||||
|
||||
This class is designed to be instantiated on place where it should be used.
|
||||
The main method is `get_input()` which is non-blocking asynchronous call. It can be used
|
||||
as synchronous call be calling the `wait_on_input` method.
|
||||
|
||||
To get result from this class use the `value` property.
|
||||
|
||||
:param callback: You can specify callback which will be called when user give input.
|
||||
:type callback: Callback function with one argument which will be user input.
|
||||
|
||||
:param source: Source of this input. It will be helpful in case of debugging an issue.
|
||||
:type source: Class which will process an input from this InputHandler.
|
||||
"""
|
||||
super().__init__()
|
||||
self._input = None
|
||||
self._input_callback = callback
|
||||
self._input_received = False
|
||||
self._input_successful = False
|
||||
self._skip_concurrency_check = False
|
||||
self._source = source
|
||||
|
||||
App.get_event_loop().register_signal_handler(InputReadySignal,
|
||||
self._input_received_handler)
|
||||
|
||||
def _input_received_handler(self, signal, args):
|
||||
if signal.input_handler_source != self:
|
||||
return
|
||||
|
||||
self._input_received = True
|
||||
self._input_successful = signal.success
|
||||
|
||||
if not self._input_successful:
|
||||
return
|
||||
|
||||
self._input = signal.data
|
||||
|
||||
# call async callback
|
||||
if self._input_callback is not None:
|
||||
cb = self._input_callback
|
||||
self._input_callback = None
|
||||
|
||||
cb(self._input)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Return user input.
|
||||
|
||||
:returns: String or None if no is input received.
|
||||
"""
|
||||
return self._input
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Get source of this input.
|
||||
|
||||
:returns: Anything probably UIScreen.
|
||||
"""
|
||||
return self._source
|
||||
|
||||
@property
|
||||
def skip_concurrency_check(self):
|
||||
"""Is this InputHandler skipping concurrency check?
|
||||
|
||||
:returns bool
|
||||
"""
|
||||
return self._skip_concurrency_check
|
||||
|
||||
@skip_concurrency_check.setter
|
||||
def skip_concurrency_check(self, value):
|
||||
"""Set if this InputHandler should skip concurrency check.
|
||||
|
||||
Note if you skip this check, you can have unexpected behavior. Use with caution.
|
||||
|
||||
:param value: True to skip the check, False if not.
|
||||
"""
|
||||
self._skip_concurrency_check = value
|
||||
|
||||
def set_callback(self, callback):
|
||||
"""Set a callback to get user input asynchronously.
|
||||
|
||||
:param callback: Callback called when user write their input.
|
||||
:type callback: Method with 1 argument which is user input: def cb(user_input)
|
||||
"""
|
||||
self._input_callback = callback
|
||||
|
||||
def input_received(self):
|
||||
"""Was user input already received?
|
||||
|
||||
:returns: True if yes, False otherwise.
|
||||
"""
|
||||
return self._input_received
|
||||
|
||||
def wait_on_input(self):
|
||||
"""Blocks execution till the user input is received.
|
||||
|
||||
Events will works as expected during this blocking.
|
||||
|
||||
Please check the `input_successful` method to test the input.
|
||||
"""
|
||||
# we already received input from user
|
||||
if self._input_received:
|
||||
return
|
||||
|
||||
while not self._input_received:
|
||||
App.get_event_loop().process_signals(InputReadySignal)
|
||||
|
||||
def input_successful(self):
|
||||
"""Was input successful?
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return self._input_successful
|
||||
|
||||
def get_input(self, prompt):
|
||||
"""Use prompt to ask for user input and wait (non-blocking) on user input.
|
||||
|
||||
This is an asynchronous call. If you want to wait for user input then use
|
||||
the `wait_on_input` method. If you want to get results asynchronously then register
|
||||
callback in constructor or by the `set_callback` method.
|
||||
|
||||
Check if user input was already received can be done by the `input_received` method call.
|
||||
|
||||
:param prompt: Ask user what you want to get.
|
||||
:type prompt: String or Prompt instance.
|
||||
|
||||
:returns: User input.
|
||||
:rtype: str
|
||||
"""
|
||||
self._clear_input()
|
||||
self._invoke_input_thread(prompt)
|
||||
|
||||
def _invoke_input_thread(self, prompt):
|
||||
thread_object = self.create_thread_object(prompt)
|
||||
InputThreadManager.get_instance().start_input_thread(thread_object,
|
||||
not self._skip_concurrency_check)
|
||||
|
||||
def create_thread_object(self, prompt):
|
||||
"""Create thread object containing all the information how to get user input.
|
||||
|
||||
:returns: Instance of class inherited from `simpleline.input.InputThread`.
|
||||
"""
|
||||
return InputHandlerRequest(App.get_configuration().width, prompt, self)
|
||||
|
||||
def _clear_input(self):
|
||||
self._input_received = False
|
||||
self._input = None
|
||||
|
||||
|
||||
class InputHandlerRequest(InputRequest):
|
||||
"""This is thread object to get input from user without blocking main thread."""
|
||||
|
||||
def __init__(self, width, prompt, input_handler):
|
||||
"""Create request object to get input in InputThreadManager.
|
||||
|
||||
:param width: Width of the screen prompt.
|
||||
:type width: int
|
||||
|
||||
:param prompt: Input prompt.
|
||||
:type prompt: Instance of `simpleline.render.prompt.Prompt` class.
|
||||
|
||||
:param input_handler: InputHandler instance which created this object.
|
||||
:type input_handler: InputHandler based instance.
|
||||
"""
|
||||
super().__init__(input_handler, input_handler.source)
|
||||
self._width = width
|
||||
self._prompt = prompt
|
||||
|
||||
def get_input(self):
|
||||
"""This method is responsible for interruptable user input.
|
||||
|
||||
It is expected to be used in a thread started on demand
|
||||
and returns the input via the communication Queue.
|
||||
"""
|
||||
# lock acquired, we can run input
|
||||
try:
|
||||
data = self._ask_input()
|
||||
except EOFError:
|
||||
data = ""
|
||||
|
||||
return data
|
||||
|
||||
def text_prompt(self):
|
||||
widget = TextWidget(str(self._prompt))
|
||||
widget.render(self._width)
|
||||
lines = widget.get_lines()
|
||||
return "\n".join(lines) + " "
|
||||
|
||||
def _ask_input(self):
|
||||
text_prompt = self.text_prompt()
|
||||
sys.stdout.write(text_prompt)
|
||||
sys.stdout.flush()
|
||||
|
||||
return self._get_input()
|
||||
|
||||
@staticmethod
|
||||
def _get_input():
|
||||
return input()
|
||||
|
||||
|
||||
class PasswordInputHandler(InputHandler):
|
||||
|
||||
def __init__(self, callback=None, source=None):
|
||||
"""Class to handle hidden password input from the terminal.
|
||||
|
||||
This class is designed to be instantiated on place where it should be used.
|
||||
The main method is `get_input()` which is non-blocking asynchronous call. It can be used
|
||||
as synchronous call be calling the `wait_on_input` method.
|
||||
|
||||
To get result from this class use the `value` property.
|
||||
|
||||
:param callback: You can specify callback which will be called when user give input.
|
||||
:type callback: Callback function with one argument which will be user input.
|
||||
|
||||
:param source: Source of this input. It will be helpful in case of debugging an issue.
|
||||
:type source: Class which will process an input from this InputHandler.
|
||||
"""
|
||||
super().__init__(callback=callback, source=source)
|
||||
self._getpass_func = App.get_configuration().password_function
|
||||
|
||||
def set_pass_func(self, getpass_func):
|
||||
"""Set a function for getting passwords."""
|
||||
if not getpass_func:
|
||||
return
|
||||
|
||||
self._getpass_func = getpass_func
|
||||
|
||||
def create_thread_object(self, prompt):
|
||||
"""Return PasswordInputThread for getting user password."""
|
||||
return PasswordInputHandlerRequest(App.get_configuration().width, prompt, self,
|
||||
self._getpass_func)
|
||||
|
||||
|
||||
class PasswordInputHandlerRequest(InputHandlerRequest):
|
||||
"""Similar as InputHandlerRequest but don't echo user keys."""
|
||||
|
||||
def __init__(self, width, prompt, input_handler, getpass_func):
|
||||
"""Create request object to get password input in InputThreadManager.
|
||||
|
||||
:param width: Width of the screen prompt.
|
||||
:type width: int
|
||||
|
||||
:param prompt: Input prompt.
|
||||
:type prompt: Instance of `simpleline.render.prompt.Prompt` class.
|
||||
|
||||
:param input_handler: InputHandler instance which created this object.
|
||||
:type input_handler: InputHandler based instance.
|
||||
|
||||
:param getpass_func: Function to get user password.
|
||||
:type getpass_func: Function which gets prompt as only parameter and returns user input
|
||||
string.
|
||||
"""
|
||||
super().__init__(width, prompt, input_handler)
|
||||
self._getpass_func = getpass_func
|
||||
|
||||
def _ask_input(self):
|
||||
text_prompt = self.text_prompt()
|
||||
|
||||
return self._getpass_func(text_prompt)
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import threading
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from simpleline import App
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
from simpleline.event_loop.signals import InputReceivedSignal, InputReadySignal
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
|
||||
INPUT_THREAD_NAME = "SimplelineInputThread"
|
||||
|
||||
|
||||
class InputThreadManager():
|
||||
"""Manager object for input threads.
|
||||
|
||||
This manager helps with concurrent user input (still you really shouldn't do that).
|
||||
"""
|
||||
|
||||
__instance = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._input_stack = []
|
||||
self._processing_input = False
|
||||
|
||||
@classmethod
|
||||
def create_new_instance(cls):
|
||||
instance = InputThreadManager()
|
||||
cls.__instance = instance
|
||||
|
||||
instance._post_init_configuration() # pylint: disable=protected-access
|
||||
|
||||
def _post_init_configuration(self):
|
||||
# pylint: disable=protected-access
|
||||
App.get_event_loop().register_signal_handler(InputReceivedSignal,
|
||||
self.__instance._input_received_handler)
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
if not cls.__instance:
|
||||
cls.create_new_instance()
|
||||
|
||||
return cls.__instance
|
||||
|
||||
def _input_received_handler(self, signal, args):
|
||||
thread_object = self._input_stack.pop()
|
||||
thread_object.emit_input_ready_signal(signal.data)
|
||||
|
||||
if thread_object.thread:
|
||||
thread_object.thread.join()
|
||||
|
||||
# wait until used object ends
|
||||
for t in self._input_stack:
|
||||
t.emit_failed_input_ready_signal()
|
||||
if t.thread:
|
||||
t.thread.join()
|
||||
|
||||
# remove all other items waiting for input
|
||||
self._input_stack.clear()
|
||||
self._processing_input = False
|
||||
|
||||
def start_input_thread(self, input_thread_object, concurrent_check=True):
|
||||
"""Start input thread to get user input.
|
||||
|
||||
:param input_thread_object: Input thread object based on InputThread class.
|
||||
:param concurrent_check: Should the concurrent thread check be fatal? (default True).
|
||||
"""
|
||||
self._input_stack.append(input_thread_object)
|
||||
self._check_input_thread_running(concurrent_check)
|
||||
self._start_user_input_async()
|
||||
|
||||
def _check_input_thread_running(self, raise_concurrent_check):
|
||||
if len(self._input_stack) != 1:
|
||||
if not raise_concurrent_check:
|
||||
log.warning("Asking for multiple inputs with concurrent check bypassed, "
|
||||
"last who asked wins! Others are dropped.")
|
||||
else:
|
||||
msg = ""
|
||||
for t in self._input_stack:
|
||||
requester_source = t.requester_source or "Unknown"
|
||||
msg += "Input handler: {} Input requester: {}\n".format(t.source,
|
||||
requester_source)
|
||||
|
||||
msg.rstrip()
|
||||
|
||||
raise KeyError("Can't run multiple input threads at the same time!\n"
|
||||
"Asking for input:\n"
|
||||
"{}".format(msg))
|
||||
|
||||
def _start_user_input_async(self):
|
||||
thread_object = self._input_stack[-1]
|
||||
|
||||
if self._processing_input:
|
||||
self._print_new_prompt(thread_object)
|
||||
return
|
||||
|
||||
thread_object.initialize_thread()
|
||||
self._processing_input = True
|
||||
thread_object.start_thread()
|
||||
|
||||
@staticmethod
|
||||
def _print_new_prompt(thread_object):
|
||||
prompt = thread_object.text_prompt()
|
||||
|
||||
# print new prompt
|
||||
print(prompt, end="")
|
||||
|
||||
|
||||
class InputRequest(metaclass=ABCMeta):
|
||||
"""Base input request class.
|
||||
|
||||
This should be overloaded for every InputHandler class. Purpose of this class is to print
|
||||
prompt and get input from user.
|
||||
|
||||
The `run_input` method is the entry point for this class. Output from this method must be
|
||||
a user input.
|
||||
The `text_prompt` method is used to get textual representation of a prompt. This will be used
|
||||
on concurrent input to replace existing prompt to get new input.
|
||||
|
||||
WARNING:
|
||||
The `run_input` method will run in a separate thread!
|
||||
"""
|
||||
|
||||
def __init__(self, source, requester_source=None):
|
||||
super().__init__()
|
||||
self._source = source
|
||||
self._requester_source = requester_source
|
||||
self.thread = None
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Get direct source of this input request.
|
||||
|
||||
:returns: InputHandler instance.
|
||||
"""
|
||||
return self._source
|
||||
|
||||
@property
|
||||
def requester_source(self):
|
||||
"""Get requester -- source of this input.
|
||||
|
||||
:returns: Anything probably UIScreen based instance.
|
||||
"""
|
||||
return self._requester_source
|
||||
|
||||
def emit_input_ready_signal(self, input_data):
|
||||
"""Emit the InputReadySignal signal with collected input data.
|
||||
|
||||
:param input_data: Input data received.
|
||||
:type input_data: str
|
||||
"""
|
||||
handler_source = self.source
|
||||
signal_source = self._get_request_source()
|
||||
|
||||
new_signal = InputReadySignal(source=signal_source, input_handler_source=handler_source,
|
||||
data=input_data, success=True)
|
||||
App.get_event_loop().enqueue_signal(new_signal)
|
||||
|
||||
def emit_failed_input_ready_signal(self):
|
||||
"""Emit the InputReadySignal with failed state."""
|
||||
handler_source = self.source
|
||||
signal_source = self._get_request_source()
|
||||
|
||||
new_signal = InputReadySignal(source=signal_source, input_handler_source=handler_source,
|
||||
data="", success=False)
|
||||
App.get_event_loop().enqueue_signal(new_signal)
|
||||
|
||||
def _get_request_source(self):
|
||||
"""Get user input request source.
|
||||
|
||||
That means object who is using InputHandler.
|
||||
If this object is not specified then return InputHandler as a source.
|
||||
"""
|
||||
return self.requester_source or self.source
|
||||
|
||||
def initialize_thread(self):
|
||||
"""Initialize thread for this input request.
|
||||
|
||||
Do not call this directly! Will be called by InputThreadManager.
|
||||
"""
|
||||
self.thread = threading.Thread(name=INPUT_THREAD_NAME, target=self.run)
|
||||
self.thread.daemon = True
|
||||
|
||||
def start_thread(self):
|
||||
"""Start input thread.
|
||||
|
||||
Do not call this directly! Will be called by InputThreadManager.
|
||||
"""
|
||||
self.thread.start()
|
||||
|
||||
def run(self):
|
||||
"""Run the `run_input` method and propagate input outside.
|
||||
|
||||
Do not call this method directly. It will be called by InputThreadManager.
|
||||
"""
|
||||
data = self.get_input()
|
||||
|
||||
App.get_event_loop().enqueue_signal(InputReceivedSignal(self, data))
|
||||
|
||||
@abstractmethod
|
||||
def text_prompt(self):
|
||||
"""Get text representation of the user prompt.
|
||||
|
||||
This will be used to get high priority input.
|
||||
|
||||
:returns: String representation of the prompt or None if no prompt is present.
|
||||
"""
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def get_input(self):
|
||||
"""Print prompt and get an input from user.
|
||||
|
||||
..NOTE: Overload this method in your class.
|
||||
|
||||
Return this input from a function.
|
||||
"""
|
||||
return ""
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Logging functions and methods used by Simpleline.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
SIMPLELINE_LOGGER = "simpleline"
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Set proper logging for a library"""
|
||||
log = get_simpleline_logger()
|
||||
null_hd = logging.NullHandler()
|
||||
log.addHandler(null_hd)
|
||||
|
||||
|
||||
def get_simpleline_logger():
|
||||
"""Return logging instance that can be used in the application."""
|
||||
return logging.getLogger(SIMPLELINE_LOGGER)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Module with classes to handle rendering and input.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from simpleline.errors import SimplelineError
|
||||
|
||||
|
||||
class RenderError(SimplelineError):
|
||||
"""Exception raised when error in rendering happens."""
|
||||
|
||||
|
||||
class RenderUnexpectedError(RenderError):
|
||||
"""Exception raised when something goes really wrong."""
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+253
@@ -0,0 +1,253 @@
|
||||
# Advanced widgets
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
from simpleline.render import widgets
|
||||
from simpleline.render.containers import WindowContainer
|
||||
from simpleline.render.prompt import Prompt
|
||||
from simpleline.render.screen import UIScreen, InputState
|
||||
from simpleline.input.input_handler import PasswordInputHandler
|
||||
from simpleline.utils.i18n import _, N_, C_
|
||||
|
||||
__all__ = ["ErrorDialog", "GetInputScreen", "GetPasswordInputScreen", "HelpScreen",
|
||||
"PasswordDialog", "YesNoDialog"]
|
||||
|
||||
|
||||
class ErrorDialog(UIScreen):
|
||||
"""Dialog screen for reporting errors to user."""
|
||||
|
||||
def __init__(self, message):
|
||||
"""
|
||||
:param message: the message to show to the user
|
||||
:type message: str
|
||||
"""
|
||||
super().__init__()
|
||||
self.title = N_("Error")
|
||||
self._message = message
|
||||
|
||||
def refresh(self, args=None):
|
||||
super().refresh(args)
|
||||
text = widgets.TextWidget(self._message)
|
||||
self.window.add_with_separator(widgets.CenterWidget(text))
|
||||
|
||||
def prompt(self, args=None):
|
||||
return Prompt(_("Press %s to exit") % Prompt.ENTER)
|
||||
|
||||
def input(self, args, key):
|
||||
"""This dialog is closed by any input.
|
||||
|
||||
And causes the program to quit.
|
||||
"""
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class PasswordDialog(UIScreen):
|
||||
"""Dialog screen for password input."""
|
||||
|
||||
def __init__(self, message=None):
|
||||
"""
|
||||
:param message: password prompt question
|
||||
:type message: string
|
||||
"""
|
||||
super().__init__()
|
||||
self.title = N_("Password")
|
||||
self._message = message or _("Enter your passphrase")
|
||||
self._password = None
|
||||
|
||||
def refresh(self, args=None):
|
||||
super().refresh(args)
|
||||
text = widgets.TextWidget(self._message)
|
||||
self.window.add_with_separator(widgets.CenterWidget(text))
|
||||
|
||||
def prompt(self, args=None):
|
||||
handler = PasswordInputHandler(source=self)
|
||||
if self.password_func:
|
||||
handler.set_pass_func(self.password_func)
|
||||
|
||||
handler.get_input(_("Passphrase: "))
|
||||
handler.wait_on_input()
|
||||
|
||||
if not handler.input_successful():
|
||||
return None
|
||||
|
||||
self._password = handler.value
|
||||
|
||||
# this may seem innocuous, but it's really a giant hack; we should
|
||||
# not be calling close() from prompt(), but the input handling code
|
||||
# in the TUI is such that without this very simple workaround, we
|
||||
# would be forever pelting users with a prompt to enter their pw
|
||||
self.close()
|
||||
return None
|
||||
|
||||
@property
|
||||
def answer(self):
|
||||
"""The response can be None (no response) or the password entered."""
|
||||
return self._password
|
||||
|
||||
def input(self, args, key):
|
||||
if key:
|
||||
self._password = key
|
||||
return InputState.PROCESSED_AND_CLOSE
|
||||
|
||||
return InputState.DISCARDED
|
||||
|
||||
|
||||
class YesNoDialog(UIScreen):
|
||||
"""Dialog screen for Yes - No questions."""
|
||||
|
||||
def __init__(self, message):
|
||||
"""
|
||||
:param message: the message to show to the user
|
||||
:type message: unicode
|
||||
"""
|
||||
super().__init__()
|
||||
self.title = N_("Question")
|
||||
self._message = message
|
||||
self._response = None
|
||||
|
||||
def refresh(self, args=None):
|
||||
super().refresh(args)
|
||||
text = widgets.TextWidget(self._message)
|
||||
self.window.add_with_separator(widgets.CenterWidget(text))
|
||||
|
||||
def prompt(self, args=None):
|
||||
return Prompt(_("Please respond '%(yes)s' or '%(no)s'") % {
|
||||
# TRANSLATORS: 'yes' as positive reply
|
||||
"yes": C_('TUI|Spoke Navigation', 'yes'),
|
||||
# TRANSLATORS: 'no' as negative reply
|
||||
"no": C_('TUI|Spoke Navigation', 'no')
|
||||
})
|
||||
|
||||
def input(self, args, key):
|
||||
# TRANSLATORS: 'yes' as positive reply
|
||||
if key == C_('TUI|Spoke Navigation', 'yes'):
|
||||
self._response = True
|
||||
return InputState.PROCESSED_AND_CLOSE
|
||||
|
||||
# TRANSLATORS: 'no' as negative reply
|
||||
if key == C_('TUI|Spoke Navigation', 'no'):
|
||||
self._response = False
|
||||
return InputState.PROCESSED_AND_CLOSE
|
||||
|
||||
return InputState.DISCARDED
|
||||
|
||||
@property
|
||||
def answer(self):
|
||||
"""The response can be True (yes), False (no) or None (no response)."""
|
||||
return self._response
|
||||
|
||||
|
||||
class HelpScreen(UIScreen):
|
||||
"""Screen to display a help message."""
|
||||
|
||||
def __init__(self, help_path):
|
||||
"""
|
||||
:param help_path: help file name
|
||||
:type help_path: str
|
||||
"""
|
||||
super().__init__()
|
||||
self.title = N_("Help")
|
||||
self.help_path = help_path
|
||||
|
||||
def refresh(self, args=None):
|
||||
""" Show the help. """
|
||||
super().refresh(args)
|
||||
help_message = _("The help is not available.")
|
||||
|
||||
if self.help_path:
|
||||
with open(self.help_path, 'r') as f:
|
||||
help_message = f.read()
|
||||
|
||||
self.window.add_with_separator(widgets.TextWidget(help_message))
|
||||
|
||||
def input(self, args, key):
|
||||
""" Handle user input. """
|
||||
return InputState.PROCESSED_AND_CLOSE
|
||||
|
||||
def prompt(self, args=None):
|
||||
return Prompt(_("Press %s to return") % Prompt.ENTER)
|
||||
|
||||
|
||||
class GetInputScreen(UIScreen):
|
||||
"""Screen for getting user input."""
|
||||
|
||||
def __init__(self, message):
|
||||
"""
|
||||
:param message: Prompt printed before user input.
|
||||
:type message: str
|
||||
"""
|
||||
super().__init__()
|
||||
self._message = message
|
||||
self._value = None
|
||||
self._conditions = []
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""User input."""
|
||||
return self._value
|
||||
|
||||
def add_acceptance_condition(self, acceptance_function, args=None):
|
||||
"""Add acceptance condition to the conditions list.
|
||||
|
||||
:param acceptance_function: Functions that accepts or rejects a user input.
|
||||
:type acceptance_function: `function(input, args) -> bool` - function which takes
|
||||
user input (string) and arguments (`args`) and return True when
|
||||
input is accepted or False if rejected so we will ask for
|
||||
a new input.
|
||||
|
||||
:param args: Second argument for `acceptance_function` the first one will be user input.
|
||||
:type args: Anything.
|
||||
"""
|
||||
self._conditions.append((acceptance_function, args))
|
||||
|
||||
def clear_acceptance_conditions(self):
|
||||
"""Clear list of the acceptance conditions."""
|
||||
self._conditions.clear()
|
||||
|
||||
def refresh(self, args=None):
|
||||
super().refresh(args)
|
||||
self._window = WindowContainer()
|
||||
|
||||
def prompt(self, args=None):
|
||||
return Prompt(message=self._message)
|
||||
|
||||
def input(self, args, key):
|
||||
if not self._test_input(key):
|
||||
return InputState.DISCARDED
|
||||
|
||||
self._value = key
|
||||
|
||||
return InputState.PROCESSED_AND_CLOSE
|
||||
|
||||
def _test_input(self, key):
|
||||
for f, args in self._conditions:
|
||||
if not f(key, args):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class GetPasswordInputScreen(GetInputScreen):
|
||||
"""Screen for getting user password input."""
|
||||
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.hide_user_input = True
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
# Widgets for holding other widgets.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from math import ceil
|
||||
|
||||
from simpleline.render.widgets import Widget, TextWidget, SeparatorWidget
|
||||
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
|
||||
__all__ = ["ListRowContainer", "ListColumnContainer", "WindowContainer"]
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
|
||||
class Container(Widget):
|
||||
"""Base class for containers which will do positioning of the widgets."""
|
||||
|
||||
def __init__(self, items=None, numbering=True):
|
||||
"""Construct Container.
|
||||
|
||||
:param items: List of items for positioning in this Container. Callback
|
||||
can't be specified this way.
|
||||
:type items: List of items for rendering.
|
||||
|
||||
:param numbering: Enable/disable automatic numbering (labels) for items.
|
||||
Enabled by default (True).
|
||||
:type numbering: bool
|
||||
"""
|
||||
super().__init__()
|
||||
self._key_pattern = None
|
||||
self._items = []
|
||||
if items:
|
||||
for i in items:
|
||||
self._items.append(ContainerItem(i))
|
||||
|
||||
if numbering:
|
||||
self._key_pattern = KeyPattern()
|
||||
else:
|
||||
self._key_pattern = None
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""Return items count."""
|
||||
return len(self._items)
|
||||
|
||||
@property
|
||||
def key_pattern(self):
|
||||
"""Return key pattern which will be used for items numbering.
|
||||
|
||||
Will return `None` if not set.
|
||||
"""
|
||||
return self._key_pattern
|
||||
|
||||
@key_pattern.setter
|
||||
def key_pattern(self, key_pattern):
|
||||
"""Set the key pattern object which will be used for items numbering.
|
||||
|
||||
Setting `None` will stop doing numbering.
|
||||
"""
|
||||
self._key_pattern = key_pattern
|
||||
|
||||
def add(self, item, callback=None, data=None):
|
||||
"""Add item to the Container.
|
||||
|
||||
:param item: Add item to this container.
|
||||
:type item: Could be item (based on `simpleline.render.widgets.Widget`)
|
||||
or other container (based on `simpleline.render.containers.Container`).
|
||||
|
||||
:param callback: Add callback for this item. This callback will be called when user
|
||||
activate this `item`.
|
||||
:type callback: function ``func(data)``.
|
||||
|
||||
:param data: Data which will be passed to the callback.
|
||||
:param data: Anything.
|
||||
|
||||
:returns: ID of the item in this Container.
|
||||
:rtype: int
|
||||
"""
|
||||
self._items.append(ContainerItem(item, callback, data))
|
||||
return len(self._items) - 1
|
||||
|
||||
def process_user_input(self, key):
|
||||
"""Process input from the user if any of the items in the list was called.
|
||||
|
||||
This method must be called in `UIScreen.input()` method if list widget should call
|
||||
the callbacks.
|
||||
|
||||
:param key: Key pressed from user.
|
||||
:type key: str
|
||||
|
||||
:returns: True if key was processed. False otherwise.
|
||||
"""
|
||||
if not self._key_pattern:
|
||||
return False
|
||||
|
||||
if not isinstance(key, str):
|
||||
return False
|
||||
|
||||
res = self._key_pattern.translate_input_to_widget_id(key)
|
||||
if res is not None and res >= 0:
|
||||
try:
|
||||
item = self._items[res]
|
||||
if item.callback is not None:
|
||||
item.callback(item.data)
|
||||
return True
|
||||
except IndexError: # container widget with this id doesn't exists
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def create_number_label(self, item_id):
|
||||
"""Create TextWidget from KeyPattern.
|
||||
|
||||
:param item_id: Create label for item with this id.
|
||||
:type item_id: int
|
||||
|
||||
:returns: Widget with label for the item with item_id.
|
||||
:rtype: `simpleline.render.widgets.TextWidget` instance.
|
||||
"""
|
||||
number_widget = TextWidget(self._key_pattern.get_widget_label(item_id))
|
||||
return number_widget
|
||||
|
||||
|
||||
class WindowContainer(Container):
|
||||
"""Base container for screens.
|
||||
|
||||
This can hold other containers or Widgets for rendering.
|
||||
"""
|
||||
|
||||
def __init__(self, title=None):
|
||||
"""Construct base container for screens.
|
||||
|
||||
This container doesn't have numbering support. Input other containers in it to
|
||||
allow numbering and input processing.
|
||||
|
||||
:param title: Title line with separator after this title.
|
||||
:type title: str
|
||||
"""
|
||||
super().__init__(numbering=False)
|
||||
self._title = title
|
||||
|
||||
def add_with_separator(self, item, callback=None, data=None, blank_lines=1):
|
||||
"""Add widget and after widget add blank line.
|
||||
|
||||
This method will call
|
||||
`self.add(item, callback, data)`
|
||||
`self.add_separator(lines)`
|
||||
|
||||
:param item: Add item to this container.
|
||||
:type item: Could be item (based on `simpleline.render.widgets.Widget`)
|
||||
or other container (based on `simpleline.render.containers.Container`).
|
||||
|
||||
:param callback: Add callback for this item. This callback will be called when user
|
||||
activate this `item`.
|
||||
:type callback: function ``func(data)``.
|
||||
|
||||
:param data: Data which will be passed to the callback.
|
||||
:param data: Anything.
|
||||
|
||||
:param blank_lines: How many blank lines should be printed.
|
||||
:type blank_lines: int greater than 0.
|
||||
|
||||
:returns: ID of the item in this Container.
|
||||
:rtype: int
|
||||
"""
|
||||
item_id = self.add(item, callback, data)
|
||||
self.add_separator(blank_lines)
|
||||
|
||||
return item_id
|
||||
|
||||
def add_separator(self, lines=1):
|
||||
"""Add blank lines between widgets.
|
||||
|
||||
:param lines: How many blank lines should be printed.
|
||||
:type lines: int greater than 0.
|
||||
"""
|
||||
self.add(SeparatorWidget(lines))
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""Title of WindowContainer."""
|
||||
return self._title
|
||||
|
||||
def render(self, width):
|
||||
"""Render widgets to it's internal buffer.
|
||||
|
||||
:param width: the maximum width the item can use
|
||||
:type width: int
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
super().render(width)
|
||||
|
||||
# set cursor position to top-left corner
|
||||
self.set_cursor_position(0, 0)
|
||||
|
||||
if self._title:
|
||||
self._draw_title_and_separator(width)
|
||||
|
||||
for item in self._items:
|
||||
widget = item.widget
|
||||
widget.render(width)
|
||||
self.draw(widget)
|
||||
|
||||
def _draw_title_and_separator(self, width):
|
||||
title_widget = TextWidget(self._title)
|
||||
sep = SeparatorWidget()
|
||||
|
||||
title_widget.render(width)
|
||||
sep.render(width)
|
||||
|
||||
self.draw(title_widget)
|
||||
self.draw(sep)
|
||||
|
||||
|
||||
class ListRowContainer(Container):
|
||||
"""Place widgets in rows automatically.
|
||||
|
||||
Compared to the ColumnWidget this is able to handle word wrapping correctly.
|
||||
|
||||
There is numbering N) automatically for all items. To disable this feature call
|
||||
`self.key_pattern = None`. If you want other numbering then look on `KeyPattern` class.
|
||||
|
||||
Widgets will be placed based on the number of columns in the following way:
|
||||
|
||||
1) w1 2) w2 3) w3
|
||||
4) w4 5) w5 6) w6
|
||||
....
|
||||
"""
|
||||
|
||||
def __init__(self, columns, items=None, columns_width=None, spacing=3, numbering=True):
|
||||
"""Create ListWidget with specific number of columns.
|
||||
|
||||
:param columns: How many columns we want.
|
||||
:type columns: int, bigger than 0
|
||||
|
||||
:param items: List of items for positioning in this Container. Callback can't be
|
||||
specified this way.
|
||||
:type items: List of items for rendering.
|
||||
|
||||
:param columns_width: Width of every column. If nothing specified the maximum width
|
||||
will be distributed to columns.
|
||||
:type columns_width: int or None
|
||||
|
||||
:param spacing: Set the spacing between columns.
|
||||
:type spacing: int
|
||||
|
||||
:param numbering: Enable/disable automatic numbering (labels) for items.
|
||||
Enabled by default (True).
|
||||
:type numbering: bool
|
||||
"""
|
||||
super().__init__(items, numbering)
|
||||
self._columns = columns
|
||||
self._columns_width = columns_width
|
||||
self._spacing = spacing
|
||||
self._numbering_widgets = []
|
||||
|
||||
def render(self, width):
|
||||
"""Render widgets to it's internal buffer.
|
||||
|
||||
:param width: the maximum width the item can use
|
||||
:type width: int
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
super().render(width)
|
||||
|
||||
if self._columns_width is None:
|
||||
spaces_between_columns = self._columns - 1
|
||||
sum_spacing = spaces_between_columns * self._spacing
|
||||
self._columns_width = int((width - sum_spacing) / self._columns)
|
||||
|
||||
ordered_map = self._get_ordered_map()
|
||||
lines_per_rows = self._lines_per_every_row(ordered_map)
|
||||
|
||||
# the leftmost empty column
|
||||
col_pos = 0
|
||||
|
||||
for col in ordered_map:
|
||||
row_pos = 0
|
||||
|
||||
# render and draw contents of column
|
||||
for row_id, item_id in enumerate(col):
|
||||
container = self._items[item_id]
|
||||
widget = container.widget
|
||||
|
||||
# set cursor to first line and leftmost empty column
|
||||
self.set_cursor_position(row_pos, col_pos)
|
||||
|
||||
if self._key_pattern is not None:
|
||||
number_widget = self._numbering_widgets[item_id]
|
||||
widget_width = len(number_widget.text)
|
||||
self.draw(number_widget)
|
||||
self.set_cursor_position(row_pos, col_pos + widget_width)
|
||||
|
||||
self.draw(widget, block=True)
|
||||
row_pos = row_pos + lines_per_rows[row_id]
|
||||
|
||||
# recompute the leftmost empty column
|
||||
col_pos = max((col_pos + self._columns_width), self.width) + self._spacing
|
||||
|
||||
def _lines_per_every_row(self, items):
|
||||
self._render_all_items()
|
||||
# call `self._render_and_calculate_lines_per_rows()` method instead
|
||||
lines_per_row = []
|
||||
|
||||
# go through all items and find how many lines we need for each row
|
||||
# printed (because of wrapping)
|
||||
for column_items in items:
|
||||
for row_id, item_id in enumerate(column_items):
|
||||
item = self._items[item_id]
|
||||
if len(lines_per_row) <= row_id:
|
||||
lines_per_row.append(0)
|
||||
|
||||
lines_per_row[row_id] = max(lines_per_row[row_id], len(item.widget.get_lines()))
|
||||
|
||||
return lines_per_row
|
||||
|
||||
def _render_all_items(self):
|
||||
for item_id, item in enumerate(self._items):
|
||||
item_width = self._columns_width
|
||||
|
||||
if item_width <= 0:
|
||||
raise ValueError("Widget can't be rendered! Columns width is too small.")
|
||||
|
||||
if self._key_pattern:
|
||||
number_widget = self.create_number_label(item_id)
|
||||
# render numbers before widgets
|
||||
number_width = len(number_widget.text)
|
||||
number_widget.render(number_width)
|
||||
self._numbering_widgets.append(number_widget)
|
||||
# reduce the size of widget because of the number
|
||||
item_width -= number_width
|
||||
|
||||
if item_width <= 0:
|
||||
raise ValueError("Widget can't be rendered with numbering on! "
|
||||
"Increase column width or disable numbering.")
|
||||
|
||||
item.widget.render(item_width)
|
||||
|
||||
def _get_ordered_map(self):
|
||||
"""Return list of identifiers (index) to the original item list.
|
||||
|
||||
.. NOTE: Use of ``self._prepare_list()` is encouraged to create output list and
|
||||
just fill up this list.
|
||||
"""
|
||||
# create list of columns (lists)
|
||||
ordering_map = self._prepare_list()
|
||||
|
||||
for item_id in range(self.size):
|
||||
ordering_map[item_id % self._columns].append(item_id)
|
||||
|
||||
return ordering_map
|
||||
|
||||
def _prepare_list(self):
|
||||
"""Prepare list for items ordering to rows and columns.
|
||||
|
||||
List will be prepared as ([column 1], [column 2], ...)
|
||||
"""
|
||||
return list(map(lambda x: [], range(0, self._columns)))
|
||||
|
||||
|
||||
class ListColumnContainer(ListRowContainer):
|
||||
"""Place widgets in columns automatically.
|
||||
|
||||
Compared to the ColumnWidget this is able to handle word wrapping correctly.
|
||||
|
||||
There is numbering N) automatically for all items. To disable this feature call
|
||||
`self.key_pattern = None`. If you want other numbering then look on `KeyPattern` class.
|
||||
|
||||
Widgets will be placed based on the number of columns in the following way:
|
||||
|
||||
1) w1 4) w4 7) w7
|
||||
2) w2 5) w5 8) w8
|
||||
3) w3 6) w6 9) w9
|
||||
"""
|
||||
|
||||
def _get_ordered_map(self):
|
||||
ordering_map = self._prepare_list()
|
||||
items_in_column = ceil(len(self._items) / self._columns)
|
||||
|
||||
for item_id in range(self.size):
|
||||
col_position = int(item_id // items_in_column)
|
||||
ordering_map[col_position].append(item_id)
|
||||
|
||||
return ordering_map
|
||||
|
||||
|
||||
class KeyPattern():
|
||||
"""Pattern for automatic key printing before items."""
|
||||
|
||||
def __init__(self, pattern="{:d}) ", offset=1):
|
||||
"""Create the pattern class.
|
||||
|
||||
For enabling greater functionality than python 3 format is able to do, feel free to
|
||||
override this class and use your subclass instead.
|
||||
|
||||
:param pattern: Set pattern which will be called for every item.
|
||||
:type pattern: Strings format method.
|
||||
See https://docs.python.org/3.3/library/string.html#format-string-syntax.
|
||||
|
||||
:param offset: Set the offset for numbering items. Default is 1 to start indexing
|
||||
naturally for user.
|
||||
:type offset: int
|
||||
"""
|
||||
self._pattern = pattern
|
||||
self._offset = offset
|
||||
|
||||
def get_widget_label(self, item_id):
|
||||
"""Get widget identifier for user input description.
|
||||
|
||||
It should be something similar to the pattern.
|
||||
|
||||
:param item_id: Position of the widget in the list.
|
||||
:type item_id: int starts from 0.
|
||||
"""
|
||||
return self._pattern.format(item_id + self._offset)
|
||||
|
||||
def translate_input_to_widget_id(self, user_input):
|
||||
"""Get id of the widget from the user input.
|
||||
|
||||
This is reverse translation to `self.get_widget_identifier()`.
|
||||
|
||||
:param user_input: Input from user:
|
||||
:type user_input: str
|
||||
|
||||
:return: ID of the widget in the list or None if the input can't be translated.
|
||||
:rtype: int or None
|
||||
"""
|
||||
try:
|
||||
return int(user_input) - 1
|
||||
except ValueError:
|
||||
log.debug("No callback registered for user input %s", user_input)
|
||||
return None
|
||||
|
||||
|
||||
class ContainerItem():
|
||||
"""Item used inside of containers to store widgets callbacks and data.
|
||||
|
||||
Internal representation for Containers. Do not use this class directly.
|
||||
"""
|
||||
|
||||
def __init__(self, widget, callback=None, data=None):
|
||||
"""Construct WidgetContainer.
|
||||
|
||||
:param widget: Any item from `simpleline.render.widgets` or `Container`.
|
||||
:type widget: Class subclassing the `simpleline.render.widgets.Widget` class
|
||||
or `simpleline.render.containers.Container`.
|
||||
|
||||
:param callback: This callback will be called as reaction on user input.
|
||||
:type callback: Function with one data parameter: `def func(data):`.
|
||||
|
||||
:param data: Params which will be passed to callback.
|
||||
:type data: Anything.
|
||||
"""
|
||||
self.widget = widget
|
||||
self.callback = callback
|
||||
self.data = data
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
# Class for the Anaconda TUI prompt.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Vendula Poncova <vponcova@redhat.com>
|
||||
#
|
||||
import logging
|
||||
|
||||
from simpleline.utils.i18n import N_, _
|
||||
|
||||
log = logging.getLogger("simpleline")
|
||||
|
||||
|
||||
class Prompt():
|
||||
"""Class to create a prompt message with options."""
|
||||
# Default message of the prompt
|
||||
DEFAULT_MESSAGE = N_("Please make a selection from the above")
|
||||
|
||||
# String to use in a prompt when we want users to press the key ENTER.
|
||||
ENTER = N_("ENTER")
|
||||
|
||||
# TRANSLATORS: 'q' to quit
|
||||
QUIT_DESCRIPTION = N_("to quit")
|
||||
QUIT = 'q'
|
||||
|
||||
# TRANSLATORS:'c' to continue
|
||||
CONTINUE_DESCRIPTION = N_("to continue")
|
||||
CONTINUE = 'c'
|
||||
|
||||
# TRANSLATORS:'r' to refresh
|
||||
REFRESH_DESCRIPTION = N_("to refresh")
|
||||
REFRESH = 'r'
|
||||
|
||||
# TRANSLATORS:'h' to help
|
||||
HELP_DESCRIPTION = N_("to help")
|
||||
HELP = 'h'
|
||||
|
||||
def __init__(self, message=DEFAULT_MESSAGE):
|
||||
"""
|
||||
:param message: the message of the prompt
|
||||
:type message: str|None
|
||||
"""
|
||||
self.message = message
|
||||
self.options = dict()
|
||||
|
||||
def set_message(self, message):
|
||||
"""Set the prompt message.
|
||||
|
||||
:param message: the message of the prompt
|
||||
:type message: str|None
|
||||
"""
|
||||
self.message = message
|
||||
|
||||
def add_option(self, key, description):
|
||||
"""Add an option to the prompt.
|
||||
Causes a warning if the option already exists.
|
||||
|
||||
:param key: the key for choosing the option
|
||||
:type key: str
|
||||
|
||||
:param description: the description of the option
|
||||
:type description: str
|
||||
"""
|
||||
if key in self.options:
|
||||
log.warning("The option '%s' does already exist in '%s'.", key, self)
|
||||
|
||||
self.options[key] = description
|
||||
|
||||
def update_option(self, key, description):
|
||||
"""Update an option in the prompt.
|
||||
Causes a warning if the option does not exist.
|
||||
|
||||
:param key: the key for choosing the option
|
||||
:type key: str
|
||||
|
||||
:param description: the description of the option
|
||||
:type description: str
|
||||
"""
|
||||
if key not in self.options:
|
||||
log.warning("The option '%s' does not exist in '%s'.", key, self)
|
||||
|
||||
self.options[key] = description
|
||||
|
||||
def add_refresh_option(self, description=REFRESH_DESCRIPTION):
|
||||
"""Add the option to refresh."""
|
||||
if Prompt.REFRESH in self.options:
|
||||
self.update_option(Prompt.REFRESH, description)
|
||||
else:
|
||||
self.add_option(Prompt.REFRESH, description)
|
||||
|
||||
def add_continue_option(self, description=CONTINUE_DESCRIPTION):
|
||||
"""Add the option to continue."""
|
||||
if Prompt.CONTINUE in self.options:
|
||||
self.update_option(Prompt.CONTINUE, description)
|
||||
else:
|
||||
self.add_option(Prompt.CONTINUE, description)
|
||||
|
||||
def add_quit_option(self, description=QUIT_DESCRIPTION):
|
||||
"""Add the option to quit."""
|
||||
if Prompt.QUIT in self.options:
|
||||
self.update_option(Prompt.QUIT, description)
|
||||
else:
|
||||
self.add_option(Prompt.QUIT, description)
|
||||
|
||||
def add_help_option(self, description=HELP_DESCRIPTION):
|
||||
"""Add the option to help."""
|
||||
if Prompt.HELP in self.options:
|
||||
self.update_option(Prompt.HELP, description)
|
||||
else:
|
||||
self.add_option(Prompt.HELP, description)
|
||||
|
||||
def remove_option(self, key):
|
||||
"""Remove an option with the given key.
|
||||
|
||||
:param key: the key of the option
|
||||
:type key: str
|
||||
|
||||
:return: the removed option
|
||||
:rtype: str|None
|
||||
"""
|
||||
return self.options.pop(key, None)
|
||||
|
||||
def __str__(self):
|
||||
"""Return the string representation of the prompt."""
|
||||
if not self.message and not self.options:
|
||||
return ""
|
||||
|
||||
parts = []
|
||||
|
||||
if self.message:
|
||||
parts.append(_(self.message))
|
||||
|
||||
if self.options:
|
||||
opt_list = ["'%s' %s" % (key, _(self.options[key]))
|
||||
for key in sorted(self.options.keys())]
|
||||
opt_str = "[%s]" % ", ".join(opt_list)
|
||||
parts.append(opt_str)
|
||||
|
||||
return " ".join(parts) + ": "
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
# Base class for text window screens.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from simpleline import App
|
||||
from simpleline.render.containers import WindowContainer
|
||||
from simpleline.render.prompt import Prompt
|
||||
from simpleline.render.screen.signal_handler import SignalHandler
|
||||
from simpleline.render.screen.input_manager import InputManager
|
||||
from simpleline.utils.i18n import _
|
||||
|
||||
__all__ = ["UIScreen", "InputState"]
|
||||
|
||||
|
||||
class UIScreen(SignalHandler):
|
||||
"""Base class representing one TUI Screen.
|
||||
|
||||
Shares some API with anaconda's GUI to make it easy for devs to create similar UI
|
||||
with the familiar API.
|
||||
"""
|
||||
|
||||
def __init__(self, title=None, screen_height=30):
|
||||
""" Constructor of the TUI screen.
|
||||
|
||||
:param title: Title line of the screen.
|
||||
:type title: str
|
||||
|
||||
:param screen_height: height of the screen (useful for printing long widgets)
|
||||
:type screen_height: int (the value must be bigger than 4)
|
||||
"""
|
||||
self._title = title
|
||||
self._screen_height = screen_height
|
||||
self._screen_ready = False
|
||||
|
||||
# ask for password
|
||||
self._hide_user_input = False
|
||||
self._password_func = None
|
||||
|
||||
# do not print separator for this screen
|
||||
self._no_separator = False
|
||||
|
||||
# list that holds the content to be printed out
|
||||
self._window = WindowContainer(self.title)
|
||||
|
||||
# should the input be required after draw
|
||||
self._input_required = True
|
||||
|
||||
# index of the page (subset of screen) shown during show_all
|
||||
# indexing starts with 0
|
||||
self._page = 0
|
||||
|
||||
self._input_manager = InputManager(ui_screen=self)
|
||||
|
||||
def __str__(self):
|
||||
"""For easier logging."""
|
||||
return self.__class__.__name__
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""Screen title."""
|
||||
return self._title
|
||||
|
||||
@title.setter
|
||||
def title(self, title):
|
||||
"""Set screen title.
|
||||
|
||||
Set `None` to remove title.
|
||||
"""
|
||||
self._title = title
|
||||
|
||||
@property
|
||||
def password_func(self):
|
||||
"""Get password function.
|
||||
|
||||
This is function with one argument to get password from command line.
|
||||
"""
|
||||
return self._password_func
|
||||
|
||||
@password_func.setter
|
||||
def password_func(self, value):
|
||||
"""Set password function.
|
||||
|
||||
:param value: Function to get password from a command line.
|
||||
:type value: Function with one argument which is text representation of prompt.
|
||||
"""
|
||||
self._password_func = value
|
||||
|
||||
@property
|
||||
def screen_ready(self):
|
||||
"""This screen is ready for use."""
|
||||
return self._screen_ready
|
||||
|
||||
@screen_ready.setter
|
||||
def screen_ready(self, screen_ready):
|
||||
"""Set ready status for this screen."""
|
||||
self._screen_ready = screen_ready
|
||||
|
||||
@property
|
||||
def input_required(self):
|
||||
"""Return if the screen requires input."""
|
||||
return self._input_required
|
||||
|
||||
@input_required.setter
|
||||
def input_required(self, input_required):
|
||||
"""Set if the screen should require input."""
|
||||
self._input_required = input_required
|
||||
|
||||
@property
|
||||
def no_separator(self):
|
||||
"""Should we print separator for this screen?
|
||||
|
||||
:returns: True to print separator before this screen (default).
|
||||
False do not print separator.
|
||||
"""
|
||||
return self._no_separator
|
||||
|
||||
@no_separator.setter
|
||||
def no_separator(self, no_separator):
|
||||
"""Print or do not print separator.
|
||||
|
||||
:param no_separator: Specify if the separator should be printed.
|
||||
:type no_separator: bool (default: False).
|
||||
"""
|
||||
self._no_separator = no_separator
|
||||
|
||||
@property
|
||||
def hide_user_input(self):
|
||||
"""Hide typed user input.
|
||||
|
||||
This is main solution how to ask for password.
|
||||
|
||||
:returns: True if user input should be hidden.
|
||||
False otherwise (default).
|
||||
"""
|
||||
return self._hide_user_input
|
||||
|
||||
@hide_user_input.setter
|
||||
def hide_user_input(self, hide_input):
|
||||
"""Should be the user input hidden.
|
||||
|
||||
:param hide_input: True if user input should be hidden.
|
||||
False if not (default).
|
||||
:type hide_input: bool (default: False).
|
||||
"""
|
||||
self._hide_user_input = hide_input
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
"""Return WindowContainer instance."""
|
||||
return self._window
|
||||
|
||||
@window.setter
|
||||
def window(self, window):
|
||||
"""Set base WindowContainer instance.
|
||||
|
||||
:param window: Base window container containing other widgets and containers.
|
||||
:type window: Instance of `simpleline.render.containers.WindowContainer` class.
|
||||
"""
|
||||
self._window = window
|
||||
|
||||
def get_user_input(self, message, hidden=False):
|
||||
"""Get immediately input from the user.
|
||||
|
||||
Use this with cautious. Never call this in middle of rendering or when other
|
||||
input is already waiting. It is recommended to use `self.input_required` instead.
|
||||
|
||||
:param message: Message prompt for the user.
|
||||
:type message: str
|
||||
|
||||
:param hidden: Do not echo user input (password typing).
|
||||
:type hidden: bool
|
||||
"""
|
||||
return self._input_manager.get_input_blocking(message, hidden)
|
||||
|
||||
def setup(self, args):
|
||||
"""Do additional setup right before this screen is used.
|
||||
|
||||
It is mandatory to call this ancestor method in the child class to set ready status.
|
||||
|
||||
:param args: arguments for the setup
|
||||
:type args: array of values
|
||||
:return: whether this screen should be scheduled or not
|
||||
:rtype: bool
|
||||
"""
|
||||
self._screen_ready = True
|
||||
App.get_event_loop().register_signal_source(self)
|
||||
return True
|
||||
|
||||
def refresh(self, args=None):
|
||||
"""Method which prepares the content desired on the screen to `self.window`.
|
||||
|
||||
:param args: optional argument passed from switch_screen calls
|
||||
:type args: anything
|
||||
"""
|
||||
self.window = WindowContainer(self._title)
|
||||
|
||||
def _print_widget(self, widget):
|
||||
"""Prints a widget with user interaction (when needed).
|
||||
|
||||
Could be longer than the screen height.
|
||||
|
||||
:param widget: widget to print
|
||||
:type widget: Widget instance
|
||||
"""
|
||||
# TODO: Work even for lower screen_height than 4
|
||||
pos = 0
|
||||
lines = widget.get_lines()
|
||||
num_lines = len(lines)
|
||||
|
||||
if num_lines == 0:
|
||||
return
|
||||
|
||||
prompt_height = 2
|
||||
real_screen_height = self._screen_height - prompt_height
|
||||
|
||||
if num_lines < real_screen_height:
|
||||
# widget plus prompt are shorter than screen height, just print the widget
|
||||
print(u"\n".join(lines))
|
||||
return
|
||||
|
||||
# long widget, print it in steps and prompt user to continue
|
||||
last_line = num_lines - 1
|
||||
while pos <= last_line:
|
||||
if pos + real_screen_height > last_line:
|
||||
# enough space to print the rest of the widget plus regular
|
||||
# prompt (2 lines)
|
||||
for line in lines[pos:]:
|
||||
print(line)
|
||||
pos += self._screen_height - 1
|
||||
else:
|
||||
# print part with a prompt to continue
|
||||
for line in lines[pos:(pos + real_screen_height)]:
|
||||
print(line)
|
||||
custom_prompt = Prompt(_("\nPress %s to continue") % Prompt.ENTER)
|
||||
self._ask_user_input_blocking(custom_prompt)
|
||||
pos += real_screen_height
|
||||
|
||||
def _ask_user_input_blocking(self, prompt):
|
||||
return self._input_manager.get_input_blocking(prompt, False)
|
||||
|
||||
def show_all(self):
|
||||
"""Print WindowContainer in `self.window` with all its content."""
|
||||
self.window.render(App.get_configuration().width)
|
||||
self._print_widget(self.window)
|
||||
|
||||
def input(self, args, key):
|
||||
"""Method called to process input. If the input is not handled here, return it.
|
||||
|
||||
:param key: input string to process
|
||||
:type key: str
|
||||
:param args: optional argument passed from switch_screen calls
|
||||
:type args: anything
|
||||
:return: return `simpleline.render.InputState.PROCESSED` if key was handled,
|
||||
`simpleline.render.InputState.DISCARDED` if the screen should not process input
|
||||
on the scheduler and key if you want it to.
|
||||
:rtype: `simpleline.render.InputState` enum | str
|
||||
"""
|
||||
return key
|
||||
|
||||
def get_input_with_error_check(self, args):
|
||||
"""Get user input and redraw if user add too many invalid inputs.
|
||||
|
||||
This method should be used only by ScreenScheduler.
|
||||
|
||||
:param args: Arguments passed in when scheduling this screen.
|
||||
:type args: Anything.
|
||||
"""
|
||||
self._input_manager.get_input(args=args)
|
||||
|
||||
def prompt(self, args=None):
|
||||
"""Return the text to be shown as prompt or handle the prompt and return None.
|
||||
|
||||
:param args: optional argument passed from switch_screen calls
|
||||
:type args: anything
|
||||
:return: returns an instance of Prompt with text to be shown next to the prompt
|
||||
for input or None to skip further input processing
|
||||
:rtype: Prompt instance|None
|
||||
"""
|
||||
prompt = Prompt()
|
||||
prompt.add_refresh_option()
|
||||
prompt.add_continue_option()
|
||||
prompt.add_quit_option()
|
||||
return prompt
|
||||
|
||||
def closed(self):
|
||||
"""Callback when this screen is closed."""
|
||||
|
||||
|
||||
class InputState(Enum):
|
||||
PROCESSED = 1
|
||||
PROCESSED_AND_REDRAW = 2
|
||||
PROCESSED_AND_CLOSE = 3
|
||||
DISCARDED = 0
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+203
@@ -0,0 +1,203 @@
|
||||
# Class for managing input and output for application.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from simpleline import App
|
||||
from simpleline.event_loop import ExitMainLoop
|
||||
from simpleline.event_loop.signals import ExceptionSignal
|
||||
from simpleline.render.prompt import Prompt
|
||||
from simpleline.input import InputHandler, PasswordInputHandler
|
||||
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
|
||||
class InputManager():
|
||||
|
||||
def __init__(self, ui_screen):
|
||||
"""Processor for user input.
|
||||
|
||||
This class is mainly helper class for ScreenScheduler.
|
||||
|
||||
:param ui_screen: Screen associated with this input manager.
|
||||
:type ui_screen: The `simpleline.render.screen.UIScreen` based instance.
|
||||
"""
|
||||
super().__init__()
|
||||
self._ui_screen = ui_screen
|
||||
self._input_error_counter = 0
|
||||
self._input_error_threshold = 5
|
||||
self._input_args = None
|
||||
|
||||
@property
|
||||
def input_error_counter(self):
|
||||
"""Return how many times the user provided bad input."""
|
||||
return self._input_error_counter
|
||||
|
||||
@property
|
||||
def input_error_threshold_exceeded(self):
|
||||
"""Did the error counter pass the threshold?
|
||||
|
||||
The screen should be redraw.
|
||||
"""
|
||||
errors = self._input_error_counter % self._input_error_threshold
|
||||
return errors == 0
|
||||
|
||||
def get_input_blocking(self, message, hidden):
|
||||
"""Get blocking input from the user.
|
||||
|
||||
:param message: Message prompt for the user.
|
||||
:type message: str
|
||||
|
||||
:param hidden: Do not echo user input (password typing).
|
||||
:type hidden: bool
|
||||
"""
|
||||
if hidden:
|
||||
handler = PasswordInputHandler(source=self)
|
||||
if self._ui_screen.password_func:
|
||||
handler.set_pass_func(self._ui_screen.password_func)
|
||||
else:
|
||||
handler = InputHandler(source=self)
|
||||
|
||||
handler.get_input(message)
|
||||
handler.wait_on_input()
|
||||
return handler.value
|
||||
|
||||
def get_input(self, args=None):
|
||||
"""Get input from user.
|
||||
|
||||
:param args: Arguments passed in when UIScreen was scheduled.
|
||||
:type args: Anything.
|
||||
"""
|
||||
prompt = self._ui_screen.prompt(args)
|
||||
if not self._is_input_expected(prompt):
|
||||
return
|
||||
|
||||
self._input_args = args
|
||||
|
||||
if not self._ui_screen.hide_user_input:
|
||||
handler = InputHandler(source=self._ui_screen)
|
||||
else:
|
||||
handler = PasswordInputHandler(source=self._ui_screen)
|
||||
if self._ui_screen.password_func:
|
||||
handler.set_pass_func(self._ui_screen.password_func)
|
||||
|
||||
handler.set_callback(self.process_input)
|
||||
handler.get_input(prompt)
|
||||
|
||||
def _is_input_expected(self, prompt):
|
||||
"""Check if user handled input processing some other way.
|
||||
|
||||
Do nothing if user did handled user input.
|
||||
|
||||
:returns: True if prompt is set and we can use it to get user input.
|
||||
False if prompt is not available, which means that user handled input on their
|
||||
own.
|
||||
"""
|
||||
# None means prompt handled the input by itself -> continue
|
||||
if prompt is None:
|
||||
self._input_error_counter = 0
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def process_input(self, user_input):
|
||||
"""Process input from the screens.
|
||||
|
||||
:param user_input: User input string.
|
||||
:type user_input: String.
|
||||
|
||||
:raises: ExitMainLoop or any other kind of exception from screen processing.
|
||||
"""
|
||||
# process the input, if it wasn't processed (valid)
|
||||
# increment the error counter
|
||||
try:
|
||||
result = self._process_input(user_input)
|
||||
except ExitMainLoop: # pylint: disable=try-except-raise
|
||||
raise
|
||||
except Exception: # pylint: disable=broad-except
|
||||
App.get_event_loop().enqueue_signal(ExceptionSignal(self))
|
||||
return
|
||||
|
||||
if result.was_successful():
|
||||
self._input_error_counter = 0
|
||||
else:
|
||||
self._input_error_counter += 1
|
||||
|
||||
App.get_scheduler().process_input_result(result, self.input_error_threshold_exceeded)
|
||||
|
||||
def _process_input(self, key):
|
||||
"""Method called internally to process unhandled input key presses.
|
||||
|
||||
:param key: The string entered by user.
|
||||
:type key: String.
|
||||
|
||||
:return: Return state result object.
|
||||
:rtype: `simpleline.render.in_out_manager.UserInputResult` class.
|
||||
|
||||
:raises: Anything the Screen can raise in the input processing.
|
||||
"""
|
||||
from simpleline.render.screen import InputState # pylint: disable=import-outside-toplevel
|
||||
# delegate the handling to active screen first
|
||||
key = self._ui_screen.input(self._input_args, key)
|
||||
if key == InputState.PROCESSED:
|
||||
return UserInputAction.NOOP
|
||||
|
||||
if key == InputState.PROCESSED_AND_REDRAW:
|
||||
return UserInputAction.REDRAW
|
||||
|
||||
if key == InputState.PROCESSED_AND_CLOSE:
|
||||
return UserInputAction.CLOSE
|
||||
|
||||
if key == InputState.DISCARDED:
|
||||
return UserInputAction.INPUT_ERROR
|
||||
|
||||
# global refresh command
|
||||
if key == Prompt.REFRESH:
|
||||
return UserInputAction.REDRAW
|
||||
|
||||
# global close command
|
||||
if key == Prompt.CONTINUE:
|
||||
return UserInputAction.CLOSE
|
||||
|
||||
# global quit command
|
||||
if key == Prompt.QUIT:
|
||||
return UserInputAction.QUIT
|
||||
|
||||
if key is None:
|
||||
log.warning("Returned key from screen is None. "
|
||||
"This could be missing return in a screen input method?")
|
||||
|
||||
return UserInputAction.INPUT_ERROR
|
||||
|
||||
|
||||
class UserInputAction(Enum):
|
||||
"""Store user input result."""
|
||||
INPUT_ERROR = -1
|
||||
NOOP = 0
|
||||
REDRAW = 5
|
||||
CLOSE = 6
|
||||
QUIT = 7
|
||||
|
||||
def was_successful(self):
|
||||
return self != UserInputAction.INPUT_ERROR
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Signal handler is giving ability connect and emit to all widgets.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from simpleline import App
|
||||
from simpleline.event_loop.signals import RenderScreenSignal, CloseScreenSignal
|
||||
|
||||
|
||||
class SignalHandler():
|
||||
"""Provides methods for handling signals anc callbacks.
|
||||
|
||||
Provides main methods:
|
||||
`connect()` -- connect this widget on given signal
|
||||
`create_signal()` -- create signal class which can be used in the emit method
|
||||
`emit()` -- emit signal given signal
|
||||
"""
|
||||
|
||||
def connect(self, signal, callback, data=None):
|
||||
"""Connect this class method with given signal.
|
||||
|
||||
:param signal: signal class which you want to connect
|
||||
:type signal: class based on `simpleline.event_loop.AbstractSignal`
|
||||
|
||||
:param callback: the callback function
|
||||
:type callback: func(event_message, data)
|
||||
|
||||
:param data: Data you want to pass to the callback
|
||||
:type data: Anything
|
||||
"""
|
||||
App.get_event_loop().register_signal_handler(signal, callback, data)
|
||||
|
||||
def create_signal(self, signal_class, priority=0):
|
||||
"""Create signal instance usable in the emit method.
|
||||
|
||||
:param signal_class: signal you want to use
|
||||
:type signal_class: class based on `simpleline.event_loop.AbstractSignal`
|
||||
|
||||
:param priority: priority of the signal; please look on the
|
||||
`simpleline.event_loop.AbstractSignal.priority` for further info
|
||||
:type priority: int
|
||||
"""
|
||||
return signal_class(self, priority)
|
||||
|
||||
def emit(self, signal):
|
||||
"""Emit the signal.
|
||||
|
||||
This will add `signal` to the event loop.
|
||||
|
||||
:param signal: signal to emit
|
||||
:type signal: instance of class based on `simpleline.event_loop.AbstractSignal`
|
||||
"""
|
||||
App.get_event_loop().enqueue_signal(signal)
|
||||
|
||||
def create_and_emit(self, signal):
|
||||
"""Create the signal and emit it.
|
||||
|
||||
This is basically shortcut for calling `self.create_signal` and `self.emit`.
|
||||
"""
|
||||
created_signal = self.create_signal(signal)
|
||||
self.emit(created_signal)
|
||||
|
||||
def redraw(self):
|
||||
"""Emit signal to initiate draw.
|
||||
|
||||
Add RenderScreenSignal to the event loop.
|
||||
"""
|
||||
signal = self.create_signal(RenderScreenSignal)
|
||||
App.get_event_loop().enqueue_signal(signal)
|
||||
|
||||
def close(self):
|
||||
"""Emit signal to close this screen.
|
||||
|
||||
Add CloseScreenSignal to the event loop.
|
||||
"""
|
||||
signal = self.create_signal(CloseScreenSignal)
|
||||
App.get_event_loop().enqueue_signal(signal)
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Serves shortcuts for easy screen scheduling.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
from simpleline import App
|
||||
|
||||
|
||||
class ScreenHandler():
|
||||
|
||||
@classmethod
|
||||
def schedule_screen(cls, ui_screen, args=None):
|
||||
"""Schedule screen to the active scheduler.
|
||||
|
||||
See: `simpleline.render.screen_scheduler.schedule_screen()`.
|
||||
"""
|
||||
App.get_scheduler().schedule_screen(ui_screen=ui_screen, args=args)
|
||||
|
||||
@classmethod
|
||||
def replace_screen(cls, ui_screen, args=None):
|
||||
"""Schedule screen to the active scheduler.
|
||||
|
||||
See: `simpleline.render.screen_scheduler.replace_screen()`.
|
||||
"""
|
||||
App.get_scheduler().replace_screen(ui_screen=ui_screen, args=args)
|
||||
|
||||
@classmethod
|
||||
def push_screen(cls, ui_screen, args=None):
|
||||
"""Schedule screen to the active scheduler.
|
||||
|
||||
See: `simpleline.render.screen_scheduler.push_screen()`.
|
||||
"""
|
||||
App.get_scheduler().push_screen(ui_screen=ui_screen, args=args)
|
||||
|
||||
@classmethod
|
||||
def push_screen_modal(cls, ui_screen, args=None):
|
||||
"""Schedule screen to the active scheduler.
|
||||
|
||||
See: `simpleline.render.screen_scheduler.push_screen_modal()`.
|
||||
"""
|
||||
App.get_scheduler().push_screen_modal(ui_screen=ui_screen, args=args)
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
# Class handling rendering of the screens to console.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
from simpleline import App
|
||||
from simpleline.event_loop import ExitMainLoop
|
||||
from simpleline.event_loop.signals import ExceptionSignal, RenderScreenSignal, CloseScreenSignal
|
||||
from simpleline.render import RenderUnexpectedError
|
||||
from simpleline.render.screen.input_manager import UserInputAction
|
||||
from simpleline.render.screen_stack import ScreenStack, ScreenData, ScreenStackEmptyException
|
||||
|
||||
from simpleline.logging import get_simpleline_logger
|
||||
|
||||
log = get_simpleline_logger()
|
||||
|
||||
RAW_INPUT_LOCK = threading.Lock()
|
||||
|
||||
|
||||
__all__ = ["ScreenScheduler"]
|
||||
|
||||
|
||||
class ScreenScheduler():
|
||||
|
||||
def __init__(self, event_loop, scheduler_stack=None):
|
||||
"""Constructor where you can pass your own scheduler stack.
|
||||
|
||||
The ScreenStack will be used automatically if scheduler stack will be None.
|
||||
|
||||
:param event_loop: Event loop used for the scheduler.
|
||||
:type event_loop: Class based on `simpleline.event_loop.AbstractEventLoop`.
|
||||
:param scheduler_stack: Use custom scheduler stack if you need to.
|
||||
:type scheduler_stack: `simpleline.screen_stack.ScreenStack` based class.
|
||||
"""
|
||||
self._quit_screen = None
|
||||
self._event_loop = event_loop
|
||||
|
||||
if scheduler_stack:
|
||||
self._screen_stack = scheduler_stack
|
||||
else:
|
||||
self._screen_stack = ScreenStack()
|
||||
self._register_handlers()
|
||||
|
||||
self._first_screen_scheduled = False
|
||||
|
||||
@staticmethod
|
||||
def _spacer():
|
||||
return "\n".join(2 * [App.get_configuration().width * "="])
|
||||
|
||||
def _register_handlers(self):
|
||||
self._event_loop.register_signal_handler(RenderScreenSignal, self._process_screen_callback)
|
||||
self._event_loop.register_signal_handler(CloseScreenSignal, self._close_screen_callback)
|
||||
|
||||
@property
|
||||
def quit_screen(self):
|
||||
"""Return quit UIScreen."""
|
||||
return self._quit_screen
|
||||
|
||||
@quit_screen.setter
|
||||
def quit_screen(self, quit_screen):
|
||||
"""Set the UIScreen based instance which will be showed before the Application will quit.
|
||||
|
||||
You can also use `simpleline.render.adv_widgets.YesNoDialog` or `UIScreen` based class
|
||||
with the `answer` property. Without the `answer` property the application will always
|
||||
close.
|
||||
"""
|
||||
self._quit_screen = quit_screen
|
||||
|
||||
@property
|
||||
def nothing_to_render(self):
|
||||
"""Is something for rendering in the scheduler stack?
|
||||
|
||||
:return: True if the rendering stack is empty
|
||||
:rtype: bool
|
||||
"""
|
||||
return self._screen_stack.empty()
|
||||
|
||||
def dump_stack(self):
|
||||
"""Get string representation of actual screen stack."""
|
||||
return self._screen_stack.dump_stack()
|
||||
|
||||
def schedule_screen(self, ui_screen, args=None):
|
||||
"""Add screen to the bottom of the stack.
|
||||
|
||||
This is mostly useful at the beginning to prepare the first screen hierarchy to display.
|
||||
|
||||
:param ui_screen: screen to show
|
||||
:type ui_screen: UIScreen instance
|
||||
:param args: optional argument, please see switch_screen for details
|
||||
:type args: anything
|
||||
"""
|
||||
log.debug("Scheduling screen %s", ui_screen)
|
||||
screen = ScreenData(ui_screen, args)
|
||||
self._screen_stack.add_first(screen)
|
||||
self._redraw_on_first_scheduled_screen()
|
||||
|
||||
def _redraw_on_first_scheduled_screen(self):
|
||||
if not self._first_screen_scheduled:
|
||||
self.redraw()
|
||||
self._first_screen_scheduled = True
|
||||
|
||||
def replace_screen(self, ui_screen, args=None):
|
||||
"""Schedules a screen to replace the current one.
|
||||
|
||||
:param ui_screen: screen to show
|
||||
:type ui_screen: instance of UIScreen
|
||||
:param args: optional argument to pass to ui's refresh and setup methods
|
||||
(can be used to select what item should be displayed or so)
|
||||
:type args: anything
|
||||
"""
|
||||
log.debug("Replacing screen %s", ui_screen)
|
||||
try:
|
||||
execute_new_loop = self._screen_stack.pop().execute_new_loop
|
||||
except ScreenStackEmptyException as e:
|
||||
raise ScreenStackEmptyException("Switch screen is not possible when there is no "
|
||||
"screen scheduled!") from e
|
||||
|
||||
# we have to keep the old_loop value so we stop
|
||||
# dialog's mainloop if it ever uses switch_screen
|
||||
screen = ScreenData(ui_screen, args, execute_new_loop)
|
||||
self._screen_stack.append(screen)
|
||||
self.redraw()
|
||||
|
||||
def push_screen(self, ui_screen, args=None):
|
||||
"""Schedules a screen to show, but keeps the current one in stack to
|
||||
return to, when the new one is closed.
|
||||
|
||||
:param ui_screen: screen to show
|
||||
:type ui_screen: UIScreen instance
|
||||
:param args: optional argument
|
||||
:type args: anything
|
||||
"""
|
||||
log.debug("Pushing screen %s to stack", ui_screen)
|
||||
screen = ScreenData(ui_screen, args, False)
|
||||
self._screen_stack.append(screen)
|
||||
self.redraw()
|
||||
|
||||
def push_screen_modal(self, ui_screen, args=None):
|
||||
"""Starts a new screen right away, so the caller can collect data back.
|
||||
|
||||
When the new screen is closed, the caller is redisplayed.
|
||||
|
||||
This method does not return until the new screen is closed.
|
||||
|
||||
:param ui_screen: screen to show
|
||||
:type ui_screen: UIScreen instance
|
||||
:param args: optional argument, please see switch_screen for details
|
||||
:type args: anything
|
||||
"""
|
||||
log.debug("Pushing modal screen %s to stack", ui_screen)
|
||||
screen = ScreenData(ui_screen, args, True)
|
||||
self._screen_stack.append(screen)
|
||||
# only new events will be processed now
|
||||
# the old one will wait after this event loop will be closed
|
||||
self._event_loop.execute_new_loop(RenderScreenSignal(self))
|
||||
|
||||
def _close_screen_callback(self, signal, data):
|
||||
self.close_screen(signal.source)
|
||||
|
||||
def close_screen(self, closed_from=None):
|
||||
"""Close the currently displayed screen and exit it's main loop if necessary.
|
||||
|
||||
Next screen from the stack is then displayed.
|
||||
"""
|
||||
screen = self._screen_stack.pop()
|
||||
log.debug("Closing screen %s from %s", screen, closed_from)
|
||||
|
||||
# User can react when screen is closing
|
||||
screen.ui_screen.closed()
|
||||
|
||||
if closed_from is not None and closed_from is not screen.ui_screen:
|
||||
raise RenderUnexpectedError("You are trying to close screen %s from screen %s! "
|
||||
"This is most probably not intentional." %
|
||||
(closed_from, screen.ui_screen))
|
||||
|
||||
if screen.execute_new_loop:
|
||||
self._event_loop.close_loop()
|
||||
|
||||
# redraw screen if there is what to redraw
|
||||
# and if it is not modal screen (modal screen parent is blocked)
|
||||
if not self._screen_stack.empty() and not screen.execute_new_loop:
|
||||
self.redraw()
|
||||
|
||||
# we can't draw anything more. Kill the application.
|
||||
if self._screen_stack.empty():
|
||||
raise ExitMainLoop()
|
||||
|
||||
def redraw(self):
|
||||
"""Register rendering to the event loop for processing."""
|
||||
self._event_loop.enqueue_signal(RenderScreenSignal(self))
|
||||
|
||||
def _process_screen_callback(self, signal, data):
|
||||
self._process_screen()
|
||||
|
||||
def _process_screen(self):
|
||||
"""Process the current screen.
|
||||
|
||||
1) It will call setup if the screen is not already set.
|
||||
2a) If setup was success then draw the screen.
|
||||
2b) If setup wasn't successful then pop the screen and try to process next in the stack.
|
||||
Continue by (1).
|
||||
3)Ask for user input if requested.
|
||||
"""
|
||||
top_screen = self._get_last_screen()
|
||||
|
||||
log.debug("Processing screen %s", top_screen)
|
||||
|
||||
# this screen is used first time (call setup() method)
|
||||
if not top_screen.ui_screen.screen_ready:
|
||||
if not top_screen.ui_screen.setup(top_screen.args):
|
||||
# remove the screen and skip if setup went wrong
|
||||
self._screen_stack.pop()
|
||||
self.redraw()
|
||||
log.warning("Screen %s setup wasn't successful", top_screen)
|
||||
return
|
||||
|
||||
# get the widget tree from the screen and show it in the screen
|
||||
try:
|
||||
# refresh screen content
|
||||
top_screen.ui_screen.refresh(top_screen.args)
|
||||
|
||||
# Screen was closed in the refresh method
|
||||
if top_screen != self._get_last_screen():
|
||||
return
|
||||
|
||||
# draw screen to the console
|
||||
self._draw_screen(top_screen)
|
||||
|
||||
if top_screen.ui_screen.input_required:
|
||||
log.debug("Input is required by %s screen", top_screen)
|
||||
top_screen.ui_screen.get_input_with_error_check(top_screen.args)
|
||||
except ExitMainLoop: # pylint: disable=try-except-raise
|
||||
raise
|
||||
except Exception: # pylint: disable=broad-except
|
||||
self._event_loop.enqueue_signal(ExceptionSignal(self))
|
||||
return
|
||||
|
||||
def _draw_screen(self, active_screen):
|
||||
"""Draws the current `active_screen`.
|
||||
|
||||
:param active_screen: Screen which should be draw to the console.
|
||||
:type active_screen: Classed based on `simpleline.render.screen.UIScreen`.
|
||||
"""
|
||||
# get the widget tree from the screen and show it in the screen
|
||||
try:
|
||||
if not active_screen.ui_screen.no_separator:
|
||||
# separate the content on the screen from the stuff we are about to display now
|
||||
print(self._spacer())
|
||||
|
||||
# print UIScreen content
|
||||
active_screen.ui_screen.show_all()
|
||||
except ExitMainLoop: # pylint: disable=try-except-raise
|
||||
raise
|
||||
except Exception: # pylint: disable=broad-except
|
||||
self._event_loop.enqueue_signal(ExceptionSignal(self))
|
||||
|
||||
def _get_last_screen(self):
|
||||
if self._screen_stack.empty():
|
||||
raise ExitMainLoop()
|
||||
|
||||
return self._screen_stack.pop(False)
|
||||
|
||||
def process_input_result(self, input_result, should_redraw):
|
||||
active_screen = self._get_last_screen()
|
||||
|
||||
if not input_result.was_successful():
|
||||
if should_redraw:
|
||||
self.redraw()
|
||||
else:
|
||||
log.debug("Input was not successful, ask for new input.")
|
||||
active_screen.ui_screen.get_input_with_error_check(active_screen.args)
|
||||
else:
|
||||
if input_result == UserInputAction.NOOP:
|
||||
return
|
||||
|
||||
if input_result == UserInputAction.REDRAW:
|
||||
self.redraw()
|
||||
elif input_result == UserInputAction.CLOSE:
|
||||
self.close_screen()
|
||||
elif input_result == UserInputAction.QUIT:
|
||||
if self.quit_screen:
|
||||
self.push_screen_modal(self.quit_screen)
|
||||
try:
|
||||
if self.quit_screen.answer is True:
|
||||
raise ExitMainLoop()
|
||||
|
||||
self.redraw()
|
||||
except AttributeError as e:
|
||||
raise ExitMainLoop() from e
|
||||
else:
|
||||
raise ExitMainLoop()
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
# Classes implementation for storing and manipulating Screen stack.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author(s): Jiri Konecny <jkonecny@redhat.com>
|
||||
#
|
||||
|
||||
|
||||
from simpleline.errors import SimplelineError
|
||||
|
||||
|
||||
class ScreenStackException(SimplelineError):
|
||||
"""General screen stack exception."""
|
||||
|
||||
|
||||
class ScreenStackEmptyException(ScreenStackException):
|
||||
"""Screen stack exception when stack is empty."""
|
||||
|
||||
|
||||
class ScreenStack():
|
||||
"""Managing screen stack used in `ScreenScheduler`."""
|
||||
|
||||
def __init__(self):
|
||||
self._screens = []
|
||||
|
||||
def empty(self):
|
||||
"""Test if screen stack is empty.
|
||||
|
||||
:return: True if empty.
|
||||
:rtype: bool
|
||||
"""
|
||||
return not self._screens
|
||||
|
||||
def size(self):
|
||||
"""Get size of the stack.
|
||||
|
||||
:return: Size of the stack.
|
||||
"""
|
||||
return len(self._screens)
|
||||
|
||||
def append(self, screen):
|
||||
"""Add new screen to the top of the stack.
|
||||
|
||||
:param screen: Screen for the future rendering.
|
||||
:type screen: Class based on `simpleline.render.ui_screen.UIScreen`.
|
||||
"""
|
||||
self._screens.append(screen)
|
||||
|
||||
def pop(self, remove=True):
|
||||
"""Return top item from the stack.
|
||||
|
||||
:param remove: If True (default) also remove this items from the stack.
|
||||
:return: The top screen on the stack.
|
||||
"""
|
||||
try:
|
||||
if remove:
|
||||
return self._screens.pop()
|
||||
|
||||
return self._screens[-1]
|
||||
except IndexError as e:
|
||||
raise ScreenStackEmptyException(e) from e
|
||||
|
||||
def add_first(self, screen):
|
||||
"""Add `screen` to the bottom of the stack.
|
||||
|
||||
:param screen: Add the `screen` to the bottom of the stack.
|
||||
:type screen: Class based on `simpleline.render.ui_screen.UIScreen`.
|
||||
"""
|
||||
self._screens.insert(0, screen)
|
||||
|
||||
def dump_stack(self):
|
||||
"""Dump screen stack structure.
|
||||
|
||||
:returns: Screen stack representation.
|
||||
:rtype: str
|
||||
"""
|
||||
msg = '======= Screen stack =======\n'
|
||||
msg += '----------- TOP ------------\n'
|
||||
|
||||
for screen in reversed(self._screens):
|
||||
msg += str(screen)
|
||||
msg += "\n"
|
||||
|
||||
msg += '============================\n'
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
class ScreenData():
|
||||
"""Inner data class to store screen data."""
|
||||
|
||||
def __init__(self, ui_screen, args=None, execute_new_loop=False):
|
||||
self.ui_screen = ui_screen
|
||||
self.args = args
|
||||
self.execute_new_loop = execute_new_loop
|
||||
|
||||
def __str__(self):
|
||||
msg = self.__class__.__name__
|
||||
msg += "("
|
||||
msg += ",".join((str(self.ui_screen), str(self.args), str(self.execute_new_loop)))
|
||||
msg += ")"
|
||||
return msg
|
||||
+495
@@ -0,0 +1,495 @@
|
||||
# Widgets for Text UI framework.
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
import functools
|
||||
from textwrap import wrap
|
||||
from simpleline.utils.i18n import _
|
||||
from simpleline.utils import ensure_str
|
||||
|
||||
__all__ = ["Widget", "TextWidget", "SeparatorWidget", "EntryWidget", "ColumnWidget",
|
||||
"CheckboxWidget", "CenterWidget"]
|
||||
|
||||
|
||||
class Widget():
|
||||
|
||||
def __init__(self, max_width=None, default=None):
|
||||
"""Initializes base Widgets buffer.
|
||||
|
||||
This class can be subclassed to create customized widgets.
|
||||
|
||||
:param max_width: serves as a hint about screen size to write method with default arguments
|
||||
:type max_width: int
|
||||
|
||||
:param default: string containing the default content to fill the buffer with
|
||||
:type default: string
|
||||
"""
|
||||
self._buffer = []
|
||||
if default:
|
||||
self._buffer = [[c for c in l] for l in default.split("\n")] # pylint: disable=unnecessary-comprehension
|
||||
self._max_width = max_width
|
||||
self._cursor = (0, 0) # row, col
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""The current height of the internal buffer."""
|
||||
return len(self._buffer)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""The current width of the internal buffer (id of the first empty column)."""
|
||||
return functools.reduce(lambda acc, l: max(acc, len(l)), self._buffer, 0)
|
||||
|
||||
def clear(self):
|
||||
"""Clears this widgets buffer and resets cursor."""
|
||||
self._buffer = list()
|
||||
self._cursor = (0, 0)
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""Return a list (rows) of lists (columns) with one character elements."""
|
||||
return self._buffer
|
||||
|
||||
def render(self, width):
|
||||
"""Redraw the widget's self._buffer.
|
||||
|
||||
:param width: the width of buffer requested by the caller
|
||||
:type width: int
|
||||
|
||||
Commonly, call render of child widgets and then draw and write
|
||||
methods to copy their contents to self._buffer.
|
||||
"""
|
||||
self.clear()
|
||||
|
||||
def get_lines(self):
|
||||
"""Return lines to write out in order to show this widget.
|
||||
|
||||
:return: lines representing this widget
|
||||
:rtype: list(str)
|
||||
"""
|
||||
return [str(u"".join(line)) for line in self._buffer]
|
||||
|
||||
def set_cursor_position(self, row, col):
|
||||
"""Set cursor position.
|
||||
|
||||
:param row: row id, starts with 0 at the top of the screen
|
||||
:type row: int
|
||||
|
||||
:param col: column id, starts with 0 on the left side of the screen
|
||||
:type col: int
|
||||
"""
|
||||
self._cursor = (row, col)
|
||||
|
||||
@property
|
||||
def cursor(self):
|
||||
return self._cursor
|
||||
|
||||
def set_end(self):
|
||||
"""Set the cursor to first column in new line at the end."""
|
||||
self._cursor = (self.height, 0)
|
||||
|
||||
def draw(self, w, row=None, col=None, block=False):
|
||||
"""Copy w widget's content to this widget's buffer at row, col position.
|
||||
|
||||
:param w: widget to take content from
|
||||
:type w: class Widget
|
||||
|
||||
:param row: row number to start at (default is at the cursor position)
|
||||
:type row: int
|
||||
|
||||
:param col: column number to start at (default is at the cursor position)
|
||||
:type col: int
|
||||
|
||||
:param block: when printing newline, start at column col (True) or at column 0 (False)
|
||||
:type block: boolean
|
||||
"""
|
||||
# if the starting row is not present, start at the cursor position
|
||||
if row is None:
|
||||
row = self._cursor[0]
|
||||
|
||||
# if the starting column is not present, start at the cursor position
|
||||
if col is None:
|
||||
col = self._cursor[1]
|
||||
|
||||
# fill up rows to accommodate for w.height
|
||||
if self.height < row + w.height:
|
||||
for _i in range(row + w.height - self.height):
|
||||
self._buffer.append(list())
|
||||
|
||||
# append columns to accommodate for w.width
|
||||
for l in range(row, row + w.height):
|
||||
l_len = len(self._buffer[l])
|
||||
w_len = len(w.content[l - row])
|
||||
if l_len < col + w_len:
|
||||
self._buffer[l] += ((col + w_len - l_len) * list(u" "))
|
||||
self._buffer[l][col:col + w_len] = w.content[l - row][:]
|
||||
|
||||
# move the cursor to new spot
|
||||
if block:
|
||||
self._cursor = (row + w.height, col)
|
||||
else:
|
||||
self._cursor = (row + w.height, 0)
|
||||
|
||||
def write(self, text, row=None, col=None, width=None, block=False, wordwrap=False):
|
||||
"""Emulate the typing machine writing to this widget's buffer.
|
||||
|
||||
:param text: text to type
|
||||
:type text: str
|
||||
|
||||
:param row: row number to start at (default is at the cursor position)
|
||||
:type row: int
|
||||
|
||||
:param col: column number to start at (default is at the cursor position)
|
||||
:type col: int
|
||||
|
||||
:param width: wrap at "col" + "width" column (default is at self._max_width)
|
||||
:type width: int
|
||||
|
||||
:param block: when printing newline, start at column col (True) or at column 0 (False)
|
||||
:type block: boolean
|
||||
|
||||
:param wordwrap: wrap by words
|
||||
:type wordwrap: boolean
|
||||
"""
|
||||
if not text:
|
||||
return
|
||||
|
||||
text = ensure_str(text)
|
||||
if row is None:
|
||||
row = self._cursor[0]
|
||||
|
||||
if col is None:
|
||||
col = self._cursor[1]
|
||||
|
||||
if width is None and self._max_width:
|
||||
width = self._max_width - col
|
||||
|
||||
x = row
|
||||
y = col
|
||||
|
||||
if wordwrap:
|
||||
text = self._wrap_words(text, width)
|
||||
|
||||
# emulate typing machine
|
||||
for character in text:
|
||||
# FIXME: Remove the code duplication below and optimize it
|
||||
# process newline
|
||||
if character == "\n":
|
||||
x += 1
|
||||
if block:
|
||||
y = col
|
||||
else:
|
||||
y = 0
|
||||
|
||||
self._increase_x_buffer_size(x)
|
||||
continue
|
||||
|
||||
self._increase_x_buffer_size(x)
|
||||
self._increase_y_buffer_size(x, y)
|
||||
|
||||
self._save_character_to_buffer(x, y, character)
|
||||
|
||||
# shift to the next char
|
||||
y += 1
|
||||
if width is not None and y >= col + width:
|
||||
x += 1
|
||||
if block:
|
||||
y = col
|
||||
else:
|
||||
y = 0
|
||||
|
||||
self._cursor = (x, y)
|
||||
|
||||
def _increase_x_buffer_size(self, x):
|
||||
if x >= len(self._buffer):
|
||||
for _i in range(x - len(self._buffer) + 1):
|
||||
self._buffer.append(list())
|
||||
|
||||
def _increase_y_buffer_size(self, x, y):
|
||||
if y >= len(self._buffer[x]):
|
||||
self._buffer[x] += ((y - len(self._buffer[x]) + 1) * list(u" "))
|
||||
|
||||
def _save_character_to_buffer(self, x, y, character):
|
||||
self._buffer[x][y] = character
|
||||
|
||||
@staticmethod
|
||||
def _wrap_words(text, width):
|
||||
lines = []
|
||||
# Wrap each line separately
|
||||
for line in text.split('\n'):
|
||||
sublines = []
|
||||
for subline in wrap(line, width):
|
||||
sublines.append(subline)
|
||||
if len(subline) < width:
|
||||
# line shorter than width will be wrapped by '\n' we add
|
||||
sublines.append('\n')
|
||||
# line with length == width will be wrapped by the width based
|
||||
# wrapping logic
|
||||
# end of line will be wrapped by '\n' following the line in
|
||||
# original text
|
||||
if sublines and sublines[-1] == '\n':
|
||||
sublines.pop()
|
||||
lines.append("".join(sublines))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class TextWidget(Widget):
|
||||
"""Class to handle wrapped text output."""
|
||||
|
||||
def __init__(self, text):
|
||||
"""
|
||||
:param text: text to format
|
||||
:type text: str
|
||||
"""
|
||||
super().__init__()
|
||||
self._text = text
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Contains text of this widget."""
|
||||
return self._text
|
||||
|
||||
def render(self, width):
|
||||
"""Renders the text widget limited to width number of columns.
|
||||
|
||||
Wraps to the next line when the text is longer.
|
||||
|
||||
:param width: maximum width allocated to the string
|
||||
:type width: int
|
||||
"""
|
||||
super().render(width)
|
||||
self.write(self._text, width=width, wordwrap=True)
|
||||
|
||||
|
||||
class EntryWidget(TextWidget):
|
||||
"""This is the easy way how to generate entry items for containers.
|
||||
|
||||
If the numbering in a container is turned on the output looks like:
|
||||
|
||||
N) title
|
||||
value
|
||||
|
||||
Without numbering turned on:
|
||||
|
||||
title
|
||||
value
|
||||
"""
|
||||
|
||||
def __init__(self, title, value=None):
|
||||
""" Create Entry widget instance.
|
||||
|
||||
:param title: Title of this entry.
|
||||
:type title: String.
|
||||
|
||||
:param value: Actual value printed in second line below the title.
|
||||
:type value: String.
|
||||
"""
|
||||
text = self._create_text(title=title, value=value)
|
||||
super().__init__(text)
|
||||
|
||||
@staticmethod
|
||||
def _create_text(title, value):
|
||||
msg = title
|
||||
|
||||
if value:
|
||||
msg += "\n"
|
||||
msg += value
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
class SeparatorWidget(Widget):
|
||||
"""Print empty line."""
|
||||
|
||||
def __init__(self, lines=1):
|
||||
"""Construct SeparatorWidget for printing blank lines.
|
||||
|
||||
:param lines: How many lines should be blank.
|
||||
:type lines: int greater than 0.
|
||||
"""
|
||||
super().__init__()
|
||||
self._lines = lines
|
||||
|
||||
def render(self, width):
|
||||
"""Render empty line to the buffer.
|
||||
|
||||
:param width: maximum width allocated to the string
|
||||
:type width: int
|
||||
"""
|
||||
super().render(width)
|
||||
self.write("")
|
||||
|
||||
def write(self, text, row=None, col=None, width=None, block=False, wordwrap=False):
|
||||
"""Optimize write function.
|
||||
|
||||
To print just a blank line we don't need too much logic.
|
||||
"""
|
||||
for i in range(0, self._lines):
|
||||
self._buffer.append(list())
|
||||
self._buffer[i] += u""
|
||||
self.set_cursor_position(self._lines - 1, 0)
|
||||
|
||||
|
||||
class CenterWidget(Widget):
|
||||
"""Class to handle horizontal centering of content."""
|
||||
|
||||
def __init__(self, w):
|
||||
"""
|
||||
:param w: widget to center
|
||||
:type w: Widget
|
||||
"""
|
||||
super().__init__()
|
||||
self._w = w
|
||||
|
||||
def render(self, width):
|
||||
"""Render the centered widget to internal buffer.
|
||||
|
||||
:param width: maximum width the widget should use
|
||||
:type width: int
|
||||
"""
|
||||
super().render(width)
|
||||
self._w.render(width)
|
||||
# make sure col is an integer
|
||||
self.draw(self._w, col=(width - self._w.width) // 2)
|
||||
|
||||
|
||||
class CheckboxWidget(Widget):
|
||||
"""Widget to show checkbox with (un)checked box, name and description."""
|
||||
|
||||
def __init__(self, key="x", title=None, text=None, completed=None):
|
||||
"""
|
||||
:param key: tick character to be used inside [ ]
|
||||
:type key: character
|
||||
|
||||
:param title: the title next to the [ ] box
|
||||
:type title: str
|
||||
|
||||
:param text: the description text to be shown on the second row in ()
|
||||
:type text: str
|
||||
|
||||
:param completed: is the checkbox ticked or not?
|
||||
:type completed: True|False
|
||||
"""
|
||||
super().__init__()
|
||||
self._key = key
|
||||
self._title = title
|
||||
self._text = text
|
||||
self._completed = completed
|
||||
|
||||
def render(self, width):
|
||||
"""Render the widget to internal buffer.
|
||||
|
||||
It should be max width characters wide.
|
||||
"""
|
||||
super().render(width)
|
||||
|
||||
if self.completed:
|
||||
checkchar = self._key
|
||||
else:
|
||||
checkchar = " "
|
||||
|
||||
# prepare the checkbox
|
||||
checkbox = TextWidget("[%s]" % checkchar)
|
||||
|
||||
data = []
|
||||
|
||||
# append lines
|
||||
if self.title:
|
||||
data.append(TextWidget(_(self.title)))
|
||||
|
||||
if self.text:
|
||||
data.append(TextWidget("(%s)" % self.text))
|
||||
|
||||
# the checkbox has two columns
|
||||
# [x] is one and is 3 chars wide
|
||||
# text is second and can occupy width - 3 - 1 (for space) chars
|
||||
cols = ColumnWidget([(3, [checkbox]), (width - 4, data)], 1)
|
||||
cols.render(width)
|
||||
|
||||
# transfer the column widget rendered stuff to internal buffer
|
||||
self.draw(cols)
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""Returns the first line (main title) of the checkbox."""
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def completed(self):
|
||||
"""Returns the state of the checkbox, checked is True."""
|
||||
return self._completed
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Contains the description text from the second line."""
|
||||
return self._text
|
||||
|
||||
|
||||
class ColumnWidget(Widget):
|
||||
|
||||
def __init__(self, columns, spacing=0):
|
||||
"""Create text columns
|
||||
|
||||
Deprecated. Please do not use this widget, use containers instead.
|
||||
|
||||
:param columns: list containing (column width, [list of widgets to put into this column])
|
||||
:type columns: [(int, [...]), ...]
|
||||
|
||||
:param spacing: number of spaces to use between columns
|
||||
:type spacing: int
|
||||
"""
|
||||
super().__init__()
|
||||
self._spacing = spacing
|
||||
self._columns = columns
|
||||
|
||||
def render(self, width):
|
||||
"""Render the widget to it's internal buffer
|
||||
|
||||
:param width: the maximum width the widget can use
|
||||
:type width: int
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
super().render(width)
|
||||
|
||||
# the leftmost empty column
|
||||
col_pos = 0
|
||||
|
||||
# iterate over tuples (column width, column content)
|
||||
for col_width, col in self._columns:
|
||||
|
||||
# set cursor to first line and leftmost empty column
|
||||
self.set_cursor_position(0, col_pos)
|
||||
|
||||
# if requested width is None, limit the maximum to width
|
||||
# and set minimum to 0
|
||||
if col_width is None:
|
||||
col_max_width = width - self.cursor[1]
|
||||
col_width = 0
|
||||
else:
|
||||
col_max_width = col_width
|
||||
|
||||
# render and draw contents of column
|
||||
for item in col:
|
||||
item.render(col_max_width)
|
||||
self.draw(item, block=True)
|
||||
|
||||
# recompute the leftmost empty column
|
||||
col_pos = max((col_pos + col_width), self.width) + self._spacing
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# iutil.py - generic install utility functions
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import sys
|
||||
import string # pylint: disable=deprecated-module
|
||||
import unicodedata
|
||||
|
||||
|
||||
def ensure_str(str_or_bytes, keep_none=True):
|
||||
"""
|
||||
Returns a str instance for given string or ``None`` if requested to keep it.
|
||||
|
||||
:param str_or_bytes: string to be kept or converted to str type
|
||||
:type str_or_bytes: str or bytes
|
||||
:param bool keep_none: whether to keep None as it is or raise ValueError if
|
||||
``None`` is passed
|
||||
:raises ValueError: if applied on an object not being of type bytes nor str
|
||||
(nor NoneType if ``keep_none`` is ``False``)
|
||||
"""
|
||||
if keep_none and str_or_bytes is None:
|
||||
return None
|
||||
if isinstance(str_or_bytes, str):
|
||||
return str_or_bytes
|
||||
if isinstance(str_or_bytes, bytes):
|
||||
return str_or_bytes.decode(sys.getdefaultencoding())
|
||||
|
||||
raise ValueError(
|
||||
"str_or_bytes must be of type 'str' or 'bytes', not '%s'" % type(str_or_bytes))
|
||||
|
||||
|
||||
# Define translations between ASCII uppercase and lowercase for
|
||||
# locale-independent string conversions. The tables are 256-byte string used
|
||||
# with str.translate. If str.translate is used with a unicode string,
|
||||
# even if the string contains only 7-bit characters, str.translate will
|
||||
# raise a UnicodeDecodeError.
|
||||
_ASCIIlower_table = str.maketrans(string.ascii_uppercase, string.ascii_lowercase)
|
||||
_ASCIIupper_table = str.maketrans(string.ascii_lowercase, string.ascii_uppercase)
|
||||
|
||||
|
||||
def _toASCII(s):
|
||||
"""Convert a unicode string to ASCII"""
|
||||
if isinstance(s, str):
|
||||
# Decompose the string using the NFK decomposition, which in addition
|
||||
# to the canonical decomposition replaces characters based on
|
||||
# compatibility equivalence (e.g., ROMAN NUMERAL ONE has its own code
|
||||
# point but it's really just a capital I), so that we can keep as much
|
||||
# of the ASCII part of the string as possible.
|
||||
s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode("ascii")
|
||||
elif not isinstance(s, bytes):
|
||||
s = ''
|
||||
return s
|
||||
|
||||
|
||||
def lowerASCII(s):
|
||||
"""Convert a string to lowercase using only ASCII character definitions.
|
||||
|
||||
The returned string will contain only ASCII characters. This function is
|
||||
locale-independent.
|
||||
"""
|
||||
# XXX: Python 3 has str.maketrans() and bytes.maketrans() so we should
|
||||
# ideally use one or the other depending on the type of 's'. But it turns
|
||||
# out we expect this function to always return string even if given bytes.
|
||||
s = ensure_str(s)
|
||||
return str.translate(_toASCII(s), _ASCIIlower_table)
|
||||
|
||||
|
||||
def upperASCII(s):
|
||||
"""Convert a string to uppercase using only ASCII character definitions.
|
||||
|
||||
The returned string will contain only ASCII characters. This function is
|
||||
locale-independent.
|
||||
"""
|
||||
# XXX: Python 3 has str.maketrans() and bytes.maketrans() so we should
|
||||
# ideally use one or the other depending on the type of 's'. But it turns
|
||||
# out we expect this function to always return string even if given bytes.
|
||||
s = ensure_str(s)
|
||||
return str.translate(_toASCII(s), _ASCIIupper_table)
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+61
@@ -0,0 +1,61 @@
|
||||
# Translation functions we use all over the place
|
||||
#
|
||||
# This file is part of Simpleline Text UI library.
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat, Inc.
|
||||
#
|
||||
# Simpleline is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Simpleline is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
__all__ = ["_", "N_", "P_", "C_", "CN_", "CP_"]
|
||||
|
||||
import gettext
|
||||
|
||||
N_ = lambda x: x
|
||||
_ = lambda x: gettext.translation("python-simpleline", fallback=True).gettext(x) if x != "" else ""
|
||||
P_ = lambda x, y, z: gettext.translation("python-simpleline", fallback=True).ngettext(x, y, z)
|
||||
|
||||
# This is equivalent to "pgettext" in GNU gettext. The pgettext functions
|
||||
# are not exported by Python, but all they really do is a stick a EOT
|
||||
# character between msgctxt and msgid and check that msgctxt isn't part
|
||||
# of the return value.
|
||||
|
||||
|
||||
def C_(msgctxt, msgid):
|
||||
ctxid = "%s\x04%s" % (msgctxt, msgid)
|
||||
translation = _(ctxid)
|
||||
|
||||
# If there is no translation for msgctxt<EOT>msgid, return only msgid
|
||||
if translation == ctxid:
|
||||
return msgid
|
||||
|
||||
return translation
|
||||
|
||||
# Mark as translatable with context
|
||||
CN_ = lambda c, x: x
|
||||
|
||||
# npgettext; i.e., gettext with plural form and context
|
||||
|
||||
|
||||
def CP_(msgctxt, msgid, msgid_plural, n):
|
||||
ctxid = "%s\x04%s" % (msgctxt, msgid)
|
||||
translation = P_(ctxid, msgid_plural, n)
|
||||
|
||||
# If the returned value is msgctxt<EOT>msgid, ngettext was trying to
|
||||
# fallback to msgid. We don't add msgctxt to msgid_plural, so any other
|
||||
# return value is correct.
|
||||
if translation == ctxid:
|
||||
return msgid
|
||||
|
||||
return translation
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
### GNU LESSER GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates the
|
||||
terms and conditions of version 3 of the GNU General Public License,
|
||||
supplemented by the additional permissions listed below.
|
||||
|
||||
#### 0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the
|
||||
GNU General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License, other
|
||||
than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
#### 1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
#### 2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
- a) under this License, provided that you make a good faith effort
|
||||
to ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
- b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
#### 3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from a
|
||||
header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
- a) Give prominent notice with each copy of the object code that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
- b) Accompany the object code with a copy of the GNU GPL and this
|
||||
license document.
|
||||
|
||||
#### 4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that, taken
|
||||
together, effectively do not restrict modification of the portions of
|
||||
the Library contained in the Combined Work and reverse engineering for
|
||||
debugging such modifications, if you also do each of the following:
|
||||
|
||||
- a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
- b) Accompany the Combined Work with a copy of the GNU GPL and this
|
||||
license document.
|
||||
- c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
- d) Do one of the following:
|
||||
- 0) Convey the Minimal Corresponding Source under the terms of
|
||||
this License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
- 1) Use a suitable shared library mechanism for linking with
|
||||
the Library. A suitable mechanism is one that (a) uses at run
|
||||
time a copy of the Library already present on the user's
|
||||
computer system, and (b) will operate properly with a modified
|
||||
version of the Library that is interface-compatible with the
|
||||
Linked Version.
|
||||
- e) Provide Installation Information, but only if you would
|
||||
otherwise be required to provide such information under section 6
|
||||
of the GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the Application
|
||||
with a modified version of the Linked Version. (If you use option
|
||||
4d0, the Installation Information must accompany the Minimal
|
||||
Corresponding Source and Corresponding Application Code. If you
|
||||
use option 4d1, you must provide the Installation Information in
|
||||
the manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.)
|
||||
|
||||
#### 5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the Library
|
||||
side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
- a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities, conveyed under the terms of this License.
|
||||
- b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
#### 6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
as you received it specifies that a certain numbered version of the
|
||||
GNU Lesser General Public License "or any later version" applies to
|
||||
it, you have the option of following the terms and conditions either
|
||||
of that published version or of any later version published by the
|
||||
Free Software Foundation. If the Library as you received it does not
|
||||
specify a version number of the GNU Lesser General Public License, you
|
||||
may choose any version of the GNU Lesser General Public License ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
Reference in New Issue
Block a user