Daniel Brötzmann pushed to branch omemo_integration at gajim / gajim
Commits: f4c51a34 by wurstsalat at 2022-10-18T00:45:00+02:00 new: Add OMEMOTrustManager
- - - - -
12 changed files:
- gajim/data/gui/contact_info.ui - gajim/data/gui/groupchat_details.ui - + gajim/data/gui/omemo_trust_manager.ui - + gajim/data/icons/hicolor/scalable/categories/qr-code-scan.svg - gajim/data/style/gajim.css - gajim/gtk/accounts.py - gajim/gtk/builder.pyi - gajim/gtk/chat_stack.py - gajim/gtk/contact_info.py - gajim/gtk/groupchat_details.py - gajim/gtk/message_actions_box.py - + gajim/gtk/omemo_trust_manager.py
Changes:
===================================== gajim/data/gui/contact_info.ui ===================================== @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.38.2 --> +<!-- Generated with glade 3.40.0 --> <interface> <requires lib="gtk+" version="3.24"/> <!-- n-columns=2 n-rows=6 --> @@ -725,6 +725,39 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkScrolledWindow" id="encryption_scrolled"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">never</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkBox" id="encryption_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-start">36</property> + <property name="margin-end">36</property> + <property name="margin-top">36</property> + <property name="margin-bottom">36</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="name">encryption-omemo</property> + <property name="title" translatable="yes">Encryption (OMEMO)</property> + <property name="icon-name">channel-secure-symbolic</property> + <property name="position">2</property> + </packing> + </child> <child> <object class="GtkBox" id="groups_page_box"> <property name="visible">True</property> @@ -873,7 +906,7 @@ <property name="name">groups</property> <property name="title" translatable="yes">Groups</property> <property name="icon-name">view-pin-symbolic</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> <child> @@ -934,7 +967,7 @@ <property name="name">notes</property> <property name="title" translatable="yes">Notes</property> <property name="icon-name">x-office-address-book-symbolic</property> - <property name="position">3</property> + <property name="position">4</property> </packing> </child> <child> @@ -1004,7 +1037,7 @@ <property name="name">devices</property> <property name="title" translatable="yes">Devices</property> <property name="icon-name">computer-symbolic</property> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> <style>
===================================== gajim/data/gui/groupchat_details.ui ===================================== @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.38.2 --> +<!-- Generated with glade 3.40.0 --> <interface> <requires lib="gtk+" version="3.24"/> <!-- n-columns=2 n-rows=1 --> @@ -129,6 +129,39 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkScrolledWindow" id="encryption_scrolled"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">never</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkBox" id="encryption_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-start">36</property> + <property name="margin-end">36</property> + <property name="margin-top">36</property> + <property name="margin-bottom">36</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="name">encryption-omemo</property> + <property name="title" translatable="yes">Encryption (OMEMO)</property> + <property name="icon-name">channel-secure-symbolic</property> + <property name="position">2</property> + </packing> + </child> <child> <object class="GtkBox" id="manage_box"> <property name="visible">True</property> @@ -151,7 +184,7 @@ <property name="name">manage</property> <property name="title" translatable="yes">Manage</property> <property name="icon-name">preferences-other-symbolic</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> <child> @@ -170,7 +203,7 @@ <property name="name">affiliations</property> <property name="title" translatable="yes">Affiliations</property> <property name="icon-name">system-users-symbolic</property> - <property name="position">3</property> + <property name="position">4</property> </packing> </child> <child> @@ -189,7 +222,7 @@ <property name="name">outcasts</property> <property name="title" translatable="yes">Outcasts</property> <property name="icon-name">system-users-symbolic</property> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> <child> @@ -205,7 +238,7 @@ <property name="name">config</property> <property name="title" translatable="yes">Configuration</property> <property name="icon-name">document-properties-symbolic</property> - <property name="position">5</property> + <property name="position">6</property> </packing> </child> <style>
===================================== gajim/data/gui/omemo_trust_manager.ui ===================================== @@ -0,0 +1,416 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkPopover" id="search_popover"> + <property name="can-focus">False</property> + <child> + <object class="GtkSearchEntry" id="search"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="caps-lock-warning">False</property> + <property name="primary-icon-name">edit-find-symbolic</property> + <property name="primary-icon-activatable">False</property> + <property name="primary-icon-sensitive">False</property> + <signal name="search-changed" handler="_on_search_changed" swapped="no"/> + </object> + </child> + </object> + <object class="GtkPopover" id="qr_code_popover"> + <property name="can-focus">False</property> + <property name="relative-to">qr_menu_button</property> + <property name="constrain-to">none</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">This Device</property> + <style> + <class name="bold16"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="comparing_instructions"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-bottom">13</property> + <property name="wrap">True</property> + <property name="max-width-chars">42</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="our_fingerprint_2"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selectable">True</property> + <style> + <class name="fingerprint"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkImage" id="qr_code_image"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon_size">6</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="qr_dependency_missing"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">For verification via QR-Code you have to install <tt>python-qrcode</tt>.</property> + <property name="use-markup">True</property> + <property name="wrap">True</property> + <property name="max-width-chars">42</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <style> + <class name="padding-18"/> + </style> + </object> + </child> + </object> + <object class="GtkStack" id="stack"> + <property name="width-request">400</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="transition-type">crossfade</property> + <property name="interpolate-size">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">This Device</property> + <property name="xalign">0</property> + <style> + <class name="bold"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkListBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selection-mode">none</property> + <child> + <object class="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="selectable">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">3</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Fingerprint for this Device</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="our_fingerprint_1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selectable">True</property> + <property name="xalign">0</property> + <style> + <class name="fingerprint"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="qr_menu_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Scan QR Code</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="direction">up</property> + <property name="popover">qr_code_popover</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">qr-code-scan</property> + </object> + </child> + <style> + <class name="image-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="manage_trust_button"> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="no-show-all">True</property> + <property name="tooltip-text" translatable="yes">Manage your Devices…</property> + <property name="valign">center</property> + <signal name="clicked" handler="_on_manage_trust_clicked" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">channel-secure-symbolic</property> + </object> + </child> + <style> + <class name="image-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <style> + <class name="settings-box"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="list_heading"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-top">12</property> + <property name="wrap">True</property> + <property name="max-width-chars">42</property> + <property name="xalign">0</property> + <style> + <class name="bold"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="list_heading_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkSwitch" id="show_inactive_switch"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="valign">center</property> + <signal name="notify::active" handler="_on_show_inactive" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Show Inactive Devices</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="search_button"> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="no-show-all">True</property> + <property name="tooltip-text" translatable="yes">Search…</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="popover">search_popover</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">edit-find-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkListBox" id="list"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selection-mode">none</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkButton" id="clear_devices_button"> + <property name="label" translatable="yes">Clear Devices…</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="no-show-all">True</property> + <property name="halign">start</property> + <signal name="clicked" handler="_on_clear_devices_clicked" swapped="no"/> + <style> + <class name="destructive-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + <style> + <class name="omemo-trust-manager"/> + </style> + </object> + <packing> + <property name="name">manage-keys</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Account is not connected.</property> + <property name="wrap">True</property> + <property name="max-width-chars">42</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="name">no-connection</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface>
===================================== gajim/data/icons/hicolor/scalable/categories/qr-code-scan.svg ===================================== @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="296" + height="296" + viewBox="0 0 296 296" + version="1.1" + id="svg879" + sodipodi:docname="qr-code-scan.svg" + xml:space="preserve" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg%22%3E<sodipodi:namedview + id="namedview881" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="1.1820013" + inkscape:cx="-30.033808" + inkscape:cy="205.58353" + inkscape:window-width="1920" + inkscape:window-height="998" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg879" /><defs + id="defs5"><rect + id="p" + width="8" + height="8" /></defs><g + id="g3488" + transform="matrix(1.1697246,0,0,1.1697246,-27.551074,-25.261865)"><path + style="fill:#000000;stroke-width:0.476103" + d="M 251.48889,66.946387 V 46.712014 H 231.25452 211.02015 V 36.475802 26.23959 h 21.16314 c 22.41235,0 24.39388,0.156166 28.55025,2.250069 3.06508,1.544132 6.94528,5.251442 8.51507,8.135659 2.59204,4.762428 2.71271,6.062675 2.71271,29.230841 V 87.18076 h -10.23621 -10.23622 z" + id="path3464" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 28.196638,66.017616 c 0,-22.412343 0.156166,-24.393876 2.250069,-28.550247 1.544132,-3.065083 5.251442,-6.945286 8.135659,-8.515074 4.762428,-2.592038 6.062675,-2.712705 29.230841,-2.712705 H 89.137808 V 36.475802 46.712014 H 68.903435 48.669062 V 66.946387 87.18076 H 38.43285 28.196638 Z" + id="path3462" /><path + style="fill:#000000;stroke-width:0.476103" + d="M 170.5514,97.175398 V 66.701287 l 30.35156,0.12255 30.35156,0.12255 0.12255,30.351559 0.12255,30.351564 H 201.02551 170.5514 Z m 40.46875,0.241574 V 87.18076 h -10.23621 -10.23622 v 9.918809 c 0,5.455351 0.14283,10.061641 0.31741,10.236211 0.17457,0.17457 4.78086,0.3174 10.23621,0.3174 h 9.91881 z" + id="path3460" /><path + style="fill:#000000;stroke-width:0.476103" + d="M 140.08082,107.41513 V 87.18076 h 9.99816 9.99816 v 20.23437 20.23438 h -9.99816 -9.99816 z" + id="path3458" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 68.780885,97.297946 0.12255,-30.351559 30.351559,-0.12255 30.351556,-0.12255 V 97.175398 127.64951 H 99.132446 68.658335 Z m 40.719795,0 0.12849,-10.117186 H 99.38349 89.137808 v 10.245682 10.245678 l 10.117186,-0.12849 10.117186,-0.1285 z" + id="path3456" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 68.665384,148.12193 v -9.99816 h 10.236212 10.236212 v 9.99816 9.99816 H 78.901596 68.665384 Z" + id="path3454" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 139.97126,168.47533 -0.12849,-10.11719 -10.11719,-0.12849 -30.662081,-0.28013 v -9.98869 -9.98869 l 40.779271,0.15163 h 20.23437 v 20.23437 20.23437 h -9.98869 -9.98869 z" + id="path3452" + sodipodi:nodetypes="ccccccccccccc" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 211.02015,193.82781 v -15.2353 h -10.23621 -10.23622 v 15.2353 15.23529 h 10.23622 10.23621 z" + id="path3474" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 140.08082,209.0631 v -20.47242 h 15.23529 15.23529 v -4.99908 -4.99909 h 9.99816 9.99816 V 168.3563 158.12009 h -9.99816 -9.99816 v -9.99816 -9.99816 h 9.99816 9.99816 v 9.99816 9.99816 h 10.23622 10.23621 v -9.99816 -9.99816 h 10.23621 10.23621 v 9.99816 9.99816 h -10.23621 -10.23621 v 10.23621 10.23621 h 10.23621 10.23621 v 15.2353 15.23529 h -10.23621 -10.23621 v 10.24568 10.24568 l -10.11719,-0.12849 -10.11718,-0.1285 -0.1285,-10.11718 -0.1285,-10.11719 h -15.22582 -15.22582 v 10.23621 10.23621 h -9.99816 -9.99816 z" + id="path3450" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 68.982786,229.21812 c -0.174571,-0.17457 -0.317402,-13.88633 -0.317402,-30.47058 v -30.15319 h 30.470585 30.470581 v 30.47059 30.47058 H 99.453372 c -16.584254,0 -30.296015,-0.14283 -30.470586,-0.3174 z m 40.517894,-30.27221 -0.1285,-10.11718 -10.117186,-0.1285 -10.117186,-0.12849 v 10.24568 10.24568 h 10.245682 10.24568 z" + id="path3448" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 211.02015,259.76806 v -10.23621 h 20.23437 20.23437 V 229.29747 209.0631 h 10.23622 10.23621 v 21.3246 c 0,23.16817 -0.12067,24.46841 -2.71271,29.23084 -1.56979,2.88422 -5.44999,6.59153 -8.51507,8.13566 -4.15637,2.0939 -6.1379,2.25007 -28.55025,2.25007 h -21.16314 z" + id="path3446" /><path + style="fill:#000000;stroke-width:0.476103" + d="m 43.551277,269.26862 c -1.374571,-0.37014 -3.39419,-1.12956 -4.488044,-1.6876 -2.760243,-1.40817 -6.658458,-5.21488 -8.15389,-7.96248 -2.592038,-4.76243 -2.712705,-6.06267 -2.712705,-29.23084 V 209.0631 H 38.43285 48.669062 v 20.23437 20.23438 h 20.234373 20.234373 v 10.23621 10.23621 l -21.543656,-0.0313 c -16.989841,-0.0247 -22.071931,-0.17358 -24.042875,-0.70431 z" + id="path3346" /></g></svg>
===================================== gajim/data/style/gajim.css ===================================== @@ -577,7 +577,7 @@ list.settings > row:not(:last-child) { } list.settings row { min-height: 52px; } list.settings > row > box { - margin: 6px 18px; + margin: 10px 20px 10px 10px; }
/* Settings Window */ @@ -625,7 +625,7 @@ #SendFileDialog grid list > row { padding: 10px 20px 10px 10px; } /* Accounts Window */ .accounts-menu { border-right: 1px solid; border-color: @borders;} .accounts-settings-border {border: 1px solid; border-color: @borders;} -.accounts-settings stack > box { margin:30px; } +.accounts-settings stack > box.settings-page { margin:30px; } .accounts-label-row { padding-top: 10px; padding-bottom: 10px; @@ -766,6 +766,8 @@ .large-header { font-size: 1.75rem; font-weight: bold; } +.monospace { font-family: monospace; } + .insensitive-fg-color {color: @insensitive_fg_color;}
/* Padding/Margins */ @@ -799,6 +801,28 @@ .selected-color { color: @theme_selected_bg_color; } .encrypted-color { color: rgb(75, 181, 67); } .unencrypted-color { color: @error_color; }
+/* OMEMO */ +.fingerprint { + color: @insensitive_fg_color; + font-family: monospace; + font-size: 90%; +} +.omemo-inactive-color { color: @insensitive_fg_color; } + +.omemo-trust-manager list row { + border-bottom: 1px solid; + border-color: @unfocused_borders; + padding: 10px 20px 10px 10px; +} +.omemo-trust-manager list row:last-child { border-bottom: 0px} +.omemo-trust-manager list { + border: 1px solid; + border-color:@unfocused_borders; +} +.omemo-trust-manager list > row { outline: none; } + +.omemo-trust-popover row { padding: 10px 15px 10px 10px; } + /*Receipts*/ .receipt-received-color { color: rgb(75, 181, 67); }
===================================== gajim/gtk/accounts.py ===================================== @@ -40,6 +40,7 @@ from .const import Setting from .const import SettingKind from .const import SettingType +from .omemo_trust_manager import OMEMOTrustManager from .settings import SettingsDialog from .settings import SettingsBox from .settings import PopoverSetting @@ -317,6 +318,7 @@ def __init__(self, account: str) -> None: self.add(BackMenuItem()) self.add(PageMenuItem('general', _('General'))) self.add(PageMenuItem('privacy', _('Privacy'))) + self.add(PageMenuItem('encryption', _('Encryption (OMEMO)'))) self.add(PageMenuItem('connection', _('Connection'))) self.add(PageMenuItem('advanced', _('Advanced'))) self.add(RemoveMenuItem()) @@ -411,6 +413,8 @@ def __init__(self, name: str, label: str) -> None: icon = 'preferences-system-symbolic' elif name == 'privacy': icon = 'preferences-system-privacy-symbolic' + elif name == 'encryption': + icon = 'channel-secure-symbolic' elif name == 'connection': icon = 'preferences-system-network-symbolic' elif name == 'advanced': @@ -436,8 +440,9 @@ def __init__(self, self._settings = settings
self._settings.add_page(GeneralPage(account)) - self._settings.add_page(ConnectionPage(account)) self._settings.add_page(PrivacyPage(account)) + self._settings.add_page(EncryptionPage(account)) + self._settings.add_page(ConnectionPage(account)) self._settings.add_page(AdvancedPage(account))
self._account_row = AccountRow(account) @@ -616,6 +621,7 @@ def __init__(self, account: str, settings: list[Setting]) -> None: Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=12) self.set_valign(Gtk.Align.START) self.set_vexpand(True) + self.get_style_context().add_class('settings-page') self.account = account
self.listbox = SettingsBox(account) @@ -891,6 +897,40 @@ def _reset_send_read_marker(self, button: Gtk.Button) -> None: 'send_marker', None, context='private')
+class EncryptionPage(GenericSettingPage): + + name = 'encryption' + + def __init__(self, account: str) -> None: + settings = [ + Setting(SettingKind.SWITCH, + _('Blind Trust'), + SettingType.ACCOUNT_CONFIG, + 'omemo_blind_trust', + desc=_('Blindly trust new devices until you verify them')) + ] + GenericSettingPage.__init__(self, account, settings) + + heading = Gtk.Label(label=_('Trust Management')) + heading.get_style_context().add_class('bold') + heading.set_xalign(0) + self.add(heading) + + btbv_label = Gtk.Label() + btbv_label.set_xalign(0) + markup = '<a href="%s">%s</a>' % ( + 'https://dev.gajim.org/gajim/gajim-plugins/-/' + 'wikis/omemogajimplugin', + _('Read more about blind trust')) + btbv_label.set_markup(markup) + self.add(btbv_label) + + omemo_trust_manager = OMEMOTrustManager(account) + omemo_trust_manager.set_margin_top(18) + self.pack_end(omemo_trust_manager, True, True, 0) + self.reorder_child(omemo_trust_manager, 0) + + class ConnectionPage(GenericSettingPage):
name = 'connection'
===================================== gajim/gtk/builder.pyi ===================================== @@ -283,6 +283,8 @@ class ContactInfoBuilder(Builder): to_subscription_button: Gtk.Button contact_settings_box: Gtk.Box remove_history_button: Gtk.Button + encryption_scrolled: Gtk.ScrolledWindow + encryption_box: Gtk.Box groups_page_box: Gtk.Box groups_treeview: Gtk.TreeView tree_selection: Gtk.TreeSelection @@ -412,6 +414,8 @@ class GroupchatDetailsBuilder(Builder): main_stack: Gtk.Stack info_box: Gtk.Box settings_box: Gtk.Box + encryption_scrolled: Gtk.ScrolledWindow + encryption_box: Gtk.Box manage_box: Gtk.Box affiliation_box: Gtk.Box outcasts_box: Gtk.Box @@ -589,6 +593,26 @@ class MessageActionsBoxBuilder(Builder): input_scrolled: Gtk.ScrolledWindow
+class OmemoTrustManagerBuilder(Builder): + search_popover: Gtk.Popover + search: Gtk.SearchEntry + qr_code_popover: Gtk.Popover + comparing_instructions: Gtk.Label + our_fingerprint_2: Gtk.Label + qr_code_image: Gtk.Image + qr_dependency_missing: Gtk.Label + stack: Gtk.Stack + our_fingerprint_1: Gtk.Label + qr_menu_button: Gtk.MenuButton + manage_trust_button: Gtk.Button + list_heading: Gtk.Label + list_heading_box: Gtk.Box + show_inactive_switch: Gtk.Switch + search_button: Gtk.MenuButton + list: Gtk.ListBox + clear_devices_button: Gtk.Button + + class PasswordDialogBuilder(Builder): pass_box: Gtk.Box header: Gtk.Label @@ -649,6 +673,7 @@ class PreferencesBuilder(Builder): stack: Gtk.Stack window_behaviour: Gtk.Grid plugins: Gtk.Grid + general: Gtk.Grid chats: Gtk.Grid group_chats: Gtk.Grid file_preview: Gtk.Grid @@ -1016,6 +1041,8 @@ def get_builder(file_name: Literal['manage_sounds.ui'], widgets: list[str] = ... @overload def get_builder(file_name: Literal['message_actions_box.ui'], widgets: list[str] = ...) -> MessageActionsBoxBuilder: ... @overload +def get_builder(file_name: Literal['omemo_trust_manager.ui'], widgets: list[str] = ...) -> OmemoTrustManagerBuilder: ... +@overload def get_builder(file_name: Literal['password_dialog.ui'], widgets: list[str] = ...) -> PasswordDialogBuilder: ... @overload def get_builder(file_name: Literal['pep_config.ui'], widgets: list[str] = ...) -> PepConfigBuilder: ...
===================================== gajim/gtk/chat_stack.py ===================================== @@ -718,7 +718,7 @@ def _on_send_message(self) -> None: assert contact is not None
encryption = contact.settings.get('encryption') - if encryption: + if encryption and encryption != 'OMEMO': if encryption not in app.plugin_manager.encryption_plugins: ErrorDialog(_('Encryption error'), _('Missing necessary encryption plugin'))
===================================== gajim/gtk/contact_info.py ===================================== @@ -49,6 +49,7 @@ from .contact_settings import ContactSettings from .dialogs import ConfirmationDialog from .dialogs import DialogButton +from .omemo_trust_manager import OMEMOTrustManager from .sidebar_switcher import SideBarSwitcher from .builder import get_builder from .util import connect_destroy @@ -109,6 +110,7 @@ def __init__(self,
if isinstance(self.contact, BareContact): self._fill_settings_page(self.contact) + self._fill_encryption_page(self.contact) self._fill_device_info(self.contact) self._fill_groups_page(self.contact) self._fill_note_page(self.contact) @@ -120,7 +122,6 @@ def __init__(self, self.connect('key-press-event', self._on_key_press) self.connect('destroy', self._on_destroy)
- # pylint: disable=line-too-long connect_destroy(self._ui.tree_selection, 'changed', self._on_group_selection_changed) connect_destroy(self._ui.toggle_renderer, @@ -134,7 +135,6 @@ def __init__(self, ('unsubscribed-presence-received', ged.GUI1, self._on_unsubscribed_presence_received), ]) - # pylint: enable=line-too-long
self.add(self._ui.main_grid) self.show_all() @@ -185,6 +185,11 @@ def _fill_information_page(self, contact: ContactT) -> None:
self._switcher.set_row_visible('information', True)
+ def _fill_encryption_page(self, contact: ContactT) -> None: + self._ui.encryption_box.add( + OMEMOTrustManager(self.contact.account, self.contact)) + self._switcher.set_row_visible('encryption-omemo', True) + def _fill_note_page(self, contact: BareContact) -> None: if not contact.is_in_roster: return
===================================== gajim/gtk/groupchat_details.py ===================================== @@ -34,6 +34,7 @@ from .groupchat_config import GroupchatConfig from .groupchat_manage import GroupchatManage from .groupchat_settings import GroupChatSettings +from .omemo_trust_manager import OMEMOTrustManager from .sidebar_switcher import SideBarSwitcher from .structs import RemoveHistoryActionParams
@@ -71,6 +72,7 @@ def __init__(self,
self._add_groupchat_info() self._add_groupchat_settings() + self._add_groupchat_encryption() self._add_groupchat_manage() self._add_affiliations() self._add_outcasts() @@ -173,6 +175,10 @@ def _add_groupchat_settings(self) -> None:
self._ui.settings_box.add(scrolled_window)
+ def _add_groupchat_encryption(self) -> None: + self._ui.encryption_box.add( + OMEMOTrustManager(self._contact.account, self._contact)) + def _add_affiliations(self) -> None: affiliations = GroupchatAffiliation(self._client, self._contact) self._ui.affiliation_box.add(affiliations)
===================================== gajim/gtk/message_actions_box.py ===================================== @@ -42,6 +42,7 @@ from gajim.common.client import Client from gajim.common.const import SimpleClientState from gajim.common.const import Direction +from gajim.common.modules.contacts import BareContact from gajim.common.modules.contacts import GroupchatContact from gajim.common.modules.contacts import GroupchatParticipant from gajim.common.types import ChatContactT @@ -59,7 +60,7 @@ from .menus import get_self_contact_menu from .menus import get_singlechat_menu from .message_input import MessageInputTextView - +from .util import open_window
log = logging.getLogger('gajim.gui.messageactionsbox')
@@ -297,7 +298,7 @@ def _change_encryption(self, if current_state == new_state: return
- if new_state: + if new_state and new_state != 'OMEMO': plugin = app.plugin_manager.encryption_plugins.get(new_state) if plugin is None: # TODO: Add GUI error here @@ -353,6 +354,9 @@ def _update_encryption_details_button(self) -> None: app.window.get_control(), encryption_state)
+ if state == 'OMEMO': + encryption_state['authenticated'] = True + visible, enc_type, authenticated = encryption_state.values() assert isinstance(visible, bool)
@@ -378,6 +382,24 @@ def _update_encryption_details_button(self) -> None: def _on_encryption_details_clicked(self, _button: Gtk.Button) -> None: contact = self.get_current_contact() encryption = contact.settings.get('encryption') + if encryption == 'OMEMO': + if contact.is_groupchat: + open_window('GroupchatDetails', + contact=contact, + page='encryption-omemo') + return + + if isinstance(contact, BareContact) and contact.is_self: + window = open_window('AccountsWindow') + window.select_account(contact.account) + return + + open_window('ContactInfo', + account=contact.account, + contact=contact, + page='encryption-omemo') + return + app.plugin_manager.extension_point( f'encryption_dialog{encryption}', app.window.get_control())
===================================== gajim/gtk/omemo_trust_manager.py ===================================== @@ -0,0 +1,533 @@ +# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> +# +# This file is part of Gajim. +# +# OMEMO Gajim Plugin is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# OMEMO Gajim Plugin is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with OMEMO Gajim Plugin. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations + +from typing import Any +from typing import cast +from typing import Optional +from typing import Union + +import os +import time +import locale +import logging +import tempfile +from packaging.version import Version as V + +from pkg_resources import get_distribution + +from axolotl.identitykeypair import IdentityKeyPair # pyright: ignore + +from nbxmpp.protocol import JID + +from gi.repository import Gtk +from gi.repository import GdkPixbuf + +from gajim.common import app +from gajim.common import ged +from gajim.common import types +from gajim.common.events import AccountConnected +from gajim.common.events import AccountDisconnected +from gajim.common.i18n import _ +from gajim.common.ged import EventHelper +from gajim.common.modules.contacts import BareContact +from gajim.common.modules.omemobackend.util import Trust +from gajim.common.modules.omemobackend.util import IdentityKeyExtended +from gajim.common.modules.omemobackend.util import get_fingerprint + +from .builder import get_builder +from .dialogs import ConfirmationDialog +from .dialogs import DialogButton +from .util import open_window + +log = logging.getLogger('gajim.gui.omemo_trust_dialog') + + +TRUST_DATA = { + Trust.UNTRUSTED: ('dialog-error-symbolic', + _('Untrusted'), + 'error-color'), + Trust.UNDECIDED: ('security-low-symbolic', + _('Not Decided'), + 'warning-color'), + Trust.VERIFIED: ('security-high-symbolic', + _('Verified'), + 'encrypted-color'), + Trust.BLIND: ('security-medium-symbolic', + _('Blind Trust'), + 'encrypted-color') +} + + +class OMEMOTrustManager(Gtk.Box, EventHelper): + def __init__(self, + account: str, + contact: Optional[types.ChatContactT] = None + ) -> None: + + Gtk.Box.__init__(self) + EventHelper.__init__(self) + + self._account = account + self._contact = contact + + self._ui = get_builder('omemo_trust_manager.ui') + self.add(self._ui.stack) + + self._ui.list.set_filter_func(self._filter_func, None) + self._ui.list.set_sort_func(self._sort_func, None) + + self._ui.connect_signals(self) + + self.connect('destroy', self._on_destroy) + self.show_all() + + self.register_events([ + ('account-connected', ged.GUI2, self._on_account_state), + ('account-disconnected', ged.GUI2, self._on_account_state) + ]) + + if not app.account_is_connected(account): + self._ui.stack.set_visible_child_name('no-connection') + return + + self._update() + + def _update(self) -> None: + client = app.get_client(self._account) + if self._contact is None: + self._contact = client.get_module('Contacts').get_contact( + client.get_own_jid().bare) + + if isinstance(self._contact, BareContact) and self._contact.is_self: + header_text = _('Other devices connected with your account') + popover_qr_text = _('Compare this code with the one shown on your ' + 'contact’s screen to ensure the safety of ' + 'your end-to-end encrypted chat.') + else: + header_text = _('Devices connected with %s') % self._contact.name + popover_qr_text = _('Compare this code with the one shown on your ' + 'contact’s screen to ensure the safety of ' + 'your end-to-end encrypted chat ' + 'with %s.') % self._contact.name + + self._ui.list_heading.set_text(header_text) + self._ui.comparing_instructions.set_text(popover_qr_text) + + self._omemo = client.get_module('OMEMO') + + self._identity_key = cast( + IdentityKeyPair, self._omemo.backend.storage.getIdentityKeyPair()) + our_fpr_formatted = get_fingerprint(self._identity_key, formatted=True) + self._ui.our_fingerprint_1.set_text(our_fpr_formatted) + self._ui.our_fingerprint_2.set_text(our_fpr_formatted) + + self.update() + self._load_qrcode() + + def update(self) -> None: + assert self._contact is not None + self._ui.list.foreach(self._ui.list.remove) + + if isinstance(self._contact, BareContact) and self._contact.is_self: + self._ui.clear_devices_button.show() + self._ui.list_heading_box.set_halign(Gtk.Align.START) + else: + self._ui.manage_trust_button.show() + if self._contact.is_groupchat: + self._ui.search_button.show() + + self._load_fingerprints(self._contact) + + def _on_destroy(self, *args: Any) -> None: + app.check_finalize(self) + + def _on_account_state(self, + event: Union[AccountConnected, AccountDisconnected] + ) -> None: + + if isinstance(event, AccountConnected): + self._update() + self._ui.stack.set_visible_child_name('manage-keys') + else: + self._ui.stack.set_visible_child_name('no-connection') + + def _filter_func(self, row: KeyRow, _user_data: Any) -> bool: + search_text = self._ui.search.get_text() + if search_text and search_text.lower() not in str(row.jid): + return False + if self._ui.show_inactive_switch.get_active(): + return True + return row.active + + @staticmethod + def _sort_func(row1: KeyRow, row2: KeyRow, _user_data: Any) -> int: + result = locale.strcoll(str(row1.jid), str(row2.jid)) + if result != 0: + return result + + if row1.active != row2.active: + return -1 if row1.active else 1 + + if row1.trust != row2.trust: + return -1 if row1.trust > row2.trust else 1 + return 0 + + def _on_search_changed(self, _entry: Gtk.SearchEntry) -> None: + self._ui.list.invalidate_filter() + + def _load_fingerprints(self, contact: types.ChatContactT) -> None: + if contact.is_groupchat: + members = list(self._omemo.backend.get_muc_members( + str(contact.jid))) + sessions = self._omemo.backend.storage.getSessionsFromJids(members) + results = self._omemo.backend.storage.getMucFingerprints(members) + else: + sessions = self._omemo.backend.storage.getSessionsFromJid( + str(contact.jid)) + results = self._omemo.backend.storage.getFingerprints( + str(contact.jid)) + + rows: dict[IdentityKeyExtended, KeyRow] = {} + for result in results: + rows[result.public_key] = KeyRow(contact, + result.recipient_id, + result.public_key, + result.trust, + result.timestamp) + + for item in sessions: + if item.record.isFresh(): + return + identity_key = item.record.getSessionState().getRemoteIdentityKey() + identity_key = IdentityKeyExtended(identity_key.getPublicKey()) + try: + key_row = rows[identity_key] + except KeyError: + log.warning('Could not find session identitykey %s', + item.device_id) + self._omemo.backend.storage.deleteSession(item.recipient_id, + item.device_id) + continue + + key_row.active = item.active + key_row.device_id = item.device_id + + for row in rows.values(): + self._ui.list.add(row) + + @staticmethod + def _get_qrcode(jid: str, sid: int, identity_key: IdentityKeyPair) -> str: + fingerprint = get_fingerprint(identity_key) + path = os.path.join(tempfile.gettempdir(), + 'omemo_{}.png'.format(jid)) + + ver_string = 'xmpp:{}?omemo-sid-{}={}'.format(jid, sid, fingerprint) + log.debug('Verification String: %s', ver_string) + + import qrcode + qr = qrcode.QRCode(version=None, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=6, + border=4) + qr.add_data(ver_string) + qr.make(fit=True) + + fill_color = 'black' + back_color = 'white' + if V(get_distribution('qrcode').version) < V('6.0'): + # meaning of fill_color and back_color were switched + # before this commit in qrcode between versions 5.3 + # and 6.0: https://github.com/lincolnloop/python-qrcode/ + # commit/01f440d64b7d1f61bb75161ce118b86eca85b15c + back_color, fill_color = fill_color, back_color + + img = qr.make_image(fill_color=fill_color, back_color=back_color) + img.save(path) + return path + + def _load_qrcode(self) -> None: + try: + client = app.get_client(self._account) + path = self._get_qrcode(client.get_own_jid().bare, + self._omemo.backend.own_device, + self._identity_key) + except ImportError: + log.exception('Failed to generate QR code') + self._ui.qr_code_image.hide() + self._ui.qr_dependency_missing.show() + else: + pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) + self._ui.qr_code_image.set_from_pixbuf(pixbuf) + self._ui.qr_code_image.show() + self._ui.qr_dependency_missing.hide() + + def _on_show_inactive(self, switch: Gtk.Switch, _param: Any) -> None: + self._ui.list.invalidate_filter() + + def _on_clear_devices_clicked(self, _button: Gtk.Button) -> None: + def _clear(): + self._omemo.clear_devicelist() + + window = self.get_window() + assert isinstance(window, Gtk.Window) + + ConfirmationDialog( + _('Clear Devices'), + _('Clear Devices Now?'), + _('This will clear the devices store for your account.'), + [DialogButton.make('Cancel'), + DialogButton.make('Accept', + text=_('_Clear Devices'), + callback=_clear)], + transient_for=window).show() + + def _on_manage_trust_clicked(self, _button: Gtk.Button) -> None: + assert self._contact is not None + window = open_window('AccountsWindow') + window.select_account(self._contact.account) + + +class KeyRow(Gtk.ListBoxRow): + def __init__(self, + contact: types.ChatContactT, + jid: JID, + identity_key: IdentityKeyExtended, + trust: Trust, + last_seen: Optional[float] + ) -> None: + + Gtk.ListBoxRow.__init__(self) + self.set_activatable(False) + + self._active = False + self._device_id: Optional[int] = None + self._identity_key = identity_key + self.trust = trust + self.jid = jid + + client = app.get_client(contact.account) + self._omemo = client.get_module('OMEMO') + + grid = Gtk.Grid() + grid.set_column_spacing(12) + + self._trust_button = TrustButton(self) + grid.attach(self._trust_button, 1, 1, 1, 3) + + self.fingerprint = Gtk.Label( + label=self._identity_key.get_fingerprint(formatted=True)) + self.fingerprint.get_style_context().add_class('omemo-mono') + self.fingerprint.get_style_context().add_class('omemo-inactive-color') + self.fingerprint.set_selectable(True) + self.fingerprint.set_halign(Gtk.Align.START) + self.fingerprint.set_valign(Gtk.Align.START) + self.fingerprint.set_hexpand(True) + grid.attach(self.fingerprint, 2, 1, 1, 1) + + if last_seen is not None: + last_seen_str = time.strftime('%d-%m-%Y %H:%M:%S', + time.localtime(last_seen)) + else: + last_seen_str = _('Never') + last_seen_label = Gtk.Label(label=_('Last seen: %s') % last_seen_str) + last_seen_label.set_halign(Gtk.Align.START) + last_seen_label.set_valign(Gtk.Align.START) + last_seen_label.set_hexpand(True) + last_seen_label.get_style_context().add_class('small-label') + last_seen_label.get_style_context().add_class('dim-label') + grid.attach(last_seen_label, 2, 3, 1, 1) + + self.add(grid) + self.show_all() + + def delete_fingerprint(self, *args: Any) -> None: + listbox = cast(Gtk.ListBox, self.get_parent()) + window = cast(Gtk.Window, listbox.get_window()) + + def _remove(): + self._omemo.backend.remove_device(str(self.jid), self.device_id) + self._omemo.backend.storage.deleteSession( + str(self.jid), self.device_id) + self._omemo.backend.storage.deleteIdentity( + str(self.jid), self._identity_key) + + listbox.remove(self) + self.destroy() + + ConfirmationDialog( + _('Delete'), + _('Delete Fingerprint'), + _('Doing so will permanently delete this Fingerprint'), + [DialogButton.make('Cancel'), + DialogButton.make('Remove', + text=_('Delete'), + callback=_remove)], + transient_for=window).show() + + def set_trust(self) -> None: + icon_name, tooltip, css_class = TRUST_DATA[self.trust] + image = cast(Gtk.Image, self._trust_button.get_child()) + image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) + image.get_style_context().add_class(css_class) + image.set_tooltip_text(tooltip) + + self._omemo.backend.storage.setTrust( + str(self.jid), self._identity_key, self.trust) + + @property + def active(self) -> bool: + return self._active + + @active.setter + def active(self, active: bool) -> None: + context = self.fingerprint.get_style_context() + self._active = bool(active) + if self._active: + context.remove_class('omemo-inactive-color') + else: + context.add_class('omemo-inactive-color') + self._trust_button.update() + + @property + def device_id(self) -> Optional[int]: + return self._device_id + + @device_id.setter + def device_id(self, device_id: int) -> None: + self._device_id = device_id + + +class TrustButton(Gtk.MenuButton): + def __init__(self, row: KeyRow) -> None: + Gtk.MenuButton.__init__(self) + self._row = row + self._css_class = '' + self.set_popover(TrustPopver(row)) + self.set_valign(Gtk.Align.CENTER) + self.update() + + def update(self) -> None: + icon_name, tooltip, css_class = TRUST_DATA[self._row.trust] + image = cast(Gtk.Image, self.get_child()) + image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) + image.get_style_context().remove_class(self._css_class) + + if not self._row.active: + css_class = 'omemo-inactive-color' + tooltip = '%s - %s' % (_('Inactive'), tooltip) + + image.get_style_context().add_class(css_class) + self._css_class = css_class + self.set_tooltip_text(tooltip) + + +class TrustPopver(Gtk.Popover): + def __init__(self, row: KeyRow) -> None: + Gtk.Popover.__init__(self) + self._row = row + self._listbox = Gtk.ListBox() + self._listbox.set_selection_mode(Gtk.SelectionMode.NONE) + self.update() + self.add(self._listbox) + self._listbox.show_all() + self._listbox.connect('row-activated', self._activated) + self.get_style_context().add_class('omemo-trust-popover') + + def _activated(self, _listbox: Gtk.ListBox, row: MenuOption) -> None: + self.popdown() + if row.type_ is None: + self._row.delete_fingerprint() + else: + self._row.trust = row.type_ + self._row.set_trust() + trust_button = cast(TrustButton, self.get_relative_to()) + trust_button.update() + self.update() + + def update(self) -> None: + self._listbox.foreach(self._listbox.remove) + if self._row.trust != Trust.VERIFIED: + self._listbox.add(VerifiedOption()) + if self._row.trust != Trust.BLIND: + self._listbox.add(BlindOption()) + if self._row.trust != Trust.UNTRUSTED: + self._listbox.add(NotTrustedOption()) + self._listbox.add(DeleteOption()) + + +class MenuOption(Gtk.ListBoxRow): + def __init__(self, + icon: str, + label_text: str, + color: str, + type_: Optional[Trust] = None + ) -> None: + + Gtk.ListBoxRow.__init__(self) + + self.type_ = type_ + self.icon = icon + self.label = label_text + self.color = color + + box = Gtk.Box() + box.set_spacing(6) + + image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU) + label = Gtk.Label(label=label_text) + image.get_style_context().add_class(color) + + box.add(image) + box.add(label) + self.add(box) + self.show_all() + + +class BlindOption(MenuOption): + def __init__(self) -> None: + MenuOption.__init__(self, + 'security-medium-symbolic', + _('Blind Trust'), + 'encrypted-color', + Trust.BLIND) + + +class VerifiedOption(MenuOption): + def __init__(self) -> None: + MenuOption.__init__(self, + 'security-high-symbolic', + _('Verified'), + 'encrypted-color', + Trust.VERIFIED) + + +class NotTrustedOption(MenuOption): + def __init__(self) -> None: + MenuOption.__init__(self, + 'dialog-error-symbolic', + _('Untrusted'), + 'error-color', + Trust.UNTRUSTED) + + +class DeleteOption(MenuOption): + def __init__(self) -> None: + MenuOption.__init__(self, + 'user-trash-symbolic', + _('Delete'), + '')
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/f4c51a3435979ba740a58767be11fcc8d...