Merge remote-tracking branch 'upstream/master' into master-windows-changes
This commit is contained in:
commit
7872d21f03
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
|
@ -5,10 +5,35 @@ jobs:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- run: sudo apt-get update
|
- run: sudo apt-get update
|
||||||
- run: sudo apt-get remove libunwind-14-dev
|
- run: sudo apt-get remove libunwind-14-dev
|
||||||
- run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev libadwaita-1-dev
|
- run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev libadwaita-1-dev
|
||||||
- run: ./configure --with-tests --with-libsignal-in-tree
|
- run: ./configure --with-tests --with-libsignal-in-tree
|
||||||
- run: make
|
- run: make
|
||||||
- run: build/xmpp-vala-test
|
- run: build/xmpp-vala-test
|
||||||
- run: build/signal-protocol-vala-test
|
- run: build/signal-protocol-vala-test
|
||||||
|
build-meson:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- run: sudo apt-get update
|
||||||
|
- run: sudo apt-get remove libunwind-14-dev
|
||||||
|
- run: sudo apt-get install -y build-essential gettext libadwaita-1-dev libgee-0.8-dev libgtk-4-dev libsqlite3-dev meson valac
|
||||||
|
- run: meson setup build
|
||||||
|
- run: meson compile -C build
|
||||||
|
build-flatpak:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
container:
|
||||||
|
image: bilelmoussaoui/flatpak-github-actions:gnome-44
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1
|
||||||
|
with:
|
||||||
|
manifest-path: im.dino.Dino.json
|
|
@ -1,14 +0,0 @@
|
||||||
include(PkgConfigWithFallback)
|
|
||||||
find_pkg_config_with_fallback(Gspell
|
|
||||||
PKG_CONFIG_NAME gspell-1
|
|
||||||
LIB_NAMES gspell-1
|
|
||||||
INCLUDE_NAMES gspell.h
|
|
||||||
INCLUDE_DIR_SUFFIXES gspell-1 gspell-1/gspell
|
|
||||||
DEPENDS GTK3
|
|
||||||
)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(Gspell
|
|
||||||
REQUIRED_VARS Gspell_LIBRARY
|
|
||||||
VERSION_VAR Gspell_VERSION)
|
|
||||||
|
|
108
dino.doap
108
dino.doap
|
@ -227,24 +227,28 @@
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0027.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0027.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0045.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0045.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -252,6 +256,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0047.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0047.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0261</xmpp:note>
|
<xmpp:note>For use with XEP-0261</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -259,12 +264,14 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html"/>
|
||||||
<xmpp:status>deprecated</xmpp:status>
|
<xmpp:status>deprecated</xmpp:status>
|
||||||
<xmpp:note>Migrating to XEP-0402 if supported by server</xmpp:note>
|
<xmpp:note>Migrating to XEP-0402 if supported by server</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0049.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0049.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -272,6 +279,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>Only for viewing avatars</xmpp:note>
|
<xmpp:note>Only for viewing avatars</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -279,12 +287,14 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0059.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0059.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0313</xmpp:note>
|
<xmpp:note>For use with XEP-0313</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -292,6 +302,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0065.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0065.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0260</xmpp:note>
|
<xmpp:note>For use with XEP-0260</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -299,42 +310,49 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0066.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0066.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note>For file transfers using XEP-0363</xmpp:note>
|
<xmpp:note>For file transfers using XEP-0363</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0077.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0077.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0082.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0082.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html"/>
|
||||||
<xmpp:status>deprecated</xmpp:status>
|
<xmpp:status>deprecated</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
<xmpp:note>Only to fetch Avatars from other users</xmpp:note>
|
<xmpp:note>Only to fetch Avatars from other users</xmpp:note>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
|
@ -342,78 +360,98 @@
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0177.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0203.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0203.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0222.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0222.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -421,6 +459,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0245.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0245.html"/>
|
||||||
<xmpp:version>1.0</xmpp:version>
|
<xmpp:version>1.0</xmpp:version>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -428,36 +467,49 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0249.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0249.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>No support for sending</xmpp:note>
|
<xmpp:note>No support for sending</xmpp:note>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0272.html"/>
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0293.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0293.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0294.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0294.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -465,6 +517,14 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0297.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0297.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0280</xmpp:note>
|
<xmpp:note>For use with XEP-0280</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0298.html"/>
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -479,7 +539,7 @@
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>Not for MUCs</xmpp:note>
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -487,18 +547,21 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>1.0.0</xmpp:version>
|
<xmpp:version>1.0.0</xmpp:version>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -506,24 +569,28 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0353.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0353.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>0.3.1</xmpp:version>
|
<xmpp:version>0.3.1</xmpp:version>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -531,6 +598,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>Only for outgoing messages</xmpp:note>
|
<xmpp:note>Only for outgoing messages</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -538,42 +606,71 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>0.3.0</xmpp:version>
|
<xmpp:version>0.3.0</xmpp:version>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0392.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.5</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0396.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0396.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.2</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0421.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.4</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0444.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.1.1</xmpp:version>
|
||||||
|
<xmpp:since>0.4</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -581,6 +678,15 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>No support for embedded thumbnails</xmpp:note>
|
<xmpp:note>No support for embedded thumbnails</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0461.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.2.0</xmpp:version>
|
||||||
|
<xmpp:since>0.4</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
108
dino.doap.in
108
dino.doap.in
|
@ -47,24 +47,28 @@
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0027.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0027.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0045.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0045.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -72,6 +76,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0047.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0047.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0261</xmpp:note>
|
<xmpp:note>For use with XEP-0261</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -79,12 +84,14 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html" />
|
||||||
<xmpp:status>deprecated</xmpp:status>
|
<xmpp:status>deprecated</xmpp:status>
|
||||||
<xmpp:note>Migrating to XEP-0402 if supported by server</xmpp:note>
|
<xmpp:note>Migrating to XEP-0402 if supported by server</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0049.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0049.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -92,6 +99,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>Only for viewing avatars</xmpp:note>
|
<xmpp:note>Only for viewing avatars</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -99,12 +107,14 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0059.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0059.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0313</xmpp:note>
|
<xmpp:note>For use with XEP-0313</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -112,6 +122,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0065.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0065.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0260</xmpp:note>
|
<xmpp:note>For use with XEP-0260</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -119,42 +130,49 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0066.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0066.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note>For file transfers using XEP-0363</xmpp:note>
|
<xmpp:note>For file transfers using XEP-0363</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0077.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0077.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0082.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0082.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html" />
|
||||||
<xmpp:status>deprecated</xmpp:status>
|
<xmpp:status>deprecated</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
<xmpp:note>Only to fetch Avatars from other users</xmpp:note>
|
<xmpp:note>Only to fetch Avatars from other users</xmpp:note>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
|
@ -162,78 +180,98 @@
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0177.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0203.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0203.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0222.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0222.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -241,6 +279,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0245.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0245.html" />
|
||||||
<xmpp:version>1.0</xmpp:version>
|
<xmpp:version>1.0</xmpp:version>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -248,36 +287,49 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0249.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0249.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>No support for sending</xmpp:note>
|
<xmpp:note>No support for sending</xmpp:note>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0261.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0272.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0293.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0293.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0294.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0294.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -285,6 +337,14 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0297.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0297.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note>For use with XEP-0280</xmpp:note>
|
<xmpp:note>For use with XEP-0280</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0298.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -299,7 +359,7 @@
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>Not for MUCs</xmpp:note>
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -307,18 +367,21 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>1.0.0</xmpp:version>
|
<xmpp:version>1.0.0</xmpp:version>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -326,24 +389,28 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0353.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0353.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>0.3.1</xmpp:version>
|
<xmpp:version>0.3.1</xmpp:version>
|
||||||
|
<xmpp:since>0.3</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -351,6 +418,7 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>Only for outgoing messages</xmpp:note>
|
<xmpp:note>Only for outgoing messages</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -358,42 +426,71 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>0.3.0</xmpp:version>
|
<xmpp:version>0.3.0</xmpp:version>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0391.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0392.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.5</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0396.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0396.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0410.html" />
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.2</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0421.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:since>0.4</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0444.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.1.1</xmpp:version>
|
||||||
|
<xmpp:since>0.4</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
|
@ -401,6 +498,15 @@
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note>No support for embedded thumbnails</xmpp:note>
|
<xmpp:note>No support for embedded thumbnails</xmpp:note>
|
||||||
|
<xmpp:since>0.1</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0461.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.2.0</xmpp:version>
|
||||||
|
<xmpp:since>0.4</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
75
im.dino.Dino.json
Normal file
75
im.dino.Dino.json
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"id": "im.dino.Dino",
|
||||||
|
"runtime": "org.gnome.Platform",
|
||||||
|
"runtime-version": "44",
|
||||||
|
"sdk": "org.gnome.Sdk",
|
||||||
|
"command": "dino",
|
||||||
|
"finish-args": [
|
||||||
|
"--share=ipc",
|
||||||
|
"--socket=fallback-x11",
|
||||||
|
"--socket=wayland",
|
||||||
|
"--socket=pulseaudio",
|
||||||
|
"--socket=gpg-agent",
|
||||||
|
"--share=network",
|
||||||
|
"--device=all",
|
||||||
|
"--talk-name=org.freedesktop.Notifications"
|
||||||
|
],
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"name": "libsignal-protocol-c",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"config-opts": [
|
||||||
|
"-DCMAKE_C_FLAGS=-fPIC"
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/include",
|
||||||
|
"/lib"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mar-v-in/libsignal-protocol-c.git",
|
||||||
|
"tag": "v2.3.3.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "qrencode",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"cleanup": [
|
||||||
|
"/bin",
|
||||||
|
"/include",
|
||||||
|
"/lib",
|
||||||
|
"/share/man"
|
||||||
|
],
|
||||||
|
"config-opts": [
|
||||||
|
"-DCMAKE_C_FLAGS=-fPIC"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://fukuchi.org/works/qrencode/qrencode-4.1.1.tar.gz",
|
||||||
|
"sha512": "209bb656ae3f391b03c7b3ceb03e34f7320b0105babf48b619e7a299528b8828449e0e7696f0b5db0d99170a81709d0518e34835229a748701e7df784e58a9ce"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dino",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"config-opts": [
|
||||||
|
"-DSOUP_VERSION=3"
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/include",
|
||||||
|
"/share/vala"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "dir",
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ find_packages(LIBDINO_PACKAGES REQUIRED
|
||||||
vala_precompile(LIBDINO_VALA_C
|
vala_precompile(LIBDINO_VALA_C
|
||||||
SOURCES
|
SOURCES
|
||||||
src/application.vala
|
src/application.vala
|
||||||
|
src/version.vala
|
||||||
|
|
||||||
src/dbus/login1.vala
|
src/dbus/login1.vala
|
||||||
src/dbus/notifications.vala
|
src/dbus/notifications.vala
|
||||||
|
|
86
libdino/meson.build
Normal file
86
libdino/meson.build
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# version_vala
|
||||||
|
dot_git = meson.current_source_dir() / '../.git'
|
||||||
|
version_file = meson.current_source_dir() / '../VERSION'
|
||||||
|
command = [prog_python, files('version.py'), version_file, '@OUTPUT@', '--git-repo', meson.current_source_dir()]
|
||||||
|
if prog_git.found()
|
||||||
|
command += ['--git', prog_git]
|
||||||
|
endif
|
||||||
|
depend_files = []
|
||||||
|
if fs.exists(dot_git)
|
||||||
|
depend_files += [dot_git]
|
||||||
|
endif
|
||||||
|
if fs.exists(version_file)
|
||||||
|
depend_files += [version_file]
|
||||||
|
endif
|
||||||
|
version_vala = custom_target('libdino_version_vala', command: command, output: 'version.vala', depend_files: depend_files)
|
||||||
|
|
||||||
|
# libdino
|
||||||
|
dependencies = [
|
||||||
|
dep_gdk_pixbuf,
|
||||||
|
dep_gee,
|
||||||
|
dep_gio,
|
||||||
|
dep_glib,
|
||||||
|
dep_gmodule,
|
||||||
|
dep_qlite,
|
||||||
|
dep_xmpp_vala
|
||||||
|
]
|
||||||
|
sources = files(
|
||||||
|
'src/application.vala',
|
||||||
|
'src/dbus/login1.vala',
|
||||||
|
'src/dbus/notifications.vala',
|
||||||
|
'src/dbus/upower.vala',
|
||||||
|
'src/entity/account.vala',
|
||||||
|
'src/entity/call.vala',
|
||||||
|
'src/entity/conversation.vala',
|
||||||
|
'src/entity/encryption.vala',
|
||||||
|
'src/entity/file_transfer.vala',
|
||||||
|
'src/entity/message.vala',
|
||||||
|
'src/entity/settings.vala',
|
||||||
|
'src/plugin/interfaces.vala',
|
||||||
|
'src/plugin/loader.vala',
|
||||||
|
'src/plugin/registry.vala',
|
||||||
|
'src/service/avatar_manager.vala',
|
||||||
|
'src/service/blocking_manager.vala',
|
||||||
|
'src/service/call_store.vala',
|
||||||
|
'src/service/call_state.vala',
|
||||||
|
'src/service/call_peer_state.vala',
|
||||||
|
'src/service/calls.vala',
|
||||||
|
'src/service/chat_interaction.vala',
|
||||||
|
'src/service/connection_manager.vala',
|
||||||
|
'src/service/content_item_store.vala',
|
||||||
|
'src/service/conversation_manager.vala',
|
||||||
|
'src/service/counterpart_interaction_manager.vala',
|
||||||
|
'src/service/database.vala',
|
||||||
|
'src/service/entity_capabilities_storage.vala',
|
||||||
|
'src/service/entity_info.vala',
|
||||||
|
'src/service/fallback_body.vala',
|
||||||
|
'src/service/file_manager.vala',
|
||||||
|
'src/service/file_transfer_storage.vala',
|
||||||
|
'src/service/history_sync.vala',
|
||||||
|
'src/service/jingle_file_transfers.vala',
|
||||||
|
'src/service/message_correction.vala',
|
||||||
|
'src/service/message_processor.vala',
|
||||||
|
'src/service/message_storage.vala',
|
||||||
|
'src/service/module_manager.vala',
|
||||||
|
'src/service/muc_manager.vala',
|
||||||
|
'src/service/notification_events.vala',
|
||||||
|
'src/service/presence_manager.vala',
|
||||||
|
'src/service/replies.vala',
|
||||||
|
'src/service/reactions.vala',
|
||||||
|
'src/service/registration.vala',
|
||||||
|
'src/service/roster_manager.vala',
|
||||||
|
'src/service/search_processor.vala',
|
||||||
|
'src/service/stream_interactor.vala',
|
||||||
|
'src/service/util.vala',
|
||||||
|
'src/util/display_name.vala',
|
||||||
|
'src/util/util.vala',
|
||||||
|
'src/util/weak_map.vala',
|
||||||
|
)
|
||||||
|
sources += [version_vala]
|
||||||
|
c_args = [
|
||||||
|
'-DDINO_SYSTEM_LIBDIR_NAME="@0@"'.format(get_option('prefix') / get_option('libdir')),
|
||||||
|
'-DDINO_SYSTEM_PLUGIN_DIR="@0@"'.format(get_option('prefix') / get_option('plugindir')),
|
||||||
|
'-DG_LOG_DOMAIN="libdino"',
|
||||||
|
]
|
||||||
|
lib_dino = library('dino', sources, c_args: c_args, include_directories: include_directories('src'), dependencies: dependencies)
|
||||||
|
dep_dino = declare_dependency(link_with: lib_dino, include_directories: include_directories('.', 'src'))
|
|
@ -2,7 +2,6 @@ using Dino.Entities;
|
||||||
|
|
||||||
namespace Dino {
|
namespace Dino {
|
||||||
|
|
||||||
extern const string VERSION;
|
|
||||||
public string get_version() { return VERSION; }
|
public string get_version() { return VERSION; }
|
||||||
public string get_short_version() {
|
public string get_short_version() {
|
||||||
if (!VERSION.contains("~")) return VERSION;
|
if (!VERSION.contains("~")) return VERSION;
|
||||||
|
|
|
@ -151,8 +151,9 @@ public interface ConversationItemWidgetInterface: Object {
|
||||||
public abstract void set_widget(Object object, WidgetType type, int priority);
|
public abstract void set_widget(Object object, WidgetType type, int priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate void MessageActionEvoked(Object button, Plugins.MetaConversationItem evoked_on, Object widget);
|
public delegate void MessageActionEvoked(Variant? variant);
|
||||||
public class MessageAction : Object {
|
public class MessageAction : Object {
|
||||||
|
public string name;
|
||||||
public bool sensitive = true;
|
public bool sensitive = true;
|
||||||
public string icon_name;
|
public string icon_name;
|
||||||
public string? tooltip;
|
public string? tooltip;
|
||||||
|
|
|
@ -3,14 +3,14 @@ using Gee;
|
||||||
namespace Dino.Plugins {
|
namespace Dino.Plugins {
|
||||||
|
|
||||||
public class Registry {
|
public class Registry {
|
||||||
internal HashMap<Entities.Encryption, EncryptionListEntry> encryption_list_entries = new HashMap<Entities.Encryption, EncryptionListEntry>();
|
public HashMap<Entities.Encryption, EncryptionListEntry> encryption_list_entries = new HashMap<Entities.Encryption, EncryptionListEntry>();
|
||||||
internal HashMap<string, CallEncryptionEntry> call_encryption_entries = new HashMap<string, CallEncryptionEntry>();
|
public HashMap<string, CallEncryptionEntry> call_encryption_entries = new HashMap<string, CallEncryptionEntry>();
|
||||||
internal ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
|
public ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
|
||||||
internal ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
|
public ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
|
||||||
internal Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
|
public Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
|
||||||
internal Gee.List<ConversationAdditionPopulator> conversation_addition_populators = new ArrayList<ConversationAdditionPopulator>();
|
public Gee.List<ConversationAdditionPopulator> conversation_addition_populators = new ArrayList<ConversationAdditionPopulator>();
|
||||||
internal Gee.List<NotificationPopulator> notification_populators = new ArrayList<NotificationPopulator>();
|
public Gee.List<NotificationPopulator> notification_populators = new ArrayList<NotificationPopulator>();
|
||||||
internal Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((a, b) => {
|
public Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((a, b) => {
|
||||||
return (int)(a.order - b.order);
|
return (int)(a.order - b.order);
|
||||||
});
|
});
|
||||||
public VideoCallPlugin? video_call_plugin;
|
public VideoCallPlugin? video_call_plugin;
|
||||||
|
|
|
@ -12,6 +12,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
public string id { get { return IDENTITY.id; } }
|
public string id { get { return IDENTITY.id; } }
|
||||||
|
|
||||||
public signal void received_avatar(Jid jid, Account account);
|
public signal void received_avatar(Jid jid, Account account);
|
||||||
|
public signal void fetched_avatar(Jid jid, Account account);
|
||||||
|
|
||||||
private enum Source {
|
private enum Source {
|
||||||
USER_AVATARS,
|
USER_AVATARS,
|
||||||
|
@ -25,6 +26,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
||||||
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
|
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
|
||||||
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
|
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
|
||||||
|
private HashSet<string> pending_fetch = new HashSet<string>();
|
||||||
private const int MAX_PIXEL = 192;
|
private const int MAX_PIXEL = 192;
|
||||||
|
|
||||||
public static void start(StreamInteractor stream_interactor, Database db) {
|
public static void start(StreamInteractor stream_interactor, Database db) {
|
||||||
|
@ -45,6 +47,18 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File? get_avatar_file(Account account, Jid jid_) {
|
||||||
|
string? hash = get_avatar_hash(account, jid_);
|
||||||
|
if (hash == null) return null;
|
||||||
|
File file = File.new_for_path(Path.build_filename(folder, hash));
|
||||||
|
if (!file.query_exists()) {
|
||||||
|
fetch_and_store_for_jid(account, jid_);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string? get_avatar_hash(Account account, Jid jid_) {
|
private string? get_avatar_hash(Account account, Jid jid_) {
|
||||||
Jid jid = jid_;
|
Jid jid = jid_;
|
||||||
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
||||||
|
@ -59,6 +73,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Version (deprecated = true)]
|
||||||
public bool has_avatar_cached(Account account, Jid jid) {
|
public bool has_avatar_cached(Account account, Jid jid) {
|
||||||
string? hash = get_avatar_hash(account, jid);
|
string? hash = get_avatar_hash(account, jid);
|
||||||
return hash != null && cached_pixbuf.has_key(hash);
|
return hash != null && cached_pixbuf.has_key(hash);
|
||||||
|
@ -68,6 +83,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return get_avatar_hash(account, jid) != null;
|
return get_avatar_hash(account, jid) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Version (deprecated = true)]
|
||||||
public Pixbuf? get_cached_avatar(Account account, Jid jid_) {
|
public Pixbuf? get_cached_avatar(Account account, Jid jid_) {
|
||||||
string? hash = get_avatar_hash(account, jid_);
|
string? hash = get_avatar_hash(account, jid_);
|
||||||
if (hash == null) return null;
|
if (hash == null) return null;
|
||||||
|
@ -75,6 +91,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Version (deprecated = true)]
|
||||||
public async Pixbuf? get_avatar(Account account, Jid jid_) {
|
public async Pixbuf? get_avatar(Account account, Jid jid_) {
|
||||||
Jid jid = jid_;
|
Jid jid = jid_;
|
||||||
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
||||||
|
@ -111,17 +128,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
cached_pixbuf[hash] = image;
|
cached_pixbuf[hash] = image;
|
||||||
} else {
|
} else {
|
||||||
Bytes? bytes = null;
|
if (yield fetch_and_store(stream, account, jid, source, hash)) {
|
||||||
if (source == 1) {
|
|
||||||
bytes = yield Xmpp.Xep.UserAvatars.fetch_image(stream, jid, hash);
|
|
||||||
} else if (source == 2) {
|
|
||||||
bytes = yield Xmpp.Xep.VCard.fetch_image(stream, jid, hash);
|
|
||||||
if (bytes == null && jid.is_bare()) {
|
|
||||||
db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(jid)).perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bytes != null) {
|
|
||||||
store_image(hash, bytes);
|
|
||||||
image = yield get_image(hash);
|
image = yield get_image(hash);
|
||||||
}
|
}
|
||||||
cached_pixbuf[hash] = image;
|
cached_pixbuf[hash] = image;
|
||||||
|
@ -162,7 +169,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) {
|
foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) {
|
||||||
user_avatars[entry.key] = entry.value;
|
on_user_avatar_received(account, entry.key, entry.value);
|
||||||
}
|
}
|
||||||
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
|
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
|
||||||
|
|
||||||
|
@ -172,7 +179,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
vcard_avatars[entry.key] = entry.value;
|
on_vcard_avatar_received(account, entry.key, entry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,12 +225,53 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void store_image(string id, Bytes data) {
|
public async bool fetch_and_store_for_jid(Account account, Jid jid) {
|
||||||
|
int source = -1;
|
||||||
|
string? hash = null;
|
||||||
|
if (user_avatars.has_key(jid)) {
|
||||||
|
hash = user_avatars[jid];
|
||||||
|
source = 1;
|
||||||
|
} else if (vcard_avatars.has_key(jid)) {
|
||||||
|
hash = vcard_avatars[jid];
|
||||||
|
source = 2;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
|
if (stream == null || !stream.negotiation_complete) return false;
|
||||||
|
|
||||||
|
return yield fetch_and_store(stream, account, jid, source, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async bool fetch_and_store(XmppStream stream, Account account, Jid jid, int source, string? hash) {
|
||||||
|
if (hash == null || pending_fetch.contains(hash)) return false;
|
||||||
|
|
||||||
|
pending_fetch.add(hash);
|
||||||
|
Bytes? bytes = null;
|
||||||
|
if (source == 1) {
|
||||||
|
bytes = yield Xmpp.Xep.UserAvatars.fetch_image(stream, jid, hash);
|
||||||
|
} else if (source == 2) {
|
||||||
|
bytes = yield Xmpp.Xep.VCard.fetch_image(stream, jid, hash);
|
||||||
|
if (bytes == null && jid.is_bare()) {
|
||||||
|
db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(jid)).perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes != null) {
|
||||||
|
yield store_image(hash, bytes);
|
||||||
|
fetched_avatar(jid, account);
|
||||||
|
}
|
||||||
|
pending_fetch.remove(hash);
|
||||||
|
return bytes != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void store_image(string id, Bytes data) {
|
||||||
File file = File.new_for_path(Path.build_filename(folder, id));
|
File file = File.new_for_path(Path.build_filename(folder, id));
|
||||||
try {
|
try {
|
||||||
if (file.query_exists()) file.delete(); //TODO y?
|
if (file.query_exists()) file.delete(); //TODO y?
|
||||||
DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
|
DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
|
||||||
fos.write_bytes_async.begin(data);
|
yield fos.write_bytes_async(data);
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
// Ignore: we failed in storing, so we refuse to display later...
|
// Ignore: we failed in storing, so we refuse to display later...
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ using Dino.Entities;
|
||||||
namespace Dino {
|
namespace Dino {
|
||||||
|
|
||||||
public class Database : Qlite.Database {
|
public class Database : Qlite.Database {
|
||||||
private const int VERSION = 25;
|
private const int VERSION = 26;
|
||||||
|
|
||||||
public class AccountTable : Table {
|
public class AccountTable : Table {
|
||||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||||
|
@ -93,6 +93,11 @@ public class Database : Qlite.Database {
|
||||||
|
|
||||||
// deduplication
|
// deduplication
|
||||||
index("message_account_counterpart_stanzaid_idx", {account_id, counterpart_id, stanza_id});
|
index("message_account_counterpart_stanzaid_idx", {account_id, counterpart_id, stanza_id});
|
||||||
|
index("message_account_counterpart_serverid_idx", {account_id, counterpart_id, server_id});
|
||||||
|
|
||||||
|
// message by marked
|
||||||
|
index("message_account_marked_idx", {account_id, marked});
|
||||||
|
|
||||||
fts({body});
|
fts({body});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,9 +282,14 @@ public class FileManager : StreamInteractionModule, Object {
|
||||||
if (conversation.type_.is_muc_semantic()) {
|
if (conversation.type_.is_muc_semantic()) {
|
||||||
file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid;
|
file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid;
|
||||||
file_transfer.direction = from.equals(file_transfer.ourpart) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED;
|
file_transfer.direction = from.equals(file_transfer.ourpart) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED;
|
||||||
|
} else {
|
||||||
|
if (from.equals_bare(conversation.account.bare_jid)) {
|
||||||
|
file_transfer.ourpart = from;
|
||||||
|
file_transfer.direction = FileTransfer.DIRECTION_SENT;
|
||||||
} else {
|
} else {
|
||||||
file_transfer.ourpart = conversation.account.full_jid;
|
file_transfer.ourpart = conversation.account.full_jid;
|
||||||
file_transfer.direction = from.equals_bare(file_transfer.ourpart) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED;
|
file_transfer.direction = FileTransfer.DIRECTION_RECEIVED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
file_transfer.time = time;
|
file_transfer.time = time;
|
||||||
file_transfer.local_time = local_time;
|
file_transfer.local_time = local_time;
|
||||||
|
|
|
@ -163,7 +163,7 @@ public class Dino.HistorySync {
|
||||||
if (current_row[db.mam_catchup.from_end]) return;
|
if (current_row[db.mam_catchup.from_end]) return;
|
||||||
|
|
||||||
debug("[%s] Fetching between ranges %s - %s", mam_server.to_string(), previous_row[db.mam_catchup.to_time].to_string(), current_row[db.mam_catchup.from_time].to_string());
|
debug("[%s] Fetching between ranges %s - %s", mam_server.to_string(), previous_row[db.mam_catchup.to_time].to_string(), current_row[db.mam_catchup.from_time].to_string());
|
||||||
current_row = yield fetch_between_ranges(account, mam_server, previous_row, current_row);
|
current_row = yield fetch_between_ranges(account, mam_server, previous_row, current_row, cancellable);
|
||||||
if (current_row == null) return;
|
if (current_row == null) return;
|
||||||
|
|
||||||
RowOption previous_row_opt = db.mam_catchup.select()
|
RowOption previous_row_opt = db.mam_catchup.select()
|
||||||
|
@ -214,13 +214,11 @@ public class Dino.HistorySync {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get PageResult.Duplicate, we still want to update the db row to the latest message.
|
|
||||||
|
|
||||||
// Catchup finished within first page. Update latest db entry.
|
// Catchup finished within first page. Update latest db entry.
|
||||||
if (latest_row_id != -1 &&
|
if (latest_row_id != -1 &&
|
||||||
page_result.page_result in new PageResult[] { PageResult.TargetReached, PageResult.NoMoreMessages, PageResult.Duplicate }) {
|
page_result.page_result in new PageResult[] { PageResult.TargetReached, PageResult.NoMoreMessages }) {
|
||||||
|
|
||||||
if (page_result.stanzas == null || page_result.stanzas.is_empty) return null;
|
if (page_result.stanzas == null) return null;
|
||||||
|
|
||||||
string latest_mam_id = page_result.query_result.last;
|
string latest_mam_id = page_result.query_result.last;
|
||||||
long latest_mam_time = (long) mam_times[account][latest_mam_id].to_unix();
|
long latest_mam_time = (long) mam_times[account][latest_mam_id].to_unix();
|
||||||
|
@ -272,7 +270,7 @@ public class Dino.HistorySync {
|
||||||
** Merges the `earlier_range` db row into the `later_range` db row.
|
** Merges the `earlier_range` db row into the `later_range` db row.
|
||||||
** @return The resulting range comprising `earlier_range`, `later_rage`, and everything in between. null if fetching/merge failed.
|
** @return The resulting range comprising `earlier_range`, `later_rage`, and everything in between. null if fetching/merge failed.
|
||||||
**/
|
**/
|
||||||
private async Row? fetch_between_ranges(Account account, Jid mam_server, Row earlier_range, Row later_range) {
|
private async Row? fetch_between_ranges(Account account, Jid mam_server, Row earlier_range, Row later_range, Cancellable? cancellable = null) {
|
||||||
int later_range_id = (int) later_range[db.mam_catchup.id];
|
int later_range_id = (int) later_range[db.mam_catchup.id];
|
||||||
DateTime earliest_time = new DateTime.from_unix_utc(earlier_range[db.mam_catchup.to_time]);
|
DateTime earliest_time = new DateTime.from_unix_utc(earlier_range[db.mam_catchup.to_time]);
|
||||||
DateTime latest_time = new DateTime.from_unix_utc(later_range[db.mam_catchup.from_time]);
|
DateTime latest_time = new DateTime.from_unix_utc(later_range[db.mam_catchup.from_time]);
|
||||||
|
@ -282,9 +280,9 @@ public class Dino.HistorySync {
|
||||||
earliest_time, earlier_range[db.mam_catchup.to_id],
|
earliest_time, earlier_range[db.mam_catchup.to_id],
|
||||||
latest_time, later_range[db.mam_catchup.from_id]);
|
latest_time, later_range[db.mam_catchup.from_id]);
|
||||||
|
|
||||||
PageRequestResult page_result = yield fetch_query(account, query_params, later_range_id);
|
PageRequestResult page_result = yield fetch_query(account, query_params, later_range_id, cancellable);
|
||||||
|
|
||||||
if (page_result.page_result == PageResult.TargetReached) {
|
if (page_result.page_result == PageResult.TargetReached || page_result.page_result == PageResult.NoMoreMessages) {
|
||||||
debug("[%s | %s] Merging range %i into %i", account.bare_jid.to_string(), mam_server.to_string(), earlier_range[db.mam_catchup.id], later_range_id);
|
debug("[%s | %s] Merging range %i into %i", account.bare_jid.to_string(), mam_server.to_string(), earlier_range[db.mam_catchup.id], later_range_id);
|
||||||
// Merge earlier range into later one.
|
// Merge earlier range into later one.
|
||||||
db.mam_catchup.update()
|
db.mam_catchup.update()
|
||||||
|
@ -330,9 +328,9 @@ public class Dino.HistorySync {
|
||||||
PageRequestResult? page_result = null;
|
PageRequestResult? page_result = null;
|
||||||
do {
|
do {
|
||||||
page_result = yield get_mam_page(account, query_params, page_result, cancellable);
|
page_result = yield get_mam_page(account, query_params, page_result, cancellable);
|
||||||
debug("Page result %s %b", page_result.page_result.to_string(), page_result.stanzas == null);
|
debug("[%s | %s] Page result %s (got stanzas: %s)", account.bare_jid.to_string(), query_params.mam_server.to_string(), page_result.page_result.to_string(), (page_result.stanzas != null).to_string());
|
||||||
|
|
||||||
if (page_result.page_result == PageResult.Error || page_result.page_result == PageResult.Cancelled || page_result.stanzas == null) return page_result;
|
if (page_result.page_result == PageResult.Error || page_result.page_result == PageResult.Cancelled || page_result.query_result.first == null) return page_result;
|
||||||
|
|
||||||
string earliest_mam_id = page_result.query_result.first;
|
string earliest_mam_id = page_result.query_result.first;
|
||||||
long earliest_mam_time = (long)mam_times[account][earliest_mam_id].to_unix();
|
long earliest_mam_time = (long)mam_times[account][earliest_mam_id].to_unix();
|
||||||
|
@ -357,7 +355,6 @@ public class Dino.HistorySync {
|
||||||
MorePagesAvailable,
|
MorePagesAvailable,
|
||||||
TargetReached,
|
TargetReached,
|
||||||
NoMoreMessages,
|
NoMoreMessages,
|
||||||
Duplicate,
|
|
||||||
Error,
|
Error,
|
||||||
Cancelled
|
Cancelled
|
||||||
}
|
}
|
||||||
|
@ -399,23 +396,25 @@ public class Dino.HistorySync {
|
||||||
string query_id = query_params.query_id;
|
string query_id = query_params.query_id;
|
||||||
string? after_id = query_params.start_id;
|
string? after_id = query_params.start_id;
|
||||||
|
|
||||||
|
var stanzas_for_query = stanzas.has_key(query_id) && !stanzas[query_id].is_empty ? stanzas[query_id] : null;
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas[query_id]);
|
stanzas.unset(query_id);
|
||||||
|
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stanzas.has_key(query_id) && !stanzas[query_id].is_empty) {
|
if (stanzas_for_query != null) {
|
||||||
|
|
||||||
// Check it we reached our target (from_id)
|
// Check it we reached our target (from_id)
|
||||||
foreach (Xmpp.MessageStanza message in stanzas[query_id]) {
|
foreach (Xmpp.MessageStanza message in stanzas_for_query) {
|
||||||
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
||||||
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
||||||
if (after_id != null && mam_message_flag.mam_id == after_id) {
|
if (after_id != null && mam_message_flag.mam_id == after_id) {
|
||||||
// Successfully fetched the whole range
|
// Successfully fetched the whole range
|
||||||
yield send_messages_back_into_pipeline(account, query_id, cancellable);
|
yield send_messages_back_into_pipeline(account, query_id, cancellable);
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,37 +422,9 @@ public class Dino.HistorySync {
|
||||||
// Message got filtered out by xmpp-vala, but succesful range fetch nevertheless
|
// Message got filtered out by xmpp-vala, but succesful range fetch nevertheless
|
||||||
yield send_messages_back_into_pipeline(account, query_id);
|
yield send_messages_back_into_pipeline(account, query_id);
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.Cancelled, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas[query_id]);
|
return new PageRequestResult(PageResult.TargetReached, query_result, stanzas_for_query);
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicates. Go through all messages and build a db query.
|
|
||||||
foreach (Xmpp.MessageStanza message in stanzas[query_id]) {
|
|
||||||
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
|
||||||
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
|
||||||
if (selection == null) {
|
|
||||||
selection = @"$(db.message.server_id) = ?";
|
|
||||||
} else {
|
|
||||||
selection += @" OR $(db.message.server_id) = ?";
|
|
||||||
}
|
|
||||||
selection_args += mam_message_flag.mam_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var duplicates_qry = db.message.select()
|
|
||||||
.with(db.message.account_id, "=", account.id)
|
|
||||||
.where(selection, selection_args);
|
|
||||||
// We don't want messages from different MAM servers to interfere with each other.
|
|
||||||
if (!query_params.mam_server.equals_bare(account.bare_jid)) {
|
|
||||||
duplicates_qry.with(db.message.counterpart_id, "=", db.get_jid_id(query_params.mam_server));
|
|
||||||
} else {
|
|
||||||
duplicates_qry.with(db.message.type_, "=", Message.Type.CHAT);
|
|
||||||
}
|
|
||||||
var duplicates_count = duplicates_qry.count();
|
|
||||||
if (duplicates_count > 0) {
|
|
||||||
// We got a duplicate although we thought we have to catch up.
|
|
||||||
// There was a server bug where prosody would send all messages if it didn't know the after ID that was given
|
|
||||||
page_result = PageResult.Duplicate;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +432,7 @@ public class Dino.HistorySync {
|
||||||
if (cancellable != null && cancellable.is_cancelled()) {
|
if (cancellable != null && cancellable.is_cancelled()) {
|
||||||
page_result = PageResult.Cancelled;
|
page_result = PageResult.Cancelled;
|
||||||
}
|
}
|
||||||
return new PageRequestResult(page_result, query_result, stanzas.has_key(query_id) ? stanzas[query_id] : null);
|
return new PageRequestResult(page_result, query_result, stanzas_for_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void send_messages_back_into_pipeline(Account account, string query_id, Cancellable? cancellable = null) {
|
private async void send_messages_back_into_pipeline(Account account, string query_id, Cancellable? cancellable = null) {
|
||||||
|
|
|
@ -451,6 +451,10 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conversation.get_send_typing_setting(stream_interactor) == Conversation.Setting.ON) {
|
||||||
|
ChatStateNotifications.add_state_to_message(new_message, ChatStateNotifications.STATE_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, new_message, (_, res) => {
|
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, new_message, (_, res) => {
|
||||||
try {
|
try {
|
||||||
stream.get_module(MessageModule.IDENTITY).send_message.end(res);
|
stream.get_module(MessageModule.IDENTITY).send_message.end(res);
|
||||||
|
|
6
libdino/src/version.vala
Normal file
6
libdino/src/version.vala
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// Not used in Meson.
|
||||||
|
namespace Dino {
|
||||||
|
|
||||||
|
extern const string VERSION;
|
||||||
|
|
||||||
|
}
|
36
libdino/version.py
Normal file
36
libdino/version.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
VERSION_VALA = """\
|
||||||
|
namespace Dino {{
|
||||||
|
|
||||||
|
public const string VERSION = "{}";
|
||||||
|
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def compute_version(file, git_repo, git):
|
||||||
|
try:
|
||||||
|
with open(file) as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return subprocess.check_output([git, "describe", "--tags"], cwd=git_repo, text=True).strip()
|
||||||
|
|
||||||
|
def generate_version_vala(version):
|
||||||
|
if "\\" in version or "\"" in version:
|
||||||
|
raise ValueError(f"invalid version {version!r}")
|
||||||
|
return VERSION_VALA.format(version)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
p = argparse.ArgumentParser(description="Compute the Dino version")
|
||||||
|
p.add_argument("--git-repo", help="Path to checked out git repository")
|
||||||
|
p.add_argument("--git", help="Path to git executable", default="git")
|
||||||
|
p.add_argument("version_file", metavar="VERSION_FILE", help="Use this file's contents as version if the file exists")
|
||||||
|
p.add_argument("output", metavar="OUTPUT", help="Vala file to output to")
|
||||||
|
args = p.parse_args()
|
||||||
|
out = generate_version_vala(compute_version(args.version_file, args.git_repo, args.git))
|
||||||
|
with open(args.output, "w") as f:
|
||||||
|
f.write(out)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -124,8 +124,6 @@ SOURCES
|
||||||
src/main.vala
|
src/main.vala
|
||||||
|
|
||||||
src/ui/application.vala
|
src/ui/application.vala
|
||||||
src/ui/avatar_drawer.vala
|
|
||||||
src/ui/avatar_image.vala
|
|
||||||
src/ui/conversation_list_titlebar.vala
|
src/ui/conversation_list_titlebar.vala
|
||||||
src/ui/conversation_view.vala
|
src/ui/conversation_view.vala
|
||||||
src/ui/conversation_view_controller.vala
|
src/ui/conversation_view_controller.vala
|
||||||
|
@ -209,6 +207,7 @@ SOURCES
|
||||||
src/ui/util/sizing_bin.vala
|
src/ui/util/sizing_bin.vala
|
||||||
src/ui/util/size_request_box.vala
|
src/ui/util/size_request_box.vala
|
||||||
|
|
||||||
|
src/ui/widgets/avatar_picture.vala
|
||||||
src/ui/widgets/date_separator.vala
|
src/ui/widgets/date_separator.vala
|
||||||
src/ui/widgets/fixed_ratio_picture.vala
|
src/ui/widgets/fixed_ratio_picture.vala
|
||||||
src/ui/widgets/natural_size_increase.vala
|
src/ui/widgets/natural_size_increase.vala
|
||||||
|
@ -238,6 +237,7 @@ endif(WIN32)
|
||||||
add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
|
add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
|
||||||
target_include_directories(dino PRIVATE src)
|
target_include_directories(dino PRIVATE src)
|
||||||
target_link_libraries(dino libdino ${MAIN_PACKAGES})
|
target_link_libraries(dino libdino ${MAIN_PACKAGES})
|
||||||
|
set_target_properties(dino PROPERTIES ENABLE_EXPORTS TRUE)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(dino -mwindows)
|
target_link_libraries(dino -mwindows)
|
||||||
|
|
|
@ -8,10 +8,9 @@
|
||||||
<property name="margin-bottom">3</property>
|
<property name="margin-bottom">3</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="column-spacing">10</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="allow_gray">False</property>
|
<property name="height-request">30</property>
|
||||||
<property name="height">30</property>
|
<property name="width-request">30</property>
|
||||||
<property name="width">30</property>
|
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
|
@ -30,10 +30,10 @@
|
||||||
<property name="margin-start">100</property>
|
<property name="margin-start">100</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="column-spacing">10</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="avatar">
|
<object class="DinoUiAvatarPicture" id="avatar">
|
||||||
<property name="height">50</property>
|
<property name="height-request">50</property>
|
||||||
<property name="width">50</property>
|
<property name="width-request">50</property>
|
||||||
<property name="allow_gray">False</property>
|
<property name="valign">center</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">0</property>
|
<property name="row">0</property>
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<property name="column-spacing">7</property>
|
<property name="column-spacing">7</property>
|
||||||
<property name="row-spacing">2</property>
|
<property name="row-spacing">2</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="avatar_image">
|
<object class="DinoUiAvatarPicture" id="avatar_picture">
|
||||||
<property name="height">35</property>
|
<property name="height-request">35</property>
|
||||||
<property name="width">35</property>
|
<property name="width-request">35</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
<property name="margin-top">2</property>
|
<property name="margin-top">2</property>
|
||||||
<layout>
|
<layout>
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
<property name="margin-start">7</property>
|
<property name="margin-start">7</property>
|
||||||
<property name="margin-end">14</property>
|
<property name="margin-end">14</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">35</property>
|
<property name="height-request">35</property>
|
||||||
<property name="width">35</property>
|
<property name="width-request">35</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
74
main/data/gresource.xml
Normal file
74
main/data/gresource.xml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/im/dino/Dino">
|
||||||
|
<file>add_conversation/add_contact_dialog.ui</file>
|
||||||
|
<file>add_conversation/add_groupchat_dialog.ui</file>
|
||||||
|
<file>add_conversation/conference_details_fragment.ui</file>
|
||||||
|
<file>add_conversation/list_row.ui</file>
|
||||||
|
<file>add_conversation/select_jid_fragment.ui</file>
|
||||||
|
<file>call_widget.ui</file>
|
||||||
|
<file>chat_input.ui</file>
|
||||||
|
<file>contact_details_dialog.ui</file>
|
||||||
|
<file>conversation_content_view/item_metadata_header.ui</file>
|
||||||
|
<file>conversation_content_view/view.ui</file>
|
||||||
|
<file>conversation_item_widget.ui</file>
|
||||||
|
<file>conversation_list_titlebar.ui</file>
|
||||||
|
<file>conversation_list_titlebar_csd.ui</file>
|
||||||
|
<file>conversation_row.ui</file>
|
||||||
|
<file>conversation_view.ui</file>
|
||||||
|
<file>dino-conversation-list-placeholder-arrow.svg</file>
|
||||||
|
<file>file_default_widget.ui</file>
|
||||||
|
<file>file_send_overlay.ui</file>
|
||||||
|
<file>global_search.ui</file>
|
||||||
|
<file>icons/scalable/actions/dino-account-plus-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/actions/dino-emoticon-add-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/actions/dino-emoticon-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/actions/dino-qr-code-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/apps/im.dino.Dino-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/apps/im.dino.Dino.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-device-desktop-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-device-phone-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-phone-hangup-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-phone-in-talk-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-phone-missed-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-phone-ring-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/devices/dino-phone-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-document-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-download-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-image-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-music-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-table-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/mimetypes/dino-file-video-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-double-tick-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-microphone-off-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-microphone-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-party-popper-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-security-high-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-status-away.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-status-chat.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-status-dnd.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-status-online.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-tick-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-video-off-symbolic.svg</file>
|
||||||
|
<file>icons/scalable/status/dino-video-symbolic.svg</file>
|
||||||
|
<file>manage_accounts/account_row.ui</file>
|
||||||
|
<file>manage_accounts/add_account_dialog.ui</file>
|
||||||
|
<file>manage_accounts/dialog.ui</file>
|
||||||
|
<file>menu_add.ui</file>
|
||||||
|
<file>menu_app.ui</file>
|
||||||
|
<file>menu_conversation.ui</file>
|
||||||
|
<file>menu_encryption.ui</file>
|
||||||
|
<file>message_item_widget_edit_mode.ui</file>
|
||||||
|
<file>occupant_list.ui</file>
|
||||||
|
<file>occupant_list_item.ui</file>
|
||||||
|
<file>quote.ui</file>
|
||||||
|
<file>search_autocomplete.ui</file>
|
||||||
|
<file>settings_dialog.ui</file>
|
||||||
|
<file>shortcuts.ui</file>
|
||||||
|
<file>style-dark.css</file>
|
||||||
|
<file>style.css</file>
|
||||||
|
<file>unified_main_content.ui</file>
|
||||||
|
<file>unified_window_placeholder.ui</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
|
@ -5,7 +5,7 @@ GenericName=Jabber/XMPP Client
|
||||||
Keywords=chat;talk;im;message;xmpp;jabber;
|
Keywords=chat;talk;im;message;xmpp;jabber;
|
||||||
Exec=dino %U
|
Exec=dino %U
|
||||||
Icon=im.dino.Dino
|
Icon=im.dino.Dino
|
||||||
StartupNotify=false
|
StartupNotify=true
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=GTK;Network;Chat;InstantMessaging;
|
Categories=GTK;Network;Chat;InstantMessaging;
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
<property name="margin-bottom">6</property>
|
<property name="margin-bottom">6</property>
|
||||||
<property name="column-spacing">6</property>
|
<property name="column-spacing">6</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">40</property>
|
<property name="height-request">40</property>
|
||||||
<property name="width">40</property>
|
<property name="width-request">40</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|
|
@ -93,11 +93,9 @@
|
||||||
<class name="image-button"/>
|
<class name="image-button"/>
|
||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">50</property>
|
<property name="height-request">50</property>
|
||||||
<property name="width">50</property>
|
<property name="width-request">50</property>
|
||||||
<!-- <property name="xalign">1</property>-->
|
|
||||||
<property name="allow_gray">False</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
<property name="margin-end">7</property>
|
<property name="margin-end">7</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="column-spacing">10</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">30</property>
|
<property name="height-request">30</property>
|
||||||
<property name="width">30</property>
|
<property name="width-request">30</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
<class name="dino-quote"/>
|
<class name="dino-quote"/>
|
||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="avatar">
|
<object class="DinoUiAvatarPicture" id="avatar">
|
||||||
<property name="allow_gray">False</property>
|
<property name="height-request">15</property>
|
||||||
<property name="height">15</property>
|
<property name="width-request">15</property>
|
||||||
<property name="width">15</property>
|
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
<object class="GtkBox" id="root">
|
<object class="GtkBox" id="root">
|
||||||
<property name="orientation">horizontal</property>
|
<property name="orientation">horizontal</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="margin-top">4</property>
|
<property name="margin-top">4</property>
|
||||||
<property name="margin-bottom">4</property>
|
<property name="margin-bottom">4</property>
|
||||||
<property name="margin-start">6</property>
|
<property name="margin-start">6</property>
|
||||||
<property name="margin-end">6</property>
|
<property name="margin-end">6</property>
|
||||||
<property name="height">24</property>
|
<property name="height-request">24</property>
|
||||||
<property name="width">24</property>
|
<property name="width-request">24</property>
|
||||||
<property name="allow_gray">False</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|
|
@ -31,8 +31,8 @@ window.dino-main .dino-conversation viewport /* Some themes set this */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes highlight {
|
@keyframes highlight {
|
||||||
from { background: alpha(@warning_color, 0.5); }
|
from { background-color: alpha(@accent_color, 0.5); }
|
||||||
to { background: transparent; }
|
to { background-color: transparent; }
|
||||||
}
|
}
|
||||||
|
|
||||||
window.dino-main .dino-conversation .highlight-once {
|
window.dino-main .dino-conversation .highlight-once {
|
||||||
|
@ -42,7 +42,7 @@ window.dino-main .dino-conversation .highlight-once {
|
||||||
animation-name: highlight;
|
animation-name: highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.dino-main .dino-conversation .message-box.highlight {
|
window.dino-main .dino-conversation .message-box.highlight:not(.highlight-once) {
|
||||||
background: @window_bg_color;
|
background: @window_bg_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +119,10 @@ window.dino-main .dino-quote:hover {
|
||||||
background: alpha(@theme_fg_color, 0.08);
|
background: alpha(@theme_fg_color, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
picture.avatar {
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Overlay Toolbar */
|
/* Overlay Toolbar */
|
||||||
|
|
||||||
.dino-main .overlay-toolbar {
|
.dino-main .overlay-toolbar {
|
||||||
|
|
104
main/meson.build
Normal file
104
main/meson.build
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
dependencies = [
|
||||||
|
dep_dino,
|
||||||
|
dep_gee,
|
||||||
|
dep_glib,
|
||||||
|
dep_gmodule,
|
||||||
|
dep_gtk4,
|
||||||
|
dep_icu_uc,
|
||||||
|
dep_libadwaita,
|
||||||
|
dep_m,
|
||||||
|
dep_qlite,
|
||||||
|
dep_xmpp_vala,
|
||||||
|
]
|
||||||
|
sources = files(
|
||||||
|
'src/main.vala',
|
||||||
|
'src/ui/add_conversation/add_conference_dialog.vala',
|
||||||
|
'src/ui/add_conversation/add_contact_dialog.vala',
|
||||||
|
'src/ui/add_conversation/add_groupchat_dialog.vala',
|
||||||
|
'src/ui/add_conversation/conference_details_fragment.vala',
|
||||||
|
'src/ui/add_conversation/conference_list.vala',
|
||||||
|
'src/ui/add_conversation/list_row.vala',
|
||||||
|
'src/ui/add_conversation/roster_list.vala',
|
||||||
|
'src/ui/add_conversation/select_contact_dialog.vala',
|
||||||
|
'src/ui/add_conversation/select_jid_fragment.vala',
|
||||||
|
'src/ui/application.vala',
|
||||||
|
'src/ui/call_window/audio_settings_popover.vala',
|
||||||
|
'src/ui/call_window/call_bottom_bar.vala',
|
||||||
|
'src/ui/call_window/call_connection_details_window.vala',
|
||||||
|
'src/ui/call_window/call_encryption_button.vala',
|
||||||
|
'src/ui/call_window/call_window.vala',
|
||||||
|
'src/ui/call_window/call_window_controller.vala',
|
||||||
|
'src/ui/call_window/participant_widget.vala',
|
||||||
|
'src/ui/call_window/video_settings_popover.vala',
|
||||||
|
'src/ui/chat_input/chat_input_controller.vala',
|
||||||
|
'src/ui/chat_input/chat_text_view.vala',
|
||||||
|
'src/ui/chat_input/encryption_button.vala',
|
||||||
|
'src/ui/chat_input/occupants_tab_completer.vala',
|
||||||
|
'src/ui/chat_input/smiley_converter.vala',
|
||||||
|
'src/ui/chat_input/view.vala',
|
||||||
|
'src/ui/contact_details/blocking_provider.vala',
|
||||||
|
'src/ui/contact_details/dialog.vala',
|
||||||
|
'src/ui/contact_details/muc_config_form_provider.vala',
|
||||||
|
'src/ui/contact_details/permissions_provider.vala',
|
||||||
|
'src/ui/contact_details/settings_provider.vala',
|
||||||
|
'src/ui/conversation_content_view/call_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/chat_state_populator.vala',
|
||||||
|
'src/ui/conversation_content_view/content_populator.vala',
|
||||||
|
'src/ui/conversation_content_view/conversation_item_skeleton.vala',
|
||||||
|
'src/ui/conversation_content_view/conversation_view.vala',
|
||||||
|
'src/ui/conversation_content_view/date_separator_populator.vala',
|
||||||
|
'src/ui/conversation_content_view/file_default_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/file_image_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/file_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/item_actions.vala',
|
||||||
|
'src/ui/conversation_content_view/message_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/quote_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/reactions_widget.vala',
|
||||||
|
'src/ui/conversation_content_view/subscription_notification.vala',
|
||||||
|
'src/ui/conversation_list_titlebar.vala',
|
||||||
|
'src/ui/conversation_selector/conversation_selector.vala',
|
||||||
|
'src/ui/conversation_selector/conversation_selector_row.vala',
|
||||||
|
'src/ui/conversation_titlebar/call_entry.vala',
|
||||||
|
'src/ui/conversation_titlebar/conversation_titlebar.vala',
|
||||||
|
'src/ui/conversation_titlebar/menu_entry.vala',
|
||||||
|
'src/ui/conversation_titlebar/occupants_entry.vala',
|
||||||
|
'src/ui/conversation_titlebar/search_entry.vala',
|
||||||
|
'src/ui/conversation_view.vala',
|
||||||
|
'src/ui/conversation_view_controller.vala',
|
||||||
|
'src/ui/file_send_overlay.vala',
|
||||||
|
'src/ui/global_search.vala',
|
||||||
|
'src/ui/main_window.vala',
|
||||||
|
'src/ui/main_window_controller.vala',
|
||||||
|
'src/ui/manage_accounts/account_row.vala',
|
||||||
|
'src/ui/manage_accounts/add_account_dialog.vala',
|
||||||
|
'src/ui/manage_accounts/dialog.vala',
|
||||||
|
'src/ui/notifier_freedesktop.vala',
|
||||||
|
'src/ui/notifier_gnotifications.vala',
|
||||||
|
'src/ui/occupant_menu/list.vala',
|
||||||
|
'src/ui/occupant_menu/list_row.vala',
|
||||||
|
'src/ui/occupant_menu/view.vala',
|
||||||
|
'src/ui/settings_dialog.vala',
|
||||||
|
'src/ui/util/accounts_combo_box.vala',
|
||||||
|
'src/ui/util/config.vala',
|
||||||
|
'src/ui/util/data_forms.vala',
|
||||||
|
'src/ui/util/helper.vala',
|
||||||
|
'src/ui/util/label_hybrid.vala',
|
||||||
|
'src/ui/util/size_request_box.vala',
|
||||||
|
'src/ui/util/sizing_bin.vala',
|
||||||
|
'src/ui/widgets/avatar_picture.vala',
|
||||||
|
'src/ui/widgets/date_separator.vala',
|
||||||
|
'src/ui/widgets/fixed_ratio_picture.vala',
|
||||||
|
'src/ui/widgets/natural_size_increase.vala',
|
||||||
|
)
|
||||||
|
sources += import('gnome').compile_resources(
|
||||||
|
'dino-resources',
|
||||||
|
'data/gresource.xml',
|
||||||
|
source_dir: 'data',
|
||||||
|
)
|
||||||
|
|
||||||
|
c_args = [
|
||||||
|
'-DG_LOG_DOMAIN="dino"',
|
||||||
|
'-DGETTEXT_PACKAGE="dino"',
|
||||||
|
'-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')),
|
||||||
|
]
|
||||||
|
exe_dino = executable('dino', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies)
|
|
@ -112,7 +112,7 @@ internal class ConferenceListRow : ListRow {
|
||||||
via_label.visible = false;
|
via_label.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
image.set_conversation(stream_interactor, new Conversation(jid, account, Conversation.Type.GROUPCHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(new Conversation(jid, account, Conversation.Type.GROUPCHAT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Dino.Ui {
|
||||||
public class ListRow : Widget {
|
public class ListRow : Widget {
|
||||||
|
|
||||||
public Grid outer_grid;
|
public Grid outer_grid;
|
||||||
public AvatarImage image;
|
public AvatarPicture picture;
|
||||||
public Label name_label;
|
public Label name_label;
|
||||||
public Label via_label;
|
public Label via_label;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ public class ListRow : Widget {
|
||||||
construct {
|
construct {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/add_conversation/list_row.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/add_conversation/list_row.ui");
|
||||||
outer_grid = (Grid) builder.get_object("outer_grid");
|
outer_grid = (Grid) builder.get_object("outer_grid");
|
||||||
image = (AvatarImage) builder.get_object("image");
|
picture = (AvatarPicture) builder.get_object("picture");
|
||||||
name_label = (Label) builder.get_object("name_label");
|
name_label = (Label) builder.get_object("name_label");
|
||||||
via_label = (Label) builder.get_object("via_label");
|
via_label = (Label) builder.get_object("via_label");
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ public class ListRow : Widget {
|
||||||
via_label.visible = false;
|
via_label.visible = false;
|
||||||
}
|
}
|
||||||
name_label.label = display_name;
|
name_label.label = display_name;
|
||||||
image.set_conversation(stream_interactor, conv);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conv);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void dispose() {
|
public override void dispose() {
|
||||||
|
|
|
@ -132,7 +132,7 @@ public class SelectJidFragment : Gtk.Box {
|
||||||
} else {
|
} else {
|
||||||
via_label.visible = false;
|
via_label.visible = false;
|
||||||
}
|
}
|
||||||
image.set_text("?");
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add("+");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
using Cairo;
|
|
||||||
using Gee;
|
|
||||||
using Gdk;
|
|
||||||
using Gtk;
|
|
||||||
using Xmpp.Util;
|
|
||||||
|
|
||||||
namespace Dino.Ui {
|
|
||||||
|
|
||||||
public class AvatarDrawer {
|
|
||||||
public const string GRAY = "555753";
|
|
||||||
|
|
||||||
private Gee.List<AvatarTile> tiles = new ArrayList<AvatarTile>();
|
|
||||||
private int height = 35;
|
|
||||||
private int width = 35;
|
|
||||||
private bool gray;
|
|
||||||
private int base_factor = 1;
|
|
||||||
private string font_family = "Sans";
|
|
||||||
|
|
||||||
public AvatarDrawer size(int height, int width = height) {
|
|
||||||
this.height = height;
|
|
||||||
this.width = width;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer grayscale() {
|
|
||||||
this.gray = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer tile(Pixbuf? image, string? name, string? hex_color) {
|
|
||||||
tiles.add(new AvatarTile(image, name, hex_color));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer plus() {
|
|
||||||
tiles.add(new AvatarTile(null, "…", GRAY));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer scale(int base_factor) {
|
|
||||||
this.base_factor = base_factor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer font(string font_family) {
|
|
||||||
this.font_family = font_family;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageSurface draw_image_surface() {
|
|
||||||
ImageSurface surface = new ImageSurface(Format.ARGB32, width, height);
|
|
||||||
draw_on_context(new Context(surface));
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw_on_context(Cairo.Context ctx) {
|
|
||||||
double radius = 3 * base_factor;
|
|
||||||
double degrees = Math.PI / 180.0;
|
|
||||||
ctx.new_sub_path();
|
|
||||||
ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees);
|
|
||||||
ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees);
|
|
||||||
ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees);
|
|
||||||
ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees);
|
|
||||||
ctx.close_path();
|
|
||||||
ctx.clip();
|
|
||||||
|
|
||||||
if (this.tiles.size == 4) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
bufctx.scale(0.5, 0.5);
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1);
|
|
||||||
bufctx.paint();
|
|
||||||
|
|
||||||
ctx.set_source_surface(buffer, 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 3) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
bufctx.scale(0.5, 0.5);
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1);
|
|
||||||
bufctx.paint();
|
|
||||||
|
|
||||||
ctx.set_source_surface(buffer, 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 2) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
bufctx.scale(0.5, 0.5);
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
|
|
||||||
ctx.set_source_surface(buffer, 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 1) {
|
|
||||||
ctx.set_source_surface(sub_surface_idx(ctx, 0, width, height, base_factor), 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 0) {
|
|
||||||
ctx.set_source_surface(sub_surface_idx(ctx, -1, width, height, base_factor), 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gray) {
|
|
||||||
// convert to greyscale
|
|
||||||
ctx.set_operator(Cairo.Operator.HSL_COLOR);
|
|
||||||
ctx.set_source_rgb(1, 1, 1);
|
|
||||||
ctx.rectangle(0, 0, width, height);
|
|
||||||
ctx.fill();
|
|
||||||
// make the visible part more light
|
|
||||||
ctx.set_operator(Cairo.Operator.ATOP);
|
|
||||||
ctx.set_source_rgba(1, 1, 1, 0.7);
|
|
||||||
ctx.rectangle(0, 0, width, height);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
ctx.set_source_rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) {
|
|
||||||
Gdk.Pixbuf? avatar = idx >= 0 ? tiles[idx].image : null;
|
|
||||||
string? name = idx >= 0 ? tiles[idx].name : "";
|
|
||||||
string hex_color = !gray && idx >= 0 ? tiles[idx].hex_color : GRAY;
|
|
||||||
return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, string? hex_color, int width, int height, int font_factor = 1) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
if (avatar == null) {
|
|
||||||
set_source_hex_color(bufctx, hex_color ?? GRAY);
|
|
||||||
bufctx.rectangle(0, 0, width, height);
|
|
||||||
bufctx.fill();
|
|
||||||
|
|
||||||
string text = name == null ? "…" : name.get_char(0).toupper().to_string();
|
|
||||||
bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
|
|
||||||
bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25);
|
|
||||||
Cairo.TextExtents extents;
|
|
||||||
bufctx.text_extents(text, out extents);
|
|
||||||
double x_pos = width/2 - (extents.width/2 + extents.x_bearing);
|
|
||||||
double y_pos = height/2 - (extents.height/2 + extents.y_bearing);
|
|
||||||
bufctx.move_to(x_pos, y_pos);
|
|
||||||
bufctx.set_source_rgba(1, 1, 1, 1);
|
|
||||||
bufctx.show_text(text);
|
|
||||||
} else {
|
|
||||||
double w_scale = (double) width / avatar.width;
|
|
||||||
double h_scale = (double) height / avatar.height;
|
|
||||||
double scale = double.max(w_scale, h_scale);
|
|
||||||
bufctx.scale(scale, scale);
|
|
||||||
|
|
||||||
double x_off = 0, y_off = 0;
|
|
||||||
if (scale == h_scale) {
|
|
||||||
x_off = (width / scale - avatar.width) / 2.0;
|
|
||||||
} else {
|
|
||||||
y_off = (height / scale - avatar.height) / 2.0;
|
|
||||||
}
|
|
||||||
Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off);
|
|
||||||
bufctx.get_source().set_filter(Cairo.Filter.BEST);
|
|
||||||
bufctx.paint();
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void set_source_hex_color(Cairo.Context ctx, string hex_color) {
|
|
||||||
ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255,
|
|
||||||
(double) from_hex(hex_color.substring(2, 2)) / 255,
|
|
||||||
(double) from_hex(hex_color.substring(4, 2)) / 255,
|
|
||||||
hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AvatarTile {
|
|
||||||
public Pixbuf? image { get; private set; }
|
|
||||||
public string? name { get; private set; }
|
|
||||||
public string? hex_color { get; private set; }
|
|
||||||
|
|
||||||
public AvatarTile(Pixbuf? image, string? name, string? hex_color) {
|
|
||||||
this.image = image;
|
|
||||||
this.name = name;
|
|
||||||
this.hex_color = hex_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,267 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
using Dino.Entities;
|
|
||||||
using Xmpp;
|
|
||||||
using Xmpp.Util;
|
|
||||||
|
|
||||||
namespace Dino.Ui {
|
|
||||||
|
|
||||||
public class AvatarImage : Widget {
|
|
||||||
public int height { get; set; default = 35; }
|
|
||||||
public int width { get; set; default = 35; }
|
|
||||||
public bool allow_gray { get; set; default = true; }
|
|
||||||
public bool force_gray { get; set; default = false; }
|
|
||||||
public StreamInteractor? stream_interactor { get; set; }
|
|
||||||
public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
|
||||||
public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
|
||||||
public PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } }
|
|
||||||
public ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } }
|
|
||||||
public Account account { get { return conversation.account; } }
|
|
||||||
private AvatarDrawer? drawer;
|
|
||||||
private Conversation conversation;
|
|
||||||
private Jid[] jids;
|
|
||||||
private Cairo.ImageSurface? cached_surface;
|
|
||||||
private static int8 use_image_surface = -1;
|
|
||||||
|
|
||||||
public AvatarImage() {
|
|
||||||
can_focus = false;
|
|
||||||
add_css_class("avatar");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void dispose() {
|
|
||||||
base.dispose();
|
|
||||||
drawer = null;
|
|
||||||
cached_surface = null;
|
|
||||||
disconnect_stream_interactor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
|
||||||
if (orientation == Orientation.HORIZONTAL) {
|
|
||||||
minimum = width;
|
|
||||||
natural = width;
|
|
||||||
} else {
|
|
||||||
minimum = height;
|
|
||||||
natural = height;
|
|
||||||
}
|
|
||||||
minimum_baseline = natural_baseline = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void snapshot(Snapshot snapshot) {
|
|
||||||
Cairo.Context context = snapshot.append_cairo(Graphene.Rect.alloc().init(0, 0, width, height));
|
|
||||||
draw(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool draw(Cairo.Context ctx_in) {
|
|
||||||
Cairo.Context ctx = ctx_in;
|
|
||||||
int width = this.width, height = this.height, base_factor = 1;
|
|
||||||
if (use_image_surface == -1) {
|
|
||||||
// TODO: detect if we have to buffer in image surface
|
|
||||||
use_image_surface = 1;
|
|
||||||
}
|
|
||||||
if (use_image_surface == 1) {
|
|
||||||
ctx_in.scale(1f / scale_factor, 1f / scale_factor);
|
|
||||||
if (cached_surface != null) {
|
|
||||||
ctx_in.set_source_surface(cached_surface, 0, 0);
|
|
||||||
ctx_in.paint();
|
|
||||||
ctx_in.set_source_rgb(0, 0, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
width *= scale_factor;
|
|
||||||
height *= scale_factor;
|
|
||||||
base_factor *= scale_factor;
|
|
||||||
cached_surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
|
|
||||||
ctx = new Cairo.Context(cached_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
AvatarDrawer drawer = this.drawer;
|
|
||||||
Jid[] jids = this.jids;
|
|
||||||
if (drawer == null && jids.length == 0) {
|
|
||||||
switch (conversation.type_) {
|
|
||||||
case Conversation.Type.CHAT:
|
|
||||||
case Conversation.Type.GROUPCHAT_PM:
|
|
||||||
// In direct chats or group chats, conversation avatar is same as counterpart avatar
|
|
||||||
jids = { conversation.counterpart };
|
|
||||||
break;
|
|
||||||
case Conversation.Type.GROUPCHAT:
|
|
||||||
string user_color = Util.get_avatar_hex_color(stream_interactor, account, conversation.counterpart, conversation);
|
|
||||||
if (avatar_manager.has_avatar_cached(account, conversation.counterpart)) {
|
|
||||||
drawer = new AvatarDrawer().tile(avatar_manager.get_cached_avatar(account, conversation.counterpart), "#", user_color);
|
|
||||||
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
|
||||||
} else {
|
|
||||||
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, account);
|
|
||||||
if (muc_manager.is_private_room(account, conversation.counterpart) && occupants != null && occupants.size > 0) {
|
|
||||||
jids = occupants.to_array();
|
|
||||||
} else {
|
|
||||||
drawer = new AvatarDrawer().tile(null, "#", user_color);
|
|
||||||
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
|
||||||
}
|
|
||||||
try_load_avatar_async(conversation.counterpart);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (drawer == null && jids.length > 0) {
|
|
||||||
drawer = new AvatarDrawer();
|
|
||||||
for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) {
|
|
||||||
Jid avatar_jid = jids[i];
|
|
||||||
Jid? real_avatar_jid = null;
|
|
||||||
if (conversation.type_ != Conversation.Type.CHAT && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(account, conversation.counterpart.bare_jid)) {
|
|
||||||
// In private room, consider real jid
|
|
||||||
real_avatar_jid = muc_manager.get_real_jid(avatar_jid, account) ?? avatar_jid;
|
|
||||||
}
|
|
||||||
string display_name = Util.get_participant_display_name(stream_interactor, conversation, jids[i]);
|
|
||||||
string user_color = Util.get_avatar_hex_color(stream_interactor, account, jids[i], conversation);
|
|
||||||
if (avatar_manager.has_avatar_cached(account, avatar_jid)) {
|
|
||||||
drawer.tile(avatar_manager.get_cached_avatar(account, avatar_jid), display_name, user_color);
|
|
||||||
} else if (real_avatar_jid != null && avatar_manager.has_avatar_cached(account, real_avatar_jid)) {
|
|
||||||
drawer.tile(avatar_manager.get_cached_avatar(account, real_avatar_jid), display_name, user_color);
|
|
||||||
} else {
|
|
||||||
drawer.tile(null, display_name, user_color);
|
|
||||||
try_load_avatar_async(avatar_jid);
|
|
||||||
if (real_avatar_jid != null) try_load_avatar_async(real_avatar_jid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jids.length > 4) {
|
|
||||||
drawer.plus();
|
|
||||||
}
|
|
||||||
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (drawer == null) return false;
|
|
||||||
drawer.size(height, width)
|
|
||||||
.scale(base_factor)
|
|
||||||
.font(get_pango_context().get_font_description().get_family())
|
|
||||||
.draw_on_context(ctx);
|
|
||||||
|
|
||||||
if (use_image_surface == 1) {
|
|
||||||
ctx_in.set_source_surface(ctx.get_target(), 0, 0);
|
|
||||||
ctx_in.paint();
|
|
||||||
ctx_in.set_source_rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void try_load_avatar_async(Jid jid) {
|
|
||||||
if (avatar_manager.has_avatar(account, jid)) {
|
|
||||||
avatar_manager.get_avatar.begin(account, jid, (_, res) => {
|
|
||||||
var avatar = avatar_manager.get_avatar.end(res);
|
|
||||||
if (avatar != null) force_redraw();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void force_redraw() {
|
|
||||||
this.cached_surface = null;
|
|
||||||
queue_draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disconnect_stream_interactor() {
|
|
||||||
if (stream_interactor != null) {
|
|
||||||
presence_manager.show_received.disconnect(on_show_received);
|
|
||||||
presence_manager.received_offline_presence.disconnect(on_show_received);
|
|
||||||
avatar_manager.received_avatar.disconnect(on_received_avatar);
|
|
||||||
stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed);
|
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated);
|
|
||||||
muc_manager.private_room_occupant_updated.disconnect(on_private_room_occupant_updated);
|
|
||||||
muc_manager.room_info_updated.disconnect(on_room_info_updated);
|
|
||||||
stream_interactor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_show_received(Jid jid, Account account) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_received_avatar(Jid jid, Account account) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_avatar_if_jid(Jid jid) {
|
|
||||||
if (jid.equals_bare(this.conversation.counterpart)) {
|
|
||||||
force_redraw();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (Jid ours in this.jids) {
|
|
||||||
if (jid.equals_bare(ours)) {
|
|
||||||
force_redraw();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
force_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_roster_updated(Account account, Jid jid, Roster.Item roster_item) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_private_room_occupant_updated(Account account, Jid room, Jid occupant) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_room_info_updated(Account account, Jid muc_jid) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(muc_jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool is_self_online() {
|
|
||||||
if (connection_manager != null) {
|
|
||||||
return connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool is_counterpart_online() {
|
|
||||||
return presence_manager.get_full_jids(conversation.counterpart, account) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_conversation(StreamInteractor stream_interactor, Conversation conversation) {
|
|
||||||
set_avatar(stream_interactor, conversation, new Jid[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_conversation_participant(StreamInteractor stream_interactor, Conversation conversation, Jid sub_jid) {
|
|
||||||
set_avatar(stream_interactor, conversation, new Jid[] {sub_jid});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_conversation_participants(StreamInteractor stream_interactor, Conversation conversation, Jid[] sub_jids) {
|
|
||||||
set_avatar(stream_interactor, conversation, sub_jids);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_avatar(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) {
|
|
||||||
if (this.stream_interactor != null && stream_interactor != this.stream_interactor) {
|
|
||||||
disconnect_stream_interactor();
|
|
||||||
}
|
|
||||||
if (this.stream_interactor != stream_interactor) {
|
|
||||||
this.stream_interactor = stream_interactor;
|
|
||||||
presence_manager.show_received.connect(on_show_received);
|
|
||||||
presence_manager.received_offline_presence.connect(on_show_received);
|
|
||||||
stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar);
|
|
||||||
stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed);
|
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
|
|
||||||
muc_manager.private_room_occupant_updated.connect(on_private_room_occupant_updated);
|
|
||||||
muc_manager.room_info_updated.connect(on_room_info_updated);
|
|
||||||
}
|
|
||||||
this.cached_surface = null;
|
|
||||||
this.conversation = conversation;
|
|
||||||
this.jids = jids;
|
|
||||||
|
|
||||||
force_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_text(string text, bool gray = true) {
|
|
||||||
disconnect_stream_interactor();
|
|
||||||
this.drawer = new AvatarDrawer().tile(null, text, null);
|
|
||||||
if (gray) drawer.grayscale();
|
|
||||||
force_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -96,11 +96,11 @@ namespace Dino.Ui {
|
||||||
shows_video = false;
|
shows_video = false;
|
||||||
Box box = new Box(Orientation.HORIZONTAL, 0);
|
Box box = new Box(Orientation.HORIZONTAL, 0);
|
||||||
box.add_css_class("video-placeholder-box");
|
box.add_css_class("video-placeholder-box");
|
||||||
AvatarImage avatar = new AvatarImage() { allow_gray=false, hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100 };
|
AvatarPicture avatar = new AvatarPicture() { hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height_request=100, width_request=100 };
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
avatar.set_conversation(stream_interactor, conversation);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
|
||||||
} else {
|
} else {
|
||||||
avatar.set_text("?", false);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add("?");
|
||||||
}
|
}
|
||||||
box.append(avatar);
|
box.append(avatar);
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,12 @@ public class ChatInputController : Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
string text = chat_input.chat_text_view.text_view.buffer.text;
|
string text = chat_input.chat_text_view.text_view.buffer.text;
|
||||||
|
ContentItem? quoted_content_item_bak = quoted_content_item;
|
||||||
|
|
||||||
|
// Reset input state. Has do be done before parsing commands, because those directly return.
|
||||||
|
chat_input.chat_text_view.text_view.buffer.text = "";
|
||||||
|
chat_input.unset_quoted_message();
|
||||||
|
quoted_content_item = null;
|
||||||
|
|
||||||
if (text.has_prefix("/")) {
|
if (text.has_prefix("/")) {
|
||||||
string[] token = text.split(" ", 2);
|
string[] token = text.split(" ", 2);
|
||||||
|
@ -189,15 +195,10 @@ public class ChatInputController : Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message out_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(text, conversation);
|
Message out_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(text, conversation);
|
||||||
if (quoted_content_item != null) {
|
if (quoted_content_item_bak != null) {
|
||||||
stream_interactor.get_module(Replies.IDENTITY).set_message_is_reply_to(out_message, quoted_content_item);
|
stream_interactor.get_module(Replies.IDENTITY).set_message_is_reply_to(out_message, quoted_content_item_bak);
|
||||||
}
|
}
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).send_message(out_message, conversation);
|
stream_interactor.get_module(MessageProcessor.IDENTITY).send_message(out_message, conversation);
|
||||||
|
|
||||||
// Reset input state
|
|
||||||
chat_input.chat_text_view.text_view.buffer.text = "";
|
|
||||||
chat_input.unset_quoted_message();
|
|
||||||
quoted_content_item = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_text_input_changed() {
|
private void on_text_input_changed() {
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Dino.Ui.ContactDetails {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")]
|
||||||
public class Dialog : Gtk.Dialog {
|
public class Dialog : Gtk.Dialog {
|
||||||
|
|
||||||
[GtkChild] public unowned AvatarImage avatar;
|
[GtkChild] public unowned AvatarPicture avatar;
|
||||||
[GtkChild] public unowned Util.EntryLabelHybrid name_hybrid;
|
[GtkChild] public unowned Util.EntryLabelHybrid name_hybrid;
|
||||||
[GtkChild] public unowned Label name_label;
|
[GtkChild] public unowned Label name_label;
|
||||||
[GtkChild] public unowned Label jid_label;
|
[GtkChild] public unowned Label jid_label;
|
||||||
|
@ -87,7 +87,7 @@ public class Dialog : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
jid_label.label = conversation.counterpart.to_string();
|
jid_label.label = conversation.counterpart.to_string();
|
||||||
account_label.label = "via " + conversation.account.bare_jid.to_string();
|
account_label.label = "via " + conversation.account.bare_jid.to_string();
|
||||||
avatar.set_conversation(stream_interactor, conversation);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add_entry(string category, string label, string? description, Object wo) {
|
private void add_entry(string category, string label, string? description, Object wo) {
|
||||||
|
|
|
@ -94,15 +94,15 @@ namespace Dino.Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Jid counterpart in call.counterparts) {
|
foreach (Jid counterpart in call.counterparts) {
|
||||||
AvatarImage image = new AvatarImage() { force_gray=true, margin_top=2 };
|
AvatarPicture picture = new AvatarPicture() { margin_top=2 };
|
||||||
image.set_conversation_participant(stream_interactor, conversation, counterpart.bare_jid);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, counterpart.bare_jid);
|
||||||
multiparty_peer_box.append(image);
|
multiparty_peer_box.append(picture);
|
||||||
multiparty_peer_widgets.add(image);
|
multiparty_peer_widgets.add(picture);
|
||||||
}
|
}
|
||||||
AvatarImage image2 = new AvatarImage() { force_gray=true, margin_top=2 };
|
AvatarPicture picture2 = new AvatarPicture() { margin_top=2 };
|
||||||
image2.set_conversation_participant(stream_interactor, conversation, call.account.bare_jid);
|
picture2.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, call.account.bare_jid);
|
||||||
multiparty_peer_box.append(image2);
|
multiparty_peer_box.append(picture2);
|
||||||
multiparty_peer_widgets.add(image2);
|
multiparty_peer_widgets.add(picture2);
|
||||||
|
|
||||||
outer_additional_box.add_css_class("multiparty-participants");
|
outer_additional_box.add_css_class("multiparty-participants");
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
private Conversation conversation;
|
private Conversation conversation;
|
||||||
private Gee.List<Jid> jids = new ArrayList<Jid>();
|
private Gee.List<Jid> jids = new ArrayList<Jid>();
|
||||||
private Label label;
|
private Label label;
|
||||||
private AvatarImage image;
|
private AvatarPicture picture;
|
||||||
|
|
||||||
public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Gee.List<Jid> jids) {
|
public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Gee.List<Jid> jids) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
@ -79,10 +79,10 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) {
|
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) {
|
||||||
label = new Label("") { xalign=0, vexpand=true };
|
label = new Label("") { xalign=0, vexpand=true };
|
||||||
label.add_css_class("dim-label");
|
label.add_css_class("dim-label");
|
||||||
image = new AvatarImage() { margin_top=2, valign=Align.START };
|
picture = new AvatarPicture() { margin_top=2, valign=Align.START };
|
||||||
|
|
||||||
Box image_content_box = new Box(Orientation.HORIZONTAL, 8);
|
Box image_content_box = new Box(Orientation.HORIZONTAL, 8);
|
||||||
image_content_box.append(image);
|
image_content_box.append(picture);
|
||||||
image_content_box.append(label);
|
image_content_box.append(label);
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
@ -97,9 +97,7 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
if (image == null || label == null) return;
|
if (picture == null || label == null) return;
|
||||||
|
|
||||||
image.set_conversation_participants(stream_interactor, conversation, jids.to_array());
|
|
||||||
|
|
||||||
Gee.List<string> display_names = new ArrayList<string>();
|
Gee.List<string> display_names = new ArrayList<string>();
|
||||||
foreach (Jid jid in jids) {
|
foreach (Jid jid in jids) {
|
||||||
|
@ -108,12 +106,26 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
string new_text = "";
|
string new_text = "";
|
||||||
if (jids.size > 3) {
|
if (jids.size > 3) {
|
||||||
new_text = _("%s, %s and %i others are typing…").printf(display_names[0], display_names[1], jids.size - 2);
|
new_text = _("%s, %s and %i others are typing…").printf(display_names[0], display_names[1], jids.size - 2);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0])
|
||||||
|
.add_participant(conversation, jids[1])
|
||||||
|
.add_participant(conversation, jids[2])
|
||||||
|
.add("+");
|
||||||
} else if (jids.size == 3) {
|
} else if (jids.size == 3) {
|
||||||
new_text = _("%s, %s and %s are typing…").printf(display_names[0], display_names[1], display_names[2]);
|
new_text = _("%s, %s and %s are typing…").printf(display_names[0], display_names[1], display_names[2]);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0])
|
||||||
|
.add_participant(conversation, jids[1])
|
||||||
|
.add_participant(conversation, jids[2]);
|
||||||
} else if (jids.size == 2) {
|
} else if (jids.size == 2) {
|
||||||
new_text =_("%s and %s are typing…").printf(display_names[0], display_names[1]);
|
new_text =_("%s and %s are typing…").printf(display_names[0], display_names[1]);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0])
|
||||||
|
.add_participant(conversation, jids[1]);
|
||||||
} else {
|
} else {
|
||||||
new_text = _("%s is typing…").printf(display_names[0]);
|
new_text = _("%s is typing…").printf(display_names[0]);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
label.label = new_text;
|
label.label = new_text;
|
||||||
|
|
|
@ -12,7 +12,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
public Grid main_grid { get; set; }
|
public Grid main_grid { get; set; }
|
||||||
public Label name_label { get; set; }
|
public Label name_label { get; set; }
|
||||||
public Label time_label { get; set; }
|
public Label time_label { get; set; }
|
||||||
public AvatarImage avatar_image { get; set; }
|
public AvatarPicture avatar_picture { get; set; }
|
||||||
public Image encryption_image { get; set; }
|
public Image encryption_image { get; set; }
|
||||||
public Image received_image { get; set; }
|
public Image received_image { get; set; }
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
main_grid.add_css_class("message-box");
|
main_grid.add_css_class("message-box");
|
||||||
name_label = (Label) builder.get_object("name_label");
|
name_label = (Label) builder.get_object("name_label");
|
||||||
time_label = (Label) builder.get_object("time_label");
|
time_label = (Label) builder.get_object("time_label");
|
||||||
avatar_image = (AvatarImage) builder.get_object("avatar_image");
|
avatar_picture = (AvatarPicture) builder.get_object("avatar_picture");
|
||||||
encryption_image = (Image) builder.get_object("encrypted_image");
|
encryption_image = (Image) builder.get_object("encrypted_image");
|
||||||
received_image = (Image) builder.get_object("marked_image");
|
received_image = (Image) builder.get_object("marked_image");
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.requires_header) {
|
if (item.requires_header) {
|
||||||
avatar_image.set_conversation_participant(stream_interactor, conversation, item.jid);
|
// TODO: For MUC messags, use real jid from message if known
|
||||||
|
avatar_picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, item.jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notify["show-skeleton"].connect(update_margin);
|
this.notify["show-skeleton"].connect(update_margin);
|
||||||
|
@ -71,9 +72,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
ContentMetaItem? content_meta_item = item as ContentMetaItem;
|
ContentMetaItem? content_meta_item = item as ContentMetaItem;
|
||||||
if (content_meta_item != null) {
|
if (content_meta_item != null) {
|
||||||
reactions_controller = new ReactionsController(conversation, content_meta_item.content_item, stream_interactor);
|
reactions_controller = new ReactionsController(conversation, content_meta_item.content_item, stream_interactor);
|
||||||
reactions_controller.box_activated.connect((widget) => {
|
reactions_controller.box_activated.connect(on_reaction_box_activated);
|
||||||
set_widget(widget, Plugins.WidgetType.GTK4, 3);
|
|
||||||
});
|
|
||||||
reactions_controller.init();
|
reactions_controller.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +117,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_margin() {
|
private void update_margin() {
|
||||||
avatar_image.visible = show_skeleton;
|
avatar_picture.visible = show_skeleton;
|
||||||
name_label.visible = show_skeleton;
|
name_label.visible = show_skeleton;
|
||||||
time_label.visible = show_skeleton;
|
time_label.visible = show_skeleton;
|
||||||
encryption_image.visible = show_skeleton;
|
encryption_image.visible = show_skeleton;
|
||||||
|
@ -152,7 +151,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
if (item.encryption != Encryption.NONE && item.encryption != Encryption.UNKNOWN && ci != null) {
|
if (item.encryption != Encryption.NONE && item.encryption != Encryption.UNKNOWN && ci != null) {
|
||||||
string? icon_name = null;
|
string? icon_name = null;
|
||||||
var encryption_entry = app.plugin_registry.encryption_list_entries[item.encryption];
|
var encryption_entry = app.plugin_registry.encryption_list_entries[item.encryption];
|
||||||
icon_name = encryption_entry.get_encryption_icon_name(conversation, ci.content_item);
|
if (encryption_entry != null) icon_name = encryption_entry.get_encryption_icon_name(conversation, ci.content_item);
|
||||||
encryption_image.icon_name = icon_name ?? "changes-prevent-symbolic";
|
encryption_image.icon_name = icon_name ?? "changes-prevent-symbolic";
|
||||||
encryption_image.visible = true;
|
encryption_image.visible = true;
|
||||||
}
|
}
|
||||||
|
@ -170,6 +169,10 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void on_reaction_box_activated(Widget widget) {
|
||||||
|
set_widget(widget, Plugins.WidgetType.GTK4, 3);
|
||||||
|
}
|
||||||
|
|
||||||
private void update_time() {
|
private void update_time() {
|
||||||
time_label.label = get_relative_time(item.time.to_local()).to_string();
|
time_label.label = get_relative_time(item.time.to_local()).to_string();
|
||||||
|
|
||||||
|
@ -271,6 +274,34 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).disconnect(updated_roster_handler_id);
|
stream_interactor.get_module(RosterManager.IDENTITY).disconnect(updated_roster_handler_id);
|
||||||
updated_roster_handler_id = 0;
|
updated_roster_handler_id = 0;
|
||||||
}
|
}
|
||||||
|
reactions_controller = null;
|
||||||
|
|
||||||
|
// Children won't be disposed automatically
|
||||||
|
if (name_label != null) {
|
||||||
|
name_label.unparent();
|
||||||
|
name_label.dispose();
|
||||||
|
name_label = null;
|
||||||
|
}
|
||||||
|
if (time_label != null) {
|
||||||
|
time_label.unparent();
|
||||||
|
time_label.dispose();
|
||||||
|
time_label = null;
|
||||||
|
}
|
||||||
|
if (avatar_picture != null) {
|
||||||
|
avatar_picture.unparent();
|
||||||
|
avatar_picture.dispose();
|
||||||
|
avatar_picture = null;
|
||||||
|
}
|
||||||
|
if (encryption_image != null) {
|
||||||
|
encryption_image.unparent();
|
||||||
|
encryption_image.dispose();
|
||||||
|
encryption_image = null;
|
||||||
|
}
|
||||||
|
if (received_image != null) {
|
||||||
|
received_image.unparent();
|
||||||
|
received_image.dispose();
|
||||||
|
received_image = null;
|
||||||
|
}
|
||||||
base.dispose();
|
base.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
[GtkChild] private unowned Box main;
|
[GtkChild] private unowned Box main;
|
||||||
[GtkChild] private unowned Box main_wrap_box;
|
[GtkChild] private unowned Box main_wrap_box;
|
||||||
|
|
||||||
private ArrayList<Widget> action_buttons = new ArrayList<Widget>();
|
private HashMap<string, Widget> action_buttons = new HashMap<string, Widget>();
|
||||||
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
|
private Gee.List<Dino.Plugins.MessageAction>? message_actions = null;
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
|
@ -46,6 +46,30 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.layout_manager = new BinLayout();
|
this.layout_manager = new BinLayout();
|
||||||
|
|
||||||
|
// Setup all message menu buttons
|
||||||
|
var correction_button = new Button() { name="correction" };
|
||||||
|
correction_button.clicked.connect((button) => {
|
||||||
|
on_action_button_clicked(button, null);
|
||||||
|
});
|
||||||
|
action_buttons["correction"] = correction_button;
|
||||||
|
message_menu_box.append(correction_button);
|
||||||
|
|
||||||
|
var reply_button = new Button() { name="reply" };
|
||||||
|
reply_button.clicked.connect((button) => {
|
||||||
|
on_action_button_clicked(button, null);
|
||||||
|
});
|
||||||
|
action_buttons["reply"] = reply_button;
|
||||||
|
message_menu_box.append(reply_button);
|
||||||
|
|
||||||
|
var reaction_button = new MenuButton() { name="reaction" };
|
||||||
|
EmojiChooser chooser = new EmojiChooser();
|
||||||
|
chooser.emoji_picked.connect((emoji) => {
|
||||||
|
on_action_button_clicked(reaction_button, new GLib.Variant.string(emoji));
|
||||||
|
});
|
||||||
|
reaction_button.popover = chooser;
|
||||||
|
action_buttons["reaction"] = reaction_button;
|
||||||
|
message_menu_box.append(reaction_button);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConversationView init(StreamInteractor stream_interactor) {
|
public ConversationView init(StreamInteractor stream_interactor) {
|
||||||
|
@ -112,7 +136,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool is_highlight_fixed() {
|
private bool is_highlight_fixed() {
|
||||||
foreach (Widget widget in action_buttons) {
|
foreach (Widget widget in action_buttons.values) {
|
||||||
MenuButton? menu_button = widget as MenuButton;
|
MenuButton? menu_button = widget as MenuButton;
|
||||||
if (menu_button != null && menu_button.popover.visible) return true;
|
if (menu_button != null && menu_button.popover.visible) return true;
|
||||||
|
|
||||||
|
@ -192,39 +216,32 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Widget widget in action_buttons) {
|
var current_message_actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK4);
|
||||||
message_menu_box.remove(widget);
|
|
||||||
}
|
|
||||||
action_buttons.clear();
|
|
||||||
|
|
||||||
message_actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK4);
|
message_actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK4);
|
||||||
|
|
||||||
if (message_actions != null) {
|
if (message_actions != null) {
|
||||||
message_menu_box.visible = true;
|
message_menu_box.visible = true;
|
||||||
|
|
||||||
// Configure as many buttons as we need with the actions for the current meta item
|
foreach (Widget widget in action_buttons.values) {
|
||||||
foreach (var message_action in message_actions) {
|
widget.visible = false;
|
||||||
if (message_action.popover != null) {
|
|
||||||
MenuButton button = new MenuButton();
|
|
||||||
button.sensitive = message_action.sensitive;
|
|
||||||
button.icon_name = message_action.icon_name;
|
|
||||||
button.set_popover(message_action.popover as Popover);
|
|
||||||
button.tooltip_text = Util.string_if_tooltips_active(message_action.tooltip);
|
|
||||||
action_buttons.add(button);
|
|
||||||
} else if (message_action.callback != null) {
|
|
||||||
Button button = new Button();
|
|
||||||
button.sensitive = message_action.sensitive;
|
|
||||||
button.icon_name = message_action.icon_name;
|
|
||||||
button.clicked.connect(() => {
|
|
||||||
message_action.callback(button, current_meta_item, currently_highlighted);
|
|
||||||
});
|
|
||||||
button.tooltip_text = Util.string_if_tooltips_active(message_action.tooltip);
|
|
||||||
action_buttons.add(button);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Widget widget in action_buttons) {
|
// Configure as many buttons as we need with the actions for the current meta item
|
||||||
message_menu_box.append(widget);
|
foreach (var message_action in current_message_actions) {
|
||||||
|
Widget button_widget = action_buttons[message_action.name];
|
||||||
|
button_widget.visible = true;
|
||||||
|
if (message_action.name == "reaction") {
|
||||||
|
MenuButton button = (MenuButton) button_widget;
|
||||||
|
button.sensitive = message_action.sensitive;
|
||||||
|
button.icon_name = message_action.icon_name;
|
||||||
|
button.tooltip_text = Util.string_if_tooltips_active(message_action.tooltip);
|
||||||
|
} else if (message_action.callback != null) {
|
||||||
|
Button button = (Button) button_widget;
|
||||||
|
button.sensitive = message_action.sensitive;
|
||||||
|
button.icon_name = message_action.icon_name;
|
||||||
|
button.tooltip_text = Util.string_if_tooltips_active(message_action.tooltip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message_menu_box.visible = false;
|
message_menu_box.visible = false;
|
||||||
|
@ -409,6 +426,7 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
content_items.remove((ContentMetaItem)item);
|
content_items.remove((ContentMetaItem)item);
|
||||||
}
|
}
|
||||||
meta_items.remove(item);
|
meta_items.remove(item);
|
||||||
|
skeleton.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
removed_item(item);
|
removed_item(item);
|
||||||
|
@ -498,12 +516,10 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
(upper_item.mark == Message.Marked.WONTSEND) == (lower_item.mark == Message.Marked.WONTSEND);
|
(upper_item.mark == Message.Marked.WONTSEND) == (lower_item.mark == Message.Marked.WONTSEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_action_button_clicked(ToggleButton button) {
|
private void on_action_button_clicked(Widget widget, GLib.Variant? variant = null) {
|
||||||
int button_idx = action_buttons.index_of(button);
|
foreach (var action in message_actions) {
|
||||||
print(button_idx.to_string() + "\n");
|
if (action.name != widget.name) continue;
|
||||||
Plugins.MessageAction message_action = message_actions[button_idx];
|
action.callback(variant);
|
||||||
if (message_action.callback != null) {
|
|
||||||
message_action.callback(button, current_meta_item, currently_highlighted);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,12 +592,19 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
|
||||||
private void clear() {
|
private void clear() {
|
||||||
was_upper = null;
|
was_upper = null;
|
||||||
was_page_size = null;
|
was_page_size = null;
|
||||||
|
foreach (var item in content_items) {
|
||||||
|
item.dispose();
|
||||||
|
}
|
||||||
content_items.clear();
|
content_items.clear();
|
||||||
meta_items.clear();
|
meta_items.clear();
|
||||||
widget_order.clear();
|
widget_order.clear();
|
||||||
|
foreach (var skeleton in item_item_skeletons.values) {
|
||||||
|
skeleton.dispose();
|
||||||
|
}
|
||||||
item_item_skeletons.clear();
|
item_item_skeletons.clear();
|
||||||
foreach (Widget widget in widgets.values) {
|
foreach (Widget widget in widgets.values) {
|
||||||
main.remove(widget);
|
widget.unparent();
|
||||||
|
widget.dispose();
|
||||||
}
|
}
|
||||||
widgets.clear();
|
widgets.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ public class FileWidget : SizeRequestBox {
|
||||||
} catch (Error e) { }
|
} catch (Error e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != State.DEFAULT) {
|
if (!show_image() && state != State.DEFAULT) {
|
||||||
if (content != null) this.remove(content);
|
if (content != null) this.remove(content);
|
||||||
FileDefaultWidget default_file_widget = new FileDefaultWidget();
|
FileDefaultWidget default_file_widget = new FileDefaultWidget();
|
||||||
default_widget_controller = new FileDefaultWidgetController(default_file_widget);
|
default_widget_controller = new FileDefaultWidgetController(default_file_widget);
|
||||||
|
@ -121,8 +121,12 @@ public class FileWidget : SizeRequestBox {
|
||||||
|
|
||||||
private bool show_image() {
|
private bool show_image() {
|
||||||
if (file_transfer.mime_type == null) return false;
|
if (file_transfer.mime_type == null) return false;
|
||||||
if (file_transfer.state != FileTransfer.State.COMPLETE &&
|
|
||||||
!(file_transfer.direction == FileTransfer.DIRECTION_SENT && file_transfer.state == FileTransfer.State.IN_PROGRESS)) {
|
// If the image is being sent by this client, we already have the file
|
||||||
|
bool in_progress_from_us = file_transfer.direction == FileTransfer.DIRECTION_SENT &&
|
||||||
|
file_transfer.ourpart.equals(file_transfer.account.full_jid) &&
|
||||||
|
file_transfer.state == FileTransfer.State.IN_PROGRESS;
|
||||||
|
if (file_transfer.state != FileTransfer.State.COMPLETE && !in_progress_from_us) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +139,17 @@ public class FileWidget : SizeRequestBox {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
if (default_widget_controller != null) default_widget_controller.dispose();
|
||||||
|
default_widget_controller = null;
|
||||||
|
if (content != null) {
|
||||||
|
content.unparent();
|
||||||
|
content.dispose();
|
||||||
|
content = null;
|
||||||
|
}
|
||||||
|
base.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileWidgetController : Object {
|
public class FileWidgetController : Object {
|
||||||
|
|
|
@ -4,14 +4,14 @@ using Gtk;
|
||||||
namespace Dino.Ui {
|
namespace Dino.Ui {
|
||||||
public Plugins.MessageAction get_reaction_action(ContentItem content_item, Conversation conversation, StreamInteractor stream_interactor) {
|
public Plugins.MessageAction get_reaction_action(ContentItem content_item, Conversation conversation, StreamInteractor stream_interactor) {
|
||||||
Plugins.MessageAction action = new Plugins.MessageAction();
|
Plugins.MessageAction action = new Plugins.MessageAction();
|
||||||
|
action.name = "reaction";
|
||||||
action.icon_name = "dino-emoticon-add-symbolic";
|
action.icon_name = "dino-emoticon-add-symbolic";
|
||||||
action.tooltip = _("Add reaction");
|
action.tooltip = _("Add reaction");
|
||||||
|
|
||||||
EmojiChooser chooser = new EmojiChooser();
|
action.callback = (variant) => {
|
||||||
chooser.emoji_picked.connect((emoji) => {
|
string emoji = variant.get_string();
|
||||||
stream_interactor.get_module(Reactions.IDENTITY).add_reaction(conversation, content_item, emoji);
|
stream_interactor.get_module(Reactions.IDENTITY).add_reaction(conversation, content_item, emoji);
|
||||||
});
|
};
|
||||||
action.popover = chooser;
|
|
||||||
|
|
||||||
// Disable the button if reaction aren't possible.
|
// Disable the button if reaction aren't possible.
|
||||||
bool supports_reactions = stream_interactor.get_module(Reactions.IDENTITY).conversation_supports_reactions(conversation);
|
bool supports_reactions = stream_interactor.get_module(Reactions.IDENTITY).conversation_supports_reactions(conversation);
|
||||||
|
@ -29,9 +29,10 @@ namespace Dino.Ui {
|
||||||
|
|
||||||
public Plugins.MessageAction get_reply_action(ContentItem content_item, Conversation conversation, StreamInteractor stream_interactor) {
|
public Plugins.MessageAction get_reply_action(ContentItem content_item, Conversation conversation, StreamInteractor stream_interactor) {
|
||||||
Plugins.MessageAction action = new Plugins.MessageAction();
|
Plugins.MessageAction action = new Plugins.MessageAction();
|
||||||
|
action.name = "reply";
|
||||||
action.icon_name = "mail-reply-sender-symbolic";
|
action.icon_name = "mail-reply-sender-symbolic";
|
||||||
action.tooltip = _("Reply");
|
action.tooltip = _("Reply");
|
||||||
action.callback = (button, content_meta_item_activated, widget) => {
|
action.callback = () => {
|
||||||
GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(conversation.id), new GLib.Variant.int32(content_item.id) }));
|
GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(conversation.id), new GLib.Variant.int32(content_item.id) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private MessageItem message_item;
|
private MessageItem message_item;
|
||||||
public Message.Marked marked { get; set; }
|
public Message.Marked marked { get; set; }
|
||||||
|
public Plugins.ConversationItemWidgetInterface outer = null;
|
||||||
|
|
||||||
MessageItemEditMode? edit_mode = null;
|
MessageItemEditMode? edit_mode = null;
|
||||||
ChatTextViewController? controller = null;
|
ChatTextViewController? controller = null;
|
||||||
|
@ -35,6 +36,8 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
message_item = content_item as MessageItem;
|
message_item = content_item as MessageItem;
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
|
||||||
|
stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect(on_received_correction);
|
||||||
|
|
||||||
label.activate_link.connect(on_label_activate_link);
|
label.activate_link.connect(on_label_activate_link);
|
||||||
|
|
||||||
Message message = ((MessageItem) content_item).message;
|
Message message = ((MessageItem) content_item).message;
|
||||||
|
@ -146,10 +149,50 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
|
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) {
|
||||||
|
this.outer = outer;
|
||||||
|
|
||||||
stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect(on_received_correction);
|
this.notify["in-edit-mode"].connect(on_in_edit_mode_changed);
|
||||||
|
|
||||||
this.notify["in-edit-mode"].connect(() => {
|
outer.set_widget(label, Plugins.WidgetType.GTK4, 2);
|
||||||
|
|
||||||
|
if (message_item.message.quoted_item_id > 0) {
|
||||||
|
var quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(message_item.conversation, message_item.message.quoted_item_id);
|
||||||
|
if (quoted_content_item != null) {
|
||||||
|
var quote_model = new Quote.Model.from_content_item(quoted_content_item, message_item.conversation, stream_interactor);
|
||||||
|
quote_model.jump_to.connect(() => {
|
||||||
|
GLib.Application.get_default().activate_action("jump-to-conversation-message", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(quoted_content_item.id) }));
|
||||||
|
});
|
||||||
|
var quote_widget = Quote.get_widget(quote_model);
|
||||||
|
outer.set_widget(quote_widget, Plugins.WidgetType.GTK4, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
|
||||||
|
if (in_edit_mode) return null;
|
||||||
|
|
||||||
|
Gee.List<Plugins.MessageAction> actions = new ArrayList<Plugins.MessageAction>();
|
||||||
|
|
||||||
|
bool correction_allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
|
||||||
|
if (correction_allowed) {
|
||||||
|
Plugins.MessageAction action1 = new Plugins.MessageAction();
|
||||||
|
action1.name = "correction";
|
||||||
|
action1.icon_name = "document-edit-symbolic";
|
||||||
|
action1.tooltip = _("Edit message");
|
||||||
|
action1.callback = () => {
|
||||||
|
this.in_edit_mode = true;
|
||||||
|
};
|
||||||
|
actions.add(action1);
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.add(get_reply_action(content_item, message_item.conversation, stream_interactor));
|
||||||
|
actions.add(get_reaction_action(content_item, message_item.conversation, stream_interactor));
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_in_edit_mode_changed() {
|
||||||
if (in_edit_mode == false) return;
|
if (in_edit_mode == false) return;
|
||||||
bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
|
bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
|
@ -182,45 +225,6 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
} else {
|
} else {
|
||||||
this.in_edit_mode = false;
|
this.in_edit_mode = false;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
outer.set_widget(label, Plugins.WidgetType.GTK4, 2);
|
|
||||||
|
|
||||||
if (message_item.message.quoted_item_id > 0) {
|
|
||||||
var quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(message_item.conversation, message_item.message.quoted_item_id);
|
|
||||||
if (quoted_content_item != null) {
|
|
||||||
var quote_model = new Quote.Model.from_content_item(quoted_content_item, message_item.conversation, stream_interactor);
|
|
||||||
quote_model.jump_to.connect(() => {
|
|
||||||
GLib.Application.get_default().activate_action("jump-to-conversation-message", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(quoted_content_item.id) }));
|
|
||||||
});
|
|
||||||
var quote_widget = Quote.get_widget(quote_model);
|
|
||||||
outer.set_widget(quote_widget, Plugins.WidgetType.GTK4, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) {
|
|
||||||
if (content_item as FileItem != null || this.in_edit_mode) return null;
|
|
||||||
if (in_edit_mode) return null;
|
|
||||||
|
|
||||||
Gee.List<Plugins.MessageAction> actions = new ArrayList<Plugins.MessageAction>();
|
|
||||||
|
|
||||||
bool correction_allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message);
|
|
||||||
if (correction_allowed) {
|
|
||||||
Plugins.MessageAction action1 = new Plugins.MessageAction();
|
|
||||||
action1.icon_name = "document-edit-symbolic";
|
|
||||||
action1.tooltip = _("Edit message");
|
|
||||||
action1.callback = (button, content_meta_item_activated, widget) => {
|
|
||||||
this.in_edit_mode = true;
|
|
||||||
};
|
|
||||||
actions.add(action1);
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.add(get_reply_action(content_item, message_item.conversation, stream_interactor));
|
|
||||||
actions.add(get_reaction_action(content_item, message_item.conversation, stream_interactor));
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_edit_send(string text) {
|
private void on_edit_send(string text) {
|
||||||
|
@ -251,6 +255,17 @@ public class MessageMetaItem : ContentMetaItem {
|
||||||
Dino.Application.get_default().open(new File[]{file}, "");
|
Dino.Application.get_default().open(new File[]{file}, "");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.disconnect(on_received_correction);
|
||||||
|
this.notify["in-edit-mode"].disconnect(on_in_edit_mode_changed);
|
||||||
|
if (label != null) {
|
||||||
|
label.unparent();
|
||||||
|
label.dispose();
|
||||||
|
label = null;
|
||||||
|
}
|
||||||
|
base.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/message_item_widget_edit_mode.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/message_item_widget_edit_mode.ui")]
|
||||||
|
|
|
@ -61,13 +61,13 @@ namespace Dino.Ui.Quote {
|
||||||
|
|
||||||
public Widget get_widget(Model model) {
|
public Widget get_widget(Model model) {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/quote.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/quote.ui");
|
||||||
AvatarImage avatar = (AvatarImage) builder.get_object("avatar");
|
AvatarPicture avatar = (AvatarPicture) builder.get_object("avatar");
|
||||||
Label author = (Label) builder.get_object("author");
|
Label author = (Label) builder.get_object("author");
|
||||||
Label time = (Label) builder.get_object("time");
|
Label time = (Label) builder.get_object("time");
|
||||||
Label message = (Label) builder.get_object("message");
|
Label message = (Label) builder.get_object("message");
|
||||||
Button abort_button = (Button) builder.get_object("abort-button");
|
Button abort_button = (Button) builder.get_object("abort-button");
|
||||||
|
|
||||||
avatar.set_conversation_participant(model.stream_interactor, model.conversation, model.author_jid);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(model.stream_interactor).add_participant(model.conversation, model.author_jid);
|
||||||
model.bind_property("display-name", author, "label", BindingFlags.SYNC_CREATE);
|
model.bind_property("display-name", author, "label", BindingFlags.SYNC_CREATE);
|
||||||
model.bind_property("display-time", time, "label", BindingFlags.SYNC_CREATE);
|
model.bind_property("display-time", time, "label", BindingFlags.SYNC_CREATE);
|
||||||
model.bind_property("message", message, "label", BindingFlags.SYNC_CREATE);
|
model.bind_property("message", message, "label", BindingFlags.SYNC_CREATE);
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Dino.Ui {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/conversation_row.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/conversation_row.ui")]
|
||||||
public class ConversationSelectorRow : ListBoxRow {
|
public class ConversationSelectorRow : ListBoxRow {
|
||||||
|
|
||||||
[GtkChild] protected unowned AvatarImage image;
|
[GtkChild] protected unowned AvatarPicture picture;
|
||||||
[GtkChild] protected unowned Label name_label;
|
[GtkChild] protected unowned Label name_label;
|
||||||
[GtkChild] protected unowned Label time_label;
|
[GtkChild] protected unowned Label time_label;
|
||||||
[GtkChild] protected unowned Label nick_label;
|
[GtkChild] protected unowned Label nick_label;
|
||||||
|
@ -101,7 +101,7 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||||
x_button.clicked.connect(() => {
|
x_button.clicked.connect(() => {
|
||||||
stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
|
stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
|
||||||
});
|
});
|
||||||
image.set_conversation(stream_interactor, conversation);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
|
||||||
conversation.notify["read-up-to-item"].connect(() => update_read());
|
conversation.notify["read-up-to-item"].connect(() => update_read());
|
||||||
conversation.notify["pinned"].connect(() => { update_pinned_icon(); });
|
conversation.notify["pinned"].connect(() => { update_pinned_icon(); });
|
||||||
|
|
||||||
|
@ -225,7 +225,21 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||||
label.attributes = copy;
|
label.attributes = copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool update_read_pending = false;
|
||||||
|
private bool update_read_pending_force = false;
|
||||||
protected void update_read(bool force_update = false) {
|
protected void update_read(bool force_update = false) {
|
||||||
|
if (force_update) update_read_pending_force = true;
|
||||||
|
if (update_read_pending) return;
|
||||||
|
update_read_pending = true;
|
||||||
|
Idle.add(() => {
|
||||||
|
update_read_pending = false;
|
||||||
|
update_read_pending_force = false;
|
||||||
|
update_read_idle(update_read_pending_force);
|
||||||
|
return Source.REMOVE;
|
||||||
|
}, Priority.LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_read_idle(bool force_update = false) {
|
||||||
int current_num_unread = stream_interactor.get_module(ChatInteraction.IDENTITY).get_num_unread(conversation);
|
int current_num_unread = stream_interactor.get_module(ChatInteraction.IDENTITY).get_num_unread(conversation);
|
||||||
if (num_unread == current_num_unread && !force_update) return;
|
if (num_unread == current_num_unread && !force_update) return;
|
||||||
num_unread = current_num_unread;
|
num_unread = current_num_unread;
|
||||||
|
|
|
@ -116,15 +116,15 @@ public class GlobalSearch {
|
||||||
// Populate new suggestions
|
// Populate new suggestions
|
||||||
foreach(SearchSuggestion suggestion in suggestions) {
|
foreach(SearchSuggestion suggestion in suggestions) {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui");
|
||||||
AvatarImage avatar = (AvatarImage)builder.get_object("image");
|
AvatarPicture avatar = (AvatarPicture)builder.get_object("picture");
|
||||||
Label label = (Label)builder.get_object("label");
|
Label label = (Label)builder.get_object("label");
|
||||||
string display_name;
|
string display_name;
|
||||||
if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) {
|
if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) {
|
||||||
display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid);
|
display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid);
|
||||||
avatar.set_conversation_participant(stream_interactor, suggestion.conversation, suggestion.jid);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(suggestion.conversation, suggestion.jid);
|
||||||
} else {
|
} else {
|
||||||
display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation);
|
display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation);
|
||||||
avatar.set_conversation(stream_interactor, suggestion.conversation);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(suggestion.conversation);
|
||||||
}
|
}
|
||||||
if (display_name != suggestion.jid.to_string()) {
|
if (display_name != suggestion.jid.to_string()) {
|
||||||
label.set_markup("%s <span font_weight='light' fgalpha='80%%'>%s</span>".printf(Markup.escape_text(display_name), Markup.escape_text(suggestion.jid.to_string())));
|
label.set_markup("%s <span font_weight='light' fgalpha='80%%'>%s</span>".printf(Markup.escape_text(display_name), Markup.escape_text(suggestion.jid.to_string())));
|
||||||
|
@ -289,10 +289,10 @@ public class GlobalSearch {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid get_skeleton(MessageItem item) {
|
private Grid get_skeleton(MessageItem item) {
|
||||||
AvatarImage image = new AvatarImage() { height=32, width=32, margin_end=7, valign=Align.START, allow_gray = false };
|
AvatarPicture picture = new AvatarPicture() { height_request=32, width_request=32, margin_end=7, valign=Align.START };
|
||||||
image.set_conversation_participant(stream_interactor, item.conversation, item.jid);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(item.conversation, item.jid);
|
||||||
Grid grid = new Grid() { row_homogeneous=false };
|
Grid grid = new Grid() { row_homogeneous=false };
|
||||||
grid.attach(image, 0, 0, 1, 2);
|
grid.attach(picture, 0, 0, 1, 2);
|
||||||
|
|
||||||
string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid);
|
string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid);
|
||||||
Label name_label = new Label(display_name) { ellipsize=EllipsizeMode.END, xalign=0 };
|
Label name_label = new Label(display_name) { ellipsize=EllipsizeMode.END, xalign=0 };
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Dino.Ui.ManageAccounts {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")]
|
||||||
public class AccountRow : Gtk.ListBoxRow {
|
public class AccountRow : Gtk.ListBoxRow {
|
||||||
|
|
||||||
[GtkChild] public unowned AvatarImage image;
|
[GtkChild] public unowned AvatarPicture picture;
|
||||||
[GtkChild] public unowned Label jid_label;
|
[GtkChild] public unowned Label jid_label;
|
||||||
[GtkChild] public unowned Image icon;
|
[GtkChild] public unowned Image icon;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ public class AccountRow : Gtk.ListBoxRow {
|
||||||
public AccountRow(StreamInteractor stream_interactor, Account account) {
|
public AccountRow(StreamInteractor stream_interactor, Account account) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
|
||||||
jid_label.set_label(account.bare_jid.to_string());
|
jid_label.set_label(account.bare_jid.to_string());
|
||||||
|
|
||||||
stream_interactor.connection_manager.connection_error.connect((account, error) => {
|
stream_interactor.connection_manager.connection_error.connect((account, error) => {
|
||||||
|
|
|
@ -19,7 +19,7 @@ public class Dialog : Gtk.Dialog {
|
||||||
[GtkChild] public unowned Button no_accounts_add;
|
[GtkChild] public unowned Button no_accounts_add;
|
||||||
[GtkChild] public unowned Button add_account_button;
|
[GtkChild] public unowned Button add_account_button;
|
||||||
[GtkChild] public unowned Button remove_account_button;
|
[GtkChild] public unowned Button remove_account_button;
|
||||||
[GtkChild] public unowned AvatarImage image;
|
[GtkChild] public unowned AvatarPicture picture;
|
||||||
[GtkChild] public unowned Button image_button;
|
[GtkChild] public unowned Button image_button;
|
||||||
[GtkChild] public unowned Label jid_label;
|
[GtkChild] public unowned Label jid_label;
|
||||||
[GtkChild] public unowned Label state_label;
|
[GtkChild] public unowned Label state_label;
|
||||||
|
@ -178,14 +178,14 @@ public class Dialog : Gtk.Dialog {
|
||||||
|
|
||||||
private void on_received_avatar(Jid jid, Account account) {
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
if (selected_account.equals(account) && jid.equals(account.bare_jid)) {
|
if (selected_account.equals(account) && jid.equals(account.bare_jid)) {
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populate_grid_data(Account account) {
|
private void populate_grid_data(Account account) {
|
||||||
active_switch.state_set.disconnect(change_account_state);
|
active_switch.state_set.disconnect(change_account_state);
|
||||||
|
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
|
||||||
active_switch.set_active(account.enabled);
|
active_switch.set_active(account.enabled);
|
||||||
jid_label.label = account.bare_jid.to_string();
|
jid_label.label = account.bare_jid.to_string();
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
||||||
hash_table["image-data"] = yield get_conversation_icon(conversation);
|
hash_table["image-data"] = yield get_conversation_icon(conversation);
|
||||||
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
||||||
|
hash_table["category"] = new Variant.string("im.received");
|
||||||
string[] actions = new string[] {"default", "Open conversation"};
|
string[] actions = new string[] {"default", "Open conversation"};
|
||||||
try {
|
try {
|
||||||
uint32 notification_id = yield dbus_notifications.notify("Dino", replace_id, "", conversation_display_name, body, actions, hash_table, -1);
|
uint32 notification_id = yield dbus_notifications.notify("Dino", replace_id, "", conversation_display_name, body, actions, hash_table, -1);
|
||||||
|
@ -120,6 +121,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
hash_table["sound-name"] = new Variant.string("phone-incoming-call");
|
hash_table["sound-name"] = new Variant.string("phone-incoming-call");
|
||||||
hash_table["urgency"] = new Variant.byte(2);
|
hash_table["urgency"] = new Variant.byte(2);
|
||||||
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
||||||
|
hash_table["category"] = new Variant.string("im");
|
||||||
string[] actions = new string[] {"default", "Open conversation", "reject", _("Reject"), "accept", _("Accept")};
|
string[] actions = new string[] {"default", "Open conversation", "reject", _("Reject"), "accept", _("Accept")};
|
||||||
try {
|
try {
|
||||||
uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, 0);
|
uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, 0);
|
||||||
|
@ -158,6 +160,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
||||||
hash_table["image-data"] = yield get_conversation_icon(conversation);
|
hash_table["image-data"] = yield get_conversation_icon(conversation);
|
||||||
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
||||||
|
hash_table["category"] = new Variant.string("im");
|
||||||
string[] actions = new string[] {"default", "Open conversation", "accept", _("Accept"), "deny", _("Deny")};
|
string[] actions = new string[] {"default", "Open conversation", "accept", _("Accept"), "deny", _("Deny")};
|
||||||
try {
|
try {
|
||||||
uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, -1);
|
uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, -1);
|
||||||
|
@ -197,6 +200,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
|
|
||||||
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
||||||
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
||||||
|
hash_table["category"] = new Variant.string("im.error");
|
||||||
try {
|
try {
|
||||||
yield dbus_notifications.notify("Dino", 0, "im.dino.Dino", summary, body, new string[]{}, hash_table, -1);
|
yield dbus_notifications.notify("Dino", 0, "im.dino.Dino", summary, body, new string[]{}, hash_table, -1);
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
|
@ -217,6 +221,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
||||||
hash_table["image-data"] = yield get_conversation_icon(direct_conversation);
|
hash_table["image-data"] = yield get_conversation_icon(direct_conversation);
|
||||||
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
||||||
|
hash_table["category"] = new Variant.string("im");
|
||||||
string[] actions = new string[] {"default", "", "reject", _("Reject"), "accept", _("Accept")};
|
string[] actions = new string[] {"default", "", "reject", _("Reject"), "accept", _("Accept")};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -250,6 +255,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
HashTable<string, Variant> hash_table = new HashTable<string, Variant>(null, null);
|
||||||
hash_table["image-data"] = yield get_conversation_icon(conversation);
|
hash_table["image-data"] = yield get_conversation_icon(conversation);
|
||||||
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id());
|
||||||
|
hash_table["category"] = new Variant.string("im");
|
||||||
string[] actions = new string[] {"deny", _("Deny"), "accept", _("Accept")};
|
string[] actions = new string[] {"deny", _("Deny"), "accept", _("Accept")};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -287,8 +293,12 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Variant get_conversation_icon(Conversation conversation) {
|
private async Variant get_conversation_icon(Conversation conversation) {
|
||||||
AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation);
|
CompatAvatarDrawer drawer = new CompatAvatarDrawer() {
|
||||||
Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface();
|
model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation),
|
||||||
|
width_request = 40,
|
||||||
|
height_request = 40
|
||||||
|
};
|
||||||
|
Cairo.ImageSurface surface = drawer.draw_image_surface();
|
||||||
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
||||||
var bytes = avatar.pixel_bytes;
|
var bytes = avatar.pixel_bytes;
|
||||||
var image_bytes = Variant.new_from_data<Bytes>(new VariantType("ay"), bytes.get_data(), true, bytes);
|
var image_bytes = Variant.new_from_data<Bytes>(new VariantType("ay"), bytes.get_data(), true, bytes);
|
||||||
|
|
|
@ -171,8 +171,12 @@ namespace Dino.Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Icon get_conversation_icon(Conversation conversation) throws Error {
|
private async Icon get_conversation_icon(Conversation conversation) throws Error {
|
||||||
AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation);
|
CompatAvatarDrawer drawer = new CompatAvatarDrawer() {
|
||||||
Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface();
|
model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation),
|
||||||
|
width_request = 40,
|
||||||
|
height_request = 40
|
||||||
|
};
|
||||||
|
Cairo.ImageSurface surface = drawer.draw_image_surface();
|
||||||
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
||||||
uint8[] buffer;
|
uint8[] buffer;
|
||||||
avatar.save_to_buffer(out buffer, "png");
|
avatar.save_to_buffer(out buffer, "png");
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Dino.Ui.OccupantMenu {
|
||||||
public class ListRow : Object {
|
public class ListRow : Object {
|
||||||
|
|
||||||
private Grid main_grid;
|
private Grid main_grid;
|
||||||
private AvatarImage image;
|
private AvatarPicture picture;
|
||||||
public Label name_label;
|
public Label name_label;
|
||||||
|
|
||||||
public Conversation? conversation;
|
public Conversation? conversation;
|
||||||
|
@ -17,7 +17,7 @@ public class ListRow : Object {
|
||||||
construct {
|
construct {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/occupant_list_item.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/occupant_list_item.ui");
|
||||||
main_grid = (Grid) builder.get_object("main_grid");
|
main_grid = (Grid) builder.get_object("main_grid");
|
||||||
image = (AvatarImage) builder.get_object("image");
|
picture = (AvatarPicture) builder.get_object("picture");
|
||||||
name_label = (Label) builder.get_object("name_label");
|
name_label = (Label) builder.get_object("name_label");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +26,12 @@ public class ListRow : Object {
|
||||||
this.jid = jid;
|
this.jid = jid;
|
||||||
|
|
||||||
name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid);
|
name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid);
|
||||||
image.set_conversation_participant(stream_interactor, conversation, jid);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListRow.label(string c, string text) {
|
public ListRow.label(string c, string text) {
|
||||||
name_label.label = text;
|
name_label.label = text;
|
||||||
image.set_text(c);
|
picture.model = new ViewModel.CompatAvatarPictureModel(null).add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Widget get_widget() {
|
public Widget get_widget() {
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Gtk;
|
||||||
|
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
using Xmpp;
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
namespace Dino.Ui.Util {
|
namespace Dino.Ui.Util {
|
||||||
|
|
||||||
|
@ -18,20 +19,22 @@ private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673
|
||||||
private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"};
|
private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"};
|
||||||
private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"};
|
private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"};
|
||||||
|
|
||||||
|
public static string get_consistent_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false) {
|
||||||
|
uint8[] rgb;
|
||||||
|
if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid.bare_jid, account) && jid.resourcepart != null) {
|
||||||
|
rgb = ConsistentColor.string_to_rgb(jid.resourcepart);
|
||||||
|
} else {
|
||||||
|
rgb = ConsistentColor.string_to_rgb(jid.bare_jid.to_string());
|
||||||
|
}
|
||||||
|
return "%.2x%.2x%.2x".printf(rgb[0], rgb[1], rgb[2]);
|
||||||
|
}
|
||||||
|
|
||||||
public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
||||||
uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash();
|
return get_consistent_hex_color(stream_interactor, account, get_relevant_jid(stream_interactor, account, jid, conversation));
|
||||||
return material_colors_300[hash % material_colors_300.length];
|
|
||||||
// return tango_colors_light[name.hash() % tango_colors_light.length];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) {
|
public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) {
|
||||||
uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash();
|
return get_consistent_hex_color(stream_interactor, account, get_relevant_jid(stream_interactor, account, jid, conversation), dark_theme);
|
||||||
if (dark_theme) {
|
|
||||||
return material_colors_300[hash % material_colors_300.length];
|
|
||||||
} else {
|
|
||||||
return material_colors_500[hash % material_colors_500.length];
|
|
||||||
}
|
|
||||||
// return tango_colors_medium[name.hash() % tango_colors_medium.length];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
||||||
|
@ -58,62 +61,6 @@ public static string color_for_show(string show) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async AvatarDrawer get_conversation_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation) {
|
|
||||||
return yield get_conversation_participants_avatar_drawer(stream_interactor, conversation, new Jid[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async AvatarDrawer get_conversation_participants_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation, owned Jid[] jids) {
|
|
||||||
AvatarManager avatar_manager = stream_interactor.get_module(AvatarManager.IDENTITY);
|
|
||||||
MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY);
|
|
||||||
if (conversation.type_ != Conversation.Type.GROUPCHAT) {
|
|
||||||
Jid jid = jids.length == 1 ? jids[0] : conversation.counterpart;
|
|
||||||
Jid avatar_jid = jid;
|
|
||||||
if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid;
|
|
||||||
return new AvatarDrawer().tile(yield avatar_manager.get_avatar(conversation.account, avatar_jid), jids.length == 1 ?
|
|
||||||
get_participant_display_name(stream_interactor, conversation, jid) :
|
|
||||||
get_conversation_display_name(stream_interactor, conversation),
|
|
||||||
Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation));
|
|
||||||
}
|
|
||||||
if (jids.length > 0) {
|
|
||||||
AvatarDrawer drawer = new AvatarDrawer();
|
|
||||||
for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) {
|
|
||||||
Jid avatar_jid = jids[i];
|
|
||||||
Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
|
|
||||||
avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid;
|
|
||||||
part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
}
|
|
||||||
drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jids[i]),
|
|
||||||
Util.get_avatar_hex_color(stream_interactor, conversation.account, jids[i], conversation));
|
|
||||||
}
|
|
||||||
if (jids.length > 4) {
|
|
||||||
drawer.plus();
|
|
||||||
}
|
|
||||||
return drawer;
|
|
||||||
}
|
|
||||||
Gdk.Pixbuf? room_avatar = yield avatar_manager.get_avatar(conversation.account, conversation.counterpart);
|
|
||||||
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account);
|
|
||||||
if (room_avatar != null || !muc_manager.is_private_room(conversation.account, conversation.counterpart) || occupants == null || occupants.size == 0) {
|
|
||||||
return new AvatarDrawer().tile(room_avatar, "#", Util.get_avatar_hex_color(stream_interactor, conversation.account, conversation.counterpart, conversation));
|
|
||||||
}
|
|
||||||
AvatarDrawer drawer = new AvatarDrawer();
|
|
||||||
for (int i = 0; i < (occupants.size <= 4 ? occupants.size : 3); i++) {
|
|
||||||
Jid jid = occupants[i];
|
|
||||||
Jid avatar_jid = jid;
|
|
||||||
Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
|
|
||||||
avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid;
|
|
||||||
part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
}
|
|
||||||
drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jid),
|
|
||||||
Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation));
|
|
||||||
}
|
|
||||||
if (occupants.size > 4) {
|
|
||||||
drawer.plus();
|
|
||||||
}
|
|
||||||
return drawer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) {
|
public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) {
|
||||||
return Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s"));
|
return Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s"));
|
||||||
}
|
}
|
||||||
|
@ -134,27 +81,6 @@ public static string get_occupant_display_name(StreamInteractor stream_interacto
|
||||||
return Dino.get_occupant_display_name(stream_interactor, conversation, jid, me_is_me ? _("Me") : null);
|
return Dino.get_occupant_display_name(stream_interactor, conversation, jid, me_is_me ? _("Me") : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this has no usages?
|
|
||||||
//public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0, int width = 0, int height = 0) {
|
|
||||||
// if (scale == 0) scale = image.scale_factor;
|
|
||||||
// Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window());
|
|
||||||
// if (height == 0 && width != 0) {
|
|
||||||
// height = (int) ((double) width / pixbuf.width * pixbuf.height);
|
|
||||||
// } else if (height != 0 && width == 0) {
|
|
||||||
// width = (int) ((double) height / pixbuf.height * pixbuf.width);
|
|
||||||
// }
|
|
||||||
// if (width != 0) {
|
|
||||||
// Cairo.Surface surface_new = new Cairo.Surface.similar_image(surface, Cairo.Format.ARGB32, width, height);
|
|
||||||
// Cairo.Context context = new Cairo.Context(surface_new);
|
|
||||||
// context.scale((double) width * scale / pixbuf.width, (double) height * scale / pixbuf.height);
|
|
||||||
// context.set_source_surface(surface, 0, 0);
|
|
||||||
// context.get_source().set_filter(Cairo.Filter.BEST);
|
|
||||||
// context.paint();
|
|
||||||
// surface = surface_new;
|
|
||||||
// }
|
|
||||||
// image.set_from_surface(surface);
|
|
||||||
//}
|
|
||||||
|
|
||||||
public static Gdk.RGBA get_label_pango_color(Label label, string css_color) {
|
public static Gdk.RGBA get_label_pango_color(Label label, string css_color) {
|
||||||
Gtk.CssProvider provider = force_color(label, css_color);
|
Gtk.CssProvider provider = force_color(label, css_color);
|
||||||
Gdk.RGBA color_rgba = label.get_style_context().get_color();
|
Gdk.RGBA color_rgba = label.get_style_context().get_color();
|
||||||
|
|
519
main/src/ui/widgets/avatar_picture.vala
Normal file
519
main/src/ui/widgets/avatar_picture.vala
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
using Dino.Entities;
|
||||||
|
using Gtk;
|
||||||
|
using Xmpp;
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.AvatarPictureTileModel : Object {
|
||||||
|
public string display_text { get; set; }
|
||||||
|
public Gdk.RGBA background_color { get; set; }
|
||||||
|
public File? image_file { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.AvatarPictureModel : Object {
|
||||||
|
public ListModel tiles { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : AvatarPictureTileModel {
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
||||||
|
private MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
||||||
|
private Conversation? conversation;
|
||||||
|
private Jid? primary_avatar_jid;
|
||||||
|
private Jid? secondary_avatar_jid;
|
||||||
|
private Jid? display_name_jid;
|
||||||
|
|
||||||
|
public ConversationParticipantAvatarPictureTileModel(StreamInteractor stream_interactor, Conversation conversation, Jid jid) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.primary_avatar_jid = jid;
|
||||||
|
this.display_name_jid = jid;
|
||||||
|
|
||||||
|
string color_id = jid.to_string();
|
||||||
|
if (conversation.type_ != Conversation.Type.CHAT && primary_avatar_jid.equals_bare(conversation.counterpart)) {
|
||||||
|
Jid? real_jid = muc_manager.get_real_jid(primary_avatar_jid, conversation.account);
|
||||||
|
if (real_jid != null && muc_manager.is_private_room(conversation.account, conversation.counterpart.bare_jid)) {
|
||||||
|
secondary_avatar_jid = primary_avatar_jid;
|
||||||
|
primary_avatar_jid = real_jid.bare_jid;
|
||||||
|
color_id = primary_avatar_jid.to_string();
|
||||||
|
} else {
|
||||||
|
color_id = jid.resourcepart.to_string();
|
||||||
|
}
|
||||||
|
} else if (conversation.type_ == Conversation.Type.CHAT) {
|
||||||
|
primary_avatar_jid = jid.bare_jid;
|
||||||
|
color_id = primary_avatar_jid.to_string();
|
||||||
|
}
|
||||||
|
string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
|
||||||
|
display_text = display.get_char(0).toupper().to_string();
|
||||||
|
stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
|
||||||
|
|
||||||
|
float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
|
||||||
|
background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f};
|
||||||
|
|
||||||
|
update_image_file();
|
||||||
|
avatar_manager.received_avatar.connect(on_received_avatar);
|
||||||
|
avatar_manager.fetched_avatar.connect(on_received_avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_image_file() {
|
||||||
|
File image_file = avatar_manager.get_avatar_file(conversation.account, primary_avatar_jid);
|
||||||
|
if (image_file == null && secondary_avatar_jid != null) {
|
||||||
|
image_file = avatar_manager.get_avatar_file(conversation.account, secondary_avatar_jid);
|
||||||
|
}
|
||||||
|
this.image_file = image_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
|
if (account.equals(conversation.account) && (jid.equals(primary_avatar_jid) || jid.equals(secondary_avatar_jid))) {
|
||||||
|
update_image_file();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_roster_updated(Account account, Jid jid) {
|
||||||
|
if (account.equals(conversation.account) && jid.equals(display_name_jid)) {
|
||||||
|
string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
|
||||||
|
display_text = display.get_char(0).toupper().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel {
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
||||||
|
private MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
||||||
|
private PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } }
|
||||||
|
private ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } }
|
||||||
|
private Conversation? conversation;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
tiles = new GLib.ListStore(typeof(ViewModel.AvatarPictureTileModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel(StreamInteractor? stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
if (stream_interactor != null) {
|
||||||
|
connect_signals_weak(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void connect_signals_weak(CompatAvatarPictureModel model_) {
|
||||||
|
WeakRef model_weak = WeakRef(model_);
|
||||||
|
ulong muc_manager_private_room_occupant_updated_handler_id = 0;
|
||||||
|
ulong muc_manager_proom_info_updated_handler_id = 0;
|
||||||
|
ulong avatar_manager_received_avatar_handler_id = 0;
|
||||||
|
ulong avatar_manager_fetched_avatar_handler_id = 0;
|
||||||
|
muc_manager_private_room_occupant_updated_handler_id = model_.muc_manager.private_room_occupant_updated.connect((muc_manager, account, room, jid) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_room_updated(account, room);
|
||||||
|
} else if (muc_manager_private_room_occupant_updated_handler_id != 0) {
|
||||||
|
muc_manager.disconnect(muc_manager_private_room_occupant_updated_handler_id);
|
||||||
|
muc_manager_private_room_occupant_updated_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
muc_manager_proom_info_updated_handler_id = model_.muc_manager.room_info_updated.connect((muc_manager, account, room) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_room_updated(account, room);
|
||||||
|
} else if (muc_manager_proom_info_updated_handler_id != 0) {
|
||||||
|
muc_manager.disconnect(muc_manager_proom_info_updated_handler_id);
|
||||||
|
muc_manager_proom_info_updated_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatar_manager_received_avatar_handler_id = model_.avatar_manager.received_avatar.connect((avatar_manager, jid, account) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_received_avatar(jid, account);
|
||||||
|
} else if (avatar_manager_received_avatar_handler_id != 0) {
|
||||||
|
avatar_manager.disconnect(avatar_manager_received_avatar_handler_id);
|
||||||
|
avatar_manager_received_avatar_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatar_manager_fetched_avatar_handler_id = model_.avatar_manager.fetched_avatar.connect((avatar_manager, jid, account) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_received_avatar(jid, account);
|
||||||
|
} else if (avatar_manager_fetched_avatar_handler_id != 0) {
|
||||||
|
avatar_manager.disconnect(avatar_manager_fetched_avatar_handler_id);
|
||||||
|
avatar_manager_fetched_avatar_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_room_updated(Account account, Jid room) {
|
||||||
|
if (conversation != null && account.equals(conversation.account) && conversation.counterpart.equals_bare(room)) {
|
||||||
|
reset();
|
||||||
|
set_conversation(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
|
on_room_updated(account, jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
(tiles as GLib.ListStore).remove_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel set_conversation(Conversation conversation) {
|
||||||
|
if (stream_interactor == null) {
|
||||||
|
critical("set_conversation() used on CompatAvatarPictureModel without stream_interactor");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.conversation = conversation;
|
||||||
|
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
|
||||||
|
if (avatar_manager.has_avatar(conversation.account, conversation.counterpart)) {
|
||||||
|
add_internal("#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart));
|
||||||
|
} else {
|
||||||
|
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account);
|
||||||
|
if (occupants != null && !occupants.is_empty && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
|
||||||
|
int count = occupants.size > 4 ? 3 : occupants.size;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
add_participant(conversation, occupants[i]);
|
||||||
|
}
|
||||||
|
if (occupants.size > 4) {
|
||||||
|
add_internal("+");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_internal("#", conversation.counterpart.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_participant(conversation, conversation.counterpart);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel add_participant(Conversation conversation, Jid jid) {
|
||||||
|
if (stream_interactor == null) {
|
||||||
|
critical("add_participant() used on CompatAvatarPictureModel without stream_interactor");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
(tiles as GLib.ListStore).append(new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel add(string display, string? color_id = null, File? image_file = null) {
|
||||||
|
add_internal(display, color_id, image_file);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvatarPictureTileModel add_internal(string display, string? color_id = null, File? image_file = null) {
|
||||||
|
GLib.ListStore store = tiles as GLib.ListStore;
|
||||||
|
float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
|
||||||
|
var model = new ViewModel.AvatarPictureTileModel() {
|
||||||
|
display_text = display.get_char(0).toupper().to_string(),
|
||||||
|
background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f},
|
||||||
|
image_file = image_file
|
||||||
|
};
|
||||||
|
store.append(model);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Dino.Ui.CompatAvatarDrawer {
|
||||||
|
public float radius_percent { get; set; default = 0.2f; }
|
||||||
|
public ViewModel.AvatarPictureModel? model { get; set; }
|
||||||
|
public int height_request { get; set; default = 35; }
|
||||||
|
public int width_request { get; set; default = 35; }
|
||||||
|
public string font_family { get; set; default = "Sans"; }
|
||||||
|
|
||||||
|
public Cairo.ImageSurface draw_image_surface() {
|
||||||
|
Cairo.ImageSurface surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width_request, height_request);
|
||||||
|
draw_on_context(new Cairo.Context(surface));
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw_on_context(Cairo.Context ctx) {
|
||||||
|
double radius = (width_request + height_request) * 0.25f * radius_percent;
|
||||||
|
double degrees = Math.PI / 180.0;
|
||||||
|
ctx.new_sub_path();
|
||||||
|
ctx.arc(width_request - radius, radius, radius, -90 * degrees, 0 * degrees);
|
||||||
|
ctx.arc(width_request - radius, height_request - radius, radius, 0 * degrees, 90 * degrees);
|
||||||
|
ctx.arc(radius, height_request - radius, radius, 90 * degrees, 180 * degrees);
|
||||||
|
ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees);
|
||||||
|
ctx.close_path();
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
if (this.model.tiles.get_n_items() == 4) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
bufctx.scale(0.5, 0.5);
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request - 1, 2), 0, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request - 1, 2), width_request + 1, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width_request - 1, height_request - 1, 2), 0, height_request + 1);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 3, width_request - 1, height_request - 1, 2), width_request + 1, height_request + 1);
|
||||||
|
bufctx.paint();
|
||||||
|
|
||||||
|
ctx.set_source_surface(buffer, 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 3) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
bufctx.scale(0.5, 0.5);
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request - 1, 2), 0, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request * 2, 2), width_request + 1, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width_request - 1, height_request - 1, 2), 0, width_request + 1);
|
||||||
|
bufctx.paint();
|
||||||
|
|
||||||
|
ctx.set_source_surface(buffer, 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 2) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
bufctx.scale(0.5, 0.5);
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request * 2, 2), 0, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request * 2, 2), width_request + 1, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
|
||||||
|
ctx.set_source_surface(buffer, 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 1) {
|
||||||
|
ctx.set_source_surface(sub_surface_idx(ctx, 0, width_request, height_request, 1), 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 0) {
|
||||||
|
ctx.set_source_surface(sub_surface_idx(ctx, -1, width_request, height_request, 1), 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
}
|
||||||
|
ctx.set_source_rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) {
|
||||||
|
ViewModel.AvatarPictureTileModel tile = (ViewModel.AvatarPictureTileModel) this.model.tiles.get_item(idx);
|
||||||
|
Gdk.Pixbuf? avatar = new Gdk.Pixbuf.from_file(tile.image_file.get_path());
|
||||||
|
string? name = idx >= 0 ? tile.display_text : "";
|
||||||
|
Gdk.RGBA hex_color = tile.background_color;
|
||||||
|
return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, Gdk.RGBA background_color, int width, int height, int font_factor = 1) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
if (avatar == null) {
|
||||||
|
Gdk.cairo_set_source_rgba(bufctx, background_color);
|
||||||
|
bufctx.rectangle(0, 0, width, height);
|
||||||
|
bufctx.fill();
|
||||||
|
|
||||||
|
string text = name == null ? "…" : name.get_char(0).toupper().to_string();
|
||||||
|
bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
|
||||||
|
bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25);
|
||||||
|
Cairo.TextExtents extents;
|
||||||
|
bufctx.text_extents(text, out extents);
|
||||||
|
double x_pos = width/2 - (extents.width/2 + extents.x_bearing);
|
||||||
|
double y_pos = height/2 - (extents.height/2 + extents.y_bearing);
|
||||||
|
bufctx.move_to(x_pos, y_pos);
|
||||||
|
bufctx.set_source_rgba(1, 1, 1, 1);
|
||||||
|
bufctx.show_text(text);
|
||||||
|
} else {
|
||||||
|
double w_scale = (double) width / avatar.width;
|
||||||
|
double h_scale = (double) height / avatar.height;
|
||||||
|
double scale = double.max(w_scale, h_scale);
|
||||||
|
bufctx.scale(scale, scale);
|
||||||
|
|
||||||
|
double x_off = 0, y_off = 0;
|
||||||
|
if (scale == h_scale) {
|
||||||
|
x_off = (width / scale - avatar.width) / 2.0;
|
||||||
|
} else {
|
||||||
|
y_off = (height / scale - avatar.height) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off);
|
||||||
|
bufctx.get_source().set_filter(Cairo.Filter.BEST);
|
||||||
|
bufctx.paint();
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.AvatarPicture : Gtk.Widget {
|
||||||
|
public float radius_percent { get; set; default = 0.2f; }
|
||||||
|
public ViewModel.AvatarPictureModel? model { get; set; }
|
||||||
|
private Gee.List<Tile> tiles = new Gee.ArrayList<Tile>();
|
||||||
|
|
||||||
|
private ViewModel.AvatarPictureModel? old_model;
|
||||||
|
private ulong model_tiles_items_changed_handler;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
height_request = 35;
|
||||||
|
width_request = 35;
|
||||||
|
set_css_name("picture");
|
||||||
|
add_css_class("avatar");
|
||||||
|
notify["radius-percent"].connect(queue_draw);
|
||||||
|
notify["model"].connect(on_model_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_model_changed() {
|
||||||
|
if (old_model != null) {
|
||||||
|
old_model.tiles.disconnect(model_tiles_items_changed_handler);
|
||||||
|
}
|
||||||
|
foreach (Tile tile in tiles) {
|
||||||
|
tile.unparent();
|
||||||
|
tile.dispose();
|
||||||
|
}
|
||||||
|
tiles.clear();
|
||||||
|
old_model = model;
|
||||||
|
if (model != null) {
|
||||||
|
model_tiles_items_changed_handler = model.tiles.items_changed.connect(on_model_items_changed);
|
||||||
|
for(int i = 0; i < model.tiles.get_n_items(); i++) {
|
||||||
|
Tile tile = new Tile();
|
||||||
|
tile.model = model.tiles.get_item(i) as ViewModel.AvatarPictureTileModel;
|
||||||
|
tile.insert_after(this, tiles.is_empty ? null : tiles.last());
|
||||||
|
tiles.add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_model_items_changed(uint position, uint removed, uint added) {
|
||||||
|
while (removed > 0) {
|
||||||
|
Tile old = tiles.remove_at((int) position);
|
||||||
|
old.unparent();
|
||||||
|
old.dispose();
|
||||||
|
removed--;
|
||||||
|
}
|
||||||
|
while (added > 0) {
|
||||||
|
Tile tile = new Tile();
|
||||||
|
tile.model = model.tiles.get_item(position) as ViewModel.AvatarPictureTileModel;
|
||||||
|
tile.insert_after(this, position == 0 ? null : tiles[(int) position - 1]);
|
||||||
|
tiles.insert((int) position, tile);
|
||||||
|
position++;
|
||||||
|
added--;
|
||||||
|
}
|
||||||
|
queue_allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
||||||
|
minimum_baseline = natural_baseline = -1;
|
||||||
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
|
minimum = natural = width_request;
|
||||||
|
} else {
|
||||||
|
minimum = natural = height_request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void size_allocate(int width, int height, int baseline) {
|
||||||
|
int half_width_size = width / 2;
|
||||||
|
int half_height_size = height / 2;
|
||||||
|
int half_width_offset = (width % 2 == 0) ? half_width_size : half_width_size + 1;
|
||||||
|
int half_height_offset = (height % 2 == 0) ? half_height_size : half_height_size + 1;
|
||||||
|
if (tiles.size == 1) {
|
||||||
|
tiles[0].allocate(width, height, baseline, null);
|
||||||
|
} else if (tiles.size == 2) {
|
||||||
|
tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = height }, baseline);
|
||||||
|
tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = height }, baseline);
|
||||||
|
} else if (tiles.size == 3) {
|
||||||
|
tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = height }, baseline);
|
||||||
|
tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[2].allocate_size(Allocation() { x = half_width_offset, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
} else if (tiles.size == 4) {
|
||||||
|
tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[2].allocate_size(Allocation() { x = 0, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[3].allocate_size(Allocation() { x = half_width_offset, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SizeRequestMode get_request_mode() {
|
||||||
|
return SizeRequestMode.CONSTANT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void snapshot(Gtk.Snapshot snapshot) {
|
||||||
|
Graphene.Rect bounds = Graphene.Rect();
|
||||||
|
bounds.init(0, 0, get_width(), get_height());
|
||||||
|
Gsk.RoundedRect rounded_rect = Gsk.RoundedRect();
|
||||||
|
rounded_rect.init_from_rect(bounds, (get_width() + get_height()) * 0.25f * radius_percent);
|
||||||
|
snapshot.push_rounded_clip(rounded_rect);
|
||||||
|
base.snapshot(snapshot);
|
||||||
|
snapshot.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
model = null;
|
||||||
|
on_model_changed();
|
||||||
|
base.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Tile : Gtk.Widget {
|
||||||
|
public ViewModel.AvatarPictureTileModel? model { get; set; }
|
||||||
|
public Gdk.RGBA background_color { get; set; default = Gdk.RGBA(){ red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 0.0f }; }
|
||||||
|
public string display_text { get { return label.get_text(); } set { label.set_text(value); } }
|
||||||
|
public File? image_file { get { return picture.file; } set { picture.file = value; } }
|
||||||
|
|
||||||
|
private Binding? background_color_binding;
|
||||||
|
private Binding? display_text_binding;
|
||||||
|
private Binding? image_file_binding;
|
||||||
|
|
||||||
|
private Label label = new Label("");
|
||||||
|
private Picture picture = new Picture();
|
||||||
|
|
||||||
|
construct {
|
||||||
|
label.insert_after(this, null);
|
||||||
|
label.attributes = new Pango.AttrList();
|
||||||
|
label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
|
||||||
|
#if GTK_4_8 && VALA_0_58
|
||||||
|
picture.content_fit = Gtk.ContentFit.COVER;
|
||||||
|
#elif GTK_4_8
|
||||||
|
picture.@set("content-fit", 2);
|
||||||
|
#endif
|
||||||
|
picture.insert_after(this, label);
|
||||||
|
this.notify["model"].connect(on_model_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_model_changed() {
|
||||||
|
if (background_color_binding != null) background_color_binding.unbind();
|
||||||
|
if (display_text_binding != null) display_text_binding.unbind();
|
||||||
|
if (image_file_binding != null) image_file_binding.unbind();
|
||||||
|
if (model != null) {
|
||||||
|
background_color_binding = model.bind_property("background-color", this, "background-color", BindingFlags.SYNC_CREATE);
|
||||||
|
display_text_binding = model.bind_property("display-text", this, "display-text", BindingFlags.SYNC_CREATE);
|
||||||
|
image_file_binding = model.bind_property("image-file", this, "image-file", BindingFlags.SYNC_CREATE);
|
||||||
|
} else {
|
||||||
|
background_color_binding = null;
|
||||||
|
display_text_binding = null;
|
||||||
|
image_file_binding = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
if (background_color_binding != null) background_color_binding.unbind();
|
||||||
|
if (display_text_binding != null) display_text_binding.unbind();
|
||||||
|
if (image_file_binding != null) image_file_binding.unbind();
|
||||||
|
background_color_binding = null;
|
||||||
|
display_text_binding = null;
|
||||||
|
image_file_binding = null;
|
||||||
|
label.unparent();
|
||||||
|
picture.unparent();
|
||||||
|
base.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void size_allocate(int width, int height, int baseline) {
|
||||||
|
int min, nat, bl_min, bl_nat;
|
||||||
|
picture.measure(Orientation.HORIZONTAL, -1, out min, out nat, out bl_min, out bl_nat);
|
||||||
|
if (nat > 0) {
|
||||||
|
picture.allocate(width, height, baseline, null);
|
||||||
|
label.visible = false;
|
||||||
|
} else {
|
||||||
|
picture.allocate(0, 0, 0, null);
|
||||||
|
label.attributes = new Pango.AttrList();
|
||||||
|
label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
|
||||||
|
label.attributes.insert(Pango.attr_scale_new(double.min((double)width, (double)height) * 0.05));
|
||||||
|
label.margin_bottom = height/40;
|
||||||
|
label.visible = true;
|
||||||
|
label.allocate(width, height, baseline, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void snapshot(Gtk.Snapshot snapshot) {
|
||||||
|
if (label.visible) {
|
||||||
|
Graphene.Rect bounds = Graphene.Rect();
|
||||||
|
bounds.init(0, 0, get_width(), get_height());
|
||||||
|
snapshot.append_node(new Gsk.ColorNode(background_color, bounds));
|
||||||
|
}
|
||||||
|
base.snapshot(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,8 +40,14 @@ public class Dino.Ui.ViewModel.CompatDateSeparatorModel : DateSeparatorModel {
|
||||||
|
|
||||||
private void update_time_label() {
|
private void update_time_label() {
|
||||||
date_label = get_relative_time(date);
|
date_label = get_relative_time(date);
|
||||||
time_update_timeout = Timeout.add_seconds((int) get_next_time_change(), () => {
|
time_update_timeout = set_update_time_label_timeout((int) get_next_time_change(), this);
|
||||||
if (time_update_timeout != 0) update_time_label();
|
}
|
||||||
|
|
||||||
|
private static uint set_update_time_label_timeout(int interval, CompatDateSeparatorModel model_) {
|
||||||
|
WeakRef model_weak = WeakRef(model_);
|
||||||
|
return Timeout.add_seconds(interval, () => {
|
||||||
|
CompatDateSeparatorModel? model = (CompatDateSeparatorModel) model_weak.get();
|
||||||
|
if (model != null && model.time_update_timeout != 0) model.update_time_label();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
23
meson.build
Normal file
23
meson.build
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
project('xmpp-vala', 'vala')
|
||||||
|
|
||||||
|
fs = import('fs')
|
||||||
|
python = import('python')
|
||||||
|
|
||||||
|
dep_gdk_pixbuf = dependency('gdk-pixbuf-2.0')
|
||||||
|
dep_gee = dependency('gee-0.8')
|
||||||
|
dep_gio = dependency('gio-2.0')
|
||||||
|
dep_glib = dependency('glib-2.0')
|
||||||
|
dep_gmodule = dependency('gmodule-2.0')
|
||||||
|
dep_gtk4 = dependency('gtk4')
|
||||||
|
dep_icu_uc = dependency('icu-uc')
|
||||||
|
dep_libadwaita = dependency('libadwaita-1')
|
||||||
|
dep_m = meson.get_compiler('c').find_library('m', required: false)
|
||||||
|
dep_sqlite3 = dependency('sqlite3', version: '>=3.24')
|
||||||
|
|
||||||
|
prog_git = find_program('git', required: false)
|
||||||
|
prog_python = python.find_installation()
|
||||||
|
|
||||||
|
subdir('qlite')
|
||||||
|
subdir('xmpp-vala')
|
||||||
|
subdir('libdino')
|
||||||
|
subdir('main')
|
1
meson_options.txt
Normal file
1
meson_options.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
option('plugindir', type: 'string', value: 'lib/dino/plugins', description: 'Plugin directory for Dino plugins')
|
|
@ -10,13 +10,16 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private Dino.Database dino_db;
|
private Dino.Database dino_db;
|
||||||
|
private Soup.Session session;
|
||||||
private static Regex http_url_regex = /^https?:\/\/([^\s#]*)$/; // Spaces are invalid in URLs and we can't use fragments for downloads
|
private static Regex http_url_regex = /^https?:\/\/([^\s#]*)$/; // Spaces are invalid in URLs and we can't use fragments for downloads
|
||||||
private static Regex omemo_url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/;
|
private static Regex omemo_url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/;
|
||||||
|
|
||||||
public FileProvider(StreamInteractor stream_interactor, Dino.Database dino_db) {
|
public FileProvider(StreamInteractor stream_interactor, Dino.Database dino_db) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.dino_db = dino_db;
|
this.dino_db = dino_db;
|
||||||
|
this.session = new Soup.Session();
|
||||||
|
|
||||||
|
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this));
|
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +69,7 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
|
|
||||||
public bool is_readable() {
|
public bool is_readable() {
|
||||||
if (!can_poll()) throw new IOError.NOT_SUPPORTED("Stream is not pollable");
|
if (!can_poll()) throw new IOError.NOT_SUPPORTED("Stream is not pollable");
|
||||||
return ((PollableInputStream)inner).is_readable();
|
return remaining_size <= 0 || ((PollableInputStream)inner).is_readable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ssize_t check_limit(ssize_t read) throws IOError {
|
private ssize_t check_limit(ssize_t read) throws IOError {
|
||||||
|
@ -114,8 +117,6 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
||||||
if (http_receive_data == null) return file_meta;
|
if (http_receive_data == null) return file_meta;
|
||||||
|
|
||||||
var session = new Soup.Session();
|
|
||||||
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
|
||||||
var head_message = new Soup.Message("HEAD", http_receive_data.url);
|
var head_message = new Soup.Message("HEAD", http_receive_data.url);
|
||||||
head_message.request_headers.append("Accept-Encoding", "identity");
|
head_message.request_headers.append("Accept-Encoding", "identity");
|
||||||
|
|
||||||
|
@ -150,8 +151,6 @@ public class FileProvider : Dino.FileProvider, Object {
|
||||||
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
|
||||||
if (http_receive_data == null) assert(false);
|
if (http_receive_data == null) assert(false);
|
||||||
|
|
||||||
var session = new Soup.Session();
|
|
||||||
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
|
||||||
var get_message = new Soup.Message("GET", http_receive_data.url);
|
var get_message = new Soup.Message("GET", http_receive_data.url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -7,12 +7,15 @@ namespace Dino.Plugins.HttpFiles {
|
||||||
public class HttpFileSender : FileSender, Object {
|
public class HttpFileSender : FileSender, Object {
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private Database db;
|
private Database db;
|
||||||
|
private Soup.Session session;
|
||||||
private HashMap<Account, long> max_file_sizes = new HashMap<Account, long>(Account.hash_func, Account.equals_func);
|
private HashMap<Account, long> max_file_sizes = new HashMap<Account, long>(Account.hash_func, Account.equals_func);
|
||||||
|
|
||||||
public HttpFileSender(StreamInteractor stream_interactor, Database db) {
|
public HttpFileSender(StreamInteractor stream_interactor, Database db) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.session = new Soup.Session();
|
||||||
|
|
||||||
|
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
||||||
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob);
|
stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob);
|
||||||
}
|
}
|
||||||
|
@ -90,8 +93,6 @@ public class HttpFileSender : FileSender, Object {
|
||||||
Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
|
Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account);
|
||||||
if (stream == null) return;
|
if (stream == null) return;
|
||||||
|
|
||||||
var session = new Soup.Session();
|
|
||||||
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
|
|
||||||
var put_message = new Soup.Message("PUT", file_send_data.url_up);
|
var put_message = new Soup.Message("PUT", file_send_data.url_up);
|
||||||
#if SOUP_3_0
|
#if SOUP_3_0
|
||||||
put_message.set_request_body(file_meta.mime_type, file_transfer.input_stream, (ssize_t) file_meta.size);
|
put_message.set_request_body(file_meta.mime_type, file_transfer.input_stream, (ssize_t) file_meta.size);
|
||||||
|
|
|
@ -94,6 +94,7 @@ public class BadMessagesPopulator : Plugins.ConversationItemPopulator, Plugins.C
|
||||||
foreach (BadMessageItem bad_item in bad_items) {
|
foreach (BadMessageItem bad_item in bad_items) {
|
||||||
item_collection.remove_item(bad_item);
|
item_collection.remove_item(bad_item);
|
||||||
}
|
}
|
||||||
|
bad_items.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) {
|
public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) {
|
||||||
|
@ -103,7 +104,9 @@ public class BadMessagesPopulator : Plugins.ConversationItemPopulator, Plugins.C
|
||||||
init_state();
|
init_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close(Conversation conversation) { }
|
public void close(Conversation conversation) {
|
||||||
|
clear_state();
|
||||||
|
}
|
||||||
|
|
||||||
public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { }
|
public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { }
|
||||||
}
|
}
|
||||||
|
@ -131,9 +134,17 @@ public class BadMessageItem : Plugins.MetaConversationItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BadMessagesWidget : Box {
|
public class BadMessagesWidget : Box {
|
||||||
|
private Plugin plugin;
|
||||||
|
private Conversation conversation;
|
||||||
|
private Jid jid;
|
||||||
|
private Label label;
|
||||||
|
|
||||||
public BadMessagesWidget(Plugin plugin, Conversation conversation, Jid jid, BadnessType badness_type) {
|
public BadMessagesWidget(Plugin plugin, Conversation conversation, Jid jid, BadnessType badness_type) {
|
||||||
Object(orientation:Orientation.HORIZONTAL, spacing:5);
|
Object(orientation:Orientation.HORIZONTAL, spacing:5);
|
||||||
|
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.jid = jid;
|
||||||
this.halign = Align.CENTER;
|
this.halign = Align.CENTER;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
|
||||||
|
@ -159,19 +170,29 @@ public class BadMessagesWidget : Box {
|
||||||
} else {
|
} else {
|
||||||
warning_text += _("%s does not trust this device. That means, you might be missing messages.").printf(who);
|
warning_text += _("%s does not trust this device. That means, you might be missing messages.").printf(who);
|
||||||
}
|
}
|
||||||
Label label = new Label(warning_text) { margin_start=70, margin_end=70, justify=Justification.CENTER, use_markup=true, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true };
|
label = new Label(warning_text) { margin_start=70, margin_end=70, justify=Justification.CENTER, use_markup=true, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true };
|
||||||
label.add_css_class("dim-label");
|
label.add_css_class("dim-label");
|
||||||
this.append(label);
|
this.append(label);
|
||||||
|
|
||||||
label.activate_link.connect(() => {
|
|
||||||
if (badness_type == BadnessType.UNTRUSTED) {
|
if (badness_type == BadnessType.UNTRUSTED) {
|
||||||
|
label.activate_link.connect(on_label_activate_link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool on_label_activate_link() {
|
||||||
ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, jid);
|
ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, jid);
|
||||||
dialog.set_transient_for((Window) get_root());
|
dialog.set_transient_for((Window) get_root());
|
||||||
dialog.present();
|
dialog.present();
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
if (label != null) {
|
||||||
|
label.unparent();
|
||||||
|
label.dispose();
|
||||||
|
label = null;
|
||||||
|
}
|
||||||
|
base.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
private static extern unowned Gst.Video.Info gst_video_frame_get_video_info(Gst.Video.Frame frame);
|
private static extern unowned Gst.Video.Info gst_video_frame_get_video_info(Gst.Video.Frame frame);
|
||||||
|
[CCode (array_length_type = "size_t", type = "void*")]
|
||||||
private static extern unowned uint8[] gst_video_frame_get_data(Gst.Video.Frame frame);
|
private static extern unowned uint8[] gst_video_frame_get_data(Gst.Video.Frame frame);
|
||||||
|
|
||||||
public class Dino.Plugins.Rtp.Paintable : Gdk.Paintable, Object {
|
public class Dino.Plugins.Rtp.Paintable : Gdk.Paintable, Object {
|
||||||
|
|
22
qlite/meson.build
Normal file
22
qlite/meson.build
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
dependencies = [
|
||||||
|
dep_gee,
|
||||||
|
dep_glib,
|
||||||
|
dep_sqlite3,
|
||||||
|
]
|
||||||
|
sources = files(
|
||||||
|
'src/column.vala',
|
||||||
|
'src/database.vala',
|
||||||
|
'src/delete_builder.vala',
|
||||||
|
'src/insert_builder.vala',
|
||||||
|
'src/query_builder.vala',
|
||||||
|
'src/row.vala',
|
||||||
|
'src/statement_builder.vala',
|
||||||
|
'src/table.vala',
|
||||||
|
'src/update_builder.vala',
|
||||||
|
'src/upsert_builder.vala',
|
||||||
|
)
|
||||||
|
c_args = [
|
||||||
|
'-DG_LOG_DOMAIN="qlite"',
|
||||||
|
]
|
||||||
|
lib_qlite = library('qlite', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies)
|
||||||
|
dep_qlite = declare_dependency(link_with: lib_qlite, include_directories: include_directories('.'))
|
|
@ -106,6 +106,9 @@ SOURCES
|
||||||
"src/module/xep/0384_omemo/omemo_encryptor.vala"
|
"src/module/xep/0384_omemo/omemo_encryptor.vala"
|
||||||
"src/module/xep/0384_omemo/omemo_decryptor.vala"
|
"src/module/xep/0384_omemo/omemo_decryptor.vala"
|
||||||
|
|
||||||
|
"src/module/xep/0392_consistent_color/consistent_color.vala"
|
||||||
|
"src/module/xep/0392_consistent_color/hsluv.vala"
|
||||||
|
|
||||||
"src/module/xep/0184_message_delivery_receipts.vala"
|
"src/module/xep/0184_message_delivery_receipts.vala"
|
||||||
"src/module/xep/0191_blocking_command.vala"
|
"src/module/xep/0191_blocking_command.vala"
|
||||||
"src/module/xep/0198_stream_management.vala"
|
"src/module/xep/0198_stream_management.vala"
|
||||||
|
@ -160,7 +163,7 @@ DEPENDS
|
||||||
add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala")
|
add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala")
|
||||||
add_library(xmpp-vala SHARED ${ENGINE_VALA_C})
|
add_library(xmpp-vala SHARED ${ENGINE_VALA_C})
|
||||||
add_dependencies(xmpp-vala xmpp-vala-vapi)
|
add_dependencies(xmpp-vala xmpp-vala-vapi)
|
||||||
target_link_libraries(xmpp-vala ${ENGINE_PACKAGES})
|
target_link_libraries(xmpp-vala ${ENGINE_PACKAGES} m)
|
||||||
set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0)
|
set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0)
|
||||||
|
|
||||||
install(TARGETS xmpp-vala ${TARGET_INSTALL})
|
install(TARGETS xmpp-vala ${TARGET_INSTALL})
|
||||||
|
@ -175,6 +178,7 @@ if(BUILD_TESTS)
|
||||||
|
|
||||||
"tests/jid.vala"
|
"tests/jid.vala"
|
||||||
"tests/stanza.vala"
|
"tests/stanza.vala"
|
||||||
|
"tests/color.vala"
|
||||||
"tests/util.vala"
|
"tests/util.vala"
|
||||||
CUSTOM_VAPIS
|
CUSTOM_VAPIS
|
||||||
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
|
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
|
||||||
|
|
133
xmpp-vala/meson.build
Normal file
133
xmpp-vala/meson.build
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
dependencies = [
|
||||||
|
dep_gdk_pixbuf,
|
||||||
|
dep_gee,
|
||||||
|
dep_gio,
|
||||||
|
dep_glib,
|
||||||
|
dep_icu_uc,
|
||||||
|
dep_m,
|
||||||
|
]
|
||||||
|
sources = files(
|
||||||
|
'src/core/direct_tls_xmpp_stream.vala',
|
||||||
|
'src/core/io_xmpp_stream.vala',
|
||||||
|
'src/core/module_flag.vala',
|
||||||
|
'src/core/namespace_state.vala',
|
||||||
|
'src/core/stanza_attribute.vala',
|
||||||
|
'src/core/stanza_node.vala',
|
||||||
|
'src/core/stanza_reader.vala',
|
||||||
|
'src/core/stanza_writer.vala',
|
||||||
|
'src/core/starttls_xmpp_stream.vala',
|
||||||
|
'src/core/stream_connect.vala',
|
||||||
|
'src/core/tls_xmpp_stream.vala',
|
||||||
|
'src/core/xmpp_log.vala',
|
||||||
|
'src/core/xmpp_stream.vala',
|
||||||
|
'src/glib_fixes.vapi',
|
||||||
|
'src/module/bind.vala',
|
||||||
|
'src/module/bookmarks_provider.vala',
|
||||||
|
'src/module/conference.vala',
|
||||||
|
'src/module/iq/module.vala',
|
||||||
|
'src/module/iq/stanza.vala',
|
||||||
|
'src/module/jid.vala',
|
||||||
|
'src/module/message/module.vala',
|
||||||
|
'src/module/message/stanza.vala',
|
||||||
|
'src/module/presence/flag.vala',
|
||||||
|
'src/module/presence/module.vala',
|
||||||
|
'src/module/presence/stanza.vala',
|
||||||
|
'src/module/roster/flag.vala',
|
||||||
|
'src/module/roster/item.vala',
|
||||||
|
'src/module/roster/module.vala',
|
||||||
|
'src/module/roster/versioning_module.vala',
|
||||||
|
'src/module/sasl.vala',
|
||||||
|
'src/module/session.vala',
|
||||||
|
'src/module/stanza.vala',
|
||||||
|
'src/module/stanza_error.vala',
|
||||||
|
'src/module/stream_error.vala',
|
||||||
|
'src/module/util.vala',
|
||||||
|
'src/module/xep/0004_data_forms.vala',
|
||||||
|
'src/module/xep/0030_service_discovery/flag.vala',
|
||||||
|
'src/module/xep/0030_service_discovery/identity.vala',
|
||||||
|
'src/module/xep/0030_service_discovery/info_result.vala',
|
||||||
|
'src/module/xep/0030_service_discovery/item.vala',
|
||||||
|
'src/module/xep/0030_service_discovery/items_result.vala',
|
||||||
|
'src/module/xep/0030_service_discovery/module.vala',
|
||||||
|
'src/module/xep/0045_muc/flag.vala',
|
||||||
|
'src/module/xep/0045_muc/module.vala',
|
||||||
|
'src/module/xep/0045_muc/status_code.vala',
|
||||||
|
'src/module/xep/0047_in_band_bytestreams.vala',
|
||||||
|
'src/module/xep/0048_bookmarks.vala',
|
||||||
|
'src/module/xep/0048_conference.vala',
|
||||||
|
'src/module/xep/0049_private_xml_storage.vala',
|
||||||
|
'src/module/xep/0054_vcard/module.vala',
|
||||||
|
'src/module/xep/0059_result_set_management.vala',
|
||||||
|
'src/module/xep/0060_pubsub.vala',
|
||||||
|
'src/module/xep/0065_socks5_bytestreams.vala',
|
||||||
|
'src/module/xep/0066_out_of_band_data.vala',
|
||||||
|
'src/module/xep/0077_in_band_registration.vala',
|
||||||
|
'src/module/xep/0082_date_time_profiles.vala',
|
||||||
|
'src/module/xep/0084_user_avatars.vala',
|
||||||
|
'src/module/xep/0085_chat_state_notifications.vala',
|
||||||
|
'src/module/xep/0115_entity_capabilities.vala',
|
||||||
|
'src/module/xep/0166_jingle/component.vala',
|
||||||
|
'src/module/xep/0166_jingle/content.vala',
|
||||||
|
'src/module/xep/0166_jingle/content_description.vala',
|
||||||
|
'src/module/xep/0166_jingle/content_node.vala',
|
||||||
|
'src/module/xep/0166_jingle/content_security.vala',
|
||||||
|
'src/module/xep/0166_jingle/content_transport.vala',
|
||||||
|
'src/module/xep/0166_jingle/jingle_flag.vala',
|
||||||
|
'src/module/xep/0166_jingle/jingle_module.vala',
|
||||||
|
'src/module/xep/0166_jingle/jingle_structs.vala',
|
||||||
|
'src/module/xep/0166_jingle/reason_element.vala',
|
||||||
|
'src/module/xep/0166_jingle/session.vala',
|
||||||
|
'src/module/xep/0166_jingle/session_info.vala',
|
||||||
|
'src/module/xep/0167_jingle_rtp/content_parameters.vala',
|
||||||
|
'src/module/xep/0167_jingle_rtp/content_type.vala',
|
||||||
|
'src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala',
|
||||||
|
'src/module/xep/0167_jingle_rtp/payload_type.vala',
|
||||||
|
'src/module/xep/0167_jingle_rtp/session_info_type.vala',
|
||||||
|
'src/module/xep/0167_jingle_rtp/stream.vala',
|
||||||
|
'src/module/xep/0176_jingle_ice_udp/candidate.vala',
|
||||||
|
'src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala',
|
||||||
|
'src/module/xep/0176_jingle_ice_udp/transport_parameters.vala',
|
||||||
|
'src/module/xep/0177_jingle_raw_udp.vala',
|
||||||
|
'src/module/xep/0184_message_delivery_receipts.vala',
|
||||||
|
'src/module/xep/0191_blocking_command.vala',
|
||||||
|
'src/module/xep/0198_stream_management.vala',
|
||||||
|
'src/module/xep/0199_ping.vala',
|
||||||
|
'src/module/xep/0203_delayed_delivery.vala',
|
||||||
|
'src/module/xep/0215_external_service_discovery.vala',
|
||||||
|
'src/module/xep/0234_jingle_file_transfer.vala',
|
||||||
|
'src/module/xep/0249_direct_muc_invitations.vala',
|
||||||
|
'src/module/xep/0260_jingle_socks5_bytestreams.vala',
|
||||||
|
'src/module/xep/0261_jingle_in_band_bytestreams.vala',
|
||||||
|
'src/module/xep/0272_muji.vala',
|
||||||
|
'src/module/xep/0280_message_carbons.vala',
|
||||||
|
'src/module/xep/0297_stanza_forwarding.vala',
|
||||||
|
'src/module/xep/0298_coin.vala',
|
||||||
|
'src/module/xep/0308_last_message_correction.vala',
|
||||||
|
'src/module/xep/0313_2_message_archive_management.vala',
|
||||||
|
'src/module/xep/0313_message_archive_management.vala',
|
||||||
|
'src/module/xep/0333_chat_markers.vala',
|
||||||
|
'src/module/xep/0334_message_processing_hints.vala',
|
||||||
|
'src/module/xep/0353_call_invite_message.vala',
|
||||||
|
'src/module/xep/0353_jingle_message_initiation.vala',
|
||||||
|
'src/module/xep/0359_unique_stable_stanza_ids.vala',
|
||||||
|
'src/module/xep/0363_http_file_upload.vala',
|
||||||
|
'src/module/xep/0380_explicit_encryption.vala',
|
||||||
|
'src/module/xep/0384_omemo/omemo_decryptor.vala',
|
||||||
|
'src/module/xep/0384_omemo/omemo_encryptor.vala',
|
||||||
|
'src/module/xep/0391_jingle_encrypted_transports.vala',
|
||||||
|
'src/module/xep/0392_consistent_color/consistent_color.vala',
|
||||||
|
'src/module/xep/0392_consistent_color/hsluv.vala',
|
||||||
|
'src/module/xep/0402_bookmarks2.vala',
|
||||||
|
'src/module/xep/0410_muc_self_ping.vala',
|
||||||
|
'src/module/xep/0421_occupant_ids.vala',
|
||||||
|
'src/module/xep/0428_fallback_indication.vala',
|
||||||
|
'src/module/xep/0444_reactions.vala',
|
||||||
|
'src/module/xep/0461_replies.vala',
|
||||||
|
'src/module/xep/pixbuf_storage.vala',
|
||||||
|
'src/util.vala',
|
||||||
|
)
|
||||||
|
c_args = [
|
||||||
|
'-DG_LOG_DOMAIN="xmpp-vala"',
|
||||||
|
]
|
||||||
|
lib_xmpp_vala = library('xmpp-vala', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies)
|
||||||
|
dep_xmpp_vala = declare_dependency(link_with: lib_xmpp_vala, include_directories: include_directories('.'))
|
|
@ -16,14 +16,12 @@ public class Module : XmppStreamModule {
|
||||||
|
|
||||||
public signal void chat_state_received(XmppStream stream, Jid jid, string state, MessageStanza stanza);
|
public signal void chat_state_received(XmppStream stream, Jid jid, string state, MessageStanza stanza);
|
||||||
|
|
||||||
private SendPipelineListener send_pipeline_listener = new SendPipelineListener();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "A message stanza that does not contain standard messaging content [...] SHOULD be a state other than <active/>" (0085, 5.6)
|
* "A message stanza that does not contain standard messaging content [...] SHOULD be a state other than <active/>" (0085, 5.6)
|
||||||
*/
|
*/
|
||||||
public void send_state(XmppStream stream, Jid jid, string message_type, string state) {
|
public void send_state(XmppStream stream, Jid jid, string message_type, string state) {
|
||||||
MessageStanza message = new MessageStanza() { to=jid, type_=message_type };
|
MessageStanza message = new MessageStanza() { to=jid, type_=message_type };
|
||||||
message.stanza.put_node(new StanzaNode.build(state, NS_URI).add_self_xmlns());
|
add_state_to_message(message, state);
|
||||||
|
|
||||||
MessageProcessingHints.set_message_hint(message, MessageProcessingHints.HINT_NO_STORE);
|
MessageProcessingHints.set_message_hint(message, MessageProcessingHints.HINT_NO_STORE);
|
||||||
|
|
||||||
|
@ -32,14 +30,12 @@ public class Module : XmppStreamModule {
|
||||||
|
|
||||||
public override void attach(XmppStream stream) {
|
public override void attach(XmppStream stream) {
|
||||||
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
|
||||||
stream.get_module(MessageModule.IDENTITY).send_pipeline.connect(send_pipeline_listener);
|
|
||||||
stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message);
|
stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void detach(XmppStream stream) {
|
public override void detach(XmppStream stream) {
|
||||||
stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
|
||||||
stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message);
|
stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message);
|
||||||
stream.get_module(MessageModule.IDENTITY).send_pipeline.disconnect(send_pipeline_listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_ns() { return NS_URI; }
|
public override string get_ns() { return NS_URI; }
|
||||||
|
@ -57,19 +53,8 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SendPipelineListener : StanzaListener<MessageStanza> {
|
public static void add_state_to_message(MessageStanza message, string state) {
|
||||||
|
message.stanza.put_node(new StanzaNode.build(state, NS_URI).add_self_xmlns());
|
||||||
private string[] after_actions_const = {"MODIFY_BODY"};
|
|
||||||
|
|
||||||
public override string action_group { get { return "ADD_NODES"; } }
|
|
||||||
public override string[] after_actions { get { return after_actions_const; } }
|
|
||||||
|
|
||||||
public override async bool run(XmppStream stream, MessageStanza message) {
|
|
||||||
if (message.body == null) return false;
|
|
||||||
if (message.type_ != MessageStanza.TYPE_CHAT) return false;
|
|
||||||
message.stanza.put_node(new StanzaNode.build(STATE_ACTIVE, NS_URI).add_self_xmlns());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,10 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
warning("Received alleged carbon message from %s, ignoring", message.from.to_string());
|
warning("Received alleged carbon message from %s, ignoring", message.from.to_string());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (message_node == null) {
|
||||||
|
warning("Received a carbon message with no message subnode in jabber:client namespace from %s, ignoring", message.from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (received_node != null) {
|
if (received_node != null) {
|
||||||
message.add_flag(new MessageFlag(MessageFlag.TYPE_RECEIVED));
|
message.add_flag(new MessageFlag(MessageFlag.TYPE_RECEIVED));
|
||||||
} else if (sent_node != null) {
|
} else if (sent_node != null) {
|
||||||
|
|
|
@ -11,8 +11,8 @@ public class QueryResult {
|
||||||
public bool error { get; set; default=false; }
|
public bool error { get; set; default=false; }
|
||||||
public bool malformed { get; set; default=false; }
|
public bool malformed { get; set; default=false; }
|
||||||
public bool complete { get; set; default=false; }
|
public bool complete { get; set; default=false; }
|
||||||
public string first { get; set; }
|
public string? first { get; set; }
|
||||||
public string last { get; set; }
|
public string? last { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class Module : XmppStreamModule {
|
||||||
|
@ -65,16 +65,17 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
StanzaNode query_node = new StanzaNode.build("query", NS_VER(stream)).add_self_xmlns().put_node(data_form.get_submit_node());
|
StanzaNode query_node = new StanzaNode.build("query", NS_VER(stream)).add_self_xmlns().put_node(data_form.get_submit_node());
|
||||||
if (queryid != null) {
|
|
||||||
query_node.put_attribute("queryid", queryid);
|
query_node.put_attribute("queryid", queryid);
|
||||||
}
|
|
||||||
return query_node;
|
return query_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async QueryResult query_archive(XmppStream stream, string ns, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) {
|
internal async QueryResult query_archive(XmppStream stream, string ns, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) {
|
||||||
var res = new QueryResult();
|
|
||||||
|
|
||||||
if (stream.get_flag(Flag.IDENTITY) == null) { res.error = true; return res; }
|
var res = new QueryResult();
|
||||||
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
|
string? query_id = query_node.get_attribute("queryid");
|
||||||
|
if (flag == null || query_id == null) { res.error = true; return res; }
|
||||||
|
flag.active_query_ids.add(query_id);
|
||||||
|
|
||||||
// Build and send query
|
// Build and send query
|
||||||
Iq.Stanza iq = new Iq.Stanza.set(query_node) { to=mam_server };
|
Iq.Stanza iq = new Iq.Stanza.set(query_node) { to=mam_server };
|
||||||
|
@ -93,6 +94,11 @@ public class Module : XmppStreamModule {
|
||||||
if ((res.first == null) != (res.last == null)) { res.malformed = true; return res; }
|
if ((res.first == null) != (res.last == null)) { res.malformed = true; return res; }
|
||||||
res.complete = fin_node.get_attribute_bool("complete", false, ns);
|
res.complete = fin_node.get_attribute_bool("complete", false, ns);
|
||||||
|
|
||||||
|
Idle.add(() => {
|
||||||
|
flag.active_query_ids.remove(query_id);
|
||||||
|
return Source.REMOVE;
|
||||||
|
}, Priority.LOW);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +110,8 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
public override string[] after_actions { get { return after_actions_const; } }
|
public override string[] after_actions { get { return after_actions_const; } }
|
||||||
|
|
||||||
public override async bool run(XmppStream stream, MessageStanza message) {
|
public override async bool run(XmppStream stream, MessageStanza message) {
|
||||||
if (stream.get_flag(Flag.IDENTITY) == null) return false;
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
|
if (flag == null) return false;
|
||||||
|
|
||||||
StanzaNode? message_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", StanzaForwarding.NS_URI + ":forwarded", Xmpp.NS_URI + ":message");
|
StanzaNode? message_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", StanzaForwarding.NS_URI + ":forwarded", Xmpp.NS_URI + ":message");
|
||||||
if (message_node != null) {
|
if (message_node != null) {
|
||||||
|
@ -112,6 +119,28 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
DateTime? datetime = DelayedDelivery.get_time_for_node(forward_node);
|
DateTime? datetime = DelayedDelivery.get_time_for_node(forward_node);
|
||||||
string? mam_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":id");
|
string? mam_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":id");
|
||||||
string? query_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":queryid");
|
string? query_id = message.stanza.get_deep_attribute(NS_VER(stream) + ":result", NS_VER(stream) + ":queryid");
|
||||||
|
|
||||||
|
if (query_id == null) {
|
||||||
|
warning("Received MAM message without queryid from %s, ignoring", message.from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flag.active_query_ids.contains(query_id)) {
|
||||||
|
warning("Received MAM message from %s with unknown query id %s, ignoring", message.from.to_string(), query_id ?? "<none>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Jid? inner_from = null;
|
||||||
|
try {
|
||||||
|
inner_from = new Jid(message_node.get_attribute("from"));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Received MAM message with invalid from attribute in forwarded message from %s, ignoring", message.from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!message.from.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid) && !message.from.equals_bare(inner_from)) {
|
||||||
|
warning("Received MAM message from %s illegally impersonating %s, ignoring", message.from.to_string(), inner_from.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
message.add_flag(new MessageFlag(message.from, datetime, mam_id, query_id));
|
message.add_flag(new MessageFlag(message.from, datetime, mam_id, query_id));
|
||||||
|
|
||||||
message.stanza = message_node;
|
message.stanza = message_node;
|
||||||
|
@ -124,6 +153,7 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
public class Flag : XmppStreamFlag {
|
public class Flag : XmppStreamFlag {
|
||||||
public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "message_archive_management");
|
public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "message_archive_management");
|
||||||
public bool cought_up { get; set; default=false; }
|
public bool cought_up { get; set; default=false; }
|
||||||
|
public Gee.Set<string> active_query_ids { get; set; default = new HashSet<string>(); }
|
||||||
public string ns_ver;
|
public string ns_ver;
|
||||||
|
|
||||||
public Flag(string ns_ver) {
|
public Flag(string ns_ver) {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Xmpp.Xep.ConsistentColor {
|
||||||
|
private const double KR = 0.299;
|
||||||
|
private const double KG = 0.587;
|
||||||
|
private const double KB = 0.114;
|
||||||
|
private const double Y = 0.732;
|
||||||
|
|
||||||
|
public float string_to_angle(string s) {
|
||||||
|
Checksum checksum = new Checksum(ChecksumType.SHA1);
|
||||||
|
checksum.update(s.data, -1);
|
||||||
|
size_t len = 20;
|
||||||
|
uint8[] digest = new uint8[len];
|
||||||
|
checksum.get_digest(digest, ref len);
|
||||||
|
uint16 output = ((uint16)(*(uint16*)digest)).to_little_endian();
|
||||||
|
return (((float) output) / 65536.0f) * 360.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint8[] rgbd_to_rgb(double[] rgbd) {
|
||||||
|
return {(uint8)(rgbd[0] * 255.0), (uint8)(rgbd[1] * 255.0), (uint8)(rgbd[2] * 255.0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] rgbd_to_rgbf(double[] rgbd) {
|
||||||
|
return {(float)rgbd[0], (float)rgbd[1], (float)rgbd[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double[] angle_to_rgbd(double angle) {
|
||||||
|
return Hsluv.hsluv_to_rgb(new double[] {angle, 100, 50});
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] string_to_rgbf(string s) {
|
||||||
|
return rgbd_to_rgbf(angle_to_rgbd(string_to_angle(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint8[] string_to_rgb(string s) {
|
||||||
|
return rgbd_to_rgb(angle_to_rgbd(string_to_angle(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
393
xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala
Normal file
393
xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
namespace Hsluv {
|
||||||
|
|
||||||
|
private const double[] M0 = { 3.240969941904521, -1.537383177570093, -0.498610760293 };
|
||||||
|
private const double[] M1 = { -0.96924363628087, 1.87596750150772, 0.041555057407175 };
|
||||||
|
private const double[] M2 = { 0.055630079696993, -0.20397695888897, 1.056971514242878 };
|
||||||
|
|
||||||
|
private const double[] MInv0 = { 0.41239079926595, 0.35758433938387, 0.18048078840183 };
|
||||||
|
private const double[] MInv1 = { 0.21263900587151, 0.71516867876775, 0.072192315360733 };
|
||||||
|
private const double[] MInv2 = { 0.019330818715591, 0.11919477979462, 0.95053215224966 };
|
||||||
|
|
||||||
|
private double RefX = 0.95045592705167;
|
||||||
|
private double RefY = 1.0;
|
||||||
|
private double RefZ = 1.089057750759878;
|
||||||
|
|
||||||
|
private double RefU = 0.19783000664283;
|
||||||
|
private double RefV = 0.46831999493879;
|
||||||
|
|
||||||
|
private double Kappa = 903.2962962;
|
||||||
|
private double Epsilon = 0.0088564516;
|
||||||
|
|
||||||
|
private struct Bounds {
|
||||||
|
double t0;
|
||||||
|
double t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bounds get_bounds_sub(double L, double sub1, double sub2, int t, double[] m) {
|
||||||
|
double m1 = m[0];
|
||||||
|
double m2 = m[1];
|
||||||
|
double m3 = m[2];
|
||||||
|
double top1 = (284517 * m1 - 94839 * m3) * sub2;
|
||||||
|
double top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * L * sub2 - 769860 * t * L;
|
||||||
|
double bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t;
|
||||||
|
return { top1 / bottom, top2 / bottom };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bounds[] get_bounds(double L) {
|
||||||
|
double sub1 = Math.pow(L + 16, 3) / 1560896;
|
||||||
|
double sub2 = sub1 > Epsilon ? sub1 : L / Kappa;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get_bounds_sub(L, sub1, sub2, 0, M0),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 1, M0),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 0, M1),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 1, M1),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 0, M2),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 1, M2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double intersect_line_line(double[] lineA, double[] lineB) {
|
||||||
|
return (lineA[1] - lineB[1]) / (lineB[0] - lineA[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double distance_from_pole(double[] point) {
|
||||||
|
return Math.sqrt(Math.pow(point[0], 2) + Math.pow(point[1], 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool length_of_ray_until_intersect(double theta, Bounds line, out double length) {
|
||||||
|
length = line.t1 / (Math.sin(theta) - line.t0 * Math.cos(theta));
|
||||||
|
|
||||||
|
return length >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double max_safe_chroma_for_l(double L) {
|
||||||
|
Bounds[] bounds = get_bounds(L);
|
||||||
|
double min = double.MAX;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
var m1 = bounds[i].t0;
|
||||||
|
var b1 = bounds[i].t1;
|
||||||
|
var line = new double[] { m1, b1 };
|
||||||
|
|
||||||
|
double x = intersect_line_line(line, new double[] {-1 / m1, 0 });
|
||||||
|
double length = distance_from_pole(new double[] { x, b1 + x * m1 });
|
||||||
|
|
||||||
|
min = double.min(min, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double max_chroma_for_lh(double L, double H) {
|
||||||
|
double hrad = H / 360 * Math.PI * 2;
|
||||||
|
|
||||||
|
Bounds[] bounds = get_bounds(L);
|
||||||
|
double min = double.MAX;
|
||||||
|
|
||||||
|
foreach (var bound in bounds) {
|
||||||
|
double length;
|
||||||
|
|
||||||
|
if (length_of_ray_until_intersect(hrad, bound, out length)) {
|
||||||
|
min = double.min(min, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double dot_product(double[] a, double[] b) {
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < a.length; ++i) {
|
||||||
|
sum += a[i] * b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double round(double value, int places) {
|
||||||
|
double n = Math.pow(10, places);
|
||||||
|
|
||||||
|
return Math.round(value * n) / n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double from_linear(double c) {
|
||||||
|
if (c <= 0.0031308) {
|
||||||
|
return 12.92 * c;
|
||||||
|
} else {
|
||||||
|
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double to_linear(double c) {
|
||||||
|
if (c > 0.04045) {
|
||||||
|
return Math.pow((c + 0.055) / (1 + 0.055), 2.4);
|
||||||
|
} else {
|
||||||
|
return c / 12.92;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] rgb_prepare(double[] tuple) {
|
||||||
|
for (int i = 0; i < tuple.length; ++i) {
|
||||||
|
tuple[i] = round(tuple[i], 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple.length; ++i) {
|
||||||
|
double ch = tuple[i];
|
||||||
|
|
||||||
|
if (ch < -0.0001 || ch > 1.0001) {
|
||||||
|
return null; //throw new Error("Illegal rgb value: " + ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = new int[tuple.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple.length; ++i) {
|
||||||
|
results[i] = (int) Math.round(tuple[i] * 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] xyz_to_rgb(double[] tuple) {
|
||||||
|
return new double[] {
|
||||||
|
from_linear(dot_product(M0, tuple)),
|
||||||
|
from_linear(dot_product(M1, tuple)),
|
||||||
|
from_linear(dot_product(M2, tuple))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_xyz(double[] tuple) {
|
||||||
|
var rgbl = new double[] {
|
||||||
|
to_linear(tuple[0]),
|
||||||
|
to_linear(tuple[1]),
|
||||||
|
to_linear(tuple[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
return new double[] {
|
||||||
|
dot_product(MInv0, rgbl),
|
||||||
|
dot_product(MInv1, rgbl),
|
||||||
|
dot_product(MInv2, rgbl)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double y_to_l(double Y) {
|
||||||
|
if (Y <= Epsilon) {
|
||||||
|
return (Y / RefY) * Kappa;
|
||||||
|
} else {
|
||||||
|
return 116 * Math.pow(Y / RefY, 1.0 / 3.0) - 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double l_to_y(double L) {
|
||||||
|
if (L <= 8) {
|
||||||
|
return RefY * L / Kappa;
|
||||||
|
} else {
|
||||||
|
return RefY * Math.pow((L + 16) / 116, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] xyz_to_luv(double[] tuple) {
|
||||||
|
double X = tuple[0];
|
||||||
|
double Y = tuple[1];
|
||||||
|
double Z = tuple[2];
|
||||||
|
|
||||||
|
double varU = (4 * X) / (X + (15 * Y) + (3 * Z));
|
||||||
|
double varV = (9 * Y) / (X + (15 * Y) + (3 * Z));
|
||||||
|
|
||||||
|
double L = y_to_l(Y);
|
||||||
|
|
||||||
|
if (L == 0) {
|
||||||
|
return new double[] { 0, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
var U = 13 * L * (varU - RefU);
|
||||||
|
var V = 13 * L * (varV - RefV);
|
||||||
|
|
||||||
|
return new double [] { L, U, V };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] luv_to_xyz(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double U = tuple[1];
|
||||||
|
double V = tuple[2];
|
||||||
|
|
||||||
|
if (L == 0) {
|
||||||
|
return new double[] { 0, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
double varU = U / (13 * L) + RefU;
|
||||||
|
double varV = V / (13 * L) + RefV;
|
||||||
|
|
||||||
|
double Y = l_to_y(L);
|
||||||
|
double X = 0 - (9 * Y * varU) / ((varU - 4) * varV - varU * varV);
|
||||||
|
double Z = (9 * Y - (15 * varV * Y) - (varV * X)) / (3 * varV);
|
||||||
|
|
||||||
|
return new double[] { X, Y, Z };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] luv_to_lch(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double U = tuple[1];
|
||||||
|
double V = tuple[2];
|
||||||
|
|
||||||
|
double C = Math.pow(Math.pow(U, 2) + Math.pow(V, 2), 0.5);
|
||||||
|
double Hrad = Math.atan2(V, U);
|
||||||
|
|
||||||
|
double H = Hrad * 180.0 / Math.PI;
|
||||||
|
|
||||||
|
if (H < 0) {
|
||||||
|
H = 360 + H;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new double[] { L, C, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_luv(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double C = tuple[1];
|
||||||
|
double H = tuple[2];
|
||||||
|
|
||||||
|
double Hrad = H / 360.0 * 2 * Math.PI;
|
||||||
|
double U = Math.cos(Hrad) * C;
|
||||||
|
double V = Math.sin(Hrad) * C;
|
||||||
|
|
||||||
|
return new double [] { L, U, V };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hsluv_to_lch(double[] tuple) {
|
||||||
|
double H = tuple[0];
|
||||||
|
double S = tuple[1];
|
||||||
|
double L = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { 100, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { 0, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_chroma_for_lh(L, H);
|
||||||
|
double C = max / 100 * S;
|
||||||
|
|
||||||
|
return new double[] { L, C, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_hsluv(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double C = tuple[1];
|
||||||
|
double H = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { H, 0, 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { H, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_chroma_for_lh(L, H);
|
||||||
|
double S = C / max * 100;
|
||||||
|
|
||||||
|
return new double[] { H, S, L };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hpluv_to_lch(double[] tuple) {
|
||||||
|
double H = tuple[0];
|
||||||
|
double S = tuple[1];
|
||||||
|
double L = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { 100, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { 0, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_safe_chroma_for_l(L);
|
||||||
|
double C = max / 100 * S;
|
||||||
|
|
||||||
|
return new double[] { L, C, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_hpluv(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double C = tuple[1];
|
||||||
|
double H = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { H, 0, 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { H, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_safe_chroma_for_l(L);
|
||||||
|
double S = C / max * 100;
|
||||||
|
|
||||||
|
return new double[] { H, S, L };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string rgb_to_hex(double[] tuple) {
|
||||||
|
int[] prepared = rgb_prepare(tuple);
|
||||||
|
|
||||||
|
return "#%.2x%.2x%.2x".printf(prepared[0], prepared[1], prepared[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hex_to_tgb(string hex) {
|
||||||
|
return new double[] {
|
||||||
|
hex.substring(1, 2).to_long(null, 16) / 255.0,
|
||||||
|
hex.substring(3, 2).to_long(null, 16) / 255.0,
|
||||||
|
hex.substring(5, 2).to_long(null, 16) / 255.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_rgb(double[] tuple) {
|
||||||
|
return xyz_to_rgb(luv_to_xyz(lch_to_luv(tuple)));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_lch(double[] tuple) {
|
||||||
|
return luv_to_lch(xyz_to_luv(rgb_to_xyz(tuple)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rgb <--> Hsluv(p)
|
||||||
|
|
||||||
|
internal double[] hsluv_to_rgb(double[] tuple) {
|
||||||
|
return lch_to_rgb(hsluv_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_hsluv(double[] tuple) {
|
||||||
|
return lch_to_hsluv(rgb_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hpluv_to_rgb(double[] tuple) {
|
||||||
|
return lch_to_rgb(hpluv_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_hpluv(double[] tuple) {
|
||||||
|
return lch_to_hpluv(rgb_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex
|
||||||
|
|
||||||
|
internal string hsluv_to_hex(double[] tuple) {
|
||||||
|
return rgb_to_hex(hsluv_to_rgb(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string hpluv_to_hex(double[] tuple) {
|
||||||
|
return rgb_to_hex(hpluv_to_rgb(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hex_to_hsluv(string s) {
|
||||||
|
return rgb_to_hsluv(hex_to_tgb(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hex_to_hpluv(string s) {
|
||||||
|
return rgb_to_hpluv(hex_to_tgb(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -68,6 +68,11 @@ public class Module : BookmarksProvider, XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pupsub_item(XmppStream stream, Jid jid, string id, StanzaNode? node) {
|
private void on_pupsub_item(XmppStream stream, Jid jid, string id, StanzaNode? node) {
|
||||||
|
if (!jid.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) {
|
||||||
|
warning("Received alleged bookmarks:1 item from %s, ignoring", jid.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Conference conference = parse_item_node(node, id);
|
Conference conference = parse_item_node(node, id);
|
||||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
if (flag != null) {
|
if (flag != null) {
|
||||||
|
@ -77,6 +82,11 @@ public class Module : BookmarksProvider, XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
|
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
|
||||||
|
if (!jid.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) {
|
||||||
|
warning("Received alleged bookmarks:1 retract from %s, ignoring", jid.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Jid jid_parsed = new Jid(id);
|
Jid jid_parsed = new Jid(id);
|
||||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
|
|
50
xmpp-vala/tests/color.vala
Normal file
50
xmpp-vala/tests/color.vala
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
namespace Xmpp.Test {
|
||||||
|
|
||||||
|
class ColorTest : Gee.TestCase {
|
||||||
|
|
||||||
|
public ColorTest() {
|
||||||
|
base("color");
|
||||||
|
|
||||||
|
add_test("xep-vectors-angle", () => { text_xep_vectors_angle(); });
|
||||||
|
add_test("xep-vectors-rgbf", () => { test_xep_vectors_rgbf(); });
|
||||||
|
add_test("rgb-to-angle", () => { test_rgb_to_angle(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void text_xep_vectors_angle() {
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("Romeo"), 327.255249);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("juliet@capulet.lit"), 209.410400);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("😺"), 331.199341);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("council"), 359.994507);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("Board"), 171.430664);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool fail_if_not_eq_rgbf(float[] left, float[] right) {
|
||||||
|
bool failed = false;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
failed = fail_if_not_eq_float(left[i], right[i]) || failed;
|
||||||
|
}
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test_xep_vectors_rgbf() {
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("Romeo"), {0.865f,0.000f,0.686f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("juliet@capulet.lit"), {0.000f,0.515f,0.573f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("😺"), {0.872f,0.000f,0.659f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("council"), {0.918f,0.000f,0.394f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("Board"), {0.000f,0.527f,0.457f});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test_rgb_to_angle() {
|
||||||
|
string[] colors = {"e57373", "f06292", "ba68c8", "9575cd", "7986cb", "64b5f6", "4fc3f7", "4dd0e1", "4db6ac", "81c784", "aed581", "dce775", "fff176", "ffd54f", "ffb74d", "ff8a65"};
|
||||||
|
foreach(string hex_color in colors) {
|
||||||
|
uint8 r = (uint8) ((double) hex_color.substring(0, 2).to_long(null, 16));
|
||||||
|
uint8 g = (uint8) ((double) hex_color.substring(2, 2).to_long(null, 16));
|
||||||
|
uint8 b = (uint8) ((double) hex_color.substring(4, 2).to_long(null, 16));
|
||||||
|
//print(@"$hex_color, $r, $g, $b, $(ConsistentColor.rgb_to_angle(r, g, b))\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ int main(string[] args) {
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.JidTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.JidTest().get_suite());
|
||||||
|
TestSuite.get_root().add_suite(new Xmpp.Test.ColorTest().get_suite());
|
||||||
return GLib.Test.run();
|
return GLib.Test.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +69,22 @@ bool fail_if_not_eq_int(int left, int right, string? reason = null) {
|
||||||
return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right");
|
return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float float_to_accuracy(float f, float accuracy) {
|
||||||
|
return (float) (Math.round(f * Math.pow(10, accuracy)) / Math.pow(10, accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float double_to_accuracy(double f, float accuracy) {
|
||||||
|
return (float) (Math.round(f * Math.pow(10, accuracy)) / Math.pow(10, accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_if_not_eq_float(float left, float right, float accuracy = 3, string? reason = null) {
|
||||||
|
return fail_if_not(float_to_accuracy(left, accuracy) == float_to_accuracy(right, accuracy), @"$(reason + ": " ?? "")$left != $right");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_if_not_eq_double(double left, double right, float accuracy = 3, string? reason = null) {
|
||||||
|
return fail_if_not(double_to_accuracy(left, accuracy) == double_to_accuracy(right, accuracy), @"$(reason + ": " ?? "")$left != $right");
|
||||||
|
}
|
||||||
|
|
||||||
bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
|
bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
|
||||||
bool nullcheck = (left == null || right == null) && (left != null && right != null);
|
bool nullcheck = (left == null || right == null) && (left != null && right != null);
|
||||||
if (left == null) left = "(null)";
|
if (left == null) left = "(null)";
|
||||||
|
|
Loading…
Reference in a new issue