first commit

This commit is contained in:
sose 2020-06-15 21:11:22 -07:00
commit e493dc7e5f
467 changed files with 61374 additions and 0 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "includes.chroot/root/tl-setup"]
path = includes.chroot/root/tl-setup
url = https://tildegit.org/tildelinux/tl-setup

170
binary Normal file
View File

@ -0,0 +1,170 @@
# config/binary - options for live-build(7), binary stage
# $LB_BINARY_FILESYSTEM: set image filesystem
# (Default: fat32)
LB_BINARY_FILESYSTEM="fat32"
# $LB_APT_INDICES: set apt/aptitude generic indices
# (Default: true)
LB_APT_INDICES="true"
# $LB_BOOTAPPEND_LIVE: set boot parameters
# (Default: empty)
LB_BOOTAPPEND_LIVE="boot=live components quiet splash"
# $LB_BOOTAPPEND_INSTALL: set boot parameters
# (Default: empty)
LB_BOOTAPPEND_INSTALL=""
# $LB_BOOTAPPEND_LIVE_FAILSAFE: set boot parameters
# (Default: empty)
LB_BOOTAPPEND_LIVE_FAILSAFE="boot=live components memtest noapic noapm nodma nomce nolapic nomodeset nosmp nosplash vga=normal"
# $LB_BOOTLOADERS: set bootloaders
# (Default: syslinux,grub-efi)
LB_BOOTLOADERS="syslinux,grub-efi"
# $LB_CHECKSUMS: set checksums
# (Default: md5)
LB_CHECKSUMS="md5"
# $LB_COMPRESSION: set compression
# (Default: none)
LB_COMPRESSION="none"
# $LB_ZSYNC: set zsync
# (Default: true)
LB_ZSYNC="true"
# ${LB_BUILD_WITH_CHROOT: control if we build binary images chrooted
# (Default: true)
# DO NEVER, *NEVER*, *N*E*V*E*R* SET THIS OPTION to false.
LB_BUILD_WITH_CHROOT="true"
# $LB_DEBIAN_INSTALLER: set debian-installer
# (Default: false)
LB_DEBIAN_INSTALLER="false"
# $LB_DEBIAN_INSTALLER_DISTRIBUTION: set debian-installer suite
# (Default: empty)
LB_DEBIAN_INSTALLER_DISTRIBUTION="stretch"
# $LB_DEBIAN_INSTALLER_PRESEEDFILE: set debian-installer preseed filename/url
# (Default: )
LB_DEBIAN_INSTALLER_PRESEEDFILE=""
# $LB_DEBIAN_INSTALLER_GUI: toggle use of GUI debian-installer
# (Default: true)
LB_DEBIAN_INSTALLER_GUI="true"
# $LB_GRUB_SPLASH: set custom grub splash
# (Default: empty)
LB_GRUB_SPLASH=""
# $LB_HDD_LABEL: set hdd label
# (Default: DEBIAN_LIVE)
LB_HDD_LABEL="DEBIAN_LIVE"
# $LB_HDD_SIZE: set hdd filesystem size
# (Default: auto)
LB_HDD_SIZE="auto"
# $LB_HDD_PARTITION_START: set start of partition for the hdd target for BIOSes that expect a specific boot partition start (e.g. "63s"). If empty, use optimal layout.
# (Default: )
LB_HDD_PARTITION_START=""
# $LB_ISO_APPLICATION: set iso author
# (Default: Debian Live)
LB_ISO_APPLICATION="Debian Live"
# $LB_ISO_PREPARER: set iso preparer
# (Default: live-build 1:20190311; https://debian-live.alioth.debian.org/live-build)
LB_ISO_PREPARER="live-build 1:20190311; https://debian-live.alioth.debian.org/live-build"
# $LB_ISO_PUBLISHER: set iso publisher
# (Default: Live Systems project; https://debian-live.alioth.debian.org/; debian-live@lists.debian.org)
LB_ISO_PUBLISHER="Live Systems project; https://debian-live.alioth.debian.org/; debian-live@lists.debian.org"
# $LB_ISO_VOLUME: set iso volume (max 32 chars)
# (Default: Debian stretch 20200511-21:45)
LB_ISO_VOLUME="Debian stretch 20200511-21:45"
# $LB_JFFS2_ERASEBLOCK: set jffs2 eraseblock size
# (Default: unset)
LB_JFFS2_ERASEBLOCK=""
# $LB_MEMTEST: set memtest
# (Default: none)
LB_MEMTEST="none"
# $LB_LOADLIN: set loadlin
# (Default: false)
LB_LOADLIN="false"
# $LB_WIN32_LOADER: set win32-loader
# (Default: false)
LB_WIN32_LOADER="false"
# $LB_NET_ROOT_FILESYSTEM: set netboot filesystem
# (Default: nfs)
LB_NET_ROOT_FILESYSTEM="nfs"
# $LB_NET_ROOT_MOUNTOPTIONS: set nfsopts
# (Default: empty)
LB_NET_ROOT_MOUNTOPTIONS=""
# $LB_NET_ROOT_PATH: set netboot server directory
# (Default: /srv/debian-live)
LB_NET_ROOT_PATH="/srv/debian-live"
# $LB_NET_ROOT_SERVER: set netboot server address
# (Default: 192.168.1.1)
LB_NET_ROOT_SERVER="192.168.1.1"
# $LB_NET_COW_FILESYSTEM: set net client cow filesystem
# (Default: nfs)
LB_NET_COW_FILESYSTEM="nfs"
# $LB_NET_COW_MOUNTOPTIONS: set cow mount options
# (Default: empty)
LB_NET_COW_MOUNTOPTIONS=""
# $LB_NET_COW_PATH: set cow directory
# (Default: )
LB_NET_COW_PATH=""
# $LB_NET_COW_SERVER: set cow server
# (Default: )
LB_NET_COW_SERVER=""
# $LB_NET_TARBALL: set net tarball
# (Default: true)
LB_NET_TARBALL="true"
# $LB_ONIE: set onie
# (Default: false)
LB_ONIE="false"
# $LB_ONIE_KERNEL_CMDLINE: set onie additional kernel cmdline options
# (Default: )
LB_ONIE_KERNEL_CMDLINE=""
# $LB_FIRMWARE_BINARY: include firmware packages in debian-installer
# (Default: true)
LB_FIRMWARE_BINARY="true"
# $LB_FIRMWARE_CHROOT: include firmware packages in debian-installer
# (Default: true)
LB_FIRMWARE_CHROOT="true"
# $LB_SWAP_FILE_PATH: set swap file path
# (Default: )
LB_SWAP_FILE_PATH=""
# $LB_SWAP_FILE_SIZE: set swap file size
# (Default: 512)
LB_SWAP_FILE_SIZE="512"
# $LB_UEFI_SECURE_BOOT: enable/disable UEFI secure boot
# (Default: auto)
LB_UEFI_SECURE_BOOT="auto"

73
bootstrap Normal file
View File

@ -0,0 +1,73 @@
# config/bootstrap - options for live-build(7), bootstrap stage
# $LB_DISTRIBUTION: select distribution to use
# (Default: buster)
LB_DISTRIBUTION="buster"
# $LB_PARENT_DISTRIBUTION: select parent distribution to use
# (Default: stretch)
LB_PARENT_DISTRIBUTION="stretch"
# $LB_PARENT_DEBIAN_INSTALLER_DISTRIBUTION: select parent distribution for debian-installer to use
# (Default: stretch)
LB_PARENT_DEBIAN_INSTALLER_DISTRIBUTION="stretch"
# $LB_PARENT_MIRROR_BOOTSTRAP: set parent mirror to bootstrap from
# (Default: http://deb.debian.org/debian/)
LB_PARENT_MIRROR_BOOTSTRAP="http://deb.debian.org/debian/"
# $LB_PARENT_MIRROR_CHROOT: set parent mirror to fetch packages from
# (Default: http://deb.debian.org/debian/)
LB_PARENT_MIRROR_CHROOT="http://deb.debian.org/debian/"
# $LB_PARENT_MIRROR_CHROOT_SECURITY: set security parent mirror to fetch packages from
# (Default: http://security.debian.org/)
LB_PARENT_MIRROR_CHROOT_SECURITY="http://security.debian.org/"
# $LB_PARENT_MIRROR_BINARY: set parent mirror which ends up in the image
# (Default: http://deb.debian.org/debian/)
LB_PARENT_MIRROR_BINARY="http://deb.debian.org/debian/"
# $LB_PARENT_MIRROR_BINARY_SECURITY: set security parent mirror which ends up in the image
# (Default: http://security.debian.org/)
LB_PARENT_MIRROR_BINARY_SECURITY="http://security.debian.org/"
# $LB_PARENT_MIRROR_DEBIAN_INSTALLER: set debian-installer parent mirror
# (Default: http://deb.debian.org/debian/)
LB_PARENT_MIRROR_DEBIAN_INSTALLER="http://deb.debian.org/debian/"
# $LB_MIRROR_BOOTSTRAP: set mirror to bootstrap from
# (Default: http://deb.debian.org/debian/)
LB_MIRROR_BOOTSTRAP="http://deb.debian.org/debian/"
# $LB_MIRROR_CHROOT: set mirror to fetch packages from
# (Default: http://deb.debian.org/debian/)
LB_MIRROR_CHROOT="http://deb.debian.org/debian/"
# $LB_MIRROR_CHROOT_SECURITY: set security mirror to fetch packages from
# (Default: http://security.debian.org/)
LB_MIRROR_CHROOT_SECURITY="http://security.debian.org/"
# $LB_MIRROR_BINARY: set mirror which ends up in the image
# (Default: http://deb.debian.org/debian/)
LB_MIRROR_BINARY="http://deb.debian.org/debian/"
# $LB_MIRROR_BINARY_SECURITY: set security mirror which ends up in the image
# (Default: http://security.debian.org/)
LB_MIRROR_BINARY_SECURITY="http://security.debian.org/"
# $LB_MIRROR_DEBIAN_INSTALLER: set debian-installer mirror
# (Default: http://deb.debian.org/debian/)
LB_MIRROR_DEBIAN_INSTALLER="http://deb.debian.org/debian/"
# $LB_BOOTSTRAP_QEMU_ARCHITECTURES: architectures to use foreign bootstrap
# (Default: )
LB_BOOTSTRAP_QEMU_ARCHITECTURES=""
# $LB_BOOTSTRAP_QEMU_EXCLUDE: packages to exclude during foreign bootstrap
# (Default: )
LB_BOOTSTRAP_QEMU_EXCLUDE=""
# $LB_BOOTSTRAP_QEMU_STATIC: static qemu binary for foreign bootstrap
# (Default: )
LB_BOOTSTRAP_QEMU_STATIC=""

10
build Normal file
View File

@ -0,0 +1,10 @@
[Image]
Architecture: amd64
Archive-Areas: main contrib non-free
Distribution: buster
Mirror-Bootstrap: http://deb.debian.org/debian/
[FIXME]
Configuration-Version: 1:20190311
Name: live-image
Type: iso-hybrid

37
chroot Normal file
View File

@ -0,0 +1,37 @@
# config/chroot - options for live-build(7), chroot stage
# $LB_CHROOT_FILESYSTEM: set chroot filesystem
# (Default: squashfs)
LB_CHROOT_FILESYSTEM="squashfs"
# $LB_UNION_FILESYSTEM: set union filesystem
# (Default: overlay)
LB_UNION_FILESYSTEM="overlay"
# $LB_INTERACTIVE: set interactive build
# (Default: false)
LB_INTERACTIVE="false"
# $LB_KEYRING_PACKAGES: set keyring packages
# (Default: empty)
LB_KEYRING_PACKAGES="debian-archive-keyring"
# $LB_LINUX_FLAVOURS: set kernel flavour to use
# (Default: autodetected)
LB_LINUX_FLAVOURS="amd64"
# $LB_LINUX_PACKAGES: set kernel packages to use
# (Default: autodetected)
LB_LINUX_PACKAGES="linux-image"
# $LB_SECURITY: enable security updates
# (Default: true)
LB_SECURITY="true"
# $LB_UPDATES: enable updates updates
# (Default: true)
LB_UPDATES="true"
# $LB_BACKPORTS: enable backports updates
# (Default: false)
LB_BACKPORTS="false"

119
common Normal file
View File

@ -0,0 +1,119 @@
# config/common - common options for live-build(7)
# $LB_APT: set package manager
# (Default: apt)
LB_APT="apt"
# $LB_APT_FTP_PROXY: set apt/aptitude ftp proxy
# (Default: autodetected or empty)
LB_APT_FTP_PROXY=""
# $LB_APT_HTTP_PROXY: set apt/aptitude http proxy
# (Default: autodetected or empty)
LB_APT_HTTP_PROXY=""
# $LB_APT_PIPELINE: set apt/aptitude pipeline depth
# (Default: )
LB_APT_PIPELINE=""
# $LB_APT_RECOMMENDS: set apt/aptitude recommends
# (Default: true)
LB_APT_RECOMMENDS="true"
# $LB_APT_SECURE: set apt/aptitude security
# (Default: true)
LB_APT_SECURE="true"
# $LB_APT_SOURCE_ARCHIVES: set apt/aptitude source entries in sources.list
# (Default: true)
LB_APT_SOURCE_ARCHIVES="true"
# $LB_CACHE: control cache
# (Default: true)
LB_CACHE="true"
# $LB_CACHE_INDICES: control if downloaded package indices should be cached
# (Default: false)
LB_CACHE_INDICES="false"
# $LB_CACHE_PACKAGES: control if downloaded packages files should be cached
# (Default: true)
LB_CACHE_PACKAGES="true"
# $LB_CACHE_STAGES: control if completed stages should be cached
# (Default: bootstrap)
LB_CACHE_STAGES="bootstrap"
# $LB_DEBCONF_FRONTEND: set debconf(1) frontend to use
# (Default: noninteractive)
LB_DEBCONF_FRONTEND="noninteractive"
# $LB_DEBCONF_PRIORITY: set debconf(1) priority to use
# (Default: critical)
LB_DEBCONF_PRIORITY="critical"
# $LB_INITRAMFS: set initramfs hook
# (Default: live-boot)
LB_INITRAMFS="live-boot"
# $LB_INITRAMFS_COMPRESSION: set initramfs compression
# (Default: gzip)
LB_INITRAMFS_COMPRESSION="gzip"
# $LB_INITSYSTEM: set init system
# (Default: systemd)
LB_INITSYSTEM="systemd"
# $LB_FDISK: set fdisk program
# (Default: autodetected)
LB_FDISK="fdisk"
# $LB_LOSETUP: set losetup program
# (Default: autodetected)
LB_LOSETUP="losetup"
# $LB_MODE: set distribution mode
# (Default: debian)
LB_MODE="debian"
# $LB_SYSTEM: set system type
# (Default: live)
LB_SYSTEM="live"
# $LB_TASKSEL: set tasksel program
# (Default: apt)
LB_TASKSEL="apt"
# live-build options
# $_BREAKPOINTS: enable breakpoints
# (Default: false)
#_BREAKPOINTS="false"
# $_DEBUG: enable debug
# (Default: false)
#_DEBUG="false"
# $_COLOR: enable color
# (Default: false)
#_COLOR="false"
# $_FORCE: enable force
# (Default: false)
#_FORCE="false"
# $_QUIET: enable quiet
# (Default: false)
_QUIET="false"
# $_VERBOSE: enable verbose
# (Default: false)
#_VERBOSE="false"
# Internal stuff (FIXME)
APT_OPTIONS="--yes"
APTITUDE_OPTIONS="--assume-yes"
DEBOOTSTRAP_OPTIONS=""
DEBOOTSTRAP_SCRIPT=""
GZIP_OPTIONS="-6 --rsyncable"
ISOHYBRID_OPTIONS=""

View File

@ -0,0 +1,16 @@
#!/bin/sh
set -e
# Disable kexec-tools
if [ -e /sbin/kexec ]
then
echo "kexec-tools kexec-tools/load_kexec boolean false" > /root/preseed
debconf-set-selections /root/preseed
rm -f /root/preseed
dpkg-reconfigure kexec-tools
fi

View File

@ -0,0 +1,10 @@
#!/bin/sh
set -e
# Disable tmpfs on /tmp
if [ -e /etc/default/rcS ]
then
sed -i -e 's|^ *RAMTMP=.*|RAMTMP=no|' /etc/default/rcS
fi

View File

@ -0,0 +1,11 @@
#!/bin/sh
set -e
# Create /etc/mtab symlink, replacing a regular file if necessary
if [ ! -L /etc/mtab ]
then
rm -f /etc/mtab
ln -s /proc/mounts /etc/mtab
fi

View File

@ -0,0 +1,21 @@
#!/bin/sh
set -e
# Enable cryptsetup
if [ -e /sbin/cryptsetup ]
then
if [ ! -e /etc/initramfs-tools/conf.d/cryptsetup ]
then
mkdir -p /etc/initramfs-tools/conf.d
cat > /etc/initramfs-tools/conf.d/cryptsetup << EOF
# /etc/initramfs-tools/conf.d/cryptsetup
CRYPTSETUP=yes
export CRYPTSETUP
EOF
fi
fi

View File

@ -0,0 +1,7 @@
#!/bin/sh
set -e
# Create /etc/environment and /etc/default/locale
touch /etc/environment
echo "LANG=C.UTF-8" >/etc/default/locale

View File

@ -0,0 +1,11 @@
#!/bin/sh
set -e
# Reset generated file
cat > /etc/adjtime << EOF
0.0 0 0.0
0
UTC
EOF

View File

@ -0,0 +1,18 @@
#!/bin/sh
set -e
# Remove backup files
rm -f /boot/*.bak
rm -f /boot/*.old-dkms
rm -f /etc/apt/sources.list~
rm -f /etc/apt/trusted.gpg~
rm -f /etc/passwd-
rm -f /etc/group-
rm -f /etc/shadow-
rm -f /etc/gshadow-
rm -f /var/cache/debconf/*-old
rm -f /var/lib/dpkg/*-old

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Remove dbus machine id.
#
# This removes dbus machine id that cache that makes each system unique.
rm -f /var/lib/dbus/machine-id

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Remove GNOME icon cache.
#
# This saves space some space.
rm -f /usr/share/icons/*/icon-theme.cache

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Truncate log files
for _FILE in $(find /var/log/ -type f)
do
: > ${_FILE}
done

View File

@ -0,0 +1,7 @@
#!/bin/sh
set -e
# Remove generated files
rm -f /etc/mdadm/mdadm.conf

View File

@ -0,0 +1,10 @@
#!/bin/sh
set -e
# Remove OpenSSH Host Keys.
#
# This removes openssh-server host keys, they are regenerated by live-config
# on system start.
rm -f /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Remove Python *.py files.
#
# This removes byte-compiled Python modules to save some space.
find /usr -name "*.pyc" -print0 | xargs -0r rm -f

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -e
# Blank out systemd machine id. If it does not exist, systemd-journald
# will fail, but if it exists and is empty, systemd will automatically
# set up a new unique ID.
if [ -e /etc/machine-id ]
then
rm -f /etc/machine-id
: > /etc/machine-id
fi

View File

@ -0,0 +1,14 @@
#!/bin/sh
set -e
# Remove temporary files
rm -rf /var/cache/man/*
for _DIRECTORY in /tmp /var/tmp
do
rm -rf ${_DIRECTORY}
mkdir -p ${_DIRECTORY}
chmod 1777 ${_DIRECTORY}
done

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -e
# Remove ssl-cert snakeoil
if [ -e /etc/ssl/certs/ssl-cert-snakeoil.pem ]
then
rm -f /etc/ssl/certs/$(openssl x509 -hash -noout -in /etc/ssl/certs/ssl-cert-snakeoil.pem)
rm -f /etc/ssl/certs/ssl-cert-snakeoil.pem
rm -f /etc/ssl/private/ssl-cert-snakeoil.key
fi

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -e
# Remove udev persistent rules.
#
# This removes udev persistent rules that cache the host systems cd drive as
# well as the running live systems cd drive to remember its device name.
if [ -e /etc/udev/rules.d ]
then
> /etc/udev/rules.d/70-persistent-cd.rules
fi

View File

@ -0,0 +1,16 @@
#!/bin/sh
set -e
# Remove udev persistent rules.
#
# This removes udev persistent rules that cache the host systems mac address to
# remember its device name.
for _FILE in /etc/udev/rules.d/*persistent-net.rules
do
if [ -e "${_FILE}" ]
then
: > ${_FILE}
fi
done

View File

@ -0,0 +1,14 @@
#!/bin/sh
set -e
# Update the Apt File cache.
#
# This allows to use using apt-file out-of-the-box.
. /live-build/config/binary
if [ -x /usr/bin/apt-file ] && [ "${LB_APT_INDICES}" = "true" ]
then
apt-file update
fi

View File

@ -0,0 +1,14 @@
#!/bin/sh
set -e
# Update the Apt Xapian index.
#
# The package would do this itself, but (a) it checks policy-rc.d which says it
# is not allowed to, and (b) it wants to build the index in the background which
# will be racy in the context of live-build.
if [ -x /usr/sbin/update-apt-xapian-index ]
then
PYTHONDONTWRITEBYTECODE=1 /usr/sbin/update-apt-xapian-index --force --quiet
fi

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Use mesa renderer by default
if [ -e /etc/alternatives/glx ]
then
update-alternatives --quiet --set glx /usr/lib/mesa-diverted
fi

View File

@ -0,0 +1,14 @@
#!/bin/sh
set -e
# Update the mlocate database.
#
# It is convenient for this to be already up to date on the live system, and it
# means that if the live system is later installed to a hard disk then less
# work will be required after installation.
if [ -x /usr/bin/updatedb.mlocate ]
then
updatedb.mlocate
fi

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Use newest nvidia version by default
if [ -e /etc/alternatives/nvidia ] && [ -e /usr/lib/nvidia/current ]
then
update-alternatives --quiet --set nvidia /usr/lib/nvidia/current
fi

View File

@ -0,0 +1,2 @@
#!/bin/sh
/root/tl-setup/setup.sh

View File

@ -0,0 +1,4 @@
LIVE_CONFIG_CMDLINE=nox11autologin
LIVE_CONFIG_NOCOMPONENTS=user-setup
LIVE_HOSTNAME=tildeverse
LIVE_HOOKS=filesystem

@ -0,0 +1 @@
Subproject commit f16e0b61496371d2801681c4212bec0a6d533581

View File

@ -0,0 +1,2 @@
#!/bin/sh
python /usr/share/qweechat/qweechat.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,11 @@
# Debian specific defaults
#
[greeter]
background=/usr/share/images/tildelinux/tildelinux-bottom.png
default-user-image=/usr/share/images/tildelinux/onetilde.png
theme-name=oomox-team
xft-antialias=true
xft-hintstyle=hintfull
xft-rgba=rgb
font-name="DejaVu Sans Mono"

View File

@ -0,0 +1,12 @@
# Debian specific defaults
#
# - use lightdm-greeter session greeter, points to the etc-alternatives managed
# greeter
# - hide users list by default, we don't want to expose them
# - use Debian specific session wrapper, to gain support for
# /etc/X11/Xsession.d scripts
[Seat:*]
greeter-session=lightdm-greeter
greeter-hide-users=false
session-wrapper=/etc/X11/Xsession

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#

View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
#
# about.py - about dialog box
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import qt_compat
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
class AboutDialog(QtGui.QDialog):
"""About dialog."""
def __init__(self, name, messages, *args):
QtGui.QDialog.__init__(*(self,) + args)
self.setModal(True)
self.setWindowTitle(name)
close_button = QtGui.QPushButton('Close')
close_button.pressed.connect(self.close)
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(close_button)
hbox.addStretch(1)
vbox = QtGui.QVBoxLayout()
for msg in messages:
label = QtGui.QLabel(msg.decode('utf-8'))
label.setAlignment(QtCore.Qt.AlignHCenter)
vbox.addWidget(label)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.show()

Binary file not shown.

View File

@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
#
# buffer.py - management of WeeChat buffers/nicklist
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
from pkg_resources import resource_filename
import qt_compat
from chat import ChatTextEdit
from input import InputLineEdit
import weechat.color as color
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
class GenericListWidget(QtGui.QListWidget):
"""Generic QListWidget with dynamic size."""
def __init__(self, *args):
QtGui.QListWidget.__init__(*(self,) + args)
self.setMaximumWidth(100)
self.setTextElideMode(QtCore.Qt.ElideNone)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFocusPolicy(QtCore.Qt.NoFocus)
pal = self.palette()
pal.setColor(QtGui.QPalette.Highlight, QtGui.QColor('#ddddff'))
pal.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('black'))
self.setPalette(pal)
def auto_resize(self):
size = self.sizeHintForColumn(0)
if size > 0:
size += 4
self.setMaximumWidth(size)
def clear(self, *args):
"""Re-implement clear to set dynamic size after clear."""
QtGui.QListWidget.clear(*(self,) + args)
self.auto_resize()
def addItem(self, *args):
"""Re-implement addItem to set dynamic size after add."""
QtGui.QListWidget.addItem(*(self,) + args)
self.auto_resize()
def insertItem(self, *args):
"""Re-implement insertItem to set dynamic size after insert."""
QtGui.QListWidget.insertItem(*(self,) + args)
self.auto_resize()
class BufferListWidget(GenericListWidget):
"""Widget with list of buffers."""
def __init__(self, *args):
GenericListWidget.__init__(*(self,) + args)
def switch_prev_buffer(self):
if self.currentRow() > 0:
self.setCurrentRow(self.currentRow() - 1)
else:
self.setCurrentRow(self.count() - 1)
def switch_next_buffer(self):
if self.currentRow() < self.count() - 1:
self.setCurrentRow(self.currentRow() + 1)
else:
self.setCurrentRow(0)
class BufferWidget(QtGui.QWidget):
"""
Widget with (from top to bottom):
title, chat + nicklist (optional) + prompt/input.
"""
def __init__(self, display_nicklist=False):
QtGui.QWidget.__init__(self)
# title
self.title = QtGui.QLineEdit()
self.title.setFocusPolicy(QtCore.Qt.NoFocus)
# splitter with chat + nicklist
self.chat_nicklist = QtGui.QSplitter()
self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
self.chat = ChatTextEdit(debug=False)
self.chat_nicklist.addWidget(self.chat)
self.nicklist = GenericListWidget()
if not display_nicklist:
self.nicklist.setVisible(False)
self.chat_nicklist.addWidget(self.nicklist)
# prompt + input
self.hbox_edit = QtGui.QHBoxLayout()
self.hbox_edit.setContentsMargins(0, 0, 0, 0)
self.hbox_edit.setSpacing(0)
self.input = InputLineEdit(self.chat)
self.hbox_edit.addWidget(self.input)
prompt_input = QtGui.QWidget()
prompt_input.setLayout(self.hbox_edit)
# vbox with title + chat/nicklist + prompt/input
vbox = QtGui.QVBoxLayout()
vbox.setContentsMargins(0, 0, 0, 0)
vbox.setSpacing(0)
vbox.addWidget(self.title)
vbox.addWidget(self.chat_nicklist)
vbox.addWidget(prompt_input)
self.setLayout(vbox)
def set_title(self, title):
"""Set buffer title."""
self.title.clear()
if title is not None:
self.title.setText(title)
def set_prompt(self, prompt):
"""Set prompt."""
if self.hbox_edit.count() > 1:
self.hbox_edit.takeAt(0)
if prompt is not None:
label = QtGui.QLabel(prompt)
label.setContentsMargins(0, 0, 5, 0)
self.hbox_edit.insertWidget(0, label)
class Buffer(QtCore.QObject):
"""A WeeChat buffer."""
bufferInput = qt_compat.Signal(str, str)
def __init__(self, data={}):
QtCore.QObject.__init__(self)
self.data = data
self.nicklist = {}
self.widget = BufferWidget(display_nicklist=self.data.get('nicklist',
0))
self.update_title()
self.update_prompt()
self.widget.input.textSent.connect(self.input_text_sent)
def pointer(self):
"""Return pointer on buffer."""
return self.data.get('__path', [''])[0]
def update_title(self):
"""Update title."""
try:
self.widget.set_title(
color.remove(self.data['title'].decode('utf-8')))
except: # noqa: E722
self.widget.set_title(None)
def update_prompt(self):
"""Update prompt."""
try:
self.widget.set_prompt(self.data['local_variables']['nick'])
except: # noqa: E722
self.widget.set_prompt(None)
def input_text_sent(self, text):
"""Called when text has to be sent to buffer."""
if self.data:
self.bufferInput.emit(self.data['full_name'], text)
def nicklist_add_item(self, parent, group, prefix, name, visible):
"""Add a group/nick in nicklist."""
if group:
self.nicklist[name] = {
'visible': visible,
'nicks': []
}
else:
self.nicklist[parent]['nicks'].append({
'prefix': prefix,
'name': name,
'visible': visible,
})
def nicklist_remove_item(self, parent, group, name):
"""Remove a group/nick from nicklist."""
if group:
if name in self.nicklist:
del self.nicklist[name]
else:
if parent in self.nicklist:
self.nicklist[parent]['nicks'] = [
nick for nick in self.nicklist[parent]['nicks']
if nick['name'] != name
]
def nicklist_update_item(self, parent, group, prefix, name, visible):
"""Update a group/nick in nicklist."""
if group:
if name in self.nicklist:
self.nicklist[name]['visible'] = visible
else:
if parent in self.nicklist:
for nick in self.nicklist[parent]['nicks']:
if nick['name'] == name:
nick['prefix'] = prefix
nick['visible'] = visible
break
def nicklist_refresh(self):
"""Refresh nicklist."""
self.widget.nicklist.clear()
for group in sorted(self.nicklist):
for nick in sorted(self.nicklist[group]['nicks'],
key=lambda n: n['name']):
prefix_color = {
'': '',
' ': '',
'+': 'yellow',
}
color = prefix_color.get(nick['prefix'], 'green')
if color:
icon = QtGui.QIcon(
resource_filename(__name__,
'data/icons/bullet_%s_8x8.png' %
color))
else:
pixmap = QtGui.QPixmap(8, 8)
pixmap.fill()
icon = QtGui.QIcon(pixmap)
item = QtGui.QListWidgetItem(icon, nick['name'])
self.widget.nicklist.addItem(item)
self.widget.nicklist.setVisible(True)

Binary file not shown.

View File

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
#
# chat.py - chat area
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import datetime
import qt_compat
import config
import weechat.color as color
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
class ChatTextEdit(QtGui.QTextEdit):
"""Chat area."""
def __init__(self, debug, *args):
QtGui.QTextEdit.__init__(*(self,) + args)
self.debug = debug
self.readOnly = True
self.setFocusPolicy(QtCore.Qt.NoFocus)
self.setFontFamily('monospace')
self._textcolor = self.textColor()
self._bgcolor = QtGui.QColor('#FFFFFF')
self._setcolorcode = {
'F': (self.setTextColor, self._textcolor),
'B': (self.setTextBackgroundColor, self._bgcolor)
}
self._setfont = {
'*': self.setFontWeight,
'_': self.setFontUnderline,
'/': self.setFontItalic
}
self._fontvalues = {
False: {
'*': QtGui.QFont.Normal,
'_': False,
'/': False
},
True: {
'*': QtGui.QFont.Bold,
'_': True,
'/': True
}
}
self._color = color.Color(config.color_options(), self.debug)
def display(self, time, prefix, text, forcecolor=None):
if time == 0:
d = datetime.datetime.now()
else:
d = datetime.datetime.fromtimestamp(float(time))
self.setTextColor(QtGui.QColor('#999999'))
self.insertPlainText(d.strftime('%H:%M '))
prefix = self._color.convert(prefix)
text = self._color.convert(text)
if forcecolor:
if prefix:
prefix = '\x01(F%s)%s' % (forcecolor, prefix)
text = '\x01(F%s)%s' % (forcecolor, text)
if prefix:
self._display_with_colors(str(prefix).decode('utf-8') + ' ')
if text:
self._display_with_colors(str(text).decode('utf-8'))
if text[-1:] != '\n':
self.insertPlainText('\n')
else:
self.insertPlainText('\n')
self.scroll_bottom()
def _display_with_colors(self, string):
self.setTextColor(self._textcolor)
self.setTextBackgroundColor(self._bgcolor)
self._reset_attributes()
items = string.split('\x01')
for i, item in enumerate(items):
if i > 0 and item.startswith('('):
pos = item.find(')')
if pos >= 2:
action = item[1]
code = item[2:pos]
if action == '+':
# set attribute
self._set_attribute(code[0], True)
elif action == '-':
# remove attribute
self._set_attribute(code[0], False)
else:
# reset attributes and color
if code == 'r':
self._reset_attributes()
self._setcolorcode[action][0](
self._setcolorcode[action][1])
else:
# set attributes + color
while code.startswith(('*', '!', '/', '_', '|',
'r')):
if code[0] == 'r':
self._reset_attributes()
elif code[0] in self._setfont:
self._set_attribute(
code[0],
not self._font[code[0]])
code = code[1:]
if code:
self._setcolorcode[action][0](
QtGui.QColor(code))
item = item[pos+1:]
if len(item) > 0:
self.insertPlainText(item)
def _reset_attributes(self):
self._font = {}
for attr in self._setfont:
self._set_attribute(attr, False)
def _set_attribute(self, attr, value):
self._font[attr] = value
self._setfont[attr](self._fontvalues[self._font[attr]][attr])
def scroll_bottom(self):
bar = self.verticalScrollBar()
bar.setValue(bar.maximum())

Binary file not shown.

View File

@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
#
# config.py - configuration for QWeeChat (~/.qweechat/qweechat.conf)
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import ConfigParser
import os
CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME')
CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR
CONFIG_DEFAULT_RELAY_LINES = 50
CONFIG_DEFAULT_SECTIONS = ('relay', 'look', 'color')
CONFIG_DEFAULT_OPTIONS = (('relay.server', ''),
('relay.port', ''),
('relay.ssl', 'off'),
('relay.password', ''),
('relay.autoconnect', 'off'),
('relay.lines', str(CONFIG_DEFAULT_RELAY_LINES)),
('look.debug', 'off'),
('look.statusbar', 'off'))
# Default colors for WeeChat color options (option name, #rgb value)
CONFIG_DEFAULT_COLOR_OPTIONS = (
('separator', '#000066'), # 0
('chat', '#000000'), # 1
('chat_time', '#999999'), # 2
('chat_time_delimiters', '#000000'), # 3
('chat_prefix_error', '#FF6633'), # 4
('chat_prefix_network', '#990099'), # 5
('chat_prefix_action', '#000000'), # 6
('chat_prefix_join', '#00CC00'), # 7
('chat_prefix_quit', '#CC0000'), # 8
('chat_prefix_more', '#CC00FF'), # 9
('chat_prefix_suffix', '#330099'), # 10
('chat_buffer', '#000000'), # 11
('chat_server', '#000000'), # 12
('chat_channel', '#000000'), # 13
('chat_nick', '#000000'), # 14
('chat_nick_self', '*#000000'), # 15
('chat_nick_other', '#000000'), # 16
('', '#000000'), # 17 (nick1 -- obsolete)
('', '#000000'), # 18 (nick2 -- obsolete)
('', '#000000'), # 19 (nick3 -- obsolete)
('', '#000000'), # 20 (nick4 -- obsolete)
('', '#000000'), # 21 (nick5 -- obsolete)
('', '#000000'), # 22 (nick6 -- obsolete)
('', '#000000'), # 23 (nick7 -- obsolete)
('', '#000000'), # 24 (nick8 -- obsolete)
('', '#000000'), # 25 (nick9 -- obsolete)
('', '#000000'), # 26 (nick10 -- obsolete)
('chat_host', '#666666'), # 27
('chat_delimiters', '#9999FF'), # 28
('chat_highlight', '#3399CC'), # 29
('chat_read_marker', '#000000'), # 30
('chat_text_found', '#000000'), # 31
('chat_value', '#000000'), # 32
('chat_prefix_buffer', '#000000'), # 33
('chat_tags', '#000000'), # 34
('chat_inactive_window', '#000000'), # 35
('chat_inactive_buffer', '#000000'), # 36
('chat_prefix_buffer_inactive_buffer', '#000000'), # 37
('chat_nick_offline', '#000000'), # 38
('chat_nick_offline_highlight', '#000000'), # 39
('chat_nick_prefix', '#000000'), # 40
('chat_nick_suffix', '#000000'), # 41
('emphasis', '#000000'), # 42
('chat_day_change', '#000000'), # 43
)
config_color_options = []
def read():
"""Read config file."""
global config_color_options
config = ConfigParser.RawConfigParser()
if os.path.isfile(CONFIG_FILENAME):
config.read(CONFIG_FILENAME)
# add missing sections/options
for section in CONFIG_DEFAULT_SECTIONS:
if not config.has_section(section):
config.add_section(section)
for option in reversed(CONFIG_DEFAULT_OPTIONS):
section, name = option[0].split('.', 1)
if not config.has_option(section, name):
config.set(section, name, option[1])
section = 'color'
for option in reversed(CONFIG_DEFAULT_COLOR_OPTIONS):
if option[0] and not config.has_option(section, option[0]):
config.set(section, option[0], option[1])
# build list of color options
config_color_options = []
for option in CONFIG_DEFAULT_COLOR_OPTIONS:
if option[0]:
config_color_options.append(config.get('color', option[0]))
else:
config_color_options.append('#000000')
return config
def write(config):
"""Write config file."""
if not os.path.exists(CONFIG_DIR):
os.mkdir(CONFIG_DIR, 0o0755)
with open(CONFIG_FILENAME, 'wb') as cfg:
config.write(cfg)
def color_options():
"""Return color options."""
global config_color_options
return config_color_options

Binary file not shown.

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
# connection.py - connection window
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import qt_compat
QtGui = qt_compat.import_module('QtGui')
class ConnectionDialog(QtGui.QDialog):
"""Connection window."""
def __init__(self, values, *args):
QtGui.QDialog.__init__(*(self,) + args)
self.values = values
self.setModal(True)
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.fields = {}
for line, field in enumerate(('server', 'port', 'password', 'lines')):
grid.addWidget(QtGui.QLabel(field.capitalize()), line, 0)
line_edit = QtGui.QLineEdit()
line_edit.setFixedWidth(200)
if field == 'password':
line_edit.setEchoMode(QtGui.QLineEdit.Password)
if field == 'lines':
validator = QtGui.QIntValidator(0, 2147483647, self)
line_edit.setValidator(validator)
line_edit.setFixedWidth(80)
line_edit.insert(self.values[field])
grid.addWidget(line_edit, line, 1)
self.fields[field] = line_edit
if field == 'port':
ssl = QtGui.QCheckBox('SSL')
ssl.setChecked(self.values['ssl'] == 'on')
grid.addWidget(ssl, line, 2)
self.fields['ssl'] = ssl
self.dialog_buttons = QtGui.QDialogButtonBox()
self.dialog_buttons.setStandardButtons(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
self.dialog_buttons.rejected.connect(self.close)
grid.addWidget(self.dialog_buttons, 4, 0, 1, 2)
self.setLayout(grid)
self.show()

Binary file not shown.

View File

@ -0,0 +1,40 @@
Copyright and license for images
================================
Files: weechat.png, bullet_green_8x8.png, bullet_yellow_8x8.png
Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
Released under GPLv3.
Files: application-exit.png, dialog-close.png, dialog-ok-apply.png,
document-save.png, edit-find.png, help-about.png, network-connect.png,
network-disconnect.png, preferences-other.png
Files come from Debian package "oxygen-icon-theme":
The Oxygen Icon Theme
Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
Copyright (C) 2007 David Vignoni <david@icon-king.com>
Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
and others
License:
This library 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.
This library 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 this library. If not, see <http://www.gnu.org/licenses/>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
#
# debug.py - debug window
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import qt_compat
from chat import ChatTextEdit
from input import InputLineEdit
QtGui = qt_compat.import_module('QtGui')
class DebugDialog(QtGui.QDialog):
"""Debug dialog."""
def __init__(self, *args):
QtGui.QDialog.__init__(*(self,) + args)
self.resize(640, 480)
self.setWindowTitle('Debug console')
self.chat = ChatTextEdit(debug=True)
self.input = InputLineEdit(self.chat)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.chat)
vbox.addWidget(self.input)
self.setLayout(vbox)
self.show()
def display_lines(self, lines):
for line in lines:
self.chat.display(*line[0], **line[1])

Binary file not shown.

View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
#
# input.py - input line for chat and debug window
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import qt_compat
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
class InputLineEdit(QtGui.QLineEdit):
"""Input line."""
bufferSwitchPrev = qt_compat.Signal()
bufferSwitchNext = qt_compat.Signal()
textSent = qt_compat.Signal(str)
def __init__(self, scroll_widget):
QtGui.QLineEdit.__init__(self)
self.scroll_widget = scroll_widget
self._history = []
self._history_index = -1
self.returnPressed.connect(self._input_return_pressed)
def keyPressEvent(self, event):
key = event.key()
modifiers = event.modifiers()
bar = self.scroll_widget.verticalScrollBar()
if modifiers == QtCore.Qt.ControlModifier:
if key == QtCore.Qt.Key_PageUp:
self.bufferSwitchPrev.emit()
elif key == QtCore.Qt.Key_PageDown:
self.bufferSwitchNext.emit()
else:
QtGui.QLineEdit.keyPressEvent(self, event)
elif modifiers == QtCore.Qt.AltModifier:
if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Up):
self.bufferSwitchPrev.emit()
elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_Down):
self.bufferSwitchNext.emit()
elif key == QtCore.Qt.Key_PageUp:
bar.setValue(bar.value() - (bar.pageStep() / 10))
elif key == QtCore.Qt.Key_PageDown:
bar.setValue(bar.value() + (bar.pageStep() / 10))
elif key == QtCore.Qt.Key_Home:
bar.setValue(bar.minimum())
elif key == QtCore.Qt.Key_End:
bar.setValue(bar.maximum())
else:
QtGui.QLineEdit.keyPressEvent(self, event)
elif key == QtCore.Qt.Key_PageUp:
bar.setValue(bar.value() - bar.pageStep())
elif key == QtCore.Qt.Key_PageDown:
bar.setValue(bar.value() + bar.pageStep())
elif key == QtCore.Qt.Key_Up:
self._history_navigate(-1)
elif key == QtCore.Qt.Key_Down:
self._history_navigate(1)
else:
QtGui.QLineEdit.keyPressEvent(self, event)
def _input_return_pressed(self):
self._history.append(self.text().encode('utf-8'))
self._history_index = len(self._history)
self.textSent.emit(self.text())
self.clear()
def _history_navigate(self, direction):
if self._history:
self._history_index += direction
if self._history_index < 0:
self._history_index = 0
return
if self._history_index > len(self._history) - 1:
self._history_index = len(self._history)
self.clear()
return
self.setText(self._history[self._history_index])

Binary file not shown.

View File

@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
#
# network.py - I/O with WeeChat/relay
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import struct
import qt_compat
import config
QtCore = qt_compat.import_module('QtCore')
QtNetwork = qt_compat.import_module('QtNetwork')
_PROTO_INIT_CMD = ['init password=%(password)s']
_PROTO_SYNC_CMDS = [
'(listbuffers) hdata buffer:gui_buffers(*) number,full_name,short_name,'
'type,nicklist,title,local_variables',
'(listlines) hdata buffer:gui_buffers(*)/own_lines/last_line(-%(lines)d)/'
'data date,displayed,prefix,message',
'(nicklist) nicklist',
'sync',
''
]
class Network(QtCore.QObject):
"""I/O with WeeChat/relay."""
statusChanged = qt_compat.Signal(str, str)
messageFromWeechat = qt_compat.Signal(QtCore.QByteArray)
def __init__(self, *args):
QtCore.QObject.__init__(*(self,) + args)
self.status_disconnected = 'disconnected'
self.status_connecting = 'connecting...'
self.status_connected = 'connected'
self._server = None
self._port = None
self._ssl = None
self._password = None
self._lines = config.CONFIG_DEFAULT_RELAY_LINES
self._buffer = QtCore.QByteArray()
self._socket = QtNetwork.QSslSocket()
self._socket.connected.connect(self._socket_connected)
self._socket.error.connect(self._socket_error)
self._socket.readyRead.connect(self._socket_read)
self._socket.disconnected.connect(self._socket_disconnected)
def _socket_connected(self):
"""Slot: socket connected."""
self.statusChanged.emit(self.status_connected, None)
if self._password:
self.send_to_weechat('\n'.join(_PROTO_INIT_CMD + _PROTO_SYNC_CMDS)
% {'password': str(self._password),
'lines': self._lines})
def _socket_error(self, error):
"""Slot: socket error."""
self.statusChanged.emit(
self.status_disconnected,
'Failed, error: %s' % self._socket.errorString())
def _socket_read(self):
"""Slot: data available on socket."""
data = self._socket.readAll()
self._buffer.append(data)
while len(self._buffer) >= 4:
remainder = None
length = struct.unpack('>i', self._buffer[0:4])[0]
if len(self._buffer) < length:
# partial message, just wait for end of message
break
# more than one message?
if length < len(self._buffer):
# save beginning of another message
remainder = self._buffer[length:]
self._buffer = self._buffer[0:length]
self.messageFromWeechat.emit(self._buffer)
if not self.is_connected():
return
self._buffer.clear()
if remainder:
self._buffer.append(remainder)
def _socket_disconnected(self):
"""Slot: socket disconnected."""
self._server = None
self._port = None
self._ssl = None
self._password = None
self.statusChanged.emit(self.status_disconnected, None)
def is_connected(self):
"""Return True if the socket is connected, False otherwise."""
return self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState
def is_ssl(self):
"""Return True if SSL is used, False otherwise."""
return self._ssl
def connect_weechat(self, server, port, ssl, password, lines):
"""Connect to WeeChat."""
self._server = server
try:
self._port = int(port)
except ValueError:
self._port = 0
self._ssl = ssl
self._password = password
try:
self._lines = int(lines)
except ValueError:
self._lines = config.CONFIG_DEFAULT_RELAY_LINES
if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
return
if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState:
self._socket.abort()
self._socket.connectToHost(self._server, self._port)
if self._ssl:
self._socket.ignoreSslErrors()
self._socket.startClientEncryption()
self.statusChanged.emit(self.status_connecting, None)
def disconnect_weechat(self):
"""Disconnect from WeeChat."""
if self._socket.state() == QtNetwork.QAbstractSocket.UnconnectedState:
return
if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
self.send_to_weechat('quit\n')
self._socket.waitForBytesWritten(1000)
else:
self.statusChanged.emit(self.status_disconnected, None)
self._socket.abort()
def send_to_weechat(self, message):
"""Send a message to WeeChat."""
self._socket.write(message.encode('utf-8'))
def desync_weechat(self):
"""Desynchronize from WeeChat."""
self.send_to_weechat('desync\n')
def sync_weechat(self):
"""Synchronize with WeeChat."""
self.send_to_weechat('\n'.join(_PROTO_SYNC_CMDS))
def status_icon(self, status):
"""Return the name of icon for a given status."""
icon = {
self.status_disconnected: 'dialog-close.png',
self.status_connecting: 'dialog-close.png',
self.status_connected: 'dialog-ok-apply.png',
}
return icon.get(status, '')
def get_options(self):
"""Get connection options."""
return {
'server': self._server,
'port': self._port,
'ssl': 'on' if self._ssl else 'off',
'password': self._password,
'lines': str(self._lines),
}

Binary file not shown.

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# File downloaded from:
# https://github.com/epage/PythonUtils/blob/master/util/qt_compat.py
# Author: epage
# License: LGPL 2.1
#
from __future__ import with_statement
from __future__ import division
_TRY_PYSIDE = True
uses_pyside = False
try:
if not _TRY_PYSIDE:
raise ImportError()
import PySide.QtCore as _QtCore
QtCore = _QtCore
uses_pyside = True
except ImportError:
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
import PyQt4.QtCore as _QtCore
QtCore = _QtCore
uses_pyside = False
def _pyside_import_module(moduleName):
pyside = __import__('PySide', globals(), locals(), [moduleName], -1)
return getattr(pyside, moduleName)
def _pyqt4_import_module(moduleName):
pyside = __import__('PyQt4', globals(), locals(), [moduleName], -1)
return getattr(pyside, moduleName)
if uses_pyside:
import_module = _pyside_import_module
Signal = QtCore.Signal
Slot = QtCore.Slot
Property = QtCore.Property
else:
import_module = _pyqt4_import_module
Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot
Property = QtCore.pyqtProperty
if __name__ == "__main__":
pass

Binary file not shown.

View File

@ -0,0 +1,555 @@
# -*- coding: utf-8 -*-
#
# qweechat.py - WeeChat remote GUI using Qt toolkit
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
"""
QWeeChat is a WeeChat remote GUI using Qt toolkit.
It requires requires WeeChat 0.3.7 or newer, running on local or remote host.
"""
#
# History:
#
# 2011-05-27, Sébastien Helleu <flashcode@flashtux.org>:
# start dev
#
import sys
import traceback
from pkg_resources import resource_filename
import qt_compat
import config
import weechat.protocol as protocol
from network import Network
from connection import ConnectionDialog
from buffer import BufferListWidget, Buffer
from debug import DebugDialog
from about import AboutDialog
from version import qweechat_version
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
NAME = 'QWeeChat'
AUTHOR = 'Sébastien Helleu'
AUTHOR_MAIL = 'flashcode@flashtux.org'
WEECHAT_SITE = 'https://weechat.org/'
# number of lines in buffer for debug window
DEBUG_NUM_LINES = 50
class MainWindow(QtGui.QMainWindow):
"""Main window."""
def __init__(self, *args):
QtGui.QMainWindow.__init__(*(self,) + args)
self.config = config.read()
self.resize(1000, 600)
self.setWindowTitle(NAME)
self.debug_dialog = None
self.debug_lines = []
self.about_dialog = None
self.connection_dialog = None
self.preferences_dialog = None
# network
self.network = Network()
self.network.statusChanged.connect(self._network_status_changed)
self.network.messageFromWeechat.connect(self._network_weechat_msg)
# list of buffers
self.list_buffers = BufferListWidget()
self.list_buffers.currentRowChanged.connect(self._buffer_switch)
# default buffer
self.buffers = [Buffer()]
self.stacked_buffers = QtGui.QStackedWidget()
self.stacked_buffers.addWidget(self.buffers[0].widget)
# splitter with buffers + chat/input
splitter = QtGui.QSplitter()
splitter.addWidget(self.list_buffers)
splitter.addWidget(self.stacked_buffers)
self.setCentralWidget(splitter)
if self.config.getboolean('look', 'statusbar'):
self.statusBar().visible = True
# actions for menu and toolbar
actions_def = {
'connect': [
'network-connect.png', 'Connect to WeeChat',
'Ctrl+O', self.open_connection_dialog],
'disconnect': [
'network-disconnect.png', 'Disconnect from WeeChat',
'Ctrl+D', self.network.disconnect_weechat],
'debug': [
'edit-find.png', 'Debug console window',
'Ctrl+B', self.open_debug_dialog],
'preferences': [
'preferences-other.png', 'Preferences',
'Ctrl+P', self.open_preferences_dialog],
'about': [
'help-about.png', 'About',
'Ctrl+H', self.open_about_dialog],
'save connection': [
'document-save.png', 'Save connection configuration',
'Ctrl+S', self.save_connection],
'quit': [
'application-exit.png', 'Quit application',
'Ctrl+Q', self.close],
}
self.actions = {}
for name, action in list(actions_def.items()):
self.actions[name] = QtGui.QAction(
QtGui.QIcon(
resource_filename(__name__, 'data/icons/%s' % action[0])),
name.capitalize(), self)
self.actions[name].setStatusTip(action[1])
self.actions[name].setShortcut(action[2])
self.actions[name].triggered.connect(action[3])
# menu
self.menu = self.menuBar()
menu_file = self.menu.addMenu('&File')
menu_file.addActions([self.actions['connect'],
self.actions['disconnect'],
self.actions['preferences'],
self.actions['save connection'],
self.actions['quit']])
menu_window = self.menu.addMenu('&Window')
menu_window.addAction(self.actions['debug'])
menu_help = self.menu.addMenu('&Help')
menu_help.addAction(self.actions['about'])
self.network_status = QtGui.QLabel()
self.network_status.setFixedHeight(20)
self.network_status.setFixedWidth(200)
self.network_status.setContentsMargins(0, 0, 10, 0)
self.network_status.setAlignment(QtCore.Qt.AlignRight)
if hasattr(self.menu, 'setCornerWidget'):
self.menu.setCornerWidget(self.network_status,
QtCore.Qt.TopRightCorner)
self.network_status_set(self.network.status_disconnected)
# toolbar
toolbar = self.addToolBar('toolBar')
toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
toolbar.addActions([self.actions['connect'],
self.actions['disconnect'],
self.actions['debug'],
self.actions['preferences'],
self.actions['about'],
self.actions['quit']])
self.buffers[0].widget.input.setFocus()
# open debug dialog
if self.config.getboolean('look', 'debug'):
self.open_debug_dialog()
# auto-connect to relay
if self.config.getboolean('relay', 'autoconnect'):
self.network.connect_weechat(self.config.get('relay', 'server'),
self.config.get('relay', 'port'),
self.config.getboolean('relay',
'ssl'),
self.config.get('relay', 'password'),
self.config.get('relay', 'lines'))
self.show()
def _buffer_switch(self, index):
"""Switch to a buffer."""
if index >= 0:
self.stacked_buffers.setCurrentIndex(index)
self.stacked_buffers.widget(index).input.setFocus()
def buffer_input(self, full_name, text):
"""Send buffer input to WeeChat."""
if self.network.is_connected():
message = 'input %s %s\n' % (full_name, text)
self.network.send_to_weechat(message)
self.debug_display(0, '<==', message, forcecolor='#AA0000')
def open_preferences_dialog(self):
"""Open a dialog with preferences."""
# TODO: implement the preferences dialog box
messages = ['Not yet implemented!',
'']
self.preferences_dialog = AboutDialog('Preferences', messages, self)
def save_connection(self):
"""Save connection configuration."""
if self.network:
options = self.network.get_options()
for option in options.keys():
self.config.set('relay', option, options[option])
def debug_display(self, *args, **kwargs):
"""Display a debug message."""
self.debug_lines.append((args, kwargs))
self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:]
if self.debug_dialog:
self.debug_dialog.chat.display(*args, **kwargs)
def open_debug_dialog(self):
"""Open a dialog with debug messages."""
if not self.debug_dialog:
self.debug_dialog = DebugDialog(self)
self.debug_dialog.input.textSent.connect(
self.debug_input_text_sent)
self.debug_dialog.finished.connect(self._debug_dialog_closed)
self.debug_dialog.display_lines(self.debug_lines)
self.debug_dialog.chat.scroll_bottom()
def debug_input_text_sent(self, text):
"""Send debug buffer input to WeeChat."""
if self.network.is_connected():
text = str(text)
pos = text.find(')')
if text.startswith('(') and pos >= 0:
text = '(debug_%s)%s' % (text[1:pos], text[pos+1:])
else:
text = '(debug) %s' % text
self.debug_display(0, '<==', text, forcecolor='#AA0000')
self.network.send_to_weechat(text + '\n')
def _debug_dialog_closed(self, result):
"""Called when debug dialog is closed."""
self.debug_dialog = None
def open_about_dialog(self):
"""Open a dialog with info about QWeeChat."""
messages = ['<b>%s</b> %s' % (NAME, qweechat_version()),
'&copy; 2011-2020 %s &lt;<a href="mailto:%s">%s</a>&gt;'
% (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL),
'',
'Running with %s' % ('PySide' if qt_compat.uses_pyside
else 'PyQt4'),
'',
'WeeChat site: <a href="%s">%s</a>'
% (WEECHAT_SITE, WEECHAT_SITE),
'']
self.about_dialog = AboutDialog(NAME, messages, self)
def open_connection_dialog(self):
"""Open a dialog with connection settings."""
values = {}
for option in ('server', 'port', 'ssl', 'password', 'lines'):
values[option] = self.config.get('relay', option)
self.connection_dialog = ConnectionDialog(values, self)
self.connection_dialog.dialog_buttons.accepted.connect(
self.connect_weechat)
def connect_weechat(self):
"""Connect to WeeChat."""
self.network.connect_weechat(
self.connection_dialog.fields['server'].text(),
self.connection_dialog.fields['port'].text(),
self.connection_dialog.fields['ssl'].isChecked(),
self.connection_dialog.fields['password'].text(),
int(self.connection_dialog.fields['lines'].text()))
self.connection_dialog.close()
def _network_status_changed(self, status, extra):
"""Called when the network status has changed."""
if self.config.getboolean('look', 'statusbar'):
self.statusBar().showMessage(status)
self.debug_display(0, '', status, forcecolor='#0000AA')
self.network_status_set(status)
def network_status_set(self, status):
"""Set the network status."""
pal = self.network_status.palette()
if status == self.network.status_connected:
pal.setColor(self.network_status.foregroundRole(),
QtGui.QColor('green'))
else:
pal.setColor(self.network_status.foregroundRole(),
QtGui.QColor('#aa0000'))
ssl = ' (SSL)' if status != self.network.status_disconnected \
and self.network.is_ssl() else ''
self.network_status.setPalette(pal)
icon = self.network.status_icon(status)
if icon:
self.network_status.setText(
'<img src="%s"> %s' %
(resource_filename(__name__, 'data/icons/%s' % icon),
status.capitalize() + ssl))
else:
self.network_status.setText(status.capitalize())
if status == self.network.status_disconnected:
self.actions['connect'].setEnabled(True)
self.actions['disconnect'].setEnabled(False)
else:
self.actions['connect'].setEnabled(False)
self.actions['disconnect'].setEnabled(True)
def _network_weechat_msg(self, message):
"""Called when a message is received from WeeChat."""
self.debug_display(0, '==>',
'message (%d bytes):\n%s'
% (len(message),
protocol.hex_and_ascii(message, 20)),
forcecolor='#008800')
try:
proto = protocol.Protocol()
message = proto.decode(str(message))
if message.uncompressed:
self.debug_display(
0, '==>',
'message uncompressed (%d bytes):\n%s'
% (message.size_uncompressed,
protocol.hex_and_ascii(message.uncompressed, 20)),
forcecolor='#008800')
self.debug_display(0, '', 'Message: %s' % message)
self.parse_message(message)
except: # noqa: E722
print('Error while decoding message from WeeChat:\n%s'
% traceback.format_exc())
self.network.disconnect_weechat()
def _parse_listbuffers(self, message):
"""Parse a WeeChat with list of buffers."""
for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue
self.list_buffers.clear()
while self.stacked_buffers.count() > 0:
buf = self.stacked_buffers.widget(0)
self.stacked_buffers.removeWidget(buf)
self.buffers = []
for item in obj.value['items']:
buf = self.create_buffer(item)
self.insert_buffer(len(self.buffers), buf)
self.list_buffers.setCurrentRow(0)
self.buffers[0].widget.input.setFocus()
def _parse_line(self, message):
"""Parse a WeeChat message with a buffer line."""
for obj in message.objects:
lines = []
if obj.objtype != 'hda' or obj.value['path'][-1] != 'line_data':
continue
for item in obj.value['items']:
if message.msgid == 'listlines':
ptrbuf = item['__path'][0]
else:
ptrbuf = item['buffer']
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == ptrbuf]
if index:
lines.append(
(index[0],
(item['date'], item['prefix'],
item['message']))
)
if message.msgid == 'listlines':
lines.reverse()
for line in lines:
self.buffers[line[0]].widget.chat.display(*line[1])
def _parse_nicklist(self, message):
"""Parse a WeeChat message with a buffer nicklist."""
buffer_refresh = {}
for obj in message.objects:
if obj.objtype != 'hda' or \
obj.value['path'][-1] != 'nicklist_item':
continue
group = '__root'
for item in obj.value['items']:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == item['__path'][0]]
if index:
if not index[0] in buffer_refresh:
self.buffers[index[0]].nicklist = {}
buffer_refresh[index[0]] = True
if item['group']:
group = item['name']
self.buffers[index[0]].nicklist_add_item(
group, item['group'], item['prefix'], item['name'],
item['visible'])
for index in buffer_refresh:
self.buffers[index].nicklist_refresh()
def _parse_nicklist_diff(self, message):
"""Parse a WeeChat message with a buffer nicklist diff."""
buffer_refresh = {}
for obj in message.objects:
if obj.objtype != 'hda' or \
obj.value['path'][-1] != 'nicklist_item':
continue
group = '__root'
for item in obj.value['items']:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == item['__path'][0]]
if not index:
continue
buffer_refresh[index[0]] = True
if item['_diff'] == ord('^'):
group = item['name']
elif item['_diff'] == ord('+'):
self.buffers[index[0]].nicklist_add_item(
group, item['group'], item['prefix'], item['name'],
item['visible'])
elif item['_diff'] == ord('-'):
self.buffers[index[0]].nicklist_remove_item(
group, item['group'], item['name'])
elif item['_diff'] == ord('*'):
self.buffers[index[0]].nicklist_update_item(
group, item['group'], item['prefix'], item['name'],
item['visible'])
for index in buffer_refresh:
self.buffers[index].nicklist_refresh()
def _parse_buffer_opened(self, message):
"""Parse a WeeChat message with a new buffer (opened)."""
for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue
for item in obj.value['items']:
buf = self.create_buffer(item)
index = self.find_buffer_index_for_insert(item['next_buffer'])
self.insert_buffer(index, buf)
def _parse_buffer(self, message):
"""Parse a WeeChat message with a buffer event
(anything except a new buffer).
"""
for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue
for item in obj.value['items']:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == item['__path'][0]]
if not index:
continue
index = index[0]
if message.msgid == '_buffer_type_changed':
self.buffers[index].data['type'] = item['type']
elif message.msgid in ('_buffer_moved', '_buffer_merged',
'_buffer_unmerged'):
buf = self.buffers[index]
buf.data['number'] = item['number']
self.remove_buffer(index)
index2 = self.find_buffer_index_for_insert(
item['next_buffer'])
self.insert_buffer(index2, buf)
elif message.msgid == '_buffer_renamed':
self.buffers[index].data['full_name'] = item['full_name']
self.buffers[index].data['short_name'] = item['short_name']
elif message.msgid == '_buffer_title_changed':
self.buffers[index].data['title'] = item['title']
self.buffers[index].update_title()
elif message.msgid == '_buffer_cleared':
self.buffers[index].widget.chat.clear()
elif message.msgid.startswith('_buffer_localvar_'):
self.buffers[index].data['local_variables'] = \
item['local_variables']
self.buffers[index].update_prompt()
elif message.msgid == '_buffer_closing':
self.remove_buffer(index)
def parse_message(self, message):
"""Parse a WeeChat message."""
if message.msgid.startswith('debug'):
self.debug_display(0, '', '(debug message, ignored)')
elif message.msgid == 'listbuffers':
self._parse_listbuffers(message)
elif message.msgid in ('listlines', '_buffer_line_added'):
self._parse_line(message)
elif message.msgid in ('_nicklist', 'nicklist'):
self._parse_nicklist(message)
elif message.msgid == '_nicklist_diff':
self._parse_nicklist_diff(message)
elif message.msgid == '_buffer_opened':
self._parse_buffer_opened(message)
elif message.msgid.startswith('_buffer_'):
self._parse_buffer(message)
elif message.msgid == '_upgrade':
self.network.desync_weechat()
elif message.msgid == '_upgrade_ended':
self.network.sync_weechat()
def create_buffer(self, item):
"""Create a new buffer."""
buf = Buffer(item)
buf.bufferInput.connect(self.buffer_input)
buf.widget.input.bufferSwitchPrev.connect(
self.list_buffers.switch_prev_buffer)
buf.widget.input.bufferSwitchNext.connect(
self.list_buffers.switch_next_buffer)
return buf
def insert_buffer(self, index, buf):
"""Insert a buffer in list."""
self.buffers.insert(index, buf)
self.list_buffers.insertItem(index, '%d. %s'
% (buf.data['number'],
buf.data['full_name'].decode('utf-8')))
self.stacked_buffers.insertWidget(index, buf.widget)
def remove_buffer(self, index):
"""Remove a buffer."""
if self.list_buffers.currentRow == index and index > 0:
self.list_buffers.setCurrentRow(index - 1)
self.list_buffers.takeItem(index)
self.stacked_buffers.removeWidget(self.stacked_buffers.widget(index))
self.buffers.pop(index)
def find_buffer_index_for_insert(self, next_buffer):
"""Find position to insert a buffer in list."""
index = -1
if next_buffer == '0x0':
index = len(self.buffers)
else:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == next_buffer]
if index:
index = index[0]
if index < 0:
print('Warning: unable to find position for buffer, using end of '
'list by default')
index = len(self.buffers)
return index
def closeEvent(self, event):
"""Called when QWeeChat window is closed."""
self.network.disconnect_weechat()
if self.debug_dialog:
self.debug_dialog.close()
config.write(self.config)
QtGui.QMainWindow.closeEvent(self, event)
app = QtGui.QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
app.setWindowIcon(QtGui.QIcon(
resource_filename(__name__, 'data/icons/weechat.png')))
main = MainWindow()
sys.exit(app.exec_())

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
#
# version.py - version of QWeeChat
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
VERSION = '0.0.1-dev'
def qweechat_version():
return VERSION

Binary file not shown.

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#

View File

@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
#
# color.py - remove/replace colors in WeeChat strings
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import re
RE_COLOR_ATTRS = r'[*!/_|]*'
RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS
RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS
RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT)
# \x19: color code, \x1A: set attribute, \x1B: remove attribute, \x1C: reset
RE_COLOR = re.compile(
r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|E|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|'
r'\x1B.|\x1C'
% (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY))
TERMINAL_COLORS = \
'000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e5' \
'4d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \
'00000000002a0000550000800000aa0000d4002a00002a2a' \
'002a55002a80002aaa002ad400550000552a005555005580' \
'0055aa0055d400800000802a0080550080800080aa0080d4' \
'00aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \
'00d45500d48000d4aa00d4d42a00002a002a2a00552a0080' \
'2a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \
'2a55002a552a2a55552a55802a55aa2a55d42a80002a802a' \
'2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \
'2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d4' \
'55000055002a5500555500805500aa5500d4552a00552a2a' \
'552a55552a80552aaa552ad455550055552a555555555580' \
'5555aa5555d455800055802a5580555580805580aa5580d4' \
'55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a' \
'55d45555d48055d4aa55d4d480000080002a800055800080' \
'8000aa8000d4802a00802a2a802a55802a80802aaa802ad4' \
'80550080552a8055558055808055aa8055d480800080802a' \
'8080558080808080aa8080d480aa0080aa2a80aa5580aa80' \
'80aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \
'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2a' \
'aa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \
'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4' \
'aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \
'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080' \
'd400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \
'd45500d4552ad45555d45580d455aad455d4d48000d4802a' \
'd48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \
'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d4' \
'0808081212121c1c1c2626263030303a3a3a4444444e4e4e' \
'5858586262626c6c6c7676768080808a8a8a9494949e9e9e' \
'a8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee'
# WeeChat basic colors (color name, index in terminal colors)
WEECHAT_BASIC_COLORS = (
('default', 0), ('black', 0), ('darkgray', 8), ('red', 1),
('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3),
('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5),
('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7),
('white', 0))
class Color():
def __init__(self, color_options, debug=False):
self.color_options = color_options
self.debug = debug
def _rgb_color(self, index):
color = TERMINAL_COLORS[index*6:(index*6)+6]
r = int(color[0:2], 16) * 0.85
g = int(color[2:4], 16) * 0.85
b = int(color[4:6], 16) * 0.85
return '%02x%02x%02x' % (r, g, b)
def _convert_weechat_color(self, color):
try:
index = int(color)
return '\x01(Fr%s)' % self.color_options[index]
except: # noqa: E722
print('Error decoding WeeChat color "%s"' % color)
return ''
def _convert_terminal_color(self, fg_bg, attrs, color):
try:
index = int(color)
return '\x01(%s%s#%s)' % (fg_bg, attrs, self._rgb_color(index))
except: # noqa: E722
print('Error decoding terminal color "%s"' % color)
return ''
def _convert_color_attr(self, fg_bg, color):
extended = False
if color[0].startswith('@'):
extended = True
color = color[1:]
attrs = ''
# keep_attrs = False
while color.startswith(('*', '!', '/', '_', '|')):
# TODO: manage the "keep attributes" flag
# if color[0] == '|':
# keep_attrs = True
attrs += color[0]
color = color[1:]
if extended:
return self._convert_terminal_color(fg_bg, attrs, color)
try:
index = int(color)
return self._convert_terminal_color(fg_bg, attrs,
WEECHAT_BASIC_COLORS[index][1])
except: # noqa: E722
print('Error decoding color "%s"' % color)
return ''
def _attrcode_to_char(self, code):
codes = {
'\x01': '*',
'\x02': '!',
'\x03': '/',
'\x04': '_',
}
return codes.get(code, '')
def _convert_color(self, match):
color = match.group(0)
if color[0] == '\x19':
if color[1] == 'b':
# bar code, ignored
return ''
elif color[1] == '\x1C':
# reset
return '\x01(Fr)\x01(Br)'
elif color[1] in ('F', 'B'):
# foreground or background
return self._convert_color_attr(color[1], color[2:])
elif color[1] == '*':
# foreground with optional background
items = color[2:].split(',')
s = self._convert_color_attr('F', items[0])
if len(items) > 1:
s += self._convert_color_attr('B', items[1])
return s
elif color[1] == '@':
# direct ncurses pair number, ignored
return ''
elif color[1] == 'E':
# text emphasis, ignored
return ''
if color[1:].isdigit():
return self._convert_weechat_color(int(color[1:]))
# color code
pass
elif color[0] == '\x1A':
# set attribute
return '\x01(+%s)' % self._attrcode_to_char(color[1])
elif color[0] == '\x1B':
# remove attribute
return '\x01(-%s)' % self._attrcode_to_char(color[1])
elif color[0] == '\x1C':
# reset
return '\x01(Fr)\x01(Br)'
# should never be executed!
return match.group(0)
def _convert_color_debug(self, match):
group = match.group(0)
for code in (0x01, 0x02, 0x03, 0x04, 0x19, 0x1A, 0x1B):
group = group.replace(chr(code), '<x%02X>' % code)
return group
def convert(self, text):
if not text:
return ''
if self.debug:
return RE_COLOR.sub(self._convert_color_debug, text)
else:
return RE_COLOR.sub(self._convert_color, text)
def remove(text):
"""Remove colors in a WeeChat string."""
if not text:
return ''
return re.sub(RE_COLOR, '', text)

Binary file not shown.

View File

@ -0,0 +1,356 @@
# -*- coding: utf-8 -*-
#
# protocol.py - decode binary messages received from WeeChat/relay
#
# Copyright (C) 2011-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
#
# For info about protocol and format of messages, please read document
# "WeeChat Relay Protocol", available at: https://weechat.org/doc/
#
# History:
#
# 2011-11-23, Sébastien Helleu <flashcode@flashtux.org>:
# start dev
#
import collections
import struct
import zlib
if hasattr(collections, 'OrderedDict'):
# python >= 2.7
class WeechatDict(collections.OrderedDict):
def __str__(self):
return '{%s}' % ', '.join(
['%s: %s' % (repr(key), repr(self[key])) for key in self])
else:
# python <= 2.6
WeechatDict = dict
class WeechatObject:
def __init__(self, objtype, value, separator='\n'):
self.objtype = objtype
self.value = value
self.separator = separator
self.indent = ' ' if separator == '\n' else ''
self.separator1 = '\n%s' % self.indent if separator == '\n' else ''
def _str_value(self, v):
if type(v) is str and v is not None:
return '\'%s\'' % v
return str(v)
def _str_value_hdata(self):
lines = ['%skeys: %s%s%spath: %s' % (self.separator1,
str(self.value['keys']),
self.separator,
self.indent,
str(self.value['path']))]
for i, item in enumerate(self.value['items']):
lines.append(' item %d:%s%s' % (
(i + 1), self.separator,
self.separator.join(
['%s%s: %s' % (self.indent * 2, key,
self._str_value(value))
for key, value in item.items()])))
return '\n'.join(lines)
def _str_value_infolist(self):
lines = ['%sname: %s' % (self.separator1, self.value['name'])]
for i, item in enumerate(self.value['items']):
lines.append(' item %d:%s%s' % (
(i + 1), self.separator,
self.separator.join(
['%s%s: %s' % (self.indent * 2, key,
self._str_value(value))
for key, value in item.items()])))
return '\n'.join(lines)
def _str_value_other(self):
return self._str_value(self.value)
def __str__(self):
self._obj_cb = {
'hda': self._str_value_hdata,
'inl': self._str_value_infolist,
}
return '%s: %s' % (self.objtype,
self._obj_cb.get(self.objtype,
self._str_value_other)())
class WeechatObjects(list):
def __init__(self, separator='\n'):
self.separator = separator
def __str__(self):
return self.separator.join([str(obj) for obj in self])
class WeechatMessage:
def __init__(self, size, size_uncompressed, compression, uncompressed,
msgid, objects):
self.size = size
self.size_uncompressed = size_uncompressed
self.compression = compression
self.uncompressed = uncompressed
self.msgid = msgid
self.objects = objects
def __str__(self):
if self.compression != 0:
return 'size: %d/%d (%d%%), id=\'%s\', objects:\n%s' % (
self.size, self.size_uncompressed,
100 - ((self.size * 100) // self.size_uncompressed),
self.msgid, self.objects)
else:
return 'size: %d, id=\'%s\', objects:\n%s' % (self.size,
self.msgid,
self.objects)
class Protocol:
"""Decode binary message received from WeeChat/relay."""
def __init__(self):
self._obj_cb = {
'chr': self._obj_char,
'int': self._obj_int,
'lon': self._obj_long,
'str': self._obj_str,
'buf': self._obj_buffer,
'ptr': self._obj_ptr,
'tim': self._obj_time,
'htb': self._obj_hashtable,
'hda': self._obj_hdata,
'inf': self._obj_info,
'inl': self._obj_infolist,
'arr': self._obj_array,
}
def _obj_type(self):
"""Read type in data (3 chars)."""
if len(self.data) < 3:
self.data = ''
return ''
objtype = str(self.data[0:3])
self.data = self.data[3:]
return objtype
def _obj_len_data(self, length_size):
"""Read length (1 or 4 bytes), then value with this length."""
if len(self.data) < length_size:
self.data = ''
return None
if length_size == 1:
length = struct.unpack('B', self.data[0:1])[0]
self.data = self.data[1:]
else:
length = self._obj_int()
if length < 0:
return None
if length > 0:
value = self.data[0:length]
self.data = self.data[length:]
else:
value = ''
return value
def _obj_char(self):
"""Read a char in data."""
if len(self.data) < 1:
return 0
value = struct.unpack('b', self.data[0:1])[0]
self.data = self.data[1:]
return value
def _obj_int(self):
"""Read an integer in data (4 bytes)."""
if len(self.data) < 4:
self.data = ''
return 0
value = struct.unpack('>i', self.data[0:4])[0]
self.data = self.data[4:]
return value
def _obj_long(self):
"""Read a long integer in data (length on 1 byte + value as string)."""
value = self._obj_len_data(1)
if value is None:
return None
return int(str(value))
def _obj_str(self):
"""Read a string in data (length on 4 bytes + content)."""
value = self._obj_len_data(4)
if value is None:
return None
return str(value)
def _obj_buffer(self):
"""Read a buffer in data (length on 4 bytes + data)."""
return self._obj_len_data(4)
def _obj_ptr(self):
"""Read a pointer in data (length on 1 byte + value as string)."""
value = self._obj_len_data(1)
if value is None:
return None
return '0x%s' % str(value)
def _obj_time(self):
"""Read a time in data (length on 1 byte + value as string)."""
value = self._obj_len_data(1)
if value is None:
return None
return int(str(value))
def _obj_hashtable(self):
"""
Read a hashtable in data
(type for keys + type for values + count + items).
"""
type_keys = self._obj_type()
type_values = self._obj_type()
count = self._obj_int()
hashtable = WeechatDict()
for _ in range(count):
key = self._obj_cb[type_keys]()
value = self._obj_cb[type_values]()
hashtable[key] = value
return hashtable
def _obj_hdata(self):
"""Read a hdata in data."""
path = self._obj_str()
keys = self._obj_str()
count = self._obj_int()
list_path = path.split('/') if path else []
list_keys = keys.split(',') if keys else []
keys_types = []
dict_keys = WeechatDict()
for key in list_keys:
items = key.split(':')
keys_types.append(items)
dict_keys[items[0]] = items[1]
items = []
for _ in range(count):
item = WeechatDict()
item['__path'] = []
pointers = []
for _ in enumerate(list_path):
pointers.append(self._obj_ptr())
for key, objtype in keys_types:
item[key] = self._obj_cb[objtype]()
item['__path'] = pointers
items.append(item)
return {
'path': list_path,
'keys': dict_keys,
'count': count,
'items': items,
}
def _obj_info(self):
"""Read an info in data."""
name = self._obj_str()
value = self._obj_str()
return (name, value)
def _obj_infolist(self):
"""Read an infolist in data."""
name = self._obj_str()
count_items = self._obj_int()
items = []
for _ in range(count_items):
count_vars = self._obj_int()
variables = WeechatDict()
for _ in range(count_vars):
var_name = self._obj_str()
var_type = self._obj_type()
var_value = self._obj_cb[var_type]()
variables[var_name] = var_value
items.append(variables)
return {
'name': name,
'items': items
}
def _obj_array(self):
"""Read an array of values in data."""
type_values = self._obj_type()
count_values = self._obj_int()
values = []
for _ in range(count_values):
values.append(self._obj_cb[type_values]())
return values
def decode(self, data, separator='\n'):
"""Decode binary data and return list of objects."""
self.data = data
size = len(self.data)
size_uncompressed = size
uncompressed = None
# uncompress data (if it is compressed)
compression = struct.unpack('b', self.data[4:5])[0]
if compression:
uncompressed = zlib.decompress(self.data[5:])
size_uncompressed = len(uncompressed) + 5
uncompressed = '%s%s%s' % (struct.pack('>i', size_uncompressed),
struct.pack('b', 0), uncompressed)
self.data = uncompressed
else:
uncompressed = self.data[:]
# skip length and compression flag
self.data = self.data[5:]
# read id
msgid = self._obj_str()
if msgid is None:
msgid = ''
# read objects
objects = WeechatObjects(separator=separator)
while len(self.data) > 0:
objtype = self._obj_type()
value = self._obj_cb[objtype]()
objects.append(WeechatObject(objtype, value, separator=separator))
return WeechatMessage(size, size_uncompressed, compression,
uncompressed, msgid, objects)
def hex_and_ascii(data, bytes_per_line=10):
"""Convert a QByteArray to hex + ascii output."""
num_lines = ((len(data) - 1) // bytes_per_line) + 1
if num_lines == 0:
return ''
lines = []
for i in range(num_lines):
str_hex = []
str_ascii = []
for char in data[i*bytes_per_line:(i*bytes_per_line)+bytes_per_line]:
byte = struct.unpack('B', char)[0]
str_hex.append('%02X' % int(byte))
if byte >= 32 and byte <= 127:
str_ascii.append(char)
else:
str_ascii.append('.')
fmt = '%%-%ds %%s' % ((bytes_per_line * 3) - 1)
lines.append(fmt % (' '.join(str_hex), ''.join(str_ascii)))
return '\n'.join(lines)

View File

@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
#
# testproto.py - command-line program for testing WeeChat/relay protocol
#
# Copyright (C) 2013-2020 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# QWeeChat 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
"""
Command-line program for testing WeeChat/relay protocol.
"""
from __future__ import print_function
import argparse
import os
import select
import shlex
import socket
import struct
import sys
import time
import traceback
import protocol # WeeChat/relay protocol
from .. version import qweechat_version
NAME = 'qweechat-testproto'
class TestProto(object):
"""Test of WeeChat/relay protocol."""
def __init__(self, args):
self.args = args
self.sock = None
self.has_quit = False
self.address = '{self.args.hostname}/{self.args.port} ' \
'(IPv{0})'.format(6 if self.args.ipv6 else 4, self=self)
def connect(self):
"""
Connect to WeeChat/relay.
Return True if OK, False if error.
"""
inet = socket.AF_INET6 if self.args.ipv6 else socket.AF_INET
try:
self.sock = socket.socket(inet, socket.SOCK_STREAM)
self.sock.connect((self.args.hostname, self.args.port))
except: # noqa: E722
if self.sock:
self.sock.close()
print('Failed to connect to', self.address)
return False
print('Connected to', self.address)
return True
def send(self, messages):
"""
Send a text message to WeeChat/relay.
Return True if OK, False if error.
"""
try:
for msg in messages.split('\n'):
if msg == 'quit':
self.has_quit = True
self.sock.sendall(msg + '\n')
print('\x1b[33m<-- ' + msg + '\x1b[0m')
except: # noqa: E722
traceback.print_exc()
print('Failed to send message')
return False
return True
def decode(self, message):
"""
Decode a binary message received from WeeChat/relay.
Return True if OK, False if error.
"""
try:
proto = protocol.Protocol()
msgd = proto.decode(message,
separator='\n' if self.args.debug > 0
else ', ')
print('')
if self.args.debug >= 2 and msgd.uncompressed:
# display raw message
print('\x1b[32m--> message uncompressed ({0} bytes):\n'
'{1}\x1b[0m'
''.format(msgd.size_uncompressed,
protocol.hex_and_ascii(msgd.uncompressed, 20)))
# display decoded message
print('\x1b[32m--> {0}\x1b[0m'.format(msgd))
except: # noqa: E722
traceback.print_exc()
print('Error while decoding message from WeeChat')
return False
return True
def send_stdin(self):
"""
Send commands from standard input if some data is available.
Return True if OK (it's OK if stdin has no commands),
False if error.
"""
inr = select.select([sys.stdin], [], [], 0)[0]
if inr:
data = os.read(sys.stdin.fileno(), 4096)
if data:
if not self.send(data.strip()):
# self.sock.close()
return False
# open stdin to read user commands
sys.stdin = open('/dev/tty')
return True
def mainloop(self):
"""
Main loop: read keyboard, send commands, read socket,
decode/display binary messages received from WeeChat/relay.
Return 0 if OK, 4 if send error, 5 if decode error.
"""
if self.has_quit:
return 0
message = ''
recvbuf = ''
prompt = '\x1b[36mrelay> \x1b[0m'
sys.stdout.write(prompt)
sys.stdout.flush()
try:
while not self.has_quit:
inr = select.select([sys.stdin, self.sock], [], [], 1)[0]
for _file in inr:
if _file == sys.stdin:
buf = os.read(_file.fileno(), 4096)
if buf:
message += buf
if '\n' in message:
messages = message.split('\n')
msgsent = '\n'.join(messages[:-1])
if msgsent and not self.send(msgsent):
return 4
message = messages[-1]
sys.stdout.write(prompt + message)
sys.stdout.flush()
else:
buf = _file.recv(4096)
if buf:
recvbuf += buf
while len(recvbuf) >= 4:
remainder = None
length = struct.unpack('>i', recvbuf[0:4])[0]
if len(recvbuf) < length:
# partial message, just wait for the
# end of message
break
# more than one message?
if length < len(recvbuf):
# save beginning of another message
remainder = recvbuf[length:]
recvbuf = recvbuf[0:length]
if not self.decode(recvbuf):
return 5
if remainder:
recvbuf = remainder
else:
recvbuf = ''
sys.stdout.write(prompt + message)
sys.stdout.flush()
except: # noqa: E722
traceback.print_exc()
self.send('quit')
return 0
def __del__(self):
print('Closing connection with', self.address)
time.sleep(0.5)
self.sock.close()
def main():
"""Main function."""
# parse command line arguments
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
fromfile_prefix_chars='@',
description='Command-line program for testing WeeChat/relay protocol.',
epilog='''
Environment variable "QWEECHAT_PROTO_OPTIONS" can be set with default options.
Argument "@file.txt" can be used to read default options in a file.
Some commands can be piped to the script, for example:
echo "init password=xxxx" | {name} localhost 5000
{name} localhost 5000 < commands.txt
The script returns:
0: OK
2: wrong arguments (command line)
3: connection error
4: send error (message sent to WeeChat)
5: decode error (message received from WeeChat)
'''.format(name=NAME))
parser.add_argument('-6', '--ipv6', action='store_true',
help='connect using IPv6')
parser.add_argument('-d', '--debug', action='count', default=0,
help='debug mode: long objects view '
'(-dd: display raw messages)')
parser.add_argument('-v', '--version', action='version',
version=qweechat_version())
parser.add_argument('hostname',
help='hostname (or IP address) of machine running '
'WeeChat/relay')
parser.add_argument('port', type=int,
help='port of machine running WeeChat/relay')
if len(sys.argv) == 1:
parser.print_help()
sys.exit(0)
_args = parser.parse_args(
shlex.split(os.getenv('QWEECHAT_PROTO_OPTIONS') or '') + sys.argv[1:])
test = TestProto(_args)
# connect to WeeChat/relay
if not test.connect():
sys.exit(3)
# send commands from standard input if some data is available
if not test.send_stdin():
sys.exit(4)
# main loop (wait commands, display messages received)
returncode = test.mainloop()
del test
sys.exit(returncode)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 154 KiB

View File

@ -0,0 +1,38 @@
checkbox-checked-dark
checkbox-checked-insensitive-dark
checkbox-checked-insensitive
checkbox-checked
checkbox-mixed-dark
checkbox-mixed-insensitive-dark
checkbox-mixed-insensitive
checkbox-mixed
checkbox-unchecked-dark
checkbox-unchecked-insensitive-dark
checkbox-unchecked-insensitive
checkbox-unchecked
grid-selection-checked-dark
grid-selection-checked
grid-selection-unchecked-dark
grid-selection-unchecked
menuitem-checkbox-checked-hover
menuitem-checkbox-checked-insensitive
menuitem-checkbox-checked
menuitem-checkbox-mixed-hover
menuitem-checkbox-mixed-insensitive
menuitem-checkbox-mixed
menuitem-radio-checked-hover
menuitem-radio-checked-insensitive
menuitem-radio-checked
pane-handle
radio-checked-dark
radio-checked-insensitive-dark
radio-checked-insensitive
radio-checked
radio-mixed-dark
radio-mixed-insensitive-dark
radio-mixed-insensitive
radio-mixed
radio-unchecked-dark
radio-unchecked-insensitive-dark
radio-unchecked-insensitive
radio-unchecked

View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
for f in $@; do
rsvg-convert -d 300 -p 300 -f svg $f -o $f.bak ; mv $f.bak $f
done

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 16.667969 8.332031 L 116.667969 8.332031 C 121.257812 8.332031 125 12.078125 125 16.667969 L 125 116.667969 C 125 121.257812 121.257812 125 116.667969 125 L 16.667969 125 C 12.078125 125 8.332031 121.257812 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.078125 12.078125 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:1;" d="M 97.101562 32.648438 C 94.074219 32.746094 91.046875 34.015625 88.898438 36.199219 L 55.304688 68.164062 L 43.816406 55.273438 C 39.714844 49.804688 30.730469 49.21875 25.976562 54.101562 C 21.222656 58.984375 21.875 68.164062 27.277344 72.265625 L 47.136719 93.621094 C 54.949219 101.433594 58.464844 100.390625 66.339844 92.480469 C 66.339844 92.480469 93.425781 59.894531 109.308594 48.894531 C 112.824219 45.574219 114.289062 44.628906 112.433594 40.136719 C 110.613281 35.613281 101.886719 32.488281 97.101562 32.648438 Z M 97.101562 32.648438 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 14.550781 5.695312 L 118.78125 5.695312 C 123.371094 5.695312 127.117188 9.441406 127.117188 14.03125 L 127.117188 118.75 C 127.117188 123.371094 123.371094 127.082031 118.78125 127.082031 L 14.550781 127.082031 C 9.960938 127.082031 6.21875 123.371094 6.21875 118.75 L 6.21875 14.03125 C 6.21875 9.441406 9.960938 5.695312 14.550781 5.695312 Z M 14.550781 5.695312 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.15;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.3;" d="M 97.101562 32.648438 C 94.074219 32.746094 91.046875 34.015625 88.898438 36.199219 L 55.304688 68.164062 L 43.816406 55.273438 C 39.714844 49.804688 30.730469 49.21875 25.976562 54.101562 C 21.222656 58.984375 21.875 68.164062 27.277344 72.265625 L 47.136719 93.621094 C 54.949219 101.433594 58.464844 100.390625 66.339844 92.480469 C 66.339844 92.480469 93.425781 59.894531 109.308594 48.894531 C 112.824219 45.574219 114.289062 44.628906 112.433594 40.136719 C 110.613281 35.613281 101.886719 32.488281 97.101562 32.648438 Z M 97.101562 32.648438 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 14.550781 5.695312 L 118.78125 5.695312 C 123.371094 5.695312 127.117188 9.441406 127.117188 14.03125 L 127.117188 118.75 C 127.117188 123.371094 123.371094 127.082031 118.78125 127.082031 L 14.550781 127.082031 C 9.960938 127.082031 6.21875 123.371094 6.21875 118.75 L 6.21875 14.03125 C 6.21875 9.441406 9.960938 5.695312 14.550781 5.695312 Z M 14.550781 5.695312 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.15;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.3;" d="M 97.101562 32.648438 C 94.074219 32.746094 91.046875 34.015625 88.898438 36.199219 L 55.304688 68.164062 L 43.816406 55.273438 C 39.714844 49.804688 30.730469 49.21875 25.976562 54.101562 C 21.222656 58.984375 21.875 68.164062 27.277344 72.265625 L 47.136719 93.621094 C 54.949219 101.433594 58.464844 100.390625 66.339844 92.480469 C 66.339844 92.480469 93.425781 59.894531 109.308594 48.894531 C 112.824219 45.574219 114.289062 44.628906 112.433594 40.136719 C 110.613281 35.613281 101.886719 32.488281 97.101562 32.648438 Z M 97.101562 32.648438 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 16.667969 8.332031 L 116.667969 8.332031 C 121.257812 8.332031 125 12.078125 125 16.667969 L 125 116.667969 C 125 121.257812 121.257812 125 116.667969 125 L 16.667969 125 C 12.078125 125 8.332031 121.257812 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.078125 12.078125 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:1;" d="M 97.101562 32.648438 C 94.074219 32.746094 91.046875 34.015625 88.898438 36.199219 L 55.304688 68.164062 L 43.816406 55.273438 C 39.714844 49.804688 30.730469 49.21875 25.976562 54.101562 C 21.222656 58.984375 21.875 68.164062 27.277344 72.265625 L 47.136719 93.621094 C 54.949219 101.433594 58.464844 100.390625 66.339844 92.480469 C 66.339844 92.480469 93.425781 59.894531 109.308594 48.894531 C 112.824219 45.574219 114.289062 44.628906 112.433594 40.136719 C 110.613281 35.613281 101.886719 32.488281 97.101562 32.648438 Z M 97.101562 32.648438 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 16.667969 8.332031 L 116.667969 8.332031 C 121.257812 8.332031 125 12.078125 125 16.667969 L 125 116.667969 C 125 121.257812 121.257812 125 116.667969 125 L 16.667969 125 C 12.078125 125 8.332031 121.257812 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.078125 12.078125 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:1;" d="M 33.332031 58.332031 L 100 58.332031 L 100 75 L 33.332031 75 Z M 33.332031 58.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 14.453125 7.292969 L 119.402344 7.292969 C 124.25 7.292969 128.15625 11.101562 128.15625 15.851562 L 128.15625 118.554688 C 128.15625 123.273438 124.25 127.117188 119.402344 127.117188 L 14.453125 127.117188 C 9.601562 127.117188 5.695312 123.273438 5.695312 118.554688 L 5.695312 15.851562 C 5.695312 11.101562 9.601562 7.292969 14.453125 7.292969 Z M 14.453125 7.292969 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.15;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.3;" d="M 33.332031 58.332031 L 100 58.332031 L 100 75 L 33.332031 75 Z M 33.332031 58.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 14.453125 7.292969 L 119.402344 7.292969 C 124.25 7.292969 128.15625 11.101562 128.15625 15.851562 L 128.15625 118.554688 C 128.15625 123.273438 124.25 127.117188 119.402344 127.117188 L 14.453125 127.117188 C 9.601562 127.117188 5.695312 123.273438 5.695312 118.554688 L 5.695312 15.851562 C 5.695312 11.101562 9.601562 7.292969 14.453125 7.292969 Z M 14.453125 7.292969 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.15;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.3;" d="M 33.332031 58.332031 L 100 58.332031 L 100 75 L 33.332031 75 Z M 33.332031 58.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 16.667969 8.332031 L 116.667969 8.332031 C 121.257812 8.332031 125 12.078125 125 16.667969 L 125 116.667969 C 125 121.257812 121.257812 125 116.667969 125 L 16.667969 125 C 12.078125 125 8.332031 121.257812 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.078125 12.078125 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:1;" d="M 33.332031 58.332031 L 100 58.332031 L 100 75 L 33.332031 75 Z M 33.332031 58.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 16.667969 8.332031 L 116.667969 8.332031 C 121.257812 8.332031 125 12.078125 125 16.667969 L 125 116.667969 C 125 121.257812 121.257812 125 116.667969 125 L 16.667969 125 C 12.078125 125 8.332031 121.257812 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.078125 12.078125 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 13.933594 5.175781 L 118.878906 5.175781 C 123.730469 5.175781 127.636719 9.015625 127.636719 13.800781 L 127.636719 117.414062 C 127.636719 122.199219 123.730469 126.042969 118.878906 126.042969 L 13.933594 126.042969 C 9.082031 126.042969 5.175781 122.199219 5.175781 117.414062 L 5.175781 13.800781 C 5.175781 9.015625 9.082031 5.175781 13.933594 5.175781 Z M 13.933594 5.175781 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.15;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 13.933594 5.175781 L 118.878906 5.175781 C 123.730469 5.175781 127.636719 9.015625 127.636719 13.800781 L 127.636719 117.414062 C 127.636719 122.199219 123.730469 126.042969 118.878906 126.042969 L 13.933594 126.042969 C 9.082031 126.042969 5.175781 122.199219 5.175781 117.414062 L 5.175781 13.800781 C 5.175781 9.015625 9.082031 5.175781 13.933594 5.175781 Z M 13.933594 5.175781 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.15;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133pt" height="133pt" viewBox="0 0 133 133" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 16.667969 8.332031 L 116.667969 8.332031 C 121.257812 8.332031 125 12.078125 125 16.667969 L 125 116.667969 C 125 121.257812 121.257812 125 116.667969 125 L 16.667969 125 C 12.078125 125 8.332031 121.257812 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.078125 12.078125 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 16.667969 0 C 7.421875 0 0 7.421875 0 16.667969 L 0 116.667969 C 0 125.910156 7.421875 133.332031 16.667969 133.332031 L 116.667969 133.332031 C 125.910156 133.332031 133.332031 125.910156 133.332031 116.667969 L 133.332031 16.667969 C 133.332031 7.421875 125.910156 0 116.667969 0 Z M 16.667969 8.332031 L 116.667969 8.332031 C 121.289062 8.332031 125 12.042969 125 16.667969 L 125 116.667969 C 125 121.289062 121.289062 125 116.667969 125 L 16.667969 125 C 12.042969 125 8.332031 121.289062 8.332031 116.667969 L 8.332031 16.667969 C 8.332031 12.042969 12.042969 8.332031 16.667969 8.332031 Z M 16.667969 8.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="333pt" height="333pt" viewBox="0 0 333 333" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 20.246094 11.914062 L 316.082031 11.914062 C 320.671875 11.914062 324.414062 15.625 324.414062 20.246094 L 324.414062 316.082031 C 324.414062 320.671875 320.671875 324.414062 316.082031 324.414062 L 20.246094 324.414062 C 15.625 324.414062 11.914062 320.671875 11.914062 316.082031 L 11.914062 20.246094 C 11.914062 15.625 15.625 11.914062 20.246094 11.914062 Z M 20.246094 11.914062 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 242.773438 81.640625 C 235.15625 81.835938 227.636719 85.027344 222.234375 90.527344 L 138.3125 170.441406 L 109.503906 138.183594 C 99.25 124.511719 76.789062 123.046875 64.910156 135.253906 C 53.027344 147.492188 54.6875 170.378906 68.164062 180.695312 L 117.839844 234.015625 C 137.335938 253.546875 146.160156 250.945312 165.851562 231.21875 C 165.851562 231.21875 233.5625 149.773438 273.273438 122.234375 C 282.03125 113.898438 285.710938 111.589844 281.121094 100.324219 C 276.5625 89.03125 254.753906 81.21875 242.773438 81.640625 Z M 242.773438 81.640625 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="333pt" height="333pt" viewBox="0 0 333 333" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 20.246094 11.914062 L 316.082031 11.914062 C 320.671875 11.914062 324.414062 15.625 324.414062 20.246094 L 324.414062 316.082031 C 324.414062 320.671875 320.671875 324.414062 316.082031 324.414062 L 20.246094 324.414062 C 15.625 324.414062 11.914062 320.671875 11.914062 316.082031 L 11.914062 20.246094 C 11.914062 15.625 15.625 11.914062 20.246094 11.914062 Z M 20.246094 11.914062 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 242.773438 81.640625 C 235.15625 81.835938 227.636719 85.027344 222.234375 90.527344 L 138.3125 170.441406 L 109.503906 138.183594 C 99.25 124.511719 76.789062 123.046875 64.910156 135.253906 C 53.027344 147.492188 54.6875 170.378906 68.164062 180.695312 L 117.839844 234.015625 C 137.335938 253.546875 146.160156 250.945312 165.851562 231.21875 C 165.851562 231.21875 233.5625 149.773438 273.273438 122.234375 C 282.03125 113.898438 285.710938 111.589844 281.121094 100.324219 C 276.5625 89.03125 254.753906 81.21875 242.773438 81.640625 Z M 242.773438 81.640625 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="333pt" height="333pt" viewBox="0 0 333 333" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 20.246094 11.914062 L 316.082031 11.914062 C 320.671875 11.914062 324.414062 15.625 324.414062 20.246094 L 324.414062 316.082031 C 324.414062 320.671875 320.671875 324.414062 316.082031 324.414062 L 20.246094 324.414062 C 15.625 324.414062 11.914062 320.671875 11.914062 316.082031 L 11.914062 20.246094 C 11.914062 15.625 15.625 11.914062 20.246094 11.914062 Z M 20.246094 11.914062 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="333pt" height="333pt" viewBox="0 0 333 333" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:#211921;fill-opacity:1;" d="M 20.246094 11.914062 L 316.082031 11.914062 C 320.671875 11.914062 324.414062 15.625 324.414062 20.246094 L 324.414062 316.082031 C 324.414062 320.671875 320.671875 324.414062 316.082031 324.414062 L 20.246094 324.414062 C 15.625 324.414062 11.914062 320.671875 11.914062 316.082031 L 11.914062 20.246094 C 11.914062 15.625 15.625 11.914062 20.246094 11.914062 Z M 20.246094 11.914062 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#040304;fill-opacity:1;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:#3ee77b;fill-opacity:0.30;" d="M 41.667969 0 C 18.585938 0 0 18.585938 0 41.667969 L 0 291.667969 C 0 314.746094 18.585938 333.332031 41.667969 333.332031 L 291.667969 333.332031 C 314.746094 333.332031 333.332031 314.746094 333.332031 291.667969 L 333.332031 41.667969 C 333.332031 18.585938 314.746094 0 291.667969 0 Z M 41.667969 17.121094 L 291.667969 17.121094 C 303.222656 17.121094 316.960938 30.109375 316.960938 41.667969 L 316.960938 291.667969 C 316.960938 303.222656 303.222656 316.960938 291.667969 316.960938 L 41.667969 316.960938 C 30.109375 316.960938 17.121094 303.222656 17.121094 291.667969 L 17.121094 41.667969 C 17.121094 30.109375 30.109375 17.121094 41.667969 17.121094 Z M 41.667969 17.121094 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Some files were not shown because too many files have changed in this diff Show More