diff --git a/data/builder-dark.style-scheme.xml b/data/builder-dark.style-scheme.xml
deleted file mode 100644
index 3421b4a..0000000
--- a/data/builder-dark.style-scheme.xml
+++ /dev/null
@@ -1,197 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
- This file is part of GtkSourceView
-
- Copyright (C) 2007 GtkSourceView team
- Author: Paolo Borelli <pborelli@gnome.org>
-
- GtkSourceView is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- GtkSourceView is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-
--->
-
-<style-scheme id="builder-dark" name="Builder Dark" version="1.0">
-
-  <author>Paolo Borelli, Christian Hergert</author>
-  <description>Dark color scheme for Builder using the Tango color palette</description>
-
-  <!-- Tango Palette -->
-  <color name="butter1"                     value="#fce94f"/>
-  <color name="butter2"                     value="#edd400"/>
-  <color name="butter3"                     value="#c4a000"/>
-  <color name="chameleon1"                  value="#8ae234"/>
-  <color name="chameleon2"                  value="#73d216"/>
-  <color name="chameleon3"                  value="#4e9a06"/>
-  <color name="orange1"                     value="#fcaf3e"/>
-  <color name="orange2"                     value="#f57900"/>
-  <color name="orange3"                     value="#ce5c00"/>
-  <color name="skyblue1"                    value="#729fcf"/>
-  <color name="skyblue2"                    value="#3465a4"/>
-  <color name="skyblue3"                    value="#204a87"/>
-  <color name="plum1"                       value="#ad7fa8"/>
-  <color name="plum2"                       value="#75507b"/>
-  <color name="plum3"                       value="#5c3566"/>
-  <color name="chocolate1"                  value="#e9b96e"/>
-  <color name="chocolate2"                  value="#c17d11"/>
-  <color name="chocolate3"                  value="#8f5902"/>
-  <color name="scarletred1"                 value="#ef2929"/>
-  <color name="scarletred2"                 value="#cc0000"/>
-  <color name="scarletred3"                 value="#a40000"/>
-  <color name="aluminium1"                  value="#eeeeec"/>
-  <color name="aluminium2"                  value="#d3d7cf"/>
-  <color name="aluminium3"                  value="#babdb6"/>
-  <color name="aluminium4"                  value="#888a85"/>
-  <color name="aluminium5"                  value="#555753"/>
-  <color name="aluminium6"                  value="#2e3436"/>
-  <color name="white"                       value="#ffffff"/>
-  <color name="pink1"                       value="#dd4a68"/>
-  <color name="red1"                        value="#ff0000"/>
-
-  <color name="dark1"                        value="#1e1e1e"/>
-  <color name="dark2"                        value="#303030"/>
-
-  <!-- Global Settings -->
-  <style name="text"                        foreground="aluminium3" background="dark1"/>
-  <style name="selection"                   foreground="aluminium1" background="aluminium4"/>
-  <style name="cursor"                      foreground="aluminium2"/>
-  <style name="current-line"                background="dark2"/>
-  <style name="current-line-number"         background="dark2"/>
-  <style name="line-numbers"                foreground="aluminium5" background="dark1"/>
-  <style name="draw-spaces"                 foreground="aluminium5"/>
-  <style name="background-pattern"          background="#202020"/>
-  <style name="map-overlay"                 background="#rgba(136,138,133,0.25)"/>
-
-  <!-- Diagnostics Underlining -->
-  <style name="diagnostician::deprecated"   underline="error" underline-color="aluminium3"/>
-  <style name="diagnostician::error"        underline="error" underline-color="red1"/>
-  <style name="diagnostician::note"         underline="error" underline-color="skyblue1"/>
-  <style name="diagnostician::warning"      underline="error" underline-color="orange1"/>
-
-  <!-- Snippets -->
-  <style name="snippet::tab-stop"           background="orange3" foreground="aluminium6"/>
-  <style name="snippet::area"               background="#rgba(86,114,151,.5)"/>
-
-  <!-- Debugger -->
-  <style name="debugger::current-breakpoint" foreground="#2e3436" background="#fcaf3e"/>
-  <style name="debugger::breakpoint"         foreground="#ffffff" background="#204a87"/>
-
-  <!-- Hover links -->
-  <style name="action::hover-definition"    background="#41464c" underline="true"/>
-
-  <!-- Bracket Matching -->
-  <style name="bracket-match"               foreground="chocolate2" bold="true"/>
-  <style name="bracket-mismatch"            foreground="aluminium1" background="scarletred2" bold="true"/>
-
-  <!-- Right Margin -->
-  <style name="right-margin"                foreground="#484749" background="#484749"/>
-
-  <!-- Search Matching -->
-  <style name="search-match"                foreground="aluminium1" background="chameleon3"/>
-  <style name="quick-highlight-match"       background="#rgba(78,154,6,.25)"/>
-
-  <!-- Search Shadow -->
-  <style name="search-shadow"               background="#rgba(0,0,0,0.4)"/>
-
-  <!-- Spellchecker Matching -->
-  <style name="misspelled-match"            foreground="#000000" background="#b3d4fc"/>
-
-  <!-- Comments -->
-  <style name="def:comment"                 foreground="aluminium4"/>
-  <style name="def:shebang"                 foreground="aluminium4" bold="true"/>
-  <style name="def:doc-comment-element"     italic="true"/>
-
-  <!-- Constants -->
-  <style name="def:constant"                foreground="butter2"/>
-  <style name="def:string"                  foreground="#0077aa"/>
-  <style name="def:special-char"            foreground="#dd4a68"/>
-  <style name="def:special-constant"        foreground="plum1"/>
-  <style name="def:floating-point"          foreground="orange3"/>
-  <style name="def:function"                foreground="#4186A8"/>
-
-  <!-- Identifiers -->
-  <style name="def:identifier"              foreground="skyblue1"/>
-
-  <!-- Statements -->
-  <style name="def:statement"               foreground="white" bold="true"/>
-
-  <!-- Types -->
-  <style name="def:type"                    foreground="chameleon1" bold="true"/>
-
-  <!-- Others -->
-  <style name="def:preprocessor"            foreground="#dd4a68"/>
-  <style name="def:error"                   foreground="aluminium1" background="scarletred2" bold="true"/>
-  <style name="def:warning"                 foreground="aluminium1" background="plum1"/>
-  <style name="def:note"                    background="butter1" foreground="aluminium4" bold="true"/>
-  <style name="def:underlined"              italic="true" underline="true"/>
-
-  <!-- Heading styles, uncomment to enable -->
-  <!--
-  <style name="def:heading0"                scale="5.0"/>
-  <style name="def:heading1"                scale="2.5"/>
-  <style name="def:heading2"                scale="2.0"/>
-  <style name="def:heading3"                scale="1.7"/>
-  <style name="def:heading4"                scale="1.5"/>
-  <style name="def:heading5"                scale="1.3"/>
-  <style name="def:heading6"                scale="1.2"/>
-  -->
-
-  <!-- Language specific -->
-  <style name="c:comment"                   foreground="#8b9eab"/>
-  <style name="c:preprocessor"              foreground="#8194a6" bold="false"/>
-  <style name="c:boolean"                   foreground="#0077aa"/>
-  <style name="c:keyword"                   foreground="#0077aa" bold="true"/>
-  <style name="c:string"                    foreground="#669900"/>
-  <style name="c:included-file"             foreground="orange3"/>
-  <style name="c:storage-class"             foreground="orange3" bold="true"/>
-  <style name="c:type"                      foreground="#669900" bold="true"/>
-  <style name="c:macro-name"                foreground="#677685" bold="false"/>
-  <style name="c:enum-name"                 foreground="#dd4a68" bold="false"/>
-
-  <style name="diff:added-line"             foreground="chameleon2"/>
-  <style name="diff:removed-line"           foreground="plum1"/>
-  <style name="diff:changed-line"           foreground="blue1"/>
-  <style name="diff:diff-file"              foreground="chameleon1" bold="true"/>
-  <style name="diff:location"               foreground="chameleon1"/>
-  <style name="diff:special-case"           foreground="white" bold="true"/>
-
-  <style name="gutter:added-line"           foreground="chameleon2"/>
-  <style name="gutter:changed-line"         foreground="butter3"/>
-  <style name="gutter:removed-line"         foreground="scarletred3"/>
-
-  <style name="js:object"                   foreground="chameleon3" bold="true"/>
-  <style name="js:constructors"             foreground="pink1"/>
-  <style name="js:keyword"                  foreground="#2b85aa"/>
-  <style name="js:string"                   foreground="#669900"/>
-  <style name="js:function"                 foreground="pink1"/>
-
-  <style name="latex:command"               foreground="chameleon1" bold="true"/>
-  <style name="latex:include"               use-style="def:preprocessor"/>
-
-  <style name="xml:comment"                 foreground="#8b9eab"/>
-  <style name="xml:attribute-name"          foreground="#orange3" bold="false"/>
-  <style name="xml:attribute-value"         foreground="#669900"/>
-  <style name="xml:tag-match"               background="#rgba(114,159,207,.20)"/>
-
-  <!-- Symbol-tree xml-pack coloring -->
-  <style name="symboltree::label"           foreground="#000000" background="#D5E7FC"/>
-  <style name="symboltree::id"              foreground="#000000" background="#D9E7BD"/>
-  <style name="symboltree::style-class"     foreground="#000000" background="#DFCD9B"/>
-  <style name="symboltree::type"            foreground="#000000" background="#F4DAC3"/>
-  <style name="symboltree::parent"          foreground="#000000" background="#DEBECF"/>
-  <style name="symboltree::class"           foreground="#000000" background="#FFEF98"/>
-  <style name="symboltree::attribute"       foreground="#000000" background="#F0E68C"/>
-
-</style-scheme>
-
diff --git a/data/meson.build b/data/meson.build
index 2afaec2..3288dc9 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -21,8 +21,3 @@ i18n.merge_file(
   install: true,
   install_dir: join_paths(datadir, 'metainfo')
 )
-
-install_data(
-  'builder-dark.style-scheme.xml',
-  install_dir: join_paths(pkgdatadir, 'gtksourceview-4/styles')
-)
diff --git a/data/org.gnome.NautilusPreviewer.metainfo.xml.in.in b/data/org.gnome.NautilusPreviewer.metainfo.xml.in.in
index ec95eef..19020ba 100644
--- a/data/org.gnome.NautilusPreviewer.metainfo.xml.in.in
+++ b/data/org.gnome.NautilusPreviewer.metainfo.xml.in.in
@@ -22,6 +22,7 @@
     <keyword>preview</keyword>
   </keywords>
   <releases>
+    <release version="50.0" date="2026-04-26"/>
     <release version="46.0" date="2024-04-04"/>
   </releases>
   <translation type="gettext">sushi</translation>
diff --git a/flatpak/org.gnome.NautilusPreviewer.json b/flatpak/org.gnome.NautilusPreviewer.json
index eb1e9a4..810f46a 100644
--- a/flatpak/org.gnome.NautilusPreviewer.json
+++ b/flatpak/org.gnome.NautilusPreviewer.json
@@ -3,6 +3,9 @@
     "runtime": "org.gnome.Platform",
     "runtime-version": "master",
     "sdk": "org.gnome.Sdk",
+    "sdk-extensions": [
+        "org.freedesktop.Sdk.Extension.rust-stable"
+    ],
     "command": "sushi",
     "cleanup": [
         "/include",
@@ -19,6 +22,12 @@
         "--talk-name=org.freedesktop.FileManager1",
         "--filesystem=home:ro"
     ],
+    "build-options": {
+        "append-path": "/usr/lib/sdk/rust-stable/bin:",
+        "build-args": [
+            "--share=network"
+        ]
+    },
     "modules": [
         {
             "name": "popplerdata",
@@ -29,8 +38,13 @@
             "sources": [
                 {
                     "type": "archive",
-                    "url": "https://poppler.freedesktop.org/poppler-data-0.4.11.tar.gz",
-                    "sha256": "2cec05cd1bb03af98a8b06a1e22f6e6e1a65b1e2f3816cb3069bb0874825f08c"
+                    "url": "https://poppler.freedesktop.org/poppler-data-0.4.12.tar.gz",
+                    "sha256": "c835b640a40ce357e1b83666aabd95edffa24ddddd49b8daff63adb851cdab74",
+                    "x-checker-data": {
+                        "type": "anitya",
+                        "project-id": 3687,
+                        "url-template": "https://poppler.freedesktop.org/poppler-data-$version.tar.gz"
+                    }
                 }
             ]
         },
@@ -40,11 +54,14 @@
             "config-opts": [
                 "-DCMAKE_INSTALL_LIBDIR=/app/lib",
                 "-DCMAKE_INSTALL_INCLUDEDIR=/app/include",
-                "-DENABLE_LIBOPENJPEG=none",
-                "-DENABLE_BOOST=OFF",
-                "-DENABLE_CPP=OFF",
                 "-DBUILD_GTK_TESTS=OFF",
-                "-DBUILD_CPP_TESTS=OFF"
+                "-DBUILD_CPP_TESTS=OFF",
+                "-DENABLE_CPP=OFF",
+                "-DENABLE_BOOST=OFF",
+                "-DENABLE_GOBJECT_INTROSPECTION=OFF",
+                "-DENABLE_LIBOPENJPEG=openjpeg2",
+                "-DENABLE_QT5=OFF",
+                "-DENABLE_QT6=OFF"
             ],
             "cleanup": [
                 "/bin"
@@ -52,49 +69,76 @@
             "sources": [
                 {
                     "type": "archive",
-                    "url": "https://poppler.freedesktop.org/poppler-23.01.0.tar.xz",
-                    "sha256": "fae9b88d3d5033117d38477b79220cfd0d8e252c278ec870ab1832501741fd94"
+                    "url": "https://poppler.freedesktop.org/poppler-26.02.0.tar.xz",
+                    "sha256": "dded8621f7b2f695c91063aab1558691c8418374cd583501e89ed39487e7ab77",
+                    "x-checker-data": {
+                        "type": "anitya",
+                        "project-id": 3686,
+                        "url-template": "https://poppler.freedesktop.org/poppler-$version.tar.xz"
+                    }
                 }
             ]
         },
+
         {
-            "name": "evince",
+            "name": "exempi",
+            "config-opts": [
+                "--disable-unittest",
+                "--disable-samples"
+            ],
+            "sources": [
+                {
+                    "type": "git",
+                    "url": "https://gitlab.freedesktop.org/libopenraw/exempi.git",
+                    "tag": "2.6.5",
+                    "x-checker-data": {
+                        "type": "git",
+                        "tag-pattern": "^([\\d.]+)$"
+                    },
+                    "commit": "01ed352bbf18c2be2bdbd0125c88901b9db0b9c3"
+                }
+            ]
+        },
+        {
+            "name": "papers",
             "buildsystem": "meson",
             "cleanup": [
                 "/share/GConf",
                 "/share/help"
             ],
             "config-opts": [
+                "-Dshell=true",
                 "-Dnautilus=false",
-                "-Dgtk_doc=false",
-                "-Dviewer=false",
+                "-Ddocumentation=false",
                 "-Dpreviewer=false",
-                "-Ddbus=false",
-                "-Dintrospection=true",
+                "-Dthumbnailer=false",
+                "-Dintrospection=enabled",
                 "-Dcomics=disabled",
-                "-Dgspell=disabled"
+                "-Dtests=false",
+                "-Duser_doc=false",
+                "-Ddocumentation=false"
             ],
             "sources": [
                 {
-                    "type": "archive",
-                    "url": "https://download.gnome.org/sources/evince/43/evince-43.1.tar.xz",
-                    "sha256": "6d75ca62b73bfbb600f718a098103dc6b813f9050b9594be929e29b4589d2335"
+                    "type": "git",
+                    "url": "https://gitlab.gnome.org/GNOME/papers.git",
+                    "branch": "main"
                 }
             ]
         },
         {
-            "name": "sourceview-4",
+            "name": "sourceview-5",
             "buildsystem": "meson",
             "config-opts": [
-                "-Dinstall_tests=false",
-                "-Dgtk_doc=false",
+                "-Dinstall-tests=false",
+                "-Ddocumentation=false",
                 "-Dvapi=false"
             ],
             "sources": [
                 {
-                    "type": "archive",
-                    "url": "https://download.gnome.org/sources/gtksourceview/4.8/gtksourceview-4.8.4.tar.xz",
-                    "sha256": "7ec9d18fb283d1f84a3a3eff3b7a72b09a10c9c006597b3fbabbb5958420a87d"
+                    "type": "git",
+                    "url": "https://gitlab.gnome.org/GNOME/gtksourceview.git",
+                    "tag": "5.20.0"
                 }
             ]
         },
@@ -102,7 +146,7 @@
             "name": "sushi",
             "buildsystem": "meson",
             "config-opts": [
-            	"-Dprofile=development"
+                "-Dprofile=development"
             ],
             "sources": [
                 {
diff --git a/meson.build b/meson.build
index 2111a38..ca80968 100644
--- a/meson.build
+++ b/meson.build
@@ -5,23 +5,23 @@ project(
   meson_version: '>=0.58.0'
 )
 
-epoxy_dep = dependency('epoxy')
-evince_document_dep = dependency('evince-document-3.0')
-evince_view_dep = dependency('evince-view-3.0')
+papers_document_dep = dependency('papers-document-4.0')
+papers_view_dep = dependency('papers-view-4.0')
 freetype_dep = dependency('freetype2')
 gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: '>=2.23.0')
 gio_unix_dep = dependency('gio-unix-2.0', version: '>=2.29.14')
 glib_dep = dependency('glib-2.0', version: '>=2.29.14')
 gstreamer_dep = dependency('gstreamer-1.0')
-gstreamer_audio_dep = dependency('gstreamer-audio-1.0')
 gstreamer_tag_dep = dependency('gstreamer-tag-1.0')
-gstreamer_video_dep = dependency('gstreamer-video-1.0')
-gtk_dep = dependency('gtk+-3.0', version: '>=3.13.2')
-gtk_x11_dep = dependency('gtk+-x11-3.0', required: get_option('X11'))
-gtk_wayland_dep = dependency('gtk+-wayland-3.0', version: '>= 3.21.5', required: get_option('wayland'))
-gtksourceview_dep = dependency('gtksourceview-4', version: '>=4.0.3')
+gst_pbutils_dep = dependency('gstreamer-pbutils-1.0')
+gtk_wayland_dep = dependency('gtk4-wayland', required: get_option('wayland'))
+gtk_x11_dep = dependency('gtk4-x11', required: get_option('X11'))
+gtk_dep = dependency('gtk4', version: '>= 4.10')
+gtksourceview_dep = dependency('gtksourceview-5')
 harfbuzz_dep = dependency('harfbuzz', version: '>=0.9.9')
-webkit_dep = dependency('webkit2gtk-4.1', required: false)
+webkit_dep = dependency('webkitgtk-6.0')
+x11_dep = dependency('x11', required: get_option('X11'))
+libadwaita_dep = dependency('libadwaita-1')
 
 bindir = join_paths(get_option('prefix'), get_option('bindir'))
 datadir = join_paths(get_option('prefix'), get_option('datadir'))
diff --git a/po/LINGUAS b/po/LINGUAS
index f0c11c3..9fcdc14 100644
--- a/po/LINGUAS
+++ b/po/LINGUAS
@@ -47,6 +47,7 @@ kk
 kn
 ko
 kw
+lo
 lt
 lv
 mjw
diff --git a/po/lo.po b/po/lo.po
new file mode 100644
index 0000000..48491ed
--- /dev/null
+++ b/po/lo.po
@@ -0,0 +1,82 @@
+# Lao translation for sushi.
+# Copyright (C) 2025 sushi's COPYRIGHT HOLDER
+# This file is distributed under the same license as the sushi package.
+# Bone NI <bounkirdni@gmail.com>,  2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: sushi master\n"
+"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/sushi/issues/\n"
+"POT-Creation-Date: 2025-09-05 06:08+0000\n"
+"PO-Revision-Date: 2025-09-05 06:08+0000\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: Lao <lo@li.org>\n"
+"Language: lo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: data/org.gnome.NautilusPreviewer.appdata.xml.in.in:9
+msgid "Sushi"
+msgstr "Sushi"
+
+#: data/org.gnome.NautilusPreviewer.appdata.xml.in.in:10
+msgid "Provide a facility for quickly viewing different kinds of files"
+msgstr "ອຳນວຍຄວາມສະດວກໃນການເບິ່ງໄຟລ໌ຊະນິດຕ່າງໆໄດ້ຢ່າງວ່ອງໄວ"
+
+#: data/org.gnome.NautilusPreviewer.appdata.xml.in.in:12
+msgid ""
+"Sushi is a file previewer for the GNOME desktop environment. It is an "
+"independent component that integrates with GNOME Files (Nautilus)."
+msgstr "Sushi ແມ່ນໂປຣແກຣມສະແດງຕົວຢ່າງໄຟລ໌ສຳລັບສະພາບແວດລ້ອມເດັສທັອບຂອງ GNOME. ມັນເປັນສ່ວນປະກອບອິດສະຫຼະທີ່ເຮັດວຽກຮ່ວມກັບ GNOME Files (Nautilus)."
+
+#: src/ui/fallbackRenderer.js:244
+msgid "Type"
+msgstr "ປະເພດ"
+
+#: src/ui/fallbackRenderer.js:254
+#, javascript-format
+msgid "%d item"
+msgstr "%d ລາຍການ"
+
+#: src/ui/fallbackRenderer.js:259
+msgid "Empty Folder"
+msgstr "ໂຟລເດີຫວ່າງເປົ່າ"
+
+#: src/ui/fallbackRenderer.js:263
+msgid "Size"
+msgstr "ຂະໜາດ"
+
+#: src/ui/fallbackRenderer.js:269
+msgid "Modified"
+msgstr "ແກ້ໄຂຫຼ້າສຸດ"
+
+#. TRANSLATORS: This is a filename, e.g. "image.jpg"
+#: src/ui/mainWindow.js:90
+#, javascript-format
+msgid "Unable to display %s"
+msgstr "ບໍ່ສາມາດສະແດງ %s ໄດ້"
+
+#. TRANSLATORS: This is the display name of an application, e.g. "Open With Image Viewer"
+#: src/ui/mainWindow.js:358
+#, javascript-format
+msgid "Open With %s"
+msgstr "ເປີດດ້ວຍ %s"
+
+#: src/ui/mainWindow.js:364
+msgid "Open"
+msgstr "ເປີດ"
+
+#: src/viewers/audio.js:401
+msgid "from"
+msgstr "ຈາກ"
+
+#: src/viewers/audio.js:406
+msgid "by"
+msgstr "ໂດຍ"
+
+#: src/viewers/evince.js:111
+#, javascript-format
+msgid "%d of %d"
+msgstr "%d ຈາກ %d"
diff --git a/src/libsushi/SushiMediaBin.ui b/src/libsushi/SushiMediaBin.ui
deleted file mode 100644
index c06a385..0000000
--- a/src/libsushi/SushiMediaBin.ui
+++ /dev/null
@@ -1,401 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface>
-  <requires lib="gtk+" version="3.20"/>
-  <object class="GtkImage" id="audio_playback_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-playback-start-symbolic</property>
-  </object>
-  <object class="GtkImage" id="fullscreen_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_size">1</property>
-    <property name="icon_name">view-fullscreen-symbolic</property>
-  </object>
-  <object class="GtkAdjustment" id="playback_adjustment">
-    <property name="upper">128</property>
-    <property name="step_increment">60</property>
-    <property name="page_increment">300</property>
-    <signal name="value-changed" handler="on_playback_adjustment_value_changed" swapped="no"/>
-  </object>
-  <object class="GtkImage" id="playback_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_size">1</property>
-    <property name="icon_name">media-playback-start-symbolic</property>
-  </object>
-  <object class="GtkAdjustment" id="volume_adjustment">
-    <property name="upper">1</property>
-    <property name="value">1</property>
-    <property name="step_increment">0.040000000000000001</property>
-    <property name="page_increment">0.10000000000000001</property>
-  </object>
-  <template class="SushiMediaBin" parent="GtkBox">
-    <property name="visible">True</property>
-    <property name="can_focus">True</property>
-    <property name="orientation">vertical</property>
-    <signal name="realize" handler="on_sushi_media_bin_realize" swapped="no"/>
-    <child>
-      <object class="GtkStack" id="stack">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <property name="hhomogeneous">False</property>
-        <property name="vhomogeneous">False</property>
-        <child>
-          <object class="GtkOverlay" id="overlay">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
-            <property name="no_show_all">True</property>
-            <signal name="button-press-event" handler="on_overlay_button_press_event" swapped="no"/>
-            <signal name="button-release-event" handler="on_overlay_button_release_event" swapped="no"/>
-            <signal name="motion-notify-event" handler="on_overlay_motion_notify_event" swapped="no"/>
-            <child>
-              <placeholder/>
-            </child>
-            <child type="overlay">
-              <object class="GtkRevealer" id="top_revealer">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="events">GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK</property>
-                <property name="valign">start</property>
-                <signal name="leave-notify-event" handler="on_revealer_leave_notify_event" swapped="no"/>
-                <signal name="motion-notify-event" handler="on_revealer_motion_notify_event" swapped="no"/>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel" id="title_label">
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <style>
-                          <class name="title"/>
-                        </style>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkBox" id="info_box">
-                        <property name="can_focus">False</property>
-                        <child>
-                          <placeholder/>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <style>
-                      <class name="overlay-bar"/>
-                      <class name="top"/>
-                    </style>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="index">1</property>
-              </packing>
-            </child>
-            <child type="overlay">
-              <object class="GtkRevealer" id="bottom_revealer">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="events">GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK</property>
-                <property name="valign">end</property>
-                <property name="transition_type">crossfade</property>
-                <signal name="leave-notify-event" handler="on_revealer_leave_notify_event" swapped="no"/>
-                <signal name="motion-notify-event" handler="on_revealer_motion_notify_event" swapped="no"/>
-                <child>
-                  <object class="GtkBox" id="bottom_box">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">end</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="margin-start">32</property>
-                    <property name="margin-end">32</property>
-                    <property name="margin-bottom">32</property>
-                    <child>
-                      <object class="GtkButton" id="playback_button">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="image">playback_image</property>
-                        <property name="relief">none</property>
-                        <signal name="clicked" handler="sushi_media_bin_toggle_playback" swapped="yes"/>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="progress_position_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="valign">center</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkScale" id="progress_scale">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="adjustment">playback_adjustment</property>
-                        <property name="round_digits">2</property>
-                        <property name="draw_value">False</property>
-                        <signal name="format-value" handler="on_progress_scale_format_value" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="progress_duration_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="valign">center</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="fullscreen_button">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="image">fullscreen_image</property>
-                        <property name="relief">none</property>
-                        <signal name="clicked" handler="sushi_media_bin_toggle_fullscreen" swapped="yes"/>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="pack_type">end</property>
-                        <property name="position">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkVolumeButton" id="volume_button">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="focus_on_click">False</property>
-                        <property name="receives_default">False</property>
-                        <property name="relief">none</property>
-                        <property name="orientation">vertical</property>
-                        <property name="value">1</property>
-                        <property name="size">menu</property>
-                        <property name="adjustment">volume_adjustment</property>
-                        <child internal-child="plus_button">
-                          <object class="GtkButton">
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">True</property>
-                            <property name="halign">center</property>
-                            <property name="valign">center</property>
-                            <property name="relief">none</property>
-                          </object>
-                        </child>
-                        <child internal-child="minus_button">
-                          <object class="GtkButton">
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">True</property>
-                            <property name="halign">center</property>
-                            <property name="valign">center</property>
-                            <property name="relief">none</property>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="pack_type">end</property>
-                        <property name="position">5</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                    <style>
-                      <class name="osd"/>
-                      <class name="overlay-bar"/>
-                      <class name="bottom"/>
-                    </style>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="index">2</property>
-              </packing>
-            </child>
-            <child type="overlay">
-              <object class="GtkBox" id="play_box">
-                <property name="visible">False</property>
-                <property name="can_focus">False</property>
-                <property name="halign">center</property>
-                <property name="valign">center</property>
-                <child>
-                  <object class="GtkImage" id="play_image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="pixel_size">32</property>
-                    <property name="icon_name">media-playback-start-symbolic</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="duration_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="pass_through">True</property>
-                <property name="index">2</property>
-              </packing>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox" id="audio_box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="spacing">2</property>
-            <child>
-              <object class="GtkButton" id="audio_playback_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="image">audio_playback_image</property>
-                <property name="relief">none</property>
-                <signal name="clicked" handler="sushi_media_bin_toggle_playback" swapped="yes"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="audio_position_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="width_chars">4</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkScale" id="audio_progress_scale">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="adjustment">playback_adjustment</property>
-                <property name="draw_value">False</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="audio_duration_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="width_chars">4</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="pack_type">end</property>
-                <property name="position">4</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkVolumeButton" id="audio_volume_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="focus_on_click">False</property>
-                <property name="receives_default">False</property>
-                <property name="relief">none</property>
-                <property name="orientation">vertical</property>
-                <property name="value">1</property>
-                <property name="size">menu</property>
-                <property name="adjustment">volume_adjustment</property>
-                <child internal-child="plus_button">
-                  <object class="GtkButton">
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <property name="relief">none</property>
-                  </object>
-                </child>
-                <child internal-child="minus_button">
-                  <object class="GtkButton">
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <property name="relief">none</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="pack_type">end</property>
-                <property name="position">3</property>
-              </packing>
-            </child>
-            <style>
-              <class name="bottom"/>
-              <class name="audio"/>
-            </style>
-          </object>
-          <packing>
-            <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>
-  </template>
-</interface>
diff --git a/src/libsushi/externalwindow-wayland.c b/src/libsushi/externalwindow-wayland.c
deleted file mode 100644
index 077ac24..0000000
--- a/src/libsushi/externalwindow-wayland.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright © 2016 Red Hat, Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- *       Jonas Ådahl <jadahl@redhat.com>
- */
-
-#include "config.h"
-
-#include <gdk/gdk.h>
-#include <gdk/gdkwayland.h>
-
-#include "externalwindow-wayland.h"
-
-static GdkDisplay *wayland_display;
-
-struct _ExternalWindowWayland
-{
-  ExternalWindow parent;
-
-  char *handle_str;
-};
-
-struct _ExternalWindowWaylandClass
-{
-  ExternalWindowClass parent_class;
-};
-
-G_DEFINE_TYPE (ExternalWindowWayland, external_window_wayland,
-               EXTERNAL_TYPE_WINDOW)
-
-static GdkDisplay *
-get_wayland_display (void)
-{
-  if (wayland_display)
-    return wayland_display;
-
-  gdk_set_allowed_backends ("wayland");
-  wayland_display = gdk_display_open (NULL);
-  gdk_set_allowed_backends (NULL);
-  if (!wayland_display)
-    g_warning ("Failed to open Wayland display");
-
-  return wayland_display;
-}
-
-ExternalWindowWayland *
-external_window_wayland_new (const char *handle_str)
-{
-  ExternalWindowWayland *external_window_wayland;
-  GdkDisplay *display;
-
-  display = get_wayland_display ();
-  if (!display)
-    {
-      g_warning ("No Wayland display connection, ignoring Wayland parent");
-      return NULL;
-    }
-
-  external_window_wayland = g_object_new (EXTERNAL_TYPE_WINDOW_WAYLAND,
-                                          "display", display,
-                                          NULL);
-  external_window_wayland->handle_str = g_strdup (handle_str);
-
-  return external_window_wayland;
-}
-
-static void
-external_window_wayland_set_parent_of (ExternalWindow *external_window,
-                                       GdkWindow      *child_window)
-{
-  ExternalWindowWayland *external_window_wayland =
-    EXTERNAL_WINDOW_WAYLAND (external_window);
-  char *handle_str = external_window_wayland->handle_str;
-
-  if (!gdk_wayland_window_set_transient_for_exported (child_window, handle_str))
-    g_warning ("Failed to set portal window transient for external parent");
-}
-
-static void
-external_window_wayland_dispose (GObject *object)
-{
-  ExternalWindowWayland *external_window_wayland =
-    EXTERNAL_WINDOW_WAYLAND (object);
-
-  g_free (external_window_wayland->handle_str);
-
-  G_OBJECT_CLASS (external_window_wayland_parent_class)->dispose (object);
-}
-
-static void
-external_window_wayland_init (ExternalWindowWayland *external_window_wayland)
-{
-}
-
-static void
-external_window_wayland_class_init (ExternalWindowWaylandClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  ExternalWindowClass *external_window_class = EXTERNAL_WINDOW_CLASS (klass);
-
-  object_class->dispose = external_window_wayland_dispose;
-
-  external_window_class->set_parent_of = external_window_wayland_set_parent_of;
-}
diff --git a/src/libsushi/externalwindow-wayland.h b/src/libsushi/externalwindow-wayland.h
deleted file mode 100644
index ede29ef..0000000
--- a/src/libsushi/externalwindow-wayland.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright © 2016 Red Hat, Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- *       Jonas Ådahl <jadahl@redhat.com>
- */
-
-#pragma once
-
-#include <glib-object.h>
-
-#include "externalwindow.h"
-
-#define EXTERNAL_TYPE_WINDOW_WAYLAND (external_window_wayland_get_type ())
-#define EXTERNAL_WINDOW_WAYLAND(object) (G_TYPE_CHECK_INSTANCE_CAST (object, EXTERNAL_TYPE_WINDOW_WAYLAND, ExternalWindowWayland))
-
-typedef struct _ExternalWindowWayland ExternalWindowWayland;
-typedef struct _ExternalWindowWaylandClass ExternalWindowWaylandClass;
-
-GType external_window_wayland_get_type (void);
-ExternalWindowWayland *external_window_wayland_new (const char *handle_str);
diff --git a/src/libsushi/externalwindow-x11.c b/src/libsushi/externalwindow-x11.c
deleted file mode 100644
index 75de557..0000000
--- a/src/libsushi/externalwindow-x11.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright © 2016 Red Hat, Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- *       Jonas Ådahl <jadahl@redhat.com>
- */
-
-#include "config.h"
-
-#include <errno.h>
-#include <gdk/gdkx.h>
-#include <gdk/gdk.h>
-#include <stdlib.h>
-
-#include "externalwindow-x11.h"
-
-
-static GdkDisplay *x11_display;
-
-struct _ExternalWindowX11
-{
-  ExternalWindow parent;
-
-  GdkWindow *foreign_gdk_window;
-};
-
-struct _ExternalWindowX11Class
-{
-  ExternalWindowClass parent_class;
-};
-
-G_DEFINE_TYPE (ExternalWindowX11, external_window_x11,
-               EXTERNAL_TYPE_WINDOW)
-
-static GdkDisplay *
-get_x11_display (void)
-{
-  if (x11_display)
-    return x11_display;
-
-  gdk_set_allowed_backends ("x11");
-  x11_display = gdk_display_open (NULL);
-  gdk_set_allowed_backends (NULL);
-  if (!x11_display)
-    g_warning ("Failed to open X11 display");
-
-  return x11_display;
-}
-
-ExternalWindowX11 *
-external_window_x11_new (const char *handle_str)
-{
-  ExternalWindowX11 *external_window_x11;
-  GdkDisplay *display;
-  int xid;
-  GdkWindow *foreign_gdk_window;
-
-  display = get_x11_display ();
-  if (!display)
-    {
-      g_warning ("No X display connection, ignoring X11 parent");
-      return NULL;
-    }
-
-  errno = 0;
-  xid = strtol (handle_str, NULL, 16);
-  if (errno != 0)
-    {
-      g_warning ("Failed to reference external X11 window, invalid XID %s", handle_str);
-      return NULL;
-    }
-
-  foreign_gdk_window = gdk_x11_window_foreign_new_for_display (display, xid);
-  if (!foreign_gdk_window)
-    {
-      g_warning ("Failed to create foreign window for XID %d", xid);
-      return NULL;
-    }
-
-  external_window_x11 = g_object_new (EXTERNAL_TYPE_WINDOW_X11,
-                                      "display", display,
-                                      NULL);
-  external_window_x11->foreign_gdk_window = foreign_gdk_window;
-
-  return external_window_x11;
-}
-
-static void
-external_window_x11_set_parent_of (ExternalWindow *external_window,
-                                   GdkWindow      *child_window)
-{
-  ExternalWindowX11 *external_window_x11 =
-    EXTERNAL_WINDOW_X11 (external_window);
-
-  gdk_window_set_transient_for (child_window,
-                                external_window_x11->foreign_gdk_window);
-}
-
-static void
-external_window_x11_dispose (GObject *object)
-{
-  ExternalWindowX11 *external_window_x11 = EXTERNAL_WINDOW_X11 (object);
-
-  g_clear_object (&external_window_x11->foreign_gdk_window);
-
-  G_OBJECT_CLASS (external_window_x11_parent_class)->dispose (object);
-}
-
-static void
-external_window_x11_init (ExternalWindowX11 *external_window_x11)
-{
-}
-
-static void
-external_window_x11_class_init (ExternalWindowX11Class *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  ExternalWindowClass *external_window_class = EXTERNAL_WINDOW_CLASS (klass);
-
-  object_class->dispose = external_window_x11_dispose;
-
-  external_window_class->set_parent_of = external_window_x11_set_parent_of;
-}
diff --git a/src/libsushi/externalwindow-x11.h b/src/libsushi/externalwindow-x11.h
deleted file mode 100644
index d380a3d..0000000
--- a/src/libsushi/externalwindow-x11.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright © 2016 Red Hat, Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- *       Jonas Ådahl <jadahl@redhat.com>
- */
-
-#pragma once
-
-#include <glib-object.h>
-
-#include "externalwindow.h"
-
-
-#define EXTERNAL_TYPE_WINDOW_X11 (external_window_x11_get_type ())
-#define EXTERNAL_WINDOW_X11(object) (G_TYPE_CHECK_INSTANCE_CAST (object, EXTERNAL_TYPE_WINDOW_X11, ExternalWindowX11))
-
-typedef struct _ExternalWindowX11 ExternalWindowX11;
-typedef struct _ExternalWindowX11Class ExternalWindowX11Class;
-
-GType external_window_get_type (void);
-ExternalWindowX11 *external_window_x11_new (const char *handle_str);
diff --git a/src/libsushi/externalwindow.c b/src/libsushi/externalwindow.c
index 9b81670..fed5e0b 100644
--- a/src/libsushi/externalwindow.c
+++ b/src/libsushi/externalwindow.c
@@ -16,126 +16,112 @@
  *
  * Authors:
  *       Jonas Ådahl <jadahl@redhat.com>
+ *       Corey Berla <corey@berla.me>
  */
 
-#include "config.h"
+#include "externalwindow.h"
 
 #include <string.h>
 
-#include "externalwindow.h"
-#ifdef HAVE_GTK_X11
-#include "externalwindow-x11.h"
+#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/wayland/gdkwayland.h>
 #endif
-#ifdef HAVE_GTK_WAYLAND
-#include "externalwindow-wayland.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
 #endif
 
-enum
+struct _ExternalWindow
 {
-  PROP_0,
+  GObject parent_instance;
 
-  PROP_DISPLAY,
-};
-
-typedef struct _ExternalWindowPrivate
-{
   GdkDisplay *display;
-} ExternalWindowPrivate;
+  char *wayland_str;
+  Window xid;
+};
 
-G_DEFINE_TYPE_WITH_PRIVATE (ExternalWindow, external_window, G_TYPE_OBJECT)
+G_DEFINE_FINAL_TYPE (ExternalWindow, external_window, G_TYPE_OBJECT)
 
 ExternalWindow *
 create_external_window_from_handle (const char *handle_str)
 {
-#ifdef HAVE_GTK_X11
+  ExternalWindow *self = g_object_new (EXTERNAL_TYPE_WINDOW, NULL);
+  const char x11_prefix[] = "x11:";
+  const char wayland_prefix[] = "wayland:";
+
+  if (g_str_has_prefix (handle_str, x11_prefix))
     {
-      const char x11_prefix[] = "x11:";
-      if (g_str_has_prefix (handle_str, x11_prefix))
-        {
-          ExternalWindowX11 *external_window_x11;
-          const char *x11_handle_str = handle_str + strlen (x11_prefix);
+      const char *x11_handle_str = handle_str + strlen (x11_prefix);
 
-          external_window_x11 = external_window_x11_new (x11_handle_str);
-          return EXTERNAL_WINDOW (external_window_x11);
-        }
+      self->xid = strtol (x11_handle_str, NULL, 16);
+      return self;
     }
-#endif
-#ifdef HAVE_GTK_WAYLAND
+  else if (g_str_has_prefix (handle_str, wayland_prefix))
     {
-      const char wayland_prefix[] = "wayland:";
-      if (g_str_has_prefix (handle_str, wayland_prefix))
-        {
-          ExternalWindowWayland *external_window_wayland;
-          const char *wayland_handle_str = handle_str + strlen (wayland_prefix);
+      const char *wayland_handle_str = handle_str + strlen (wayland_prefix);
 
-          external_window_wayland =
-            external_window_wayland_new (wayland_handle_str);
-          return EXTERNAL_WINDOW (external_window_wayland);
-        }
+      self->wayland_str = g_strdup (wayland_handle_str);
+      return self;
     }
-#endif
 
   g_warning ("Unhandled parent window type %s\n", handle_str);
   return NULL;
 }
 
 void
-external_window_set_parent_of (ExternalWindow *external_window,
-                               GdkWindow      *child_window)
+external_window_set_parent_of (ExternalWindow *self,
+                               GtkWindow      *child_window)
 {
-  EXTERNAL_WINDOW_GET_CLASS (external_window)->set_parent_of (external_window,
-                                                              child_window);
-}
+    GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE (child_window));
 
-GdkDisplay *
-external_window_get_display (ExternalWindow *external_window)
-{
-  ExternalWindowPrivate *priv =
-    external_window_get_instance_private (external_window);
+#ifdef GDK_WINDOWING_WAYLAND
+    if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (child_window))))
+    {
+        if (self->xid != 0)
+        {
+            g_warning ("Wayland / x11 mismatch");
+            return;
+        }
 
-  return priv->display;
-}
+        if (!gdk_wayland_toplevel_set_transient_for_exported (GDK_TOPLEVEL (surface),
+                                                              self->wayland_str))
+          g_warning ("Failed to set portal window transient for external parent");
 
-static void
-external_window_set_property (GObject      *object,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
-{
-  ExternalWindow *external_window = EXTERNAL_WINDOW (object);
-  ExternalWindowPrivate *priv =
-    external_window_get_instance_private (external_window);
+        return;
+    }
+#endif
 
-  switch (prop_id)
-    {
-    case PROP_DISPLAY:
-      g_set_object (&priv->display, g_value_get_object (value));
-      break;
+#ifdef GDK_WINDOWING_X11
+    if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (child_window))))
+      {
+        GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (child_window));
+        Display *dpy = gdk_x11_display_get_xdisplay (display);
+        Window parent_xid = gdk_x11_surface_get_xid (surface);
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
+        if (self->wayland_str != NULL)
+        {
+            g_warning ("Wayland / x11 mismatch");
+            return;
+        }
+
+        if (!XSetTransientForHint (dpy, parent_xid, self->xid))
+          g_warning ("Failed to set portal window transient for external parent");
+
+        return;
+      }
+#endif
 }
 
 static void
-external_window_get_property (GObject    *object,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
+external_window_dispose (GObject *object)
 {
-  ExternalWindow *external_window = EXTERNAL_WINDOW (object);
-  ExternalWindowPrivate *priv =
-    external_window_get_instance_private (external_window);
+    ExternalWindow *self = EXTERNAL_WINDOW (object);
 
-  switch (prop_id)
-    {
-    case PROP_DISPLAY:
-      g_value_set_object (value, priv->display);
-      break;
+    g_clear_pointer (&self->wayland_str, g_free);
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
+    G_OBJECT_CLASS (external_window_parent_class)->dispose (object);
 }
 
 static void
@@ -146,18 +132,7 @@ external_window_init (ExternalWindow *external_window)
 static void
 external_window_class_init (ExternalWindowClass *klass)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->get_property = external_window_get_property;
-  object_class->set_property = external_window_set_property;
-
-  g_object_class_install_property (object_class,
-                                   PROP_DISPLAY,
-                                   g_param_spec_object ("display",
-                                                        "GdkDisplay",
-                                                        "The GdkDisplay instance",
-                                                        GDK_TYPE_DISPLAY,
-                                                        G_PARAM_READWRITE |
-                                                        G_PARAM_CONSTRUCT_ONLY |
-                                                        G_PARAM_STATIC_STRINGS));
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    object_class->dispose = external_window_dispose;
 }
diff --git a/src/libsushi/externalwindow.h b/src/libsushi/externalwindow.h
index 8282fc2..f955e3e 100644
--- a/src/libsushi/externalwindow.h
+++ b/src/libsushi/externalwindow.h
@@ -25,30 +25,9 @@
 
 
 #define EXTERNAL_TYPE_WINDOW (external_window_get_type ())
-#define EXTERNAL_WINDOW(object) (G_TYPE_CHECK_INSTANCE_CAST (object, EXTERNAL_TYPE_WINDOW, ExternalWindow))
-#define EXTERNAL_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST (klass, EXTERNAL_TYPE_WINDOW, ExternalWindowClass))
-#define EXTERNAL_WINDOW_GET_CLASS(klass) (G_TYPE_INSTANCE_GET_CLASS (klass, EXTERNAL_TYPE_WINDOW, ExternalWindowClass))
+G_DECLARE_FINAL_TYPE (ExternalWindow, external_window, EXTERNAL, WINDOW, GObject)
 
-typedef struct _ExternalWindow ExternalWindow;
-typedef struct _ExternalWindowClass ExternalWindowClass;
-
-struct _ExternalWindow
-{
-  GObject parent_instance;
-};
-
-struct _ExternalWindowClass
-{
-  GObjectClass parent_class;
-
-  void (*set_parent_of) (ExternalWindow *external_window,
-                         GdkWindow      *child_window);
-};
-
-GType external_window_get_type (void);
 ExternalWindow *create_external_window_from_handle (const char *handle_str);
 
 void external_window_set_parent_of (ExternalWindow *external_window,
-                                    GdkWindow      *child_window);
-
-GdkDisplay *external_window_get_display (ExternalWindow *external_window);
+                                    GtkWindow      *child_window);
diff --git a/src/libsushi/meson.build b/src/libsushi/meson.build
index 8740954..142f3f6 100644
--- a/src/libsushi/meson.build
+++ b/src/libsushi/meson.build
@@ -1,44 +1,20 @@
-config_data = configuration_data()
-if gtk_x11_dep.found()
-  config_data.set('HAVE_GTK_X11', 1)
-endif
-if gtk_wayland_dep.found()
-  config_data.set('HAVE_GTK_WAYLAND', 1)
-endif
-configure_file(output: 'config.h', configuration: config_data)
-
 externalwindow_sources = [
   'externalwindow.c',
   'externalwindow.h',
 ]
 
-if gtk_x11_dep.found()
-  externalwindow_sources += [
-    'externalwindow-x11.c',
-    'externalwindow-x11.h',
-  ]
-endif
-
-if gtk_wayland_dep.found()
-  externalwindow_sources += [
-    'externalwindow-wayland.c',
-    'externalwindow-wayland.h',
-  ]
-endif
-
 libsushi_deps = [
-  epoxy_dep,
-  evince_document_dep,
-  evince_view_dep,
+  papers_document_dep,
+  papers_view_dep,
   freetype_dep,
   gdk_pixbuf_dep,
   glib_dep,
   gstreamer_dep,
-  gstreamer_audio_dep,
+  gst_pbutils_dep,
   gstreamer_tag_dep,
-  gstreamer_video_dep,
   gtk_dep,
   harfbuzz_dep,
+  x11_dep,
 ]
 
 libsushi_sources = [
@@ -46,25 +22,16 @@ libsushi_sources = [
   'sushi-font-loader.h',
   'sushi-font-widget.c',
   'sushi-font-widget.h',
-  'sushi-media-bin.c',
-  'sushi-media-bin.h',
   'sushi-utils.c',
   'sushi-utils.h',
 ]
 
-libsushi_resource = gnome.compile_resources(
-  'sushi-lib-resources',
-  'org.gnome.Libsushi.gresource.xml',
-  c_name: 'sushi'
-)
-
 libsushi = shared_library(
   'sushi-1.0',
   dependencies: libsushi_deps,
   sources: [
     externalwindow_sources,
     libsushi_sources,
-    libsushi_resource
   ],
   install: true,
   install_dir: pkglibdir
@@ -82,9 +49,9 @@ gnome.generate_gir(
   includes: [
     'GstTag-1.0',
     'GdkPixbuf-2.0',
-    'Gtk-3.0',
-    'EvinceDocument-3.0',
-    'EvinceView-3.0',
+    'Gtk-4.0',
+    'PapersDocument-4.0',
+    'PapersView-4.0',
   ],
   install: true,
   install_dir_gir: join_paths(pkgdatadir, 'gir-1.0'),
diff --git a/src/libsushi/org.gnome.Libsushi.gresource.xml b/src/libsushi/org.gnome.Libsushi.gresource.xml
deleted file mode 100644
index ddbb1c1..0000000
--- a/src/libsushi/org.gnome.Libsushi.gresource.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<gresources>
-  <gresource prefix="/org/gnome/Sushi/libsushi">
-    <file>SushiMediaBin.ui</file>
-    <file>sushi-media-bin.css</file>
-  </gresource>
-</gresources>
diff --git a/src/libsushi/sushi-font-widget.c b/src/libsushi/sushi-font-widget.c
index ac1fcd4..19c589f 100644
--- a/src/libsushi/sushi-font-widget.c
+++ b/src/libsushi/sushi-font-widget.c
@@ -454,7 +454,6 @@ sushi_font_widget_size_request (GtkWidget *drawing_area,
   cairo_surface_t *surface;
   FT_Face face = self->face;
   GtkStyleContext *context;
-  GtkStateFlags state;
   GtkBorder padding;
 
   if (face == NULL) {
@@ -475,8 +474,7 @@ sushi_font_widget_size_request (GtkWidget *drawing_area,
                                         SURFACE_SIZE, SURFACE_SIZE);
   cr = cairo_create (surface);
   context = gtk_widget_get_style_context (drawing_area);
-  state = gtk_style_context_get_state (context);
-  gtk_style_context_get_padding (context, state, &padding);
+  gtk_style_context_get_padding (context, &padding);
 
   sizes = build_sizes_table (face, &n_sizes, &alpha_size, &title_size);
 
@@ -558,29 +556,29 @@ sushi_font_widget_size_request (GtkWidget *drawing_area,
 }
 
 static void
-sushi_font_widget_get_preferred_width (GtkWidget *drawing_area,
-                                       gint *minimum_width,
-                                       gint *natural_width)
+sushi_font_widget_measure (GtkWidget      *drawing_area,
+                           GtkOrientation  orientation,
+                           int             for_size,
+                           int            *min,
+                           int            *nat,
+                           int            *min_baseline,
+                           int            *nat_baseline)
 {
   gint width;
-
-  sushi_font_widget_size_request (drawing_area, &width, NULL, NULL);
-
-  *minimum_width = 0;
-  *natural_width = width;
-}
-
-static void
-sushi_font_widget_get_preferred_height (GtkWidget *drawing_area,
-                                        gint *minimum_height,
-                                        gint *natural_height)
-{
-  gint height, min_height;
-
-  sushi_font_widget_size_request (drawing_area, NULL, &height, &min_height);
-
-  *minimum_height = min_height;
-  *natural_height = height;
+  gint height;
+  gint min_height;
+
+  sushi_font_widget_size_request (drawing_area, &width, &height, &min_height);
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+  {
+    *min = 0;
+    *nat = width;
+  }
+  else
+  {
+    *min = min_height;
+    *nat = height;
+  }
 }
 
 static gboolean
@@ -595,23 +593,21 @@ sushi_font_widget_draw (GtkWidget *drawing_area,
   GtkStyleContext *context;
   GdkRGBA color;
   GtkBorder padding;
-  GtkStateFlags state;
   gint allocated_width, allocated_height;
 
   if (face == NULL)
     return FALSE;
 
   context = gtk_widget_get_style_context (drawing_area);
-  state = gtk_style_context_get_state (context);
 
-  allocated_width = gtk_widget_get_allocated_width (drawing_area);
-  allocated_height = gtk_widget_get_allocated_height (drawing_area);
+  allocated_width = gtk_widget_get_width (drawing_area);
+  allocated_height = gtk_widget_get_height (drawing_area);
 
   gtk_render_background (context, cr,
                          0, 0, allocated_width, allocated_height);
 
-  gtk_style_context_get_color (context, state, &color);
-  gtk_style_context_get_padding (context, state, &padding);
+  gtk_style_context_get_color (context, &color);
+  gtk_style_context_get_padding (context, &padding);
 
   gdk_cairo_set_source_rgba (cr, &color);
 
@@ -666,6 +662,18 @@ sushi_font_widget_draw (GtkWidget *drawing_area,
   return FALSE;
 }
 
+static void
+sushi_font_widget_snapshot (GtkWidget   *widget,
+                            GtkSnapshot *snapshot)
+{
+  int width = gtk_widget_get_width (widget);
+  int height = gtk_widget_get_height (widget);
+  cairo_t *cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, width, height));
+  sushi_font_widget_draw (widget, cr);
+
+  cairo_destroy (cr);
+}
+
 static void
 font_face_async_ready_cb (GObject *object,
                           GAsyncResult *result,
@@ -711,7 +719,7 @@ sushi_font_widget_init (SushiFontWidget *self)
     g_error ("Unable to initialize FreeType");
 
   gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)),
-                               GTK_STYLE_CLASS_VIEW);
+                               "view");
 }
 
 static void
@@ -801,9 +809,8 @@ sushi_font_widget_class_init (SushiFontWidgetClass *klass)
   oclass->get_property = sushi_font_widget_get_property;
   oclass->constructed = sushi_font_widget_constructed;
 
-  wclass->draw = sushi_font_widget_draw;
-  wclass->get_preferred_width = sushi_font_widget_get_preferred_width;
-  wclass->get_preferred_height = sushi_font_widget_get_preferred_height;
+  wclass->measure = sushi_font_widget_measure;
+  wclass->snapshot = sushi_font_widget_snapshot;
 
   properties[PROP_URI] =
     g_param_spec_string ("uri",
diff --git a/src/libsushi/sushi-media-bin.c b/src/libsushi/sushi-media-bin.c
deleted file mode 100644
index 4a4d015..0000000
--- a/src/libsushi/sushi-media-bin.c
+++ /dev/null
@@ -1,2286 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
-/*
- * sushi-media-bin.c
- * Based on ekn-media-bin.c from:
- * https://github.com/endlessm/eos-knowledge-lib/tree/master/lib/eosknowledgeprivate
- *
- * Copyright (C) 2016 Endless Mobile, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- *
- * Author: Juan Pablo Ugarte <ugarte@endlessm.com>
- *
- */
-
-#include "sushi-media-bin.h"
-#include <gst/gst.h>
-#include <gst/video/gstvideosink.h>
-#include <gst/audio/gstaudiobasesink.h>
-#include <epoxy/gl.h>
-
-#ifdef DEBUG
-
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/syscall.h>
-#define WARN_IF_NOT_MAIN_THREAD if (getpid () != syscall (SYS_gettid)) g_warning ("%s %d not in main thread", __func__, __LINE__);
-
-#endif
-
-#define AUTOHIDE_TIMEOUT_DEFAULT 2  /* Controls autohide timeout in seconds */
-
-#define INFO_N_COLUMNS           6  /* Number of info columns labels */
-
-#define FPS_WINDOW_SIZE          2  /* Window size in seconds to calculate fps */
-
-#define GET_STATE_TIMEOUT        GST_SECOND / G_GINT64_CONSTANT (10)
-
-#define SMB_ICON_SIZE            GTK_ICON_SIZE_BUTTON
-
-#define SMB_ICON_NAME_PLAY       "media-playback-start-symbolic"
-#define SMB_ICON_NAME_PAUSE      "media-playback-pause-symbolic"
-#define SMB_ICON_NAME_FULLSCREEN "view-fullscreen-symbolic"
-#define SMB_ICON_NAME_RESTORE    "view-restore-symbolic"
-
-#define SMB_INITIAL_STATE        GST_STATE_PAUSED
-
-GST_DEBUG_CATEGORY_STATIC (sushi_media_bin_debug);
-#define GST_CAT_DEFAULT sushi_media_bin_debug
-
-struct _SushiMediaBin
-{
-  GtkBox parent;
-};
-
-typedef struct
-{
-  /* Properties */
-  gchar   *uri;
-  gint     autohide_timeout;
-  gchar   *title;
-  gchar   *description;
-
-  /* Boolean properties */
-  gboolean fullscreen:1;
-  gboolean show_stream_info:1;
-  gboolean audio_mode:1;
-
-  /* We place extra flags here so the get squashed with the boolean properties */
-  gboolean title_user_set:1;            /* True if the user set title property */
-  gboolean description_user_set:1;      /* True if the user set description property */
-  gboolean dump_dot_file:1;             /* True if GST_DEBUG_DUMP_DOT_DIR is set */
-  gboolean ignore_adjustment_changes:1;
-
-  /* Internal Widgets */
-  GtkStack      *stack;
-  GtkImage      *playback_image;
-  GtkImage      *fullscreen_image;
-  GtkAdjustment *playback_adjustment;
-  GtkAdjustment *volume_adjustment;
-
-  /* Internal Video Widgets */
-  GtkWidget      *overlay;
-  GtkWidget      *play_box;
-  GtkScaleButton *volume_button;
-  GtkWidget      *info_box;
-
-  GtkLabel *title_label;
-  GtkLabel *info_column_label[INFO_N_COLUMNS];
-  GtkLabel *duration_label;
-  GtkLabel *progress_duration_label;
-  GtkLabel *progress_position_label;
-
-  /* Thanks to GSK all the blitting will be done in GL */
-  GtkRevealer *top_revealer;
-  GtkRevealer *bottom_revealer;
-
-  /* Internal Audio Widgets */
-  GtkWidget      *audio_box;
-  GtkScaleButton *audio_volume_button;
-  GtkLabel       *audio_duration_label;
-  GtkLabel       *audio_position_label;
-  GtkImage       *audio_playback_image;
-
-  /* Support Objects */
-  GtkWidget *video_widget;      /* Created at runtime from sink */
-  GtkWindow *fullscreen_window;
-  GdkCursor *blank_cursor;
-  GtkWidget *tmp_image;      /* FIXME: remove this once we can derive from GtkBin in Glade */
-
-  /* Internal variables */
-  guint timeout_id;          /* Autohide timeout source id */
-  gint  timeout_count;       /* Autohide timeout count since last move event */
-
-  guint  tick_id;           /* Widget frame clock tick callback (used to update UI) */
-  gint64 tick_start;
-  gint64 frames_window_start;
-  guint  frames_window;     /* Frames "rendered" in the last FPS_WINDOW_SIZE seconds window */
-  guint  frames_rendered;   /* Total frames "rendered" */
-  GdkEventType pressed_button_type;
-
-  gint video_width;
-  gint video_height;
-
-  /* Gst support */
-  GstElement *play;          /* playbin element */
-  GstElement *video_sink;    /* The video sink element used (glsinkbin or gtksink) */
-  GstElement *vis_plugin;    /* The visualization plugin */
-  GstBus     *bus;           /* playbin bus */
-  GstBuffer  *last_buffer;
-
-  GstTagList *audio_tags;
-  GstTagList *video_tags;
-  GstTagList *text_tags;
-
-  GstQuery *position_query;  /* Used to query position more quicker */
-
-  GstState state;            /* The desired state of the pipeline */
-  gint64   duration;         /* Stream duration */
-  guint    position;         /* Stream position in seconds */
-} SushiMediaBinPrivate;
-
-enum
-{
-  PROP_0,
-
-  PROP_URI,
-  PROP_VOLUME,
-  PROP_AUTOHIDE_TIMEOUT,
-  PROP_FULLSCREEN,
-  PROP_SHOW_STREAM_INFO,
-  PROP_AUDIO_MODE,
-  PROP_TITLE,
-  PROP_DESCRIPTION,
-  N_PROPERTIES
-};
-
-enum
-{
-  ERROR,
-  SIZE_CHANGE,
-  TAGS_CHANGE,
-  LAST_SIGNAL
-};
-
-static GParamSpec *properties[N_PROPERTIES];
-static guint sushi_media_bin_signals[LAST_SIGNAL] = { 0 };
-
-G_DEFINE_TYPE_WITH_PRIVATE (SushiMediaBin, sushi_media_bin, GTK_TYPE_BOX);
-
-#define SMB_PRIVATE(d) ((SushiMediaBinPrivate *) sushi_media_bin_get_instance_private(d))
-
-static void         sushi_media_bin_init_playbin (SushiMediaBin *self);
-static void         sushi_media_bin_set_tick_enabled (SushiMediaBin *self,
-                                                      gboolean enabled);
-static GtkWindow   *sushi_media_bin_window_new (SushiMediaBin *self);
-static const gchar *format_time (gint time);
-
-static inline gint64
-sushi_media_bin_get_position (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint64 position;
-
-  if (!priv->play || !gst_element_query (priv->play, priv->position_query))
-    return 0;
-
-  gst_query_parse_position (priv->position_query, NULL, &position);
-
-  return position;
-}
-
-static GstStateChangeReturn
-sushi_media_bin_set_state (SushiMediaBin *self, GstState state)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  priv->state = state;
-  return gst_element_set_state (priv->play, state);
-}
-
-/* Action handlers */
-static void
-sushi_media_bin_toggle_playback (SushiMediaBin *self)
-{
-  if (SMB_PRIVATE (self)->state == GST_STATE_PLAYING)
-    sushi_media_bin_pause (self);
-  else
-    sushi_media_bin_play (self);
-}
-
-static void
-sushi_media_bin_toggle_fullscreen (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  /* Do nothing in audio mode */
-  if (priv->audio_mode)
-    return;
-
-  sushi_media_bin_set_fullscreen (self, !priv->fullscreen);
-}
-
-static void
-sushi_media_bin_reveal_controls (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  gdk_window_set_cursor (gtk_widget_get_window (priv->overlay), NULL);
-
-  /* We only show the top bar if there is something in the info labels */
-  if (!g_str_equal (gtk_label_get_label (priv->title_label), "") ||
-      !g_str_equal (gtk_label_get_label (priv->info_column_label[0]), "") ||
-      !g_str_equal (gtk_label_get_label (priv->info_column_label[2]), "") ||
-      !g_str_equal (gtk_label_get_label (priv->info_column_label[4]), ""))
-    gtk_revealer_set_reveal_child (priv->top_revealer, TRUE);
-
-  gtk_revealer_set_reveal_child (priv->bottom_revealer, TRUE);
-}
-
-static gboolean
-revealer_timeout (gpointer data)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (data);
-  GdkWindow *window;
-
-  if (++priv->timeout_count < priv->autohide_timeout)
-    return G_SOURCE_CONTINUE;
-  
-  window = gtk_widget_get_window (priv->overlay);
-  
-  if (window != NULL)
-    gdk_window_set_cursor (window, priv->blank_cursor);
-
-  gtk_revealer_set_reveal_child (priv->top_revealer, FALSE);
-  gtk_revealer_set_reveal_child (priv->bottom_revealer, FALSE);
-
-  priv->timeout_id = 0;
-
-  return G_SOURCE_REMOVE;
-}
-
-static inline void
-ensure_no_timeout(SushiMediaBinPrivate *priv)
-{
-  if (!priv->timeout_id)
-    return;
-
-  g_source_remove (priv->timeout_id);
-  priv->timeout_id = 0;
-}
-
-static void
-sushi_media_bin_revealer_timeout (SushiMediaBin *self, gboolean activate)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  if (activate)
-    {
-      /* Reset counter */
-      priv->timeout_count = 0;
-
-      if (!priv->timeout_id)
-        priv->timeout_id = g_timeout_add_seconds (1, revealer_timeout, self);
-    }
-  else
-   {
-      GdkWindow *window = gtk_widget_get_window (priv->overlay);
-
-      ensure_no_timeout (priv);
-
-      if (window)
-        gdk_window_set_cursor (window, NULL);
-   }
-}
-
-static void
-sushi_media_bin_action_toggle (SushiMediaBin *self, const gchar *action)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  g_return_if_fail (action != NULL);
-
-  if (g_str_equal (action, "playback"))
-    sushi_media_bin_toggle_playback (self);
-  else if (g_str_equal (action, "fullscreen"))
-    sushi_media_bin_toggle_fullscreen (self);
-  else if (g_str_equal (action, "show-stream-info"))
-    {
-      sushi_media_bin_set_show_stream_info (self, !priv->show_stream_info);
-      sushi_media_bin_reveal_controls (self);
-    }
-  else
-    g_warning ("Ignoring unknown toggle action %s", action);
-}
-
-static void
-sushi_media_bin_action_seek (SushiMediaBin *self, gint seconds)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint64 position = sushi_media_bin_get_position (self) + (seconds * GST_SECOND);
-
-  gst_element_seek_simple (priv->play, GST_FORMAT_TIME,
-                           GST_SEEK_FLAG_FLUSH |
-                           GST_SEEK_FLAG_ACCURATE,
-                           seconds ? CLAMP (position, 0, priv->duration) : 0);
-}
-
-/* Signals handlers */
-static gboolean
-on_overlay_button_press_event (GtkWidget   *widget,
-                               GdkEvent    *event,
-                               SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  if (event->button.button != GDK_BUTTON_PRIMARY)
-    return FALSE;
-
-  priv->pressed_button_type = event->type;
-  return TRUE;
-}
-
-static gboolean
-on_overlay_button_release_event (GtkWidget   *widget,
-                                 GdkEvent    *event,
-                                 SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  if (event->button.button != GDK_BUTTON_PRIMARY)
-    return FALSE;
-
-  if (priv->pressed_button_type == GDK_BUTTON_PRESS)
-    {
-      sushi_media_bin_toggle_playback (self);
-    }
-  else if (priv->pressed_button_type == GDK_2BUTTON_PRESS)
-    {
-      sushi_media_bin_toggle_fullscreen (self);
-      sushi_media_bin_toggle_playback (self);
-    }
-
-  /* Reset state, since some widgets like GtkButton do not consume
-   * the last button press release event
-   */
-  priv->pressed_button_type = GDK_NOTHING;
-
-  return TRUE;
-}
-
-static gboolean
-on_revealer_leave_notify_event (GtkWidget   *widget,
-                                GdkEvent    *event,
-                                SushiMediaBin *self)
-{
-  sushi_media_bin_revealer_timeout (self, TRUE);
-  return FALSE;
-}
-
-static gboolean
-on_revealer_motion_notify_event (GtkWidget   *widget,
-                                 GdkEvent    *event,
-                                 SushiMediaBin *self)
-{
-  /* Do not hide controls and restore pointer */
-  sushi_media_bin_revealer_timeout (self, FALSE);
-
-  return TRUE;
-}
-
-static gboolean
-on_overlay_motion_notify_event (GtkWidget   *widget,
-                                GdkEvent    *event,
-                                SushiMediaBin *self)
-{
-  sushi_media_bin_reveal_controls (self);
-  sushi_media_bin_revealer_timeout (self, TRUE);
-  return FALSE;
-}
-
-static void
-on_playback_adjustment_value_changed (GtkAdjustment *adjustment,
-                                      SushiMediaBin   *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  if (priv->ignore_adjustment_changes)
-    return;
-
-  priv->position = gtk_adjustment_get_value (adjustment);
-
-  gst_element_seek_simple (priv->play,
-                           GST_FORMAT_TIME,
-                           GST_SEEK_FLAG_ACCURATE |
-                           GST_SEEK_FLAG_FLUSH,
-                           priv->position * GST_SECOND);
-}
-
-static gchar *
-on_progress_scale_format_value (GtkScale    *scale,
-                                gdouble      value,
-                                SushiMediaBin *self)
-{
-  /* FIXME: CSS padding does not work as expected, add some padding here */
-  return g_strdup_printf ("  %s  ", format_time (value));
-}
-
-static void
-on_volume_popup_show (GtkWidget *popup, SushiMediaBin *self)
-{
-  /* Do not hide controls */
-  sushi_media_bin_revealer_timeout (self, FALSE);
-}
-
-static void
-on_volume_popup_hide (GtkWidget *popup, SushiMediaBin *self)
-{
-  sushi_media_bin_revealer_timeout (self, TRUE);
-}
-
-static inline void
-sushi_media_bin_update_state (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  if (priv->uri && priv->video_sink)
-    {
-      g_object_set (priv->play, "uri", priv->uri, NULL);
-      gst_element_set_state (priv->play, priv->state);
-    }
-}
-
-static GdkPixbuf *
-sushi_media_bin_video_pixbuf_new (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint width, height, video_width, video_height, dx, dy;
-  cairo_surface_t *surface;
-  gdouble scale = 1.0;
-  GdkPixbuf *pixbuf;
-  cairo_t *cr;
-
-  width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
-  height = gtk_widget_get_allocated_height (GTK_WIDGET (self));
-
-  video_width = gtk_widget_get_allocated_width (priv->video_widget);
-  video_height = gtk_widget_get_allocated_height (priv->video_widget);
-
-  if ((width != video_width || height != video_height) &&
-      priv->video_width && priv->video_height)
-    {
-      scale = MIN (width/(gdouble)priv->video_width, height/(gdouble)priv->video_height);
-
-      dx = ABS (video_width - priv->video_width) * scale;
-      dy = ABS (video_height - priv->video_height) * scale;
-      width = video_width * scale;
-      height = video_height * scale;
-    }
-  else
-    dx = dy = 0;
-
-  surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
-  cr = cairo_create (surface);
-
-  if (scale != 1.0)
-    cairo_scale (cr, scale, scale);
-
-  gtk_widget_draw (priv->video_widget, cr);
-
-  pixbuf = gdk_pixbuf_get_from_surface (surface, dx/2, dy/2, width-dx, height-dy);
-
-  cairo_destroy (cr);
-  cairo_surface_destroy (surface);
-
-  return pixbuf;
-}
-
-static inline gboolean
-sushi_media_bin_gl_check (GtkWidget *widget)
-{
-  static gsize gl_works = 0;
-
-  if (g_once_init_enter (&gl_works))
-    {
-      GError *error = NULL;
-      gsize works = 1;
-      GdkGLContext *context;
-      GdkWindow *window;
-
-      if ((window  = gtk_widget_get_window (widget)) &&
-           (context = gdk_window_create_gl_context (window, &error)))
-        {
-          const gchar *vendor, *renderer;
-
-          gdk_gl_context_make_current (context);
-
-          vendor   = (const gchar *) glGetString (GL_VENDOR);
-          renderer = (const gchar *) glGetString (GL_RENDERER);
-
-          GST_INFO ("GL Vendor: %s, renderer: %s", vendor, renderer);
-
-          if (g_str_equal (vendor, "nouveau"))
-            GST_WARNING ("nouveau is blacklisted, since sharing gl contexts in "
-                         "multiple threads is not supported "
-                         "and will eventually make it crash.");
-          else if (g_strstr_len (renderer, -1, "Gallium") &&
-                   g_strstr_len (renderer, -1, "llvmpipe"))
-            GST_INFO ("Detected software GL rasterizer, falling back to gtksink");
-          else
-            works = 2;
-
-          gdk_gl_context_clear_current ();
-        }
-
-        if (error)
-          {
-            GST_WARNING ("Could not window to create GL context, %s", error->message);
-            g_error_free (error);
-          }
-
-      g_once_init_leave (&gl_works, works);
-    }
-
-  return (gl_works > 1);
-}
-
-static inline void
-sushi_media_bin_init_video_sink (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  GtkWidget *video_widget = NULL;
-  GstElement *video_sink = NULL;
-
-  if (priv->video_sink)
-    return;
-
-  if (priv->audio_mode)
-    {
-      video_sink = gst_element_factory_make ("fakesink", "SushiMediaBinNullSink");
-      g_object_set (video_sink, "sync", TRUE, NULL);
-      g_object_set (priv->play, "video-sink", video_sink, NULL);
-      priv->video_sink = gst_object_ref_sink (video_sink);
-      return;
-    }
-
-  if (sushi_media_bin_gl_check (GTK_WIDGET (self)))
-    {
-      video_sink = gst_element_factory_make ("glsinkbin", "SushiMediaBinGLVideoSink");
-
-      if (video_sink)
-        {
-          GstElement *gtkglsink = gst_element_factory_make ("gtkglsink", NULL);
-
-          if (gtkglsink)
-            {
-              GST_INFO ("Using gtkglsink");
-              g_object_set (video_sink, "sink", gtkglsink, NULL);
-              g_object_get (gtkglsink, "widget", &video_widget, NULL);
-            }
-          else
-            {
-              GST_WARNING ("Could not create gtkglsink");
-              gst_object_replace ((GstObject**)&video_sink, NULL);
-            }
-        }
-      else
-        {
-          GST_WARNING ("Could not create glsinkbin");
-        }
-    }
-
-  /* Fallback to gtksink */
-  if (!video_sink)
-    {
-      GST_INFO ("Falling back to gtksink");
-      video_sink = gst_element_factory_make ("gtksink", NULL);
-      g_object_get (video_sink, "widget", &video_widget, NULL);
-    }
-
-  /* We use a null sink as a last resort */
-  if (video_sink && video_widget)
-    {
-      g_object_set (video_widget, "expand", TRUE, NULL);
-
-      /* And pack it where we want the video to show up */
-      gtk_container_add (GTK_CONTAINER (priv->overlay), video_widget);
-      gtk_widget_show (video_widget);
-
-      /* g_object_get() returns a new reference */
-      priv->video_widget = video_widget;
-    }
-  else
-    {
-      GtkWidget *img = gtk_image_new_from_icon_name ("image-missing",
-                                                     GTK_ICON_SIZE_DIALOG);
-
-      GST_WARNING ("Could not get video widget from gtkglsink/gtksink, falling back to fakesink");
-
-      g_object_unref (video_widget);
-      gst_object_unref (video_sink);
-      video_sink = gst_element_factory_make ("fakesink", "SushiMediaBinFakeSink");
-      g_object_set (video_sink, "sync", TRUE, NULL);
-
-      gtk_container_add (GTK_CONTAINER (priv->overlay), img);
-      gtk_widget_show (img);
-
-      /* FIXME: the overlay does not get motion and press events with this code path */
-    }
-
-  /* Setup playbin video sink */
-  if (video_sink)
-    {
-      g_object_set (priv->play, "video-sink", video_sink, NULL);
-      priv->video_sink = gst_object_ref_sink (video_sink);
-    }
-}
-
-static inline void
-sushi_media_bin_deinit_video_sink (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  /* Stop Playback to give gst a chance to cleanup its mess */
-  if (priv->play)
-    gst_element_set_state (priv->play, GST_STATE_NULL);
-
-  /* Stop bus watch */
-  if (priv->bus)
-    {
-      gst_bus_set_flushing (priv->bus, TRUE);
-      gst_bus_remove_watch (priv->bus);
-      gst_object_replace ((GstObject**)&priv->bus, NULL);
-    }
-
-  /* Unref video sink */
-  gst_object_replace ((GstObject**)&priv->video_sink, NULL);
-
-  /* Destroy video widget */
-  g_clear_pointer (&priv->video_widget, gtk_widget_destroy);
-
-  /* Unref playbin */
-  gst_object_replace ((GstObject**)&priv->play, NULL);
-}
-
-static void
-sushi_media_bin_fullscreen_apply (SushiMediaBin *self, gboolean fullscreen)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint64 position = -1;
-
-  if ((fullscreen && priv->fullscreen_window) ||
-      (!fullscreen && !priv->fullscreen_window))
-    return;
-
-  /*
-   * To avoid flickering, this will make the widget pack an image with the last
-   * frame in the container before reparenting the video widget in the
-   * fullscreen window
-   */
-  if (!priv->tmp_image)
-    {
-      GdkPixbuf *pixbuf = sushi_media_bin_video_pixbuf_new (self);
-      priv->tmp_image = gtk_image_new_from_pixbuf (pixbuf);
-      g_object_set (priv->tmp_image, "expand", TRUE, NULL);
-      g_object_unref (pixbuf);
-    }
-
-  /*
-   * FIXME: GtkGstGLWidget does not support reparenting to a different toplevel
-   * because the gl context is different and the pipeline does not know it
-   * changes, so as a temporary workaround we simply reconstruct the whole
-   * pipeline.
-   *
-   * See bug https://bugzilla.gnome.org/show_bug.cgi?id=775045
-   */
-  if ((priv->state == GST_STATE_PAUSED || priv->state == GST_STATE_PLAYING) &&
-      g_strcmp0 (G_OBJECT_TYPE_NAME (priv->video_sink), "GstGLSinkBin") == 0)
-    {
-      /* NOTE: here we could set tmp_image to the content of the current sample
-       * but it wont be updated until the main window is show at which point
-       * we will see the old frame anyways.
-       */
-      position = sushi_media_bin_get_position (self);
-
-      gtk_container_remove (GTK_CONTAINER (priv->overlay), priv->video_widget);
-      sushi_media_bin_deinit_video_sink (self);
-    }
-
-  g_object_ref (priv->overlay);
-
-  if (fullscreen)
-    {
-      priv->fullscreen_window = g_object_ref (sushi_media_bin_window_new (self));
-
-      /* Reparent video widget in a fullscreen window */
-      gtk_container_remove (GTK_CONTAINER (priv->stack), priv->overlay);
-
-      /* Pack an image with the last frame inside the bin */
-      gtk_container_add (GTK_CONTAINER (priv->stack), priv->tmp_image);
-      gtk_widget_show (priv->tmp_image);
-      gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->tmp_image);
-
-      /* Pack video in the fullscreen window */
-      gtk_container_add (GTK_CONTAINER (priv->fullscreen_window), priv->overlay);
-
-      gtk_window_fullscreen (priv->fullscreen_window);
-      gtk_window_present (priv->fullscreen_window);
-
-      /* Hide cursor if controls are hidden */
-      if (!gtk_revealer_get_reveal_child (priv->bottom_revealer))
-        gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (priv->fullscreen_window)),
-                               priv->blank_cursor);
-
-      gtk_image_set_from_icon_name (priv->fullscreen_image, SMB_ICON_NAME_RESTORE, SMB_ICON_SIZE);
-    }
-  else
-    {
-      gtk_container_remove (GTK_CONTAINER (priv->stack), priv->tmp_image);
-      priv->tmp_image = NULL;
-
-      /* Reparent video widget back into ourselves */
-      gtk_container_remove (GTK_CONTAINER (priv->fullscreen_window), priv->overlay);
-      gtk_container_add (GTK_CONTAINER (priv->stack), priv->overlay);
-      gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->overlay);
-
-      gtk_widget_destroy (GTK_WIDGET (priv->fullscreen_window));
-      g_clear_object (&priv->fullscreen_window);
-
-      gtk_image_set_from_icon_name (priv->fullscreen_image, SMB_ICON_NAME_FULLSCREEN, SMB_ICON_SIZE);
-
-      gtk_widget_grab_focus (GTK_WIDGET (self));
-    }
-
-  /*
-   * FIXME: See bug https://bugzilla.gnome.org/show_bug.cgi?id=775045
-   */
-  if (priv->play == NULL)
-    {
-      sushi_media_bin_init_playbin (self);
-      sushi_media_bin_init_video_sink (self);
-
-      g_object_set (priv->play, "uri", priv->uri, NULL);
-
-      /* Init new pipeline */
-      gst_element_set_state (priv->play, GST_STATE_PAUSED);
-      gst_element_get_state (priv->play, NULL, NULL, GET_STATE_TIMEOUT);
-
-      /* Seek to position */
-      gst_element_seek_simple (priv->play, GST_FORMAT_TIME,
-                               GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH,
-                               position);
-      gst_message_unref (gst_bus_pop_filtered (priv->bus, GST_MESSAGE_ASYNC_DONE));
-
-      /* Resume playback */
-      if (priv->state == GST_STATE_PLAYING)
-        {
-          gst_element_set_state (priv->play, GST_STATE_PLAYING);
-          gst_element_get_state (priv->play, NULL, NULL, GET_STATE_TIMEOUT);
-        }
-    }
-
-  g_object_unref (priv->overlay);
-}
-
-static void
-on_sushi_media_bin_realize (GtkWidget *widget, SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  /* Create a blank_cursor */
-  priv->blank_cursor = gdk_cursor_new_from_name (gtk_widget_get_display (widget),
-                                                 "none");
-
-  /* Create video sink */
-  sushi_media_bin_init_video_sink (self);
-
-  if (priv->fullscreen)
-    sushi_media_bin_fullscreen_apply (self, TRUE);
-
-  /* Make playbin show the first video frame if there is an URI */
-  sushi_media_bin_update_state (self);
-
-  /* Disconnect after initialization */
-  g_signal_handlers_disconnect_by_func (widget, on_sushi_media_bin_realize, self);
-}
-
-static void
-on_sushi_media_bin_unrealize (GtkWidget *widget, SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  /* Remove controls timeout */
-  ensure_no_timeout (priv);
-
-  /* Disconnect after completion */
-  g_signal_handlers_disconnect_by_func (widget, on_sushi_media_bin_unrealize, self);
-}
-
-static gboolean
-sushi_media_bin_error (SushiMediaBin *self, GError *error)
-{
-  /* TODO: properly present errors to the user */
-  g_warning ("%s", error->message);
-  return TRUE;
-}
-
-static void
-sushi_media_bin_init_volume_button (SushiMediaBin    *self,
-                                    GtkScaleButton *button,
-                                    gboolean        stop_timeout)
-{
-  GtkWidget *popup = gtk_scale_button_get_popup (button);
-
-  if (stop_timeout)
-    {
-      g_signal_connect (popup, "show", G_CALLBACK (on_volume_popup_show), self);
-      g_signal_connect (popup, "hide", G_CALLBACK (on_volume_popup_hide), self);
-    }
-
-  gtk_style_context_add_class (gtk_widget_get_style_context (popup), "sushi-media-bin");
-}
-
-static void
-sushi_media_bin_init_style (SushiMediaBin *self)
-{
-  static gsize style_initialized = 0;
-
-  if (g_once_init_enter (&style_initialized))
-    {
-      GtkCssProvider *css_provider = gtk_css_provider_new ();
-
-      gtk_css_provider_load_from_resource (css_provider, "/org/gnome/Sushi/libsushi/sushi-media-bin.css");
-      gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
-                                                 GTK_STYLE_PROVIDER (css_provider),
-                                                 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION-10);
-      g_object_unref (css_provider);
-
-      g_once_init_leave (&style_initialized, 1);
-    }
-}
-
-static void
-sushi_media_bin_init (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint i;
-
-  gtk_widget_init_template (GTK_WIDGET (self));
-
-  sushi_media_bin_init_style (self);
-
-  priv->state = SMB_INITIAL_STATE;
-  priv->autohide_timeout = AUTOHIDE_TIMEOUT_DEFAULT;
-  priv->pressed_button_type = GDK_NOTHING;
-  priv->dump_dot_file = (g_getenv ("GST_DEBUG_DUMP_DOT_DIR") != NULL);
-
-  sushi_media_bin_init_playbin (self);
-
-  /* Create info box column labels */
-  for (i = 0; i < INFO_N_COLUMNS; i++)
-    {
-      GtkWidget *label = gtk_label_new ("");
-      priv->info_column_label[i] = GTK_LABEL (label);
-      gtk_container_add (GTK_CONTAINER (priv->info_box), label);
-      gtk_widget_set_valign (label, GTK_ALIGN_START);
-      gtk_widget_show (label);
-    }
-
-  /* Cache position query */
-  priv->position_query = gst_query_new_position (GST_FORMAT_TIME);
-
-  /* Make both buttons look the same */
-  g_object_bind_property (priv->playback_image, "icon-name",
-                          priv->audio_playback_image, "icon-name",
-                          G_BINDING_SYNC_CREATE);
-
-  sushi_media_bin_init_volume_button (self, priv->volume_button, TRUE);
-  sushi_media_bin_init_volume_button (self, priv->audio_volume_button, FALSE);
-}
-
-static void
-sushi_media_bin_dispose (GObject *object)
-{
-  SushiMediaBin *self = SUSHI_MEDIA_BIN (object);
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  /* Remove controls timeout */
-  ensure_no_timeout (priv);
-
-  /* Finalize gstreamer related objects */
-  sushi_media_bin_deinit_video_sink (self);
-
-  /* Destroy fullscreen window */
-  if (priv->fullscreen_window)
-    {
-      gtk_widget_destroy (GTK_WIDGET (priv->fullscreen_window));
-      g_clear_object (&priv->fullscreen_window);
-    }
-
-  /* Unref cursor */
-  g_clear_object (&priv->blank_cursor);
-
-  G_OBJECT_CLASS (sushi_media_bin_parent_class)->dispose (object);
-}
-
-static void
-sushi_media_bin_finalize (GObject *object)
-{
-  SushiMediaBin *self = SUSHI_MEDIA_BIN (object);
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  ensure_no_timeout(priv);
-
-  /* Clear position query */
-  g_clear_pointer (&priv->position_query, gst_query_unref);
-
-  /* Remove frame clock tick callback */
-  sushi_media_bin_set_tick_enabled (self, FALSE);
-
-  /* Clear tag lists */
-  g_clear_pointer (&priv->audio_tags, gst_tag_list_unref);
-  g_clear_pointer (&priv->video_tags, gst_tag_list_unref);
-  g_clear_pointer (&priv->text_tags, gst_tag_list_unref);
-
-  /* Free properties */
-  g_clear_pointer (&priv->uri, g_free);
-  g_clear_pointer (&priv->title, g_free);
-  g_clear_pointer (&priv->description, g_free);
-
-  G_OBJECT_CLASS (sushi_media_bin_parent_class)->finalize (object);
-}
-
-static inline void
-sushi_media_bin_set_audio_mode (SushiMediaBin *self, gboolean audio_mode)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  priv->audio_mode = audio_mode;
-
-  if (audio_mode)
-    gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->audio_box);
-}
-
-static void
-sushi_media_bin_set_property (GObject      *object,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
-{
-  g_return_if_fail (SUSHI_IS_MEDIA_BIN (object));
-
-  switch (prop_id)
-    {
-    case PROP_URI:
-      sushi_media_bin_set_uri (SUSHI_MEDIA_BIN (object),
-                               g_value_get_string (value));
-      break;
-    case PROP_VOLUME:
-      sushi_media_bin_set_volume (SUSHI_MEDIA_BIN (object),
-                                  g_value_get_double (value));
-      break;
-    case PROP_AUTOHIDE_TIMEOUT:
-      sushi_media_bin_set_autohide_timeout (SUSHI_MEDIA_BIN (object),
-                                            g_value_get_int (value));
-      break;
-    case PROP_FULLSCREEN:
-      sushi_media_bin_set_fullscreen (SUSHI_MEDIA_BIN (object),
-                                      g_value_get_boolean (value));
-      break;
-    case PROP_SHOW_STREAM_INFO:
-      sushi_media_bin_set_show_stream_info (SUSHI_MEDIA_BIN (object),
-                                            g_value_get_boolean (value));
-      break;
-    case PROP_AUDIO_MODE:
-      sushi_media_bin_set_audio_mode (SUSHI_MEDIA_BIN (object),
-                                      g_value_get_boolean (value));
-      break;
-    case PROP_TITLE:
-      sushi_media_bin_set_title (SUSHI_MEDIA_BIN (object),
-                                 g_value_get_string (value));
-      break;
-    case PROP_DESCRIPTION:
-      sushi_media_bin_set_description (SUSHI_MEDIA_BIN (object),
-                                       g_value_get_string (value));
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
-}
-
-static void
-sushi_media_bin_get_property (GObject    *object,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
-{
-  SushiMediaBinPrivate *priv;
-
-  g_return_if_fail (SUSHI_IS_MEDIA_BIN (object));
-  priv = SMB_PRIVATE (SUSHI_MEDIA_BIN (object));
-
-  switch (prop_id)
-    {
-    case PROP_URI:
-      g_value_set_string (value, priv->uri);
-      break;
-    case PROP_VOLUME:
-      g_value_set_double (value, gtk_adjustment_get_value (priv->volume_adjustment));
-      break;
-    case PROP_AUTOHIDE_TIMEOUT:
-      g_value_set_int (value, priv->autohide_timeout);
-      break;
-    case PROP_FULLSCREEN:
-      g_value_set_boolean (value, priv->fullscreen);
-      break;
-    case PROP_SHOW_STREAM_INFO:
-      g_value_set_boolean (value, priv->show_stream_info);
-      break;
-    case PROP_AUDIO_MODE:
-      g_value_set_boolean (value, priv->audio_mode);
-      break;
-    case PROP_TITLE:
-      g_value_set_string (value, priv->title);
-      break;
-    case PROP_DESCRIPTION:
-      g_value_set_string (value, priv->description);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
-}
-
-static GtkSizeRequestMode
-sushi_media_bin_get_request_mode (GtkWidget *self)
-{
-  return GTK_SIZE_REQUEST_CONSTANT_SIZE;
-}
-
-
-static void
-sushi_media_bin_get_preferred_width (GtkWidget *self,
-                                     gint      *minimum_width,
-                                     gint      *natural_width)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (SUSHI_MEDIA_BIN (self));
-
-  if (priv->audio_mode)
-    {
-      GTK_WIDGET_CLASS (sushi_media_bin_parent_class)->get_preferred_width
-        (self, minimum_width, natural_width);
-    }
-  else
-    {
-      *minimum_width = priv->video_width ? 320 : 0;
-      *natural_width = priv->video_width ? priv->video_width : 0;
-    }
-}
-
-static void
-sushi_media_bin_get_preferred_height (GtkWidget *self,
-                                      gint      *minimum_height,
-                                      gint      *natural_height)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (SUSHI_MEDIA_BIN (self));
-
-  if (priv->audio_mode)
-    {
-      GTK_WIDGET_CLASS (sushi_media_bin_parent_class)->get_preferred_height
-        (self, minimum_height, natural_height);
-    }
-  else
-    {
-      *minimum_height = priv->video_height ? 240 : 0;
-      *natural_height = priv->video_height ? priv->video_height : 0;
-    }
-}
-
-#define SMB_DEFINE_ACTION_SIGNAL(klass, name, handler,...) \
-  g_signal_new_class_handler (name, \
-                              G_TYPE_FROM_CLASS (klass), \
-                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, \
-                              G_CALLBACK (handler), \
-                              NULL, NULL, NULL, \
-                              G_TYPE_NONE, __VA_ARGS__)
-
-static void
-sushi_media_bin_class_init (SushiMediaBinClass *klass)
-{
-  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-
-  object_class->dispose = sushi_media_bin_dispose;
-  object_class->finalize = sushi_media_bin_finalize;
-  object_class->set_property = sushi_media_bin_set_property;
-  object_class->get_property = sushi_media_bin_get_property;
-
-  widget_class->get_request_mode = sushi_media_bin_get_request_mode;
-  widget_class->get_preferred_width = sushi_media_bin_get_preferred_width;
-  widget_class->get_preferred_height = sushi_media_bin_get_preferred_height;
-
-  /* Properties */
-  properties[PROP_URI] =
-    g_param_spec_string ("uri",
-                         "URI",
-                         "The Media URI to playback",
-                         NULL,
-                         G_PARAM_READWRITE);
-
-  properties[PROP_VOLUME] =
-    g_param_spec_double ("volume",
-                         "Volume",
-                         "Stream volume",
-                         0.0, 1.0, 1.0,
-                         G_PARAM_READWRITE);
-
-  properties[PROP_AUTOHIDE_TIMEOUT] =
-    g_param_spec_int ("autohide-timeout",
-                      "Auto hide timeout",
-                      "Controls auto hide timeout in seconds",
-                      0, G_MAXINT,
-                      AUTOHIDE_TIMEOUT_DEFAULT,
-                      G_PARAM_READWRITE);
-
-  properties[PROP_FULLSCREEN] =
-    g_param_spec_boolean ("fullscreen",
-                          "Fullscreen",
-                          "Whether to show the video in fullscreen or not",
-                          FALSE,
-                          G_PARAM_READWRITE);
-
-  properties[PROP_SHOW_STREAM_INFO] =
-    g_param_spec_boolean ("show-stream-info",
-                          "Show stream info",
-                          "Whether to show stream information or not",
-                          FALSE,
-                          G_PARAM_READWRITE);
-
-  properties[PROP_AUDIO_MODE] =
-    g_param_spec_boolean ("audio-mode",
-                          "Audio Mode",
-                          "Whether to show controls suitable for audio files only",
-                          FALSE,
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
-
-  properties[PROP_TITLE] =
-    g_param_spec_string ("title",
-                         "Title",
-                         "The title to display",
-                         NULL,
-                         G_PARAM_READWRITE);
-
-  properties[PROP_DESCRIPTION] =
-    g_param_spec_string ("description",
-                         "Description",
-                         "Audio/Video description",
-                         NULL,
-                         G_PARAM_READWRITE);
-
-  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
-
-  /**
-   * SushiMediaBin::error:
-   * @self: the #SushiMediaBin which received the signal.
-   * @error: the #GError
-   */
-  sushi_media_bin_signals[ERROR] =
-      g_signal_new_class_handler ("error",
-                                  G_TYPE_FROM_CLASS (object_class),
-                                  G_SIGNAL_RUN_LAST,
-                                  G_CALLBACK (sushi_media_bin_error),
-                                  g_signal_accumulator_true_handled, NULL,
-                                  NULL,
-                                  G_TYPE_BOOLEAN, 1, G_TYPE_ERROR);
-  /**
-   * SushiMediaBin::size-change:
-   * @self: the #SushiMediaBin which received the signal.
-   */
-  sushi_media_bin_signals[SIZE_CHANGE] =
-      g_signal_new ("size-change",
-                    G_TYPE_FROM_CLASS (object_class),
-                    G_SIGNAL_RUN_LAST,
-                    0, NULL, NULL, NULL,
-                    G_TYPE_NONE, 0);
-
-  /**
-   * SushiMediaBin::tags-change:
-   * @self: the #SushiMediaBin which received the signal.
-   */
-  sushi_media_bin_signals[TAGS_CHANGE] =
-      g_signal_new ("tags-change",
-                    G_TYPE_FROM_CLASS (object_class),
-                    G_SIGNAL_RUN_LAST,
-                    0, NULL, NULL, NULL,
-                    G_TYPE_NONE, 0);
-
-  /* Action signals for key bindings */
-  SMB_DEFINE_ACTION_SIGNAL (object_class, "toggle", sushi_media_bin_action_toggle, 1, G_TYPE_STRING);
-  SMB_DEFINE_ACTION_SIGNAL (object_class, "seek", sushi_media_bin_action_seek, 1, G_TYPE_INT);
-
-  /* Template */
-  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Sushi/libsushi/SushiMediaBin.ui");
-
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, stack);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, playback_adjustment);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, volume_adjustment);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, playback_image);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, fullscreen_image);
-
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, overlay);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, play_box);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, volume_button);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, title_label);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, info_box);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, duration_label);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, progress_duration_label);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, progress_position_label);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, top_revealer);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, bottom_revealer);
-
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, audio_box);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, audio_duration_label);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, audio_volume_button);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, audio_position_label);
-  gtk_widget_class_bind_template_child_private (widget_class, SushiMediaBin, audio_playback_image);
-
-  gtk_widget_class_bind_template_callback (widget_class, on_sushi_media_bin_realize);
-  gtk_widget_class_bind_template_callback (widget_class, on_sushi_media_bin_unrealize);
-
-  gtk_widget_class_bind_template_callback (widget_class, on_overlay_motion_notify_event);
-  gtk_widget_class_bind_template_callback (widget_class, on_overlay_button_press_event);
-  gtk_widget_class_bind_template_callback (widget_class, on_overlay_button_release_event);
-
-  gtk_widget_class_bind_template_callback (widget_class, on_revealer_motion_notify_event);
-  gtk_widget_class_bind_template_callback (widget_class, on_revealer_leave_notify_event);
-
-  gtk_widget_class_bind_template_callback (widget_class, on_progress_scale_format_value);
-  gtk_widget_class_bind_template_callback (widget_class, on_playback_adjustment_value_changed);
-
-  gtk_widget_class_bind_template_callback (widget_class, sushi_media_bin_toggle_playback);
-  gtk_widget_class_bind_template_callback (widget_class, sushi_media_bin_toggle_fullscreen);
-
-  /* Setup CSS */
-  gtk_widget_class_set_css_name (widget_class, "sushi-media-bin");
-
-  /* Init GStreamer */
-  gst_init_check (NULL, NULL, NULL);
-  GST_DEBUG_CATEGORY_INIT (sushi_media_bin_debug, "SushiMediaBin", 0, "SushiMediaBin audio/video widget");
-}
-
-/*************************** Fullscreen Window Type ***************************/
-
-G_DECLARE_FINAL_TYPE (SushiMediaBinWindow, sushi_media_bin_window, SUSHI, MEDIA_BIN_WINDOW, GtkWindow)
-
-struct _SushiMediaBinWindow
-{
-  GtkWindow parent;
-};
-
-G_DEFINE_TYPE (SushiMediaBinWindow, sushi_media_bin_window, GTK_TYPE_WINDOW);
-
-static void
-sushi_media_bin_window_init (SushiMediaBinWindow *self)
-{
-  gtk_window_set_decorated (GTK_WINDOW (self), FALSE);
-}
-
-static void
-sushi_media_bin_window_class_init (SushiMediaBinWindowClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "sushi-media-bin");
-
-  SMB_DEFINE_ACTION_SIGNAL (object_class, "toggle", NULL, 1, G_TYPE_STRING);
-  SMB_DEFINE_ACTION_SIGNAL (object_class, "seek", NULL, 1, G_TYPE_INT);
-}
-
-static GtkWindow *
-sushi_media_bin_window_new (SushiMediaBin *bin)
-{
-  GObject *window = g_object_new (sushi_media_bin_window_get_type (), NULL);
-
-  g_signal_connect_swapped (window, "delete-event", G_CALLBACK (sushi_media_bin_toggle_fullscreen), bin);
-  g_signal_connect_swapped (window, "toggle", G_CALLBACK (sushi_media_bin_action_toggle), bin);
-  g_signal_connect_swapped (window, "seek", G_CALLBACK (sushi_media_bin_action_seek), bin);
-
-  return (GtkWindow *) window;
-}
-
-/*********************************** Utils ************************************/
-
-#define TIME_HOURS(t)   (t / 3600)
-#define TIME_MINUTES(t) ((t % 3600) / 60)
-#define TIME_SECONDS(t) (t % 60)
-
-static const gchar *
-format_time (gint time)
-{
-  static gchar buffer[16];
-  gint hours = TIME_HOURS (time);
-
-  if (hours)
-    g_snprintf (buffer,
-                sizeof (buffer),
-                "%d:%02d:%02d",
-                hours,
-                TIME_MINUTES (time),
-                TIME_SECONDS (time));
-  else
-    g_snprintf (buffer,
-                sizeof (buffer),
-                "%d:%02d",
-                TIME_MINUTES (time),
-                TIME_SECONDS (time));
-
-  return (const gchar *) buffer;
-}
-
-static void
-on_widget_style_updated (GtkWidget *widget, gpointer data)
-{
-  gboolean visible = GPOINTER_TO_INT (data);
-  gdouble opacity;
-
-  gtk_style_context_get (gtk_widget_get_style_context (widget),
-                         gtk_widget_get_state_flags (widget),
-                         "opacity", &opacity, NULL);
-
-  if ((visible && opacity >= 1.0) || (!visible && opacity == 0.0))
-    {
-      gtk_widget_set_visible (widget, visible);
-      g_signal_handlers_disconnect_by_func (widget, on_widget_style_updated, data);
-    }
-}
-
-static void
-widget_set_visible (GtkWidget *widget, gboolean visible)
-{
-  GtkStyleContext *context = gtk_widget_get_style_context (widget);
-
-  g_signal_handlers_disconnect_by_func (widget, on_widget_style_updated, GINT_TO_POINTER (TRUE));
-  g_signal_handlers_disconnect_by_func (widget, on_widget_style_updated, GINT_TO_POINTER (FALSE));
-
-  gtk_style_context_remove_class (context, visible ? "hide" : "show");
-  gtk_style_context_add_class (context, visible ? "show" : "hide");
-
-  if (visible)
-    gtk_widget_show (widget);
-
-  g_signal_connect (widget, "style-updated",
-                    G_CALLBACK (on_widget_style_updated),
-                    GINT_TO_POINTER (visible));
-}
-
-/* The following macros are used to define generic getters and setters */
-
-#define SMB_DEFINE_GETTER_FULL(type, prop, retval, retstmt) \
-type \
-sushi_media_bin_get_##prop (SushiMediaBin *self) \
-{ \
-  g_return_val_if_fail (SUSHI_IS_MEDIA_BIN (self), retval); \
-  retstmt \
-}
-
-#define SMB_DEFINE_GETTER(type, prop, retval) \
-  SMB_DEFINE_GETTER_FULL (type, prop, retval, return SMB_PRIVATE (self)->prop;)
-
-#define SMB_DEFINE_SETTER_FULL(type, prop, PROP, setup, cmp, assign, code) \
-void \
-sushi_media_bin_set_##prop (SushiMediaBin *self, type prop) \
-{ \
-  SushiMediaBinPrivate *priv; \
-  g_return_if_fail (SUSHI_IS_MEDIA_BIN (self)); \
-  priv = SMB_PRIVATE (self); \
-  setup; \
-  if (cmp) \
-    { \
-      assign; \
-      code; \
-      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_##PROP]); \
-    } \
-}
-
-/* The last argument is for custom code that will be added just before calling
- * g_object_notify_by_pspec()
- */
-#define SMB_DEFINE_SETTER(type, prop, PROP, code) \
-  SMB_DEFINE_SETTER_FULL(type, prop, PROP, \
-    , \
-    priv->prop != prop, \
-    priv->prop = prop, \
-    code)
-
-#define SMB_DEFINE_SETTER_BOOLEAN(prop, PROP, code) \
-  SMB_DEFINE_SETTER_FULL(gboolean, prop, PROP, \
-    prop = (prop) ? TRUE : FALSE, \
-    priv->prop != prop, \
-    priv->prop = prop, \
-    code)
-
-#define SMB_DEFINE_SETTER_STRING(prop, PROP, code) \
-  SMB_DEFINE_SETTER_FULL(const gchar *, prop, PROP, \
-    , \
-    g_strcmp0 (priv->prop, prop), \
-    g_free (priv->prop); priv->prop = g_strdup (prop), \
-    code)
-
-
-/******************************** GST Support *********************************/
-static inline gboolean
-sushi_media_bin_handle_msg_error (SushiMediaBin *self, GstMessage *msg)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  GError *error = NULL;
-  gboolean handled;
-
-  gst_message_parse_error (msg, &error, NULL);
-
-  if (priv->play)
-    gst_element_set_state (priv->play, GST_STATE_NULL);
-
-  g_signal_emit (self, sushi_media_bin_signals[ERROR], 0, error, &handled);
-
-  g_error_free (error);
-
-  return handled;
-}
-
-static inline void
-sushi_media_bin_update_duration (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint64 duration;
-
-  if (!gst_element_query_duration (priv->play, GST_FORMAT_TIME, &duration)
-      || priv->duration == duration)
-    return;
-
-  priv->duration = duration;
-
-  duration = GST_TIME_AS_SECONDS (duration);
-  gtk_label_set_label (priv->audio_duration_label, format_time (duration));
-  gtk_label_set_label (priv->duration_label, format_time (duration));
-  gtk_label_set_label (priv->progress_duration_label, format_time (duration));
-  gtk_adjustment_set_upper (priv->playback_adjustment, duration);
-}
-
-static inline void
-sushi_media_bin_update_position (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint position = GST_TIME_AS_SECONDS (sushi_media_bin_get_position (self));
-
-  if (priv->position == position)
-    return;
-
-  priv->position = position;
-
-  priv->ignore_adjustment_changes = TRUE;
-  gtk_adjustment_set_value (priv->playback_adjustment, position);
-  priv->ignore_adjustment_changes = FALSE;
-
-  gtk_label_set_label (priv->progress_position_label, format_time (position));
-  gtk_label_set_label (priv->audio_position_label, format_time (position));
-}
-
-static inline void
-log_fps (SushiMediaBin *self, GdkFrameClock *frame_clock)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gint64 frame_time, time;
-  GstSample *sample;
-
-  /* Get current buffer and return if its the same as last tick */
-  g_object_get (priv->play, "sample", &sample, NULL);
-  if (sample)
-    {
-      GstBuffer *buffer = gst_sample_get_buffer (sample);
-      gst_sample_unref (sample);
-      
-      if (priv->last_buffer == buffer)
-        return;
-
-      priv->last_buffer = buffer;
-    }
-  else
-    return;
-
-  frame_time = gdk_frame_clock_get_frame_time (frame_clock);
-
-  /* Initialize state variables */
-  if (priv->tick_start == 0)
-    {
-      priv->tick_start = frame_time;
-      priv->frames_window_start = frame_time;
-      priv->frames_window = 0;
-      priv->frames_rendered = 0;
-    }
-  else if (priv->frames_window == 0)
-    priv->frames_window_start = frame_time;
-
-  priv->frames_window++;
-
-  /* We only print FPS once every FPS_WINDOW_SIZE seconds */
-  time = frame_time - priv->frames_window_start;
-  if (time < FPS_WINDOW_SIZE * 1000000)
-    return;
-
-  priv->frames_rendered += priv->frames_window;
-
-  GST_INFO ("FPS: %lf average: %lf",
-            priv->frames_window / (time / 1000000.0),
-            priv->frames_rendered / ((frame_time - priv->tick_start) / 1000000.0));
-
-  priv->frames_window = 0;
-}
-
-static gboolean
-sushi_media_bin_tick_callback (GtkWidget     *widget,
-                               GdkFrameClock *frame_clock,
-                               gpointer       user_data)
-{
-  static GstDebugLevel level;
-
-  sushi_media_bin_update_position ((SushiMediaBin *)user_data);
-
-  if (level == 0)
-    level = gst_debug_category_get_threshold (sushi_media_bin_debug);
-
-  if (level >= GST_LEVEL_INFO)
-    log_fps ((SushiMediaBin *)user_data, frame_clock);
-
-  return G_SOURCE_CONTINUE;
-}
-
-static void
-sushi_media_bin_set_tick_enabled (SushiMediaBin *self, gboolean enabled)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  if (priv->tick_id)
-    {
-      gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->tick_id);
-      priv->tick_id = priv->tick_start = 0;
-    }
-
-  if (enabled)
-    priv->tick_id = gtk_widget_add_tick_callback (priv->audio_mode ? priv->audio_box : priv->overlay,
-                                                  sushi_media_bin_tick_callback,
-                                                  self, NULL);
-}
-
-static inline void
-sushi_media_bin_dump_dot (SushiMediaBin *self, GstState old, GstState new)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  gchar *filename;
-
-  filename = g_strdup_printf ("%s_%s_%s", g_get_prgname (),
-                              gst_element_state_get_name (old),
-                              gst_element_state_get_name (new));
-  gst_debug_bin_to_dot_file_with_ts (GST_BIN (priv->play),
-                                     GST_DEBUG_GRAPH_SHOW_ALL,
-                                     filename);
-  g_free (filename);
-}
-
-static inline void
-sushi_media_bin_handle_msg_state_changed (SushiMediaBin *self, GstMessage *msg)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  GstState old_state, new_state;
-
-  gst_message_parse_state_changed (msg, &old_state, &new_state, NULL);
-
-  if (old_state == new_state ||
-      GST_MESSAGE_SRC (msg) != GST_OBJECT (priv->play))
-    return;
-
-  GST_DEBUG ("State changed from %s to %s",
-             gst_element_state_get_name (old_state),
-             gst_element_state_get_name (new_state));
-
-  if (priv->dump_dot_file)
-    sushi_media_bin_dump_dot (self, old_state, new_state);
-
-  /* Update UI */
-  if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED)
-    {
-      gtk_image_set_from_icon_name (priv->playback_image, SMB_ICON_NAME_PLAY, SMB_ICON_SIZE);
-      widget_set_visible (priv->play_box, FALSE);
-      sushi_media_bin_update_duration (self);
-    }
-  else if (new_state == GST_STATE_PLAYING)
-    {
-      widget_set_visible (priv->play_box, FALSE);
-      gtk_image_set_from_icon_name (priv->playback_image, SMB_ICON_NAME_PAUSE, SMB_ICON_SIZE);
-      sushi_media_bin_set_tick_enabled (self, TRUE);
-    }
-  else
-    {
-      gtk_image_set_from_icon_name (priv->playback_image, SMB_ICON_NAME_PLAY, SMB_ICON_SIZE);
-      widget_set_visible (priv->play_box, FALSE);
-      priv->position = 0;
-      sushi_media_bin_set_tick_enabled (self, FALSE);
-    }
-}
-
-typedef struct {
-  GString *tag;
-  GString *val;
-} MetaDataStrings;
-
-static void
-print_tag (const GstTagList *list, const gchar *tag, gpointer data)
-{
-  MetaDataStrings *metadata = data;
-  gint i, n;
-
-  for (i = 0, n = gst_tag_list_get_tag_size (list, tag); i < n; ++i)
-    {
-      const GValue *val = gst_tag_list_get_value_index (list, tag, i);
-      GValue str = {0, };
-
-      g_value_init (&str, G_TYPE_STRING);
-      g_value_transform (val, &str);
-
-      g_string_append_printf (metadata->tag, "\n    %s", tag);
-      g_string_append_printf (metadata->val, "\n: %s", g_value_get_string (&str));
-
-      g_value_unset (&str);
-    }
-}
-
-static inline void
-meta_data_strings_set_title (MetaDataStrings *metadata, const gchar *title)
-{
-  g_string_assign (metadata->tag, title);
-  g_string_assign (metadata->val, "");
-}
-
-static inline void
-meta_data_strings_set_info (MetaDataStrings *metadata,
-                            GtkLabel        *left,
-                            GtkLabel        *right,
-                            GstTagList      *tags)
-{
-  if (tags)
-    {
-      gst_tag_list_foreach (tags, print_tag, metadata);
-
-      gtk_label_set_label (left, metadata->tag->str);
-      gtk_label_set_label (right, metadata->val->str);
-    }
-  else
-    {
-      gtk_label_set_label (left, "");
-      gtk_label_set_label (right, "");
-    }
-}
-
-static inline void
-sushi_media_bin_update_stream_info (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  MetaDataStrings metadata = { g_string_new (""), g_string_new ("") };
-
-  meta_data_strings_set_title (&metadata, "Audio:");
-  meta_data_strings_set_info (&metadata,
-                              priv->info_column_label[0],
-                              priv->info_column_label[1],
-                              priv->audio_tags);
-
-  meta_data_strings_set_title (&metadata, "Video:");
-  if (priv->video_width && priv->video_height)
-    {
-      g_string_append_printf (metadata.tag, "\n    video-resolution");
-      g_string_append_printf (metadata.val, "\n: %dx%d", priv->video_width, priv->video_height);
-    }
-  meta_data_strings_set_info (&metadata,
-                              priv->info_column_label[2],
-                              priv->info_column_label[3],
-                              priv->video_tags);
-
-  meta_data_strings_set_title (&metadata, "Text:");
-  meta_data_strings_set_info (&metadata,
-                              priv->info_column_label[4],
-                              priv->info_column_label[5],
-                              priv->text_tags);
-
-  g_string_free (metadata.tag, TRUE);
-  g_string_free (metadata.val, TRUE);
-}
-
-static inline void
-sushi_media_bin_handle_msg_application (SushiMediaBin *self, GstMessage *msg)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  const GstStructure *structure;
-  const gchar *name;
-
-  structure = gst_message_get_structure (msg);
-  name = gst_structure_get_name (structure);
-  g_return_if_fail (name != NULL);
-
-  if (priv->show_stream_info)
-    sushi_media_bin_update_stream_info (self);
-
-  /* TODO: handle audio and text tags */
-  if (g_str_equal (name, "video-tags-changed"))
-    {
-      gchar *value = NULL;
-
-      if (!priv->title_user_set)
-        {
-          if (priv->video_tags)
-            gst_tag_list_get_string_index (priv->video_tags, GST_TAG_TITLE, 0, &value);
-
-          sushi_media_bin_set_title (self, value);
-          priv->title_user_set = FALSE;
-          g_clear_pointer (&value, g_free);
-        }
-
-      if (!priv->description_user_set)
-        {
-          /* Get description from comment or description tags */
-          if (priv->video_tags)
-            {
-              /* We try comment tag first and then description */
-              if (!gst_tag_list_get_string_index (priv->video_tags, GST_TAG_COMMENT, 0, &value))
-                gst_tag_list_get_string_index (priv->video_tags, GST_TAG_DESCRIPTION, 0, &value);
-            }
-
-          sushi_media_bin_set_description (self, value);
-          priv->description_user_set = FALSE;
-          g_clear_pointer (&value, g_free);
-        }
-    }
-}
-
-static inline void
-sushi_media_bin_handle_msg_eos (SushiMediaBin *self, GstMessage *msg)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  GST_DEBUG ("Got EOS");
-
-  gst_element_set_state (priv->play, GST_STATE_NULL);
-  sushi_media_bin_set_state (self, SMB_INITIAL_STATE);
-  sushi_media_bin_update_position (self);
-}
-
-static inline void
-sushi_media_bin_post_message_application (SushiMediaBin *self, const gchar *name)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  GstStructure *data = gst_structure_new (name, NULL, NULL);
-
-  /* Post message on the bus for the main thread to pick it up */
-  gst_element_post_message (priv->play,
-                            gst_message_new_application (GST_OBJECT (priv->play),
-                                                         data));
-}
-
-static inline void
-sushi_media_bin_handle_msg_tag (SushiMediaBin *self, GstMessage *msg)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  GstObject *src = GST_MESSAGE_SRC (msg);
-  GstTagList *tags = NULL, *old_tags = NULL;
-  const gchar *type = NULL;
-
-  gst_message_parse_tag (msg, &tags);
-
-  if (g_type_is_a (G_OBJECT_TYPE (src), GST_TYPE_VIDEO_SINK))
-    {
-      old_tags = priv->video_tags;
-      if (!old_tags || (old_tags && !gst_tag_list_is_equal (old_tags, tags)))
-        {
-          type = "video-tags-changed";
-          priv->video_tags = gst_tag_list_merge (old_tags, tags, GST_TAG_MERGE_REPLACE);
-        }
-    }
-  else if (g_type_is_a (G_OBJECT_TYPE (src), GST_TYPE_AUDIO_BASE_SINK))
-    {
-      old_tags = priv->audio_tags;
-      if (!old_tags || (old_tags && !gst_tag_list_is_equal (old_tags, tags)))
-        {
-          type = "audio-tags-changed";
-          priv->audio_tags = gst_tag_list_merge (old_tags, tags, GST_TAG_MERGE_REPLACE);
-        }
-    }
-
-  /* Post message on the bus for the main thread to pick it up */
-  if (type)
-    {
-      sushi_media_bin_post_message_application (self, type);
-      g_signal_emit (self, sushi_media_bin_signals[TAGS_CHANGE], 0);
-      g_clear_pointer (&old_tags, gst_tag_list_unref);
-    }
-
-  gst_tag_list_unref (tags);
-}
-
-static inline void
-sushi_media_bin_handle_streams_selected (SushiMediaBin *self, GstMessage *msg)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-  GstStreamCollection *collection = NULL;
-  GstStream *stream = NULL;
-  GstStructure *caps_struct;
-  GstCaps *caps;
-  gint i, n, w, h;
-
-  gst_message_parse_streams_selected (msg, &collection);
-  n = gst_stream_collection_get_size (collection);
-
-  for (i = 0; i < n; i++)
-    {
-      stream = gst_stream_collection_get_stream (collection, i);
-
-      if (gst_stream_get_stream_type (stream) == GST_STREAM_TYPE_VIDEO)
-        break;
-    }
-
-  if (!stream)
-    return;
-
-  caps = gst_stream_get_caps (stream);
-  caps_struct = gst_caps_get_structure (caps, 0);
-
-  if (gst_structure_get_int (caps_struct, "width", &w) &&
-      gst_structure_get_int (caps_struct, "height", &h))
-    {
-      if (priv->video_width != w || priv->video_height != h)
-        {
-          priv->video_width = w;
-          priv->video_height = h;
-          gtk_widget_queue_resize (GTK_WIDGET (self));
-        }
-    }
-  else
-    priv->video_width = priv->video_height = 0;
-
-  g_signal_emit (self, sushi_media_bin_signals[SIZE_CHANGE], 0);
-
-  gst_caps_unref (caps);
-  gst_object_unref (collection);
-}
-
-static gboolean
-sushi_media_bin_bus_watch (GstBus *bus, GstMessage *msg, gpointer data)
-{
-  SushiMediaBin *self = data;
-
-  switch (GST_MESSAGE_TYPE (msg))
-    {
-    case GST_MESSAGE_APPLICATION:
-      sushi_media_bin_handle_msg_application (self, msg);
-      break;
-    case GST_MESSAGE_DURATION_CHANGED:
-      sushi_media_bin_update_duration (self);
-      break;
-    case GST_MESSAGE_EOS:
-      sushi_media_bin_handle_msg_eos (self, msg);
-      break;
-    case GST_MESSAGE_ERROR:
-      return sushi_media_bin_handle_msg_error (self, msg);
-    case GST_MESSAGE_STATE_CHANGED:
-      sushi_media_bin_handle_msg_state_changed (self, msg);
-      break;
-    case GST_MESSAGE_STREAMS_SELECTED:
-      sushi_media_bin_handle_streams_selected (self, msg);
-      break;
-    case GST_MESSAGE_TAG:
-      sushi_media_bin_handle_msg_tag (self, msg);
-      break;
-    default:
-      break;
-    }
-
-  return G_SOURCE_CONTINUE;
-}
-
-static void
-sushi_media_bin_init_playbin (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv = SMB_PRIVATE (self);
-
-  priv->play = gst_element_factory_make ("playbin3", "SushiMediaBinPlayBin");
-  gst_object_ref_sink (priv->play);
-
-  /* Setup volume */
-  /* NOTE: Bidirectional binding makes the app crash on X11 */
-  g_object_bind_property (priv->volume_adjustment, "value",
-                          priv->play, "volume",
-                          G_BINDING_SYNC_CREATE);
-
-  /* Watch bus */
-  priv->bus = gst_pipeline_get_bus (GST_PIPELINE (priv->play));
-  gst_bus_add_watch (priv->bus, sushi_media_bin_bus_watch, self);
-}
-
-/********************************* Public API *********************************/
-
-/**
- * sushi_media_bin_new:
- * @audio_mode:
- *
- * Returns a new #SushiMediaBin
- *
- */
-GtkWidget *
-sushi_media_bin_new (gboolean audio_mode)
-{
-  return (GtkWidget*) g_object_new (SUSHI_TYPE_MEDIA_BIN,
-                                    "audio-mode", audio_mode,
-                                    NULL);
-}
-
-/**
- * sushi_media_bin_get_uri:
- * @self: a #SushiMediaBin
- *
- * Return the media URI
- */
-SMB_DEFINE_GETTER (const gchar *, uri, NULL)
-
-/**
- * sushi_media_bin_set_uri:
- * @self: a #SushiMediaBin
- * @uri:
- *
- * Sets the media URI to play
- */
-SMB_DEFINE_SETTER_STRING (uri, URI,
-  /* Make playbin show the first video frame if there is an URI
-   * and the widget is realized.
-   */
-  sushi_media_bin_update_state (self);
-
-  /* Clear tag lists */
-  if (priv->audio_tags)
-    {
-      g_clear_pointer (&priv->audio_tags, gst_tag_list_unref);
-      sushi_media_bin_post_message_application (self, "audio-tags-changed");
-    }
-
-  if (priv->video_tags)
-    {
-      g_clear_pointer (&priv->video_tags, gst_tag_list_unref);
-      sushi_media_bin_post_message_application (self, "video-tags-changed");
-    }
-
-  if (priv->text_tags)
-    {
-      g_clear_pointer (&priv->text_tags, gst_tag_list_unref);
-      sushi_media_bin_post_message_application (self, "text-tags-changed");
-    }
-)
-
-/**
- * sushi_media_bin_get_autohide_timeout:
- * @self: a #SushiMediaBin
- *
- * Returns control's auto hide timeout in seconds.
- */
-SMB_DEFINE_GETTER (gint, autohide_timeout, 0)
-
-/**
- * sushi_media_bin_set_autohide_timeout:
- * @self: a #SushiMediaBin
- * @autohide_timeout: A timeout in seconds
- *
- * Sets the timeout to auto hide controls
- */
-SMB_DEFINE_SETTER (gint, autohide_timeout, AUTOHIDE_TIMEOUT,)
-
-/**
- * sushi_media_bin_get_fullscreen:
- * @self: a #SushiMediaBin
- *
- * Returns whether video is fullscreen or not
- */
-SMB_DEFINE_GETTER (gboolean, fullscreen, FALSE)
-
-/**
- * sushi_media_bin_set_fullscreen:
- * @self: a #SushiMediaBin
- * @fullscreen:
- *
- * Sets whether to show the video in fullscreen mode or not
- */
-SMB_DEFINE_SETTER_BOOLEAN (fullscreen, FULLSCREEN,
-  /* If there is no video sink, delay fullscreen until realize event */
-  if (priv->video_sink)
-    sushi_media_bin_fullscreen_apply (self, fullscreen);
-)
-
-/**
- * sushi_media_bin_get_show_stream_info:
- * @self: a #SushiMediaBin
- *
- * Returns whether streams information are show or not
- */
-SMB_DEFINE_GETTER (gboolean, show_stream_info, FALSE)
-
-/**
- * sushi_media_bin_set_show_stream_info:
- * @self: a #SushiMediaBin
- * @show_stream_info:
- *
- * Sets whether to show stream information or not
- */
-SMB_DEFINE_SETTER_BOOLEAN (show_stream_info, SHOW_STREAM_INFO,
-
-  if (show_stream_info)
-    {
-      sushi_media_bin_update_stream_info (self);
-      gtk_widget_show (priv->info_box);
-    }
-  else
-    {
-      gint i;
-
-      gtk_widget_hide (priv->info_box);
-
-      for (i = 0; i < INFO_N_COLUMNS; i++)
-        gtk_label_set_label (priv->info_column_label[i], "");
-    }
-)
-
-/**
- * sushi_media_bin_get_title:
- * @self: a #SushiMediaBin
- *
- * Returns the media title if any
- */
-SMB_DEFINE_GETTER (const gchar *, title, NULL)
-
-/**
- * sushi_media_bin_set_title:
- * @self: a #SushiMediaBin
- * @title:
- *
- * Sets the media title.
- * By default SushiMediaBin will use the title from the media metadata
- */
-SMB_DEFINE_SETTER_STRING (title, TITLE,
-  gtk_label_set_label (priv->title_label, title);
-  gtk_widget_set_visible (GTK_WIDGET (priv->title_label), title != NULL);
-  priv->title_user_set = TRUE;
-)
-
-/**
- * sushi_media_bin_get_description:
- * @self: a #SushiMediaBin
- *
- * Returns the media description if any
- */
-SMB_DEFINE_GETTER (const gchar *, description, NULL)
-
-/**
- * sushi_media_bin_set_description:
- * @self: a #SushiMediaBin
- * @description:
- *
- * Sets the media description.
- * By default SushiMediaBin will use the description from the media metadata
- */
-SMB_DEFINE_SETTER_STRING (description, DESCRIPTION,
-  priv->description_user_set = TRUE;
-)
-
-/**
- * sushi_media_bin_get_volume:
- * @self: a #SushiMediaBin
- *
- * Returns audio volume from 0.0 to 1.0
- */
-SMB_DEFINE_GETTER_FULL (gdouble, volume, 1.0,
-  return gtk_adjustment_get_value (SMB_PRIVATE (self)->volume_adjustment);
-)
-
-/**
- * sushi_media_bin_set_volume:
- * @self: a #SushiMediaBin
- * @volume: from 0.0 to 1.0
- *
- * Sets the audio volume
- */
-SMB_DEFINE_SETTER_FULL (gdouble, volume, VOLUME,
-  volume = CLAMP (volume, 0.0, 1.0),
-  gtk_adjustment_get_value (priv->volume_adjustment) != volume,
-  gtk_adjustment_set_value (priv->volume_adjustment, volume),
-)
-
-/**
- * sushi_media_bin_get_audio_tags:
- * @self: a #SushiMediaBin
- *
- * Returns a #GstTagList with the audio tags for the played media
- */
-SMB_DEFINE_GETTER_FULL (GstTagList *, audio_tags, NULL,
-  return (gst_tag_list_ref (SMB_PRIVATE (self)->audio_tags));
-)
-
-/**
- * sushi_media_bin_get_video_tags:
- * @self: a #SushiMediaBin
- *
- * Returns a #GstTagList with the video tags for the played media
- */
-SMB_DEFINE_GETTER_FULL (GstTagList *, video_tags, NULL,
-  return (gst_tag_list_ref (SMB_PRIVATE (self)->video_tags));
-)
-
-/**
- * sushi_media_bin_play:
- * @self: a #SushiMediaBin
- *
- * Start media playback
- */
-void
-sushi_media_bin_play (SushiMediaBin *self)
-{
-  SushiMediaBinPrivate *priv;
-
-  g_return_if_fail (SUSHI_IS_MEDIA_BIN (self));
-  priv = SMB_PRIVATE (self);
-
-  g_object_set (priv->play, "uri", priv->uri, NULL);
-
-  sushi_media_bin_set_state (self, GST_STATE_PLAYING);
-}
-
-/**
- * sushi_media_bin_pause:
- * @self: a #SushiMediaBin
- *
- * Pause media playback
- */
-void
-sushi_media_bin_pause (SushiMediaBin *self)
-{
-  g_return_if_fail (SUSHI_IS_MEDIA_BIN (self));
-  sushi_media_bin_set_state (self, GST_STATE_PAUSED);
-}
-
-/**
- * sushi_media_bin_stop:
- * @self: a #SushiMediaBin
- *
- * Stop media playback
- */
-void
-sushi_media_bin_stop (SushiMediaBin *self)
-{
-  g_return_if_fail (SUSHI_IS_MEDIA_BIN (self));
-  sushi_media_bin_set_state (self, GST_STATE_NULL);
-}
-
-static void
-sushi_media_bin_free_pixbuf (guchar *pixels, gpointer data)
-{
-  gst_sample_unref (GST_SAMPLE (data));
-}
-
-/**
- * sushi_media_bin_screenshot:
- * @self: a #SushiMediaBin
- * @width: desired screenshot width or -1 for original size
- * @height: desired screenshot height or -1 for original size
- *
- * Takes a screenshot of the current frame.
- *
- * Returns: (transfer full): a new #GdkPixbuf
- */
-GdkPixbuf *
-sushi_media_bin_screenshot (SushiMediaBin *self, gint width, gint height)
-{
-  SushiMediaBinPrivate *priv;
-  GdkPixbuf *retval = NULL;
-  GstSample *sample;
-  GstCaps   *caps;
-  GstBuffer *buffer;
-  GstMemory *memory = NULL;
-  GstMapInfo info;
-
-  g_return_val_if_fail (SUSHI_IS_MEDIA_BIN (self), NULL);
-  priv = SMB_PRIVATE (self);
-
-  /* Create a caps object with the desired format */
-  caps = gst_caps_new_simple ("video/x-raw",
-                              "format", G_TYPE_STRING, "RGB",
-                              "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
-                              NULL);
-
-  if (width >= 0 && width >= 0)
-    gst_caps_set_simple (caps,
-                         "width", G_TYPE_INT, width,
-                         "height", G_TYPE_INT, height,
-                         NULL);
-
-  /* Get current sample in RGB */
-  g_signal_emit_by_name (priv->play, "convert-sample", caps, &sample);
-  gst_caps_unref (caps);
-
-  if (sample)
-    {
-      GstStructure *caps_struct;
-
-      if (!(caps = gst_sample_get_caps (sample)))
-        return NULL;
-
-      caps_struct = gst_caps_get_structure (caps, 0);
-
-      if (!(gst_structure_get_int (caps_struct, "width", &width) &&
-            gst_structure_get_int (caps_struct, "height", &height)))
-        return NULL;
-    }
-  else
-    {
-      /* FIXME: gst does not suport converting from video/x-raw(memory:GLMemory) */
-      g_warning ("Could not get video sample");
-      return NULL;
-    }
-
-  /* The buffer remains valid as long as sample is valid */
-  if ((buffer = gst_sample_get_buffer (sample)) && 
-      (memory = gst_buffer_get_memory (buffer, 0)) &&
-      gst_memory_map (memory, &info, GST_MAP_READ))
-    {
-      /* Create pixbuf from data with custom destroy function to free sample */
-      retval = gdk_pixbuf_new_from_data (info.data,
-                                         GDK_COLORSPACE_RGB, FALSE, 8,
-                                         width, height,
-                                         GST_ROUND_UP_4 (width * 3),
-                                         sushi_media_bin_free_pixbuf,
-                                         sample);
-      gst_memory_unmap (memory, &info);
-    }
-  else
-    {
-      g_warning ("Could not map memory from sample");
-      gst_sample_unref (sample);
-    }
-
-  gst_memory_unref (memory);
-
-  return retval;
-}
diff --git a/src/libsushi/sushi-media-bin.css b/src/libsushi/sushi-media-bin.css
deleted file mode 100644
index fb09bb6..0000000
--- a/src/libsushi/sushi-media-bin.css
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * sushi-media-bin.css
- * Based on ekn-media-bin.css from:
- * https://github.com/endlessm/eos-knowledge-lib/tree/master/lib/eosknowledgeprivate
- *
- * Copyright (C) 2016 Endless Mobile, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- *
- * Author: Juan Pablo Ugarte <ugarte@endlessm.com>
- *
- */
-
-@define-color audio-bg-color    #4c4c4c;
-
-sushi-media-bin {
-  background: black;
-}
-
-sushi-media-bin box.overlay-bar.top {
-  padding: 12px;
-}
-
-sushi-media-bin label.title {
-  font: 18px Sans;
-}
-
-/* Controls */
-sushi-media-bin box.overlay-bar.bottom {
-    padding: 13px;
-    border-radius: 5px;
-}
-
-sushi-media-bin box.overlay-bar.bottom label {
-    margin: 0px 6px;
-}
-
-/* Audio nodes */
-sushi-media-bin box.audio > box {
-  background: @audio-bg-color;
-}
-
-sushi-media-bin box.audio > scale {
-  margin: 0;
-  padding: 1px 0px;
-  background-image: linear-gradient(to top, @audio-bg-color 3px, transparent 3px);
-}
-
-sushi-media-bin box.audio > scale trough {
-  padding: 1px 0px;
-  background: #707070;
-}
-
-/* Setup key bindings */
-@binding-set smb-binding-set {
-  bind "space"     { "toggle" ("playback") };
-  bind "f"         { "toggle" ("fullscreen") };
-  bind "i"         { "toggle" ("show-stream-info") };
-  bind "Right"     { "seek" (5) };
-  bind "Left"      { "seek" (-5) };
-  bind "Up"        { "seek" (60) };
-  bind "Down"      { "seek" (-60) };
-  bind "Page_Up"   { "seek" (300) };
-  bind "Page_Down" { "seek" (-300) };
-  bind "Home"      { "seek" (0) };
-}
-
-@binding-set smb-fullscreen-binding-set {
-  bind "Escape" { "toggle" ("fullscreen") };
-}
-
-sushi-media-bin {
-  -gtk-key-bindings: smb-binding-set;
-}
-
-sushi-media-bin.fullscreen {
-  -gtk-key-bindings: smb-binding-set, smb-fullscreen-binding-set;
-}
diff --git a/src/libsushi/sushi-media-bin.h b/src/libsushi/sushi-media-bin.h
deleted file mode 100644
index 900675d..0000000
--- a/src/libsushi/sushi-media-bin.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
-/*
- * sushi-media-bin.h
- * Based on ekn-media-bin.h from:
- * https://github.com/endlessm/eos-knowledge-lib/tree/master/lib/eosknowledgeprivate
- *
- * Copyright (C) 2016 Endless Mobile, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- *
- * Author: Juan Pablo Ugarte <ugarte@endlessm.com>
- *
- */
-
-#pragma once
-
-#include <gst/gst.h>
-#include <gtk/gtk.h>
-
-G_BEGIN_DECLS
-
-#define SUSHI_TYPE_MEDIA_BIN (sushi_media_bin_get_type ())
-G_DECLARE_FINAL_TYPE (SushiMediaBin, sushi_media_bin, SUSHI, MEDIA_BIN, GtkBox)
-
-GtkWidget     *sushi_media_bin_new                  (gboolean audio_mode);
-
-const gchar   *sushi_media_bin_get_uri              (SushiMediaBin *self);
-void           sushi_media_bin_set_uri              (SushiMediaBin *self,
-                                                     const gchar *uri);
-
-gdouble        sushi_media_bin_get_volume           (SushiMediaBin *self);
-void           sushi_media_bin_set_volume           (SushiMediaBin *self,
-                                                     gdouble      volume);
-
-gint           sushi_media_bin_get_autohide_timeout (SushiMediaBin *self);
-void           sushi_media_bin_set_autohide_timeout (SushiMediaBin *self,
-                                                     gint         autohide_timeout);
-
-gboolean       sushi_media_bin_get_fullscreen       (SushiMediaBin *self);
-void           sushi_media_bin_set_fullscreen       (SushiMediaBin *self,
-                                                     gboolean     fullscreen);
-
-gboolean       sushi_media_bin_get_show_stream_info (SushiMediaBin *self);
-void           sushi_media_bin_set_show_stream_info (SushiMediaBin *self,
-                                                     gboolean     show_stream_info);
-
-const gchar   *sushi_media_bin_get_title            (SushiMediaBin *self);
-void           sushi_media_bin_set_title            (SushiMediaBin *self,
-                                                     const gchar *title);
-
-const gchar   *sushi_media_bin_get_description      (SushiMediaBin *self);
-void           sushi_media_bin_set_description      (SushiMediaBin *self,
-                                                     const gchar *description);
-
-GstTagList    *sushi_media_bin_get_audio_tags       (SushiMediaBin *self);
-GstTagList    *sushi_media_bin_get_video_tags       (SushiMediaBin *self);
-
-void           sushi_media_bin_play                 (SushiMediaBin *self);
-void           sushi_media_bin_pause                (SushiMediaBin *self);
-void           sushi_media_bin_stop                 (SushiMediaBin *self);
-
-GdkPixbuf     *sushi_media_bin_screenshot           (SushiMediaBin *self,
-                                                     gint         width,
-                                                     gint         height);
-
-G_END_DECLS
diff --git a/src/libsushi/sushi-utils.c b/src/libsushi/sushi-utils.c
index 77fb64e..c304339 100644
--- a/src/libsushi/sushi-utils.c
+++ b/src/libsushi/sushi-utils.c
@@ -29,13 +29,20 @@
 #include <gtk/gtk.h>
 
 #ifdef GDK_WINDOWING_X11
-#include <gdk/gdkx.h>
+#include <gdk/x11/gdkx.h>
 #endif
 #ifdef GDK_WINDOWING_WAYLAND
-#include <gdk/gdkwayland.h>
+#include <gdk/wayland/gdkwayland.h>
 #endif
 
+#include <gst/tag/tag.h>
+#include <gst/pbutils/pbutils.h>
+
 #include "externalwindow.h"
+#define I_KNOW_THE_PAPERS_LIBS_ARE_UNSTABLE_AND_HAVE_TALKED_WITH_THE_AUTHORS
+#include <papers-view.h>
+#include <papers-document.h>
+
 
 void
 sushi_window_set_child_of_external (GtkWindow *window,
@@ -49,64 +56,10 @@ sushi_window_set_child_of_external (GtkWindow *window,
   if (!external_window)
     return;
 
-  external_window_set_parent_of (external_window,
-                                 gtk_widget_get_window (GTK_WIDGET (window)));
+  external_window_set_parent_of (external_window, window);
   g_object_unref (external_window);
 }
 
-/**
- * sushi_get_evince_document_from_job:
- * @job:
- * @error:
- *
- * Returns: (transfer none):
- */
-EvDocument *
-sushi_get_evince_document_from_job (EvJob   *job,
-                                    GError **error)
-{
-  if (job->failed) {
-    g_propagate_error (error, job->error);
-    return NULL;
-  }
-
-  return job->document;
-}
-
-/**
- * sushi_query_supported_document_types:
- *
- * Returns: (transfer full):
- */
-gchar **
-sushi_query_supported_document_types (void)
-{
-  GList *infos, *l;
-  gchar **retval = NULL;
-  GPtrArray *array;
-  EvTypeInfo *info;
-  gint idx;
-
-  infos = ev_backends_manager_get_all_types_info ();
-
-  if (infos == NULL)
-    return NULL;
-
-  array = g_ptr_array_new ();
-
-  for (l = infos; l != NULL; l = l->next) {
-    info = l->data;
-
-    for (idx = 0; info->mime_types[idx] != NULL; idx++)
-      g_ptr_array_add (array, g_strdup (info->mime_types[idx]));
-  }
-
-  g_ptr_array_add (array, NULL);
-  retval = (gchar **) g_ptr_array_free (array, FALSE);
-
-  return retval;
-}
-
 static void load_libreoffice (GTask *task);
 
 typedef struct {
@@ -115,7 +68,6 @@ typedef struct {
 
   gboolean checked_libreoffice_flatpak;
   gboolean have_libreoffice_flatpak;
-  GPid libreoffice_pid;
 } TaskData;
 
 static void
@@ -126,11 +78,6 @@ task_data_free (TaskData *data)
     g_free (data->pdf_path);
   }
 
-  if (data->libreoffice_pid != -1) {
-    kill (data->libreoffice_pid, SIGKILL);
-    data->libreoffice_pid = -1;
-  }
-
   g_clear_object (&data->file);
   g_free (data);
 }
@@ -181,15 +128,22 @@ libreoffice_missing (GTask *task)
 }
 
 static void
-libreoffice_child_watch_cb (GPid pid,
-                            gint status,
-                            gpointer user_data)
+libreoffice_done_cb (GObject      *object,
+                     GAsyncResult *res,
+                     gpointer      user_data)
 {
-  g_autoptr(GTask) task = user_data;
+  GTask *task = user_data;
+  g_autoptr(GError) error = NULL;
   TaskData *data = g_task_get_task_data (task);
-
-  g_spawn_close_pid (pid);
-  data->libreoffice_pid = -1;
+  GSubprocess *subprocess = G_SUBPROCESS (object);
+
+  if (!g_subprocess_wait_finish (subprocess, res, &error))
+  {
+    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+      g_subprocess_force_exit (subprocess);
+    else
+      g_warning ("Error converting libreoffice file: %s", error->message);
+  }
 
   g_task_return_pointer (task, g_file_new_for_path (data->pdf_path), g_object_unref);
 }
@@ -203,7 +157,7 @@ check_libreoffice_flatpak (GTask       *task,
   const gchar *check_argv[] = { flatpak_path, "info", LIBREOFFICE_FLATPAK, NULL };
   g_autoptr(GError) error = NULL;
   gboolean ret;
-  gint exit_status = -1;
+  gint wait_status = -1;
   TaskData *data = g_task_get_task_data (task);
 
   if (data->checked_libreoffice_flatpak)
@@ -217,16 +171,16 @@ check_libreoffice_flatpak (GTask       *task,
                       G_SPAWN_STDOUT_TO_DEV_NULL,
                       NULL, NULL,
                       NULL, NULL,
-                      &exit_status, &error);
+                      &wait_status, &error);
 
   if (ret) {
     g_autoptr(GError) child_error = NULL;
-    if (g_spawn_check_exit_status (exit_status, &child_error)) {
+    if (g_spawn_check_wait_status (wait_status, &child_error)) {
       g_debug ("Found LibreOffice flatpak!");
       data->have_libreoffice_flatpak = TRUE;
     } else {
       g_debug ("LibreOffice flatpak not found, flatpak info returned %i (%s)",
-               exit_status, child_error->message);
+               wait_status, child_error->message);
     }
   } else {
     g_warning ("Error while checking for LibreOffice flatpak: %s",
@@ -245,8 +199,8 @@ load_libreoffice (GTask *task)
   g_auto(GStrv) argv = NULL;
   g_autoptr(GError) error = NULL;
   gboolean use_flatpak = FALSE;
-  gboolean res;
-  GPid pid;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  GCancellable *cancellable = g_task_get_cancellable (task);
   TaskData *data = g_task_get_task_data (task);
 
   flatpak_path = g_find_program_in_path ("flatpak");
@@ -317,28 +271,24 @@ load_libreoffice (GTask *task)
   command = g_strjoinv (" ", (gchar **) argv);
   g_debug ("Executing LibreOffice command: %s", command);
 
-  res = g_spawn_async (NULL, (gchar **) argv, NULL,
-                       G_SPAWN_DO_NOT_REAP_CHILD,
-                       NULL, NULL,
-                       &pid, &error);
+  subprocess = g_subprocess_newv ((const char **) argv, G_SUBPROCESS_FLAGS_NONE, &error);
 
-  if (!res) {
+  if (error) {
     g_warning ("Error while spawning libreoffice: %s", error->message);
     return;
   }
 
-  g_child_watch_add (pid, libreoffice_child_watch_cb, g_object_ref (task));
-  data->libreoffice_pid = pid;
+  g_subprocess_wait_async (subprocess, cancellable, libreoffice_done_cb, task);
 }
 
 void
-sushi_convert_libreoffice (GFile *file,
-                           GAsyncReadyCallback callback,
-                           gpointer user_data)
+sushi_convert_libreoffice (GFile               *file,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
 {
-  GTask *task = g_task_new (NULL, NULL, callback, user_data);
+  GTask *task = g_task_new (NULL, cancellable, callback, user_data);
   TaskData *data = g_new0 (TaskData, 1);
-  data->libreoffice_pid = -1;
   data->file = g_object_ref (file);
 
   g_task_set_task_data (task, data, (GDestroyNotify) task_data_free);
@@ -409,3 +359,89 @@ sushi_running_under_wayland (GdkDisplay *display)
 
   return FALSE;
 }
+
+struct _SushiDiscoverer {
+  GObject parent;
+
+  GstDiscoverer *disco;
+  const GstTagList *tag_list;
+};
+
+G_DEFINE_TYPE(SushiDiscoverer, sushi_discoverer, G_TYPE_OBJECT)
+
+enum {
+  TAGS_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint disco_signals[LAST_SIGNAL] = { 0, };
+
+static void
+discovered_cb (SushiDiscoverer   *self,
+               GstDiscovererInfo *info,
+               GError            *error)
+{
+  self->tag_list = gst_discoverer_info_get_tags (info);
+
+  g_signal_emit (self, disco_signals[TAGS_CHANGED], 0);
+}
+
+static void
+sushi_discoverer_finalize (GObject *object)
+{
+  SushiDiscoverer *self = SUSHI_DISCOVERER (object);
+
+  if (self->disco)
+    gst_discoverer_stop (self->disco);
+
+  g_clear_object (&self->disco);
+}
+
+const GstTagList *
+sushi_discoverer_get_tag_list (SushiDiscoverer *self)
+{
+  return self->tag_list;
+}
+
+static void
+sushi_discoverer_class_init (SushiDiscovererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = sushi_discoverer_finalize;
+
+  disco_signals[TAGS_CHANGED] = g_signal_new ("tags-changed",
+                                              G_TYPE_FROM_CLASS (object_class),
+                                              G_SIGNAL_RUN_LAST,
+                                              0, NULL, NULL, NULL,
+                                              G_TYPE_NONE, 0);
+}
+
+static void
+sushi_discoverer_init (SushiDiscoverer *self)
+{
+  g_autoptr (GError) error = NULL;
+
+  gst_init_check (NULL, NULL, NULL);
+  self->disco = gst_discoverer_new (GST_SECOND * 60, &error);
+
+  if (error)
+    {
+      g_warning ("Error creating GST discoverer: %s", error->message);
+      return;
+    }
+
+  g_signal_connect_swapped (self->disco, "discovered", G_CALLBACK (discovered_cb), self);
+  gst_discoverer_start (self->disco);
+}
+
+SushiDiscoverer *
+sushi_discoverer_new (const char *uri)
+{
+  SushiDiscoverer *self = g_object_new (SUSHI_TYPE_DISCOVERER, NULL);
+
+  if (self->disco)
+    gst_discoverer_discover_uri_async (self->disco, uri);
+
+  return self;
+}
diff --git a/src/libsushi/sushi-utils.h b/src/libsushi/sushi-utils.h
index 814174b..e960761 100644
--- a/src/libsushi/sushi-utils.h
+++ b/src/libsushi/sushi-utils.h
@@ -26,20 +26,19 @@
 #ifndef __SUSHI_UTILS_H__
 #define __SUSHI_UTILS_H__
 
-#include <evince-document.h>
-#include <evince-view.h>
 #include <gdk/gdk.h>
 #include <gio/gio.h>
+#include <gobject/gobject.h>
 #include <gst/gst.h>
+#include <gtk/gtk.h>
 
 G_BEGIN_DECLS
 
-gchar **       sushi_query_supported_document_types (void);
-
-EvDocument *   sushi_get_evince_document_from_job (EvJob   *job,
-                                                   GError **error);
+#define SUSHI_TYPE_DISCOVERER (sushi_discoverer_get_type())
+G_DECLARE_FINAL_TYPE (SushiDiscoverer, sushi_discoverer, SUSHI, DISCOVERER, GObject)
 
 void           sushi_convert_libreoffice (GFile *file,
+                                          GCancellable *cancellable,
                                           GAsyncReadyCallback callback,
                                           gpointer user_data);
 GFile *        sushi_convert_libreoffice_finish (GAsyncResult *result,
@@ -59,6 +58,9 @@ void           sushi_window_set_child_of_external (GtkWindow *window,
                                                    const char *handle);
 gboolean       sushi_running_under_wayland (GdkDisplay *display);
 
+SushiDiscoverer * sushi_discoverer_new (const char *uri);
+const GstTagList * sushi_discoverer_get_tag_list (SushiDiscoverer *self);
+
 G_END_DECLS
 
 #endif /* __SUSHI_UTILS_H__ */
diff --git a/src/org.gnome.NautilusPreviewer.src.gresource.xml.in b/src/org.gnome.NautilusPreviewer.src.gresource.xml.in
index 10b12cf..21daee1 100644
--- a/src/org.gnome.NautilusPreviewer.src.gresource.xml.in
+++ b/src/org.gnome.NautilusPreviewer.src.gresource.xml.in
@@ -13,10 +13,10 @@
     <file>viewers/audio.js</file>
     <file>viewers/evince.js</file>
     <file>viewers/font.js</file>
-    <file>viewers/gst.js</file>
     <file>viewers/html.js</file>
     <file>viewers/image.js</file>
     <file>viewers/libreoffice.js</file>
     <file>viewers/text.js</file>
+    <file>viewers/video.js</file>
   </gresource>
 </gresources>
diff --git a/src/ui/application.js b/src/ui/application.js
index f3e6786..89b63f5 100644
--- a/src/ui/application.js
+++ b/src/ui/application.js
@@ -23,7 +23,7 @@
  *
  */
 
-const {Gio, GLib, GObject, Gtk} = imports.gi;
+const {Adw, Gio, GLib, GObject, Gtk} = imports.gi;
 
 const ByteArray = imports.byteArray;
 
@@ -71,12 +71,7 @@ var NautilusPreviewer2Skeleton = class extends NautilusPreviewerSkeleton {
     }
 }
 
-var Application = GObject.registerClass(class Application extends Gtk.Application {
-    vfunc_startup() {
-        super.vfunc_startup();
-
-        this._defineStyleAndThemes();
-    }
+var Application = GObject.registerClass(class Application extends Adw.Application {
 
     vfunc_dbus_register(connection, path) {
         let actualPath = `/org/gnome/${pkg.name.split('.').at(-1)}`;
@@ -107,21 +102,16 @@ var Application = GObject.registerClass(class Application extends Gtk.Applicatio
         this._skeleton2.impl.emit_property_changed(
             'Visible', new GLib.Variant('b', true));
 
-        this._mainWindow.connect('destroy', () => {
+        this._mainWindow.connect('close-request', () => {
             this._mainWindow = null;
             this._skeleton2.impl.emit_property_changed(
                 'Visible', new GLib.Variant('b', false));
         });
     }
 
-    _defineStyleAndThemes() {
-        let settings = Gtk.Settings.get_default();
-        settings.gtk_application_prefer_dark_theme = true;
-    }
-
     close() {
         if (this._mainWindow)
-            this._mainWindow.destroy();
+            this._mainWindow.close();
     }
 
     emitSelectionEvent(direction) {
@@ -145,10 +135,11 @@ var Application = GObject.registerClass(class Application extends Gtk.Applicatio
         if (closeIfAlreadyShown &&
             this._mainWindow.file &&
             this._mainWindow.file.equal(file)) {
-            this._mainWindow.destroy();
+            this._mainWindow.close();
         } else {
             this._mainWindow.setParent(windowHandle);
             this._mainWindow.setFile(file);
+            this._mainWindow.present();
         }
     }
 });
diff --git a/src/ui/fallbackRenderer.js b/src/ui/fallbackRenderer.js
index 7013de4..98c338b 100644
--- a/src/ui/fallbackRenderer.js
+++ b/src/ui/fallbackRenderer.js
@@ -23,7 +23,7 @@
  *
  */
 
-const {Gio, GLib, GObject, Gtk, Pango} = imports.gi;
+const {Gdk, Gio, GLib, GObject, Gtk, Pango} = imports.gi;
 const Gettext = imports.gettext;
 
 const Renderer = imports.ui.renderer;
@@ -187,47 +187,49 @@ var FallbackRenderer = GObject.registerClass({
         super._init({ orientation: Gtk.Orientation.HORIZONTAL,
                       spacing: 6 });
 
-        this._image = new Gtk.Image();
-        this.pack_start(this._image, false, false, 0);
+        this._image = new Gtk.Picture ();
+
         this._updateIcon(new Gio.ThemedIcon({ name: 'text-x-generic' }));
+        this.append(this._image);
 
         let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL,
                                  spacing: 1,
-                                 margin_top: 48,
+                                 margin_top: 12,
                                  margin_start: 12,
-                                 margin_end: 12 });
-        this.pack_start(vbox, false, false, 0);
+                                 margin_end: 12,
+                                 margin_bottom: 12,
+                                 valign: Gtk.Align.CENTER,
+                                 vexpand: true});
+        this.append(vbox);
 
         let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
-                                 spacing: 6 });
-        vbox.pack_start(hbox, false, false, 0);
+                                 spacing: 6});
+        vbox.append(hbox);
 
         this._titleLabel = new Gtk.Label({ max_width_chars: 48,
                                            ellipsize: Pango.EllipsizeMode.MIDDLE });
         this._titleLabel.set_halign(Gtk.Align.START);
-        hbox.pack_start(this._titleLabel, false, false, 0);
+        hbox.append(this._titleLabel);
 
         this._spinner = new Gtk.Spinner();
-        hbox.pack_start(this._spinner, false, false, 0);
+        hbox.append(this._spinner);
         this._spinner.start();
-        this._spinner.show();
 
-        this._typeLabel = new Gtk.Label({ no_show_all: true });
+        this._typeLabel = new Gtk.Label();
         this._typeLabel.set_halign(Gtk.Align.START);
-        vbox.pack_start(this._typeLabel, false, false, 0);
+        vbox.append(this._typeLabel);
 
         this._sizeLabel = new Gtk.Label();
         this._sizeLabel.set_halign(Gtk.Align.START);
-        vbox.pack_start(this._sizeLabel, false, false, 0);
+        vbox.append(this._sizeLabel);
 
         this._dateLabel = new Gtk.Label();
         this._dateLabel.set_halign(Gtk.Align.START);
-        vbox.pack_start(this._dateLabel, false, false, 0);
+        vbox.append(this._dateLabel);
 
-        this._cancellable = new Gio.Cancellable();
-        loadFile(file, fileInfo, this._cancellable, this._onFileInfoUpdated.bind(this));
+        this.cancellable = new Gio.Cancellable();
+        loadFile(file, fileInfo, this.cancellable, this._onFileInfoUpdated.bind(this));
 
-        this.connect('destroy', this._onDestroy.bind(this));
         this.isReady();
     }
 
@@ -276,34 +278,22 @@ var FallbackRenderer = GObject.registerClass({
     }
 
     _updateIcon(icon) {
-        let iconTheme = Gtk.IconTheme.get_default();
-        let iconInfo = iconTheme.lookup_by_gicon_for_scale(icon, 256,
-            this._image.scale_factor, 0);
-        if (!iconInfo)
-            return;
-
-        try {
-            let surface = iconInfo.load_surface(this._image.get_window());
-            this._image.surface = surface;
-        } catch (e) {
-            logError(e, `Error loading surface for icon ${icon.to_string()}`);
-        }
+        let iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
+        let paintable = iconTheme.lookup_by_gicon(icon, 256, this._image.scale_factor, 0, 0);
+        if (paintable)
+            this._image.set_paintable (paintable);
     }
 
     _onFileInfoUpdated(state) {
         if (!state.loading) {
             this._spinner.stop();
-            this._spinner.hide();
+            this._spinner.set_visible(false);
         }
 
         this._applyIcon(state);
         this._applyLabels(state);
     }
 
-    _onDestroy() {
-        this._cancellable.cancel();
-    }
-
     get hasToolbar() {
         return false;
     }
diff --git a/src/ui/main.js b/src/ui/main.js
index 9f8638a..a51dc44 100644
--- a/src/ui/main.js
+++ b/src/ui/main.js
@@ -25,17 +25,18 @@
 pkg.initGettext();
 pkg.initFormat();
 pkg.require({
-    EvinceDocument: '3.0',
-    EvinceView: '3.0',
-    Gdk: '3.0',
+    Adw: '1',
+    PapersDocument: '4.0',
+    PapersView: '4.0',
+    Gdk: '4.0',
     GdkPixbuf: '2.0',
     Gio: '2.0',
     GLib: '2.0',
     GObject: '2.0',
     Gst: '1.0',
     GstTag: '1.0',
-    Gtk: '3.0',
-    GtkSource: '4',
+    Gtk: '4.0',
+    GtkSource: '5',
     Pango: '1.0',
     Soup: '3.0',
     Sushi: '1.0',
diff --git a/src/ui/mainWindow.js b/src/ui/mainWindow.js
index 23c1a79..9b7511a 100644
--- a/src/ui/mainWindow.js
+++ b/src/ui/mainWindow.js
@@ -40,24 +40,25 @@ const Embed = GObject.registerClass(class Embed extends Gtk.Overlay {
         return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
     }
 
-    vfunc_get_preferred_width() {
-        let [min, nat] = super.vfunc_get_preferred_width();
+    vfunc_measure(orientation, for_size) {
+        if (orientation == Gtk.Orientation.VERTICAL) {
+            let [min, nat, min_baseline, nat_baseline] = super.vfunc_measure(orientation, for_size);
 
-        min = Math.max(min, Constants.VIEW_MIN);
-        nat = Math.max(nat, Constants.VIEW_MIN);
-
-        return [min, nat];
-    }
-
-    vfunc_get_preferred_height_for_width(forWidth) {
-        let [min, nat] = super.vfunc_get_preferred_height_for_width(forWidth);
-
-        if (forWidth <= Constants.VIEW_MIN) {
             min = Math.max(min, Constants.VIEW_MIN);
             nat = Math.max(nat, Constants.VIEW_MIN);
+
+            return [min, nat, -1, -1];
         }
+        else {
+            let [min, nat, min_baseline, nat_baseline] = super.vfunc_measure(orientation, for_size);
+
+            if (forWidth <= Constants.VIEW_MIN) {
+                min = Math.max(min, Constants.VIEW_MIN);
+                nat = Math.max(nat, Constants.VIEW_MIN);
+            }
 
-        return [min, nat];
+            return [min, nat];
+        }
     }
 });
 
@@ -71,10 +72,10 @@ const ErrorBox = GObject.registerClass({
                                          GObject.ParamFlags.READABLE,
                                          false)
     },
-}, class ErrorBox extends Gtk.Grid {
+}, class ErrorBox extends Gtk.Box {
     _init(file, error) {
         super._init({ orientation: Gtk.Orientation.VERTICAL,
-                      row_spacing: 12,
+                      spacing: 12,
                       hexpand: true,
                       vexpand: true,
                       halign: Gtk.Align.CENTER,
@@ -84,7 +85,7 @@ const ErrorBox = GObject.registerClass({
                                     icon_name: 'face-uncertain-symbolic',
                                     halign: Gtk.Align.CENTER,
                                     valign: Gtk.Align.CENTER });
-        this.add(image);
+        this.append(image);
 
         // TRANSLATORS: This is a filename, e.g. "image.jpg"
         let primary = _("Unable to display %s").format(file.get_basename());
@@ -93,15 +94,13 @@ const ErrorBox = GObject.registerClass({
                                            use_markup: true,
                                            halign: Gtk.Align.CENTER,
                                            valign: Gtk.Align.CENTER });
-        this.add(primaryLabel);
+        this.append(primaryLabel);
 
         let secondaryLabel = new Gtk.Label({ label: error.message,
                                              wrap: true,
                                              halign: Gtk.Align.CENTER,
                                              valign: Gtk.Align.CENTER });
-        this.add(secondaryLabel);
-
-        this.show_all();
+        this.append(secondaryLabel);
     }
 });
 
@@ -127,54 +126,30 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
         this._lastWindowSize = [0, 0];
         this.file = null;
 
-        super._init({ type: Gtk.WindowType.TOPLEVEL,
-                      skipPagerHint: true,
-                      skipTaskbarHint: true,
-                      windowPosition: Gtk.WindowPosition.CENTER,
-                      gravity: Gdk.Gravity.CENTER,
-                      application: application });
+        super._init({ application: application });
 
-        this._titlebar = new Gtk.HeaderBar({ show_close_button: true,
-                                             decoration_layout: _getDecorationLayout() });
+        this._titlebar = new Gtk.HeaderBar({ decoration_layout: _getDecorationLayout() });
         this.set_titlebar(this._titlebar);
 
-        this._openButton = new Gtk.Button();
+        this._openButton = new Gtk.Button({ label: _("Open") });
         this._openButton.connect('clicked', this._onFileOpenClicked.bind(this));
         this._titlebar.pack_end(this._openButton);
 
-        this.connect('motion-notify-event', this._onMotionNotifyEvent.bind(this));
-        this.connect('realize', this._onRealize.bind(this));
-
-        let eventBox = new Gtk.EventBox({ visible_window: false });
-        eventBox.connect('button-press-event', this._onButtonPressEvent.bind(this));
-        this.add(eventBox);
+        let motion = new Gtk.EventControllerMotion();
+        this.add_controller(motion);
+        motion.connect('motion', this._onMotionNotifyEvent.bind(this));
 
         this._embed = new Embed();
-        eventBox.add(this._embed);
-
-        // call show_all() early when there's still no child Renderer, because show_all() later
-        // when the Renderer is a child may have unexpected results, see comments in !49
-        this.show_all();
 
-        // but leave MainWindow not visible, because we want it to be firstly shown/mapped
-        // when it has its final dimemnsions i.e. when the Renderer has emmitted the 'ready'
-        // signal, i.e. on the _onRendererReady() handler.
-        this.hide();
+        this.set_child(this._embed);
 
         this._defineActions();
     }
 
-    _onRealize() {
-        // don't support maximize and minimize
-        this.get_window().set_functions(Gdk.WMFunction.MOVE |
-                                        Gdk.WMFunction.RESIZE |
-                                        Gdk.WMFunction.CLOSE);
-    }
-
     _defineActions() {
         let quit = new Gio.SimpleAction({ name: 'quit' });
         quit.connect('activate', () => {
-            this.destroy();
+            this.close();
         });
         this.application.set_accels_for_action('win.quit', ['q', 'Escape', 'space']);
         this.add_action(quit);
@@ -202,19 +177,6 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
         _addSelectAction('select-down', 'Down', Gtk.DirectionType.DOWN);
     }
 
-    _onButtonPressEvent(window, event) {
-        if (!this._renderer.moveOnClick)
-            return false;
-
-        let [, rootX, rootY] = event.get_root_coords();
-        let [, button] = event.get_button();
-        this.begin_move_drag(button,
-                             rootX, rootY,
-                             event.get_time());
-
-        return false;
-    }
-
     _onMotionNotifyEvent() {
         if (this._renderer.toolbar)
             this._renderer.toolbar.resetTimeout();
@@ -222,6 +184,8 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
     }
 
     _reportError(error) {
+        if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
+          return;
         let renderer = new ErrorBox(this.file, error);
         this._embedRenderer(renderer);
     }
@@ -237,38 +201,19 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
         if (this._renderer.ready) {
             this._resizeWindow();
             this.queue_resize();
-            this.show();
         }
 
     }
 
     _getMaxSize() {
-        let gdkWin = this.get_window();
-        let display = this.get_display();
+        let display = Gdk.Display.get_default();
         let underWayland = Sushi.running_under_wayland(display);
-        let monitor = display.get_monitor_at_window(gdkWin);
+        let surface = this.get_surface();
+        let monitor = display.get_monitor_at_surface(surface);
         let geometry = monitor.get_geometry();
 
-        // Scale our maximum with the actual monitor geometry
-        let scaleW = 1.0;
-        let scaleH = 1.0;
-
-        // FIXME: We can only trust GTK >= 3.24.9 to report the right
-        // monitor geometry under Wayland when fractional scaling is enabled.
-        // Disable the scaling logic for older GTK versions.
-        // See https://gitlab.gnome.org/GNOME/gtk/issues/1828
-        let versionCheck = Gtk.check_version(3, 24, 9);
-        if (!versionCheck) {
-            scaleW = (geometry.width / WINDOW_MAX_W_BASE);
-            scaleH = (geometry.height / WINDOW_MAX_H_BASE);
-        }
-
-        // reduce by scale factor only under Wayland, otherwise
-        // X11 HiDPI windows are shown too small -- Issue #91
-        if (underWayland) {
-            scaleW = scaleW / this.get_scale_factor ();
-            scaleH = scaleH / this.get_scale_factor ();
-        }
+        let scaleW = (geometry.width / WINDOW_MAX_W_BASE) / this.get_scale_factor ();
+        let scaleH = (geometry.height / WINDOW_MAX_H_BASE) / this.get_scale_factor ();
 
         return [Math.floor(scaleW * WINDOW_MAX_W),
                 Math.floor(scaleH * WINDOW_MAX_H)];
@@ -282,8 +227,8 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
             return;
 
         let maxSize = this._getMaxSize();
-        let rendererSize = [this._renderer.get_preferred_width(), this._renderer.get_preferred_height()];
-        let natSize = [rendererSize[0][1], rendererSize[1][1]];
+        let rendererSize = this._renderer.get_preferred_size();
+        let natSize = [rendererSize[1].width, rendererSize[1].height];
         let windowSize;
         let resizePolicy = this._renderer.resizePolicy;
 
@@ -299,7 +244,8 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
         if ((windowSize[0] > 0 && windowSize[0] != this._lastWindowSize[0]) ||
             (windowSize[1] > 0 && windowSize[1] != this._lastWindowSize[1])) {
             this._lastWindowSize = windowSize;
-            this.resize(windowSize[0], windowSize[1]);
+            this.default_width = windowSize[0];
+            this.default_height = windowSize[1];
         }
     }
 
@@ -323,15 +269,11 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
     }
 
     _embedRenderer(renderer) {
-        if (this._renderer) {
-            this._renderer.destroy()
-            this._renderer = null;
-        }
-
+        if (this._renderer && this._renderer.cancellable)
+          this._renderer.cancellable.cancel();
         this._renderer = renderer;
-        this._renderer.show_all();
         this._renderer.expand = true;
-        this._embed.add(this._renderer);
+        this._embed.set_child(this._renderer);
 
         if (this._renderer.toolbar)
             this._embed.add_overlay(this._renderer.toolbar);
@@ -345,44 +287,18 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
         renderer.connect('error', (r, err) => { this._reportError(err); });
         renderer.connect('notify::fullscreen', this._onRendererFullscreen.bind(this));
         renderer.connect('notify::ready', this._onRendererReady.bind(this));
+        this._resizeWindow();
         this._onRendererReady();
 
         this.set_resizable(this._renderer.resizable);
         this.set_title(fileInfo.get_display_name());
     }
 
-    _updateTitlebar() {
-        try {
-            let appInfo = this.file.query_default_handler(null);
-            // TRANSLATORS: This is the display name of an application, e.g. "Open With Image Viewer"
-            this._openButton.set_label(_("Open With %s").format(appInfo.get_display_name()));
-        } catch (e) {
-            // This happens when running under flatpak, since we don't have direct access
-            // to the other applications
-            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_SUPPORTED))
-                logError(e, `Failed to query default handler for ${this.file.get_uri()}`);
-            this._openButton.set_label(_("Open"));
-        }
-    }
-
     _onFileOpenClicked() {
-        let ctx = this.get_display().get_app_launch_context();
-        ctx.set_timestamp(Gtk.get_current_event_time());
-        ctx.set_screen(this.get_screen());
-
-        // Ideally we would use gtk_show_uri_on_window() here, since it properly
-        // parents dialogs that may come from a flatpak portal over the window.
-        //
-        // Unfortunately we need to wait until the result of the launch before
-        // destroying our window, which gtk_show_uri_on_window() doesn't allow,
-        // so we use GIO directly.
-        Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(), ctx, null, (obj, result) => {
-            try {
-                Gio.AppInfo.launch_default_for_uri_finish(result);
-                this.destroy();
-            } catch (e) {
-                logError(e, `Failed to launch default handler for ${this.file.get_uri()}`);
-            }
+        let fileLauncher = new Gtk.FileLauncher({ file: this.file });
+        fileLauncher.launch(null, null, (obj, result) => {
+          obj.launch_finish(result);
+          this.close();
         });
     }
 
@@ -396,7 +312,6 @@ var MainWindow = GObject.registerClass(class MainWindow extends Gtk.ApplicationW
 
     setFile(file) {
         this.file = file;
-        this._updateTitlebar();
         this._createRenderer();
     }
 });
diff --git a/src/ui/renderer.js b/src/ui/renderer.js
index f939dcb..b64818c 100644
--- a/src/ui/renderer.js
+++ b/src/ui/renderer.js
@@ -54,10 +54,6 @@ var Renderer = GObject.registerClass({
         return true;
     }
 
-    get moveOnClick() {
-        return true;
-    }
-
     get ready() {
         return !!this._ready;
     }
@@ -76,15 +72,14 @@ var Renderer = GObject.registerClass({
 
         if (!this._toolbar) {
             this._toolbar = new RendererToolbar();
-            this.connect('destroy', () => { this._toolbar.destroy(); });
 
             this.populateToolbar(this._toolbar.box);
 
             if (this.canFullscreen) {
-                if (this._toolbar.box.get_children().length > 0)
-                    this._toolbar.box.add(new Gtk.Separator({ orientation: Gtk.Orientation.VERTICAL }));
+                if (this._toolbar.box.get_first_child())
+                    this._toolbar.box.append(new Gtk.Separator({ orientation: Gtk.Orientation.VERTICAL }));
 
-                this._toolbar.box.add(Utils.createFullscreenButton(this));
+                this._toolbar.box.append(Utils.createFullscreenButton(this));
             }
         }
 
@@ -114,9 +109,9 @@ var RendererToolbar = GObject.registerClass(class RendererToolbar extends Gtk.Re
                       transition_type: Gtk.RevealerTransitionType.CROSSFADE });
 
         this.box = new RendererToolbarBox();
-        this.add(this.box);
+        this.set_child(this.box);
 
-        this.connect('destroy', this._onDestroy.bind(this));
+        this.connect('unmap', this._onDestroy.bind(this));
     }
 
     resetTimeout() {
diff --git a/src/ui/utils.js b/src/ui/utils.js
index 5f70feb..4dc10ef 100644
--- a/src/ui/utils.js
+++ b/src/ui/utils.js
@@ -59,8 +59,8 @@ function getScaledSize(baseSize, allocSize, upscale) {
 }
 
 function createToolButton(renderer, iconName, callback) {
-    let button = Gtk.Button.new_from_icon_name(iconName, Gtk.IconSize.MENU);
-    button.set_relief(Gtk.ReliefStyle.NONE);
+    let button = Gtk.Button.new_from_icon_name(iconName);
+    button.add_css_class('flat');
     button.connect('clicked', () => {
         renderer.toolbar.resetTimeout();
         callback(button);
@@ -73,8 +73,8 @@ function createFullscreenButton(renderer) {
     return createToolButton(renderer, 'view-fullscreen-symbolic', (button) => {
         renderer.toggleFullscreen();
         if (renderer.fullscreen)
-            button.image.icon_name = 'view-restore-symbolic';
+            button.icon_name = 'view-restore-symbolic';
         else
-            button.image.icon_name = 'view-fullscreen-symbolic';
+            button.icon_name = 'view-fullscreen-symbolic';
     });
 }
diff --git a/src/viewers/audio.js b/src/viewers/audio.js
index 58620b8..4a0b1a9 100644
--- a/src/viewers/audio.js
+++ b/src/viewers/audio.js
@@ -29,23 +29,6 @@ const Constants = imports.util.constants;
 const Renderer = imports.ui.renderer;
 const TotemMimeTypes = imports.util.totemMimeTypes;
 
-function _formatTimeString(timeVal) {
-    let hours = Math.floor(timeVal / 3600);
-    timeVal -= hours * 3600;
-
-    let minutes = Math.floor(timeVal / 60);
-    timeVal -= minutes * 60;
-
-    let seconds = Math.floor(timeVal);
-
-    let str = ('%02d:%02d').format(minutes, seconds);
-    if (hours > 0) {
-        str = ('%d').format(hours) + ':' + str;
-    }
-
-    return str;
-}
-
 const COVER_ART_ARCHIVE_URL = "https://coverartarchive.org/release/%s";
 const MUSIC_BRAINZ_ASIN_FORMAT = "https://musicbrainz.org/ws/2/release/?query=release:\"%s\"AND artist:\"%s\"&limit=1&fmt=json";
 const fetchCoverArt = function(_tagList, _cancellable, _callback) {
@@ -256,20 +239,6 @@ const fetchCoverArt = function(_tagList, _cancellable, _callback) {
     _fetchFromMusicBrainz(_callback);
 }
 
-const AudioPlayer = GObject.registerClass({
-    CssName: 'toolbar',
-}, class AudioPlayer extends Sushi.MediaBin {
-    _init(file) {
-        super._init({ audio_mode: true,
-                      uri: file.get_uri(),
-                      margin_bottom: Constants.TOOLBAR_SPACING,
-                      margin_start: Constants.TOOLBAR_SPACING,
-                      margin_end: Constants.TOOLBAR_SPACING,
-                      valign: Gtk.Align.END });
-        this.get_style_context().add_class('osd');
-    }
-});
-
 const COVER_SIZE = 256;
 var Klass = GObject.registerClass({
     Implements: [Renderer.Renderer],
@@ -281,7 +250,7 @@ var Klass = GObject.registerClass({
                                          GObject.ParamFlags.READABLE,
                                          false)
     },
-}, class AudioRenderer extends Gtk.Overlay {
+}, class AudioRenderer extends Gtk.Box {
     get ready() {
         return !!this._ready;
     }
@@ -292,68 +261,55 @@ var Klass = GObject.registerClass({
     _init(file) {
         super._init();
 
+        this._player = new Gtk.Video({ file: file,
+                                       autoplay: true,
+                                       hexpand: true });
+
+        this.set_orientation(Gtk.Orientation.VERTICAL);
+
         this._coverFetched = false;
 
         let box = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
                                 spacing: 6 });
-        this.add(box);
-
-        this._player = new AudioPlayer(file);
-        this._player.connect('tags-change', (p) => {
-            this._updateFromTags(this._player.get_audio_tags());
-        });
-        this._player.connect('error', (p, error) => {
-            this.emit('error', error);
-            return false;
-        });
-        this.add_overlay(this._player);
-
-        this._autoplayId = GLib.idle_add(0, () => {
-            this._autoplayId = 0;
-            this._player.play();
-            return false;
-        });
+        this.append(box);
+        this.append(this._player);
 
         let frame = new Gtk.Frame({ height_request: COVER_SIZE,
-                                    width_request: COVER_SIZE,
-                                    shadow_type: Gtk.ShadowType.NONE });
-        box.pack_start(frame, false, false, 0);
+                                    width_request: COVER_SIZE });
+        box.append(frame);
 
         this._image = new Gtk.Image({ icon_name: 'media-optical-symbolic',
                                       pixel_size: COVER_SIZE });
-        frame.add(this._image);
+        frame.set_child(this._image);
 
         let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL,
                                  spacing: 1,
                                  margin_top: 48,
                                  margin_start: 12,
                                  margin_end: 12 });
-        box.pack_start(vbox, false, false, 0);
+        box.append(vbox);
 
         this._titleLabel = new Gtk.Label();
         this._titleLabel.set_halign(Gtk.Align.START);
-        vbox.pack_start(this._titleLabel, false, false, 0);
+        vbox.append(this._titleLabel);
 
         this._authorLabel = new Gtk.Label();
         this._authorLabel.set_halign(Gtk.Align.START);
-        vbox.pack_start(this._authorLabel, false, false, 0);
+        vbox.append(this._authorLabel);
 
         this._albumLabel = new Gtk.Label();
         this._albumLabel.set_halign(Gtk.Align.START);
-        vbox.pack_start(this._albumLabel, false, false, 0);
+        vbox.append(this._albumLabel);
 
-        this.connect('destroy', this._onDestroy.bind(this));
-        this._cancellable = new Gio.Cancellable();
-        this.isReady();
-    }
-
-    _onDestroy() {
-        if (this._autoplayId > 0) {
-            GLib.source_remove(this._autoplayId);
-            this._autoplayId = 0;
-        }
+        let disco = Sushi.Discoverer.new(file.get_uri());
+        disco.connect('tags-changed', () => {
+          let tag_list = disco.get_tag_list();
+          if (tag_list)
+            this._updateFromTags(tag_list);
+        });
 
-        this._cancellable.cancel();
+        this.cancellable = new Gio.Cancellable();
+        this.isReady();
     }
 
     _setCover(cover) {
@@ -371,8 +327,7 @@ var Klass = GObject.registerClass({
 
         let coverArt = cover.scale_simple(targetWidth, targetHeight,
                                           GdkPixbuf.InterpType.BILINEAR);
-        let surface = Gdk.cairo_surface_create_from_pixbuf(coverArt, scaleFactor, this.get_window());
-        this._image.set_from_surface(surface);
+        this._image.set_from_pixbuf(coverArt);
     }
 
     _onCoverArtFetched(err, cover) {
@@ -410,13 +365,13 @@ var Klass = GObject.registerClass({
         this._titleLabel.set_markup('<b>' + escaped + '</b>');
 
         if (artistName && albumName && !this._coverFetched) {
-            fetchCoverArt(tags, this._cancellable, this._onCoverArtFetched.bind(this));
+            fetchCoverArt(tags, this.cancellable, this._onCoverArtFetched.bind(this));
             this._coverFetched = true;
         }
     }
 
     get hasToolbar() {
-        // SushiMediaBin uses its own toolbar
+        // GtkVideo uses its own toolbar
         return false;
     }
 
diff --git a/src/viewers/evince.js b/src/viewers/evince.js
index 0ba40ce..4461e55 100644
--- a/src/viewers/evince.js
+++ b/src/viewers/evince.js
@@ -23,7 +23,7 @@
  *
  */
 
-const {EvinceDocument, EvinceView, Gio, GObject, Gtk, Sushi} = imports.gi;
+const {PapersDocument, PapersView, Gio, GObject, Gtk, Sushi} = imports.gi;
 
 const Constants = imports.util.constants;
 const Renderer = imports.ui.renderer;
@@ -31,6 +31,33 @@ const Utils = imports.ui.utils;
 
 const Libreoffice = imports.viewers.libreoffice;
 
+const createDocumentModel = () => {
+    return new PapersView.DocumentModel({
+        annotation_model: new PapersView.AnnotationModel(),
+    });
+};
+
+const createView = (model) => {
+    const view = new PapersView.View();
+    view.set_model(model);
+
+    const undoContext = new PapersView.UndoContext({
+        document_model: model,
+    });
+    const annotationsContext = new PapersView.AnnotationsContext({
+        document_model: model,
+        undo_context: undoContext,
+    });
+    view.set_annotations_context(annotationsContext);
+
+    const searchContext = new PapersView.SearchContext({
+        document_model: model,
+    });
+    view.set_search_context(searchContext);
+
+    return view;
+};
+
 var Klass = GObject.registerClass({
     Implements: [Renderer.Renderer],
     Properties: {
@@ -41,7 +68,7 @@ var Klass = GObject.registerClass({
                                          GObject.ParamFlags.READABLE,
                                          false)
     },
-}, class EvinceRenderer extends Gtk.ScrolledWindow {
+}, class PapersRenderer extends Gtk.ScrolledWindow {
     get ready() {
         return !!this._ready;
     }
@@ -54,11 +81,14 @@ var Klass = GObject.registerClass({
         super._init({ visible: true,
                       min_content_height: Constants.VIEW_MIN,
                       min_content_width: Constants.VIEW_MIN });
+        this._model = createDocumentModel();
+        this._view = createView(this._model);
+        this.cancellable = new Gio.Cancellable();
 
-        if (evinceTypes.includes(fileInfo.get_content_type())) {
+        if (papersTypes.includes(fileInfo.get_content_type())) {
             this._loadFile(file);
         } else {
-            Sushi.convert_libreoffice(file, (o, res) => {
+            Sushi.convert_libreoffice(file, this.cancellable, (o, res) => {
                 let convertedFile;
                 try {
                     convertedFile = Sushi.convert_libreoffice_finish(res);
@@ -73,11 +103,9 @@ var Klass = GObject.registerClass({
 
         this._defineActions();
 
-        this._view = EvinceView.View.new();
-        this._view.show();
-        this.add(this._view);
+        this.set_child(this._view);
 
-        this.connect('destroy', this._onDestroy.bind(this));
+        this.connect('unmap', this._onDestroy.bind(this));
 
         this.isReady();
     }
@@ -90,18 +118,16 @@ var Klass = GObject.registerClass({
     }
 
     _loadFile(file) {
-        if (file.has_uri_scheme("file")) {
-            this._job = EvinceView.JobLoad.new(file.get_uri());
-        } else {
-            this._job = EvinceView.JobLoadGFile.new(
-                file, EvinceDocument.DocumentLoadFlags.NONE);
-        }
+        this._job = PapersView.JobLoad.new();
+        this._job.set_uri(file.get_uri());
 
         this._jobHandlerId = this._job.connect('finished', this._onLoadJobFinished.bind(this));
-        this._job.scheduler_push_job(EvinceView.JobPriority.PRIORITY_NONE);
+        this._job.scheduler_push_job(PapersView.JobPriority.PRIORITY_NONE);
     }
 
     _updatePageLabel() {
+        this.toolbar;  // no op to make sure it's populated
+
         let curPage = this._model.get_page();
         let totPages = this._model.document.get_n_pages();
 
@@ -114,20 +140,18 @@ var Klass = GObject.registerClass({
     _onLoadJobFinished(job) {
         let document;
         try {
-            document = Sushi.get_evince_document_from_job(job);
+            document = job.get_loaded_document();
         } catch (e) {
             this.emit('error', e);
             return;
         }
 
-        this._model = EvinceView.DocumentModel.new_with_document(document);
-        this._model.set_sizing_mode(EvinceView.SizingMode.FIT_WIDTH);
+        this._model.set_document(document);
+        this._model.set_sizing_mode(PapersView.SizingMode.FIT_WIDTH);
         this._model.set_continuous(true);
 
         this._modelHandlerId = this._model.connect('page-changed', this._updatePageLabel.bind(this));
         this._updatePageLabel();
-
-        this._view.set_model(this._model);
     }
 
     _defineActions() {
@@ -142,30 +166,27 @@ var Klass = GObject.registerClass({
         this.insert_action_group ('evince', actionGroup);
     }
 
-    get moveOnClick() {
-        return false;
-    }
-
     populateToolbar(toolbar) {
         this._toolbarBack = Utils.createToolButton(this, 'go-previous-symbolic', () => {
             this._view.previous_page();
         });
-        toolbar.add(this._toolbarBack);
+        toolbar.append(this._toolbarBack);
 
         this._pageLabel = new Gtk.Label({ hexpand: true,
                                           margin_start: 10,
                                           margin_end: 10 });
-        toolbar.add(this._pageLabel);
+        toolbar.append(this._pageLabel);
 
         this._toolbarForward = Utils.createToolButton(this, 'go-next-symbolic', () => {
             this._view.next_page();
         });
-        toolbar.add(this._toolbarForward);
+        toolbar.append(this._toolbarForward);
     }
 });
 
-EvinceDocument.init();
-var evinceTypes = Sushi.query_supported_document_types();
-var mimeTypes = evinceTypes;
+PapersDocument.init();
+let app_info = Gio.DesktopAppInfo.new('org.gnome.Papers.desktop');
+let papersTypes = app_info.get_supported_types();
+var mimeTypes = papersTypes;
 if (!Libreoffice.isAvailable())
     mimeTypes = mimeTypes.concat(Libreoffice.officeTypes);
diff --git a/src/viewers/font.js b/src/viewers/font.js
index 330c92b..d95974b 100644
--- a/src/viewers/font.js
+++ b/src/viewers/font.js
@@ -48,7 +48,7 @@ var Klass = GObject.registerClass({
 
     _init(file) {
         super._init({ uri: file.get_uri(),
-                      visible: true })
+                      visible: true });
 
         this.isReady();
     }
diff --git a/src/viewers/html.js b/src/viewers/html.js
index 65ab7d6..ff1dd3c 100644
--- a/src/viewers/html.js
+++ b/src/viewers/html.js
@@ -25,15 +25,15 @@
 
 const {Gtk, GLib, GObject, Sushi} = imports.gi;
 
-var WebKit2;
+var WebKit;
 try {
-    imports.gi.versions.WebKit2 = '4.1';
-    WebKit2 = imports.gi.WebKit2;
+    imports.gi.versions.WebKit = '6.0';
+    WebKit2 = imports.gi.WebKit;
 } catch(e) {
 }
 
 function _isAvailable() {
-    return WebKit2 !== undefined;
+    return WebKit !== undefined;
 }
 
 const Renderer = imports.ui.renderer;
@@ -48,7 +48,7 @@ var Klass = _isAvailable() ? GObject.registerClass({
                                          GObject.ParamFlags.READABLE,
                                          false)
     },
-}, class HTMLRenderer extends WebKit2.WebView {
+}, class HTMLRenderer extends WebKit.WebView {
     get ready() {
         return !!this._ready;
     }
@@ -70,14 +70,6 @@ var Klass = _isAvailable() ? GObject.registerClass({
         });
         this.isReady();
     }
-
-    static {
-        WebKit2.WebContext.get_default().set_sandbox_enabled(true);
-    }
-
-    get moveOnClick() {
-        return false;
-    }
 }) : undefined;
 
 var mimeTypes = [];
diff --git a/src/viewers/image.js b/src/viewers/image.js
index 86ff20d..2e90386 100644
--- a/src/viewers/image.js
+++ b/src/viewers/image.js
@@ -37,7 +37,7 @@ var Klass = GObject.registerClass({
                                          GObject.ParamFlags.READABLE,
                                          false)
     },
-}, class ImageRenderer extends Gtk.DrawingArea {
+}, class ImageRenderer extends Gtk.Picture {
     get ready() {
         return !!this._ready;
     }
@@ -49,156 +49,26 @@ var Klass = GObject.registerClass({
     _init(file) {
         super._init();
 
-        this._cancellable = new Gio.Cancellable();
-
-        this._pix = null;
-        this._scaledSurface = null;
-        this._timeoutId = 0;
-
-        this._createImageTexture(file);
-
-        this.connect('destroy', this._onDestroy.bind(this));
-    }
-
-    vfunc_get_preferred_width() {
-        return [1, this._pix ? this._pix.get_width() : 1];
-    }
-
-    vfunc_get_preferred_height() {
-        return [1, this._pix ? this._pix.get_height() : 1];
-    }
-
-    vfunc_size_allocate(allocation) {
-        super.vfunc_size_allocate(allocation);
-        this._ensureScaledPix();
-    }
-
-    vfunc_draw(context) {
-        if (!this._scaledSurface)
-            return false;
-
-        let width = this.get_allocated_width();
-        let height = this.get_allocated_height();
-
-        let scaleFactor = this.get_scale_factor();
-        let offsetX = (width - this._scaledSurface.getWidth() / scaleFactor) / 2;
-        let offsetY = (height - this._scaledSurface.getHeight() / scaleFactor) / 2;
-
-        context.setSourceSurface(this._scaledSurface, offsetX, offsetY);
-        context.paint();
-        return false;
-    }
-
-    _createImageTexture(file) {
-        file.read_async(GLib.PRIORITY_DEFAULT, this._cancellable, (obj, res) => {
-            try {
-                let stream = obj.read_finish(res);
-                this._textureFromStream(stream);
-            } catch (e) {
-                this.emit('error', e);
-            }
-        });
-    }
-
-    _ensureScaledPix() {
-        if (!this._pix)
-            return;
-
-        let scaleFactor = this.get_scale_factor();
-        let width = this.get_allocated_width() * scaleFactor;
-        let height = this.get_allocated_height() * scaleFactor;
-
-        // Scale original to fit, if necessary
-        let origWidth = this._pix.get_width();
-        let origHeight = this._pix.get_height();
-
-        let scaleX = width / origWidth;
-        let scaleY = height / origHeight;
-        let scale = Math.min(scaleX, scaleY);
-
-        // Do not upscale unless we're fullscreen
-        if (!this.fullscreen)
-            scale = Math.min(scale, 1.0 * scaleFactor);
-
-        let newWidth = Math.floor(origWidth * scale);
-        let newHeight = Math.floor(origHeight * scale);
-
-        let scaledWidth = this._scaledSurface ? this._scaledSurface.getWidth() : 0;
-        let scaledHeight = this._scaledSurface ? this._scaledSurface.getHeight() : 0;
-
-        if (newWidth == scaledWidth && newHeight == scaledHeight)
-            return;
-
-        // Avoid blur if we're upscaling a lot, e.g. when fullscreening
-        // a small image. We use nearest neighbor interpolation for that case.
-        let interpType = GdkPixbuf.InterpType.BILINEAR;
-        if (scale >= 3.0 * scaleFactor)
-            interpType = GdkPixbuf.InterpType.NEAREST;
-
-        let scaledPixbuf = this._pix.scale_simple(newWidth, newHeight, interpType);
-        this._scaledSurface = Gdk.cairo_surface_create_from_pixbuf(scaledPixbuf,
-                                                                   scaleFactor,
-                                                                   this.get_window());
-    }
-
-    _setPix(pix) {
-        this._pix = pix;
-        this._scaledSurface = null;
-
-        this.queue_resize();
-        this.isReady();
-    }
-
-    _textureFromStream(stream) {
-        GdkPixbuf.PixbufAnimation.new_from_stream_async(stream, this._cancellable, (obj, res) => {
-            let anim;
-            try {
-                anim = GdkPixbuf.PixbufAnimation.new_from_stream_finish(res);
-            } catch (e) {
-                this.emit('error', e);
-                return;
-            }
-
-            this._iter = anim.get_iter(null);
-            this._update();
-
-            stream.close_async(GLib.PRIORITY_DEFAULT, this._cancellable, (obj, res) => {
-                try {
-                    obj.close_finish(res);
-                } catch (e) {
-                    logError(e, 'Unable to close the stream');
-                }
-            });
-         });
+        try {
+          this._texture = Gdk.Texture.new_from_file(file);
+          this.set_paintable(this._texture);
+          this.content_fit = Gtk.ContentFit.SCALE_DOWN;
+        }
+        catch (e) {
+          this.emit('error', e);
+        }
     }
 
-    _update() {
-        this._setPix(this._iter.get_pixbuf().apply_embedded_orientation());
-
-        let delay = this._iter.get_delay_time();
-        if (delay == -1)
-            return;
-
-        this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, () => {
-            this._timeoutId = 0;
-            if (this._iter.advance(null))
-                this._update();
-            return false;
-        });
+    vfunc_measure(orientation, for_size) {
+        if (orientation == Gtk.Orientation.VERTICAL)
+          return [1, this._texture ? this._texture.get_height() : 1, -1, -1];
+        else
+          return [1, this._texture ? this._texture.get_width() : 1, -1, -1];
     }
 
     get resizePolicy() {
         return Renderer.ResizePolicy.SCALED;
     }
-
-    _onDestroy() {
-        if (this._timeoutId) {
-            GLib.source_remove(this._timeoutId);
-            this._timeoutId = 0;
-        }
-
-        this._cancellable.cancel();
-    }
 });
 
 var mimeTypes = [];
diff --git a/src/viewers/libreoffice.js b/src/viewers/libreoffice.js
index 8302b4e..43751dd 100644
--- a/src/viewers/libreoffice.js
+++ b/src/viewers/libreoffice.js
@@ -43,7 +43,6 @@ var Klass = GObject.registerClass({
 
         this._view = LOKDocView.View.new(null, null);
         this._view.set_edit(false);
-        this._view.show();
         this.add(this._view);
 
         this._view.open_document(file.get_uri(), '{}', null, null);
@@ -90,10 +89,6 @@ var Klass = GObject.registerClass({
         this._view.zoom_level = zoomLevel;
         this._lastAllocWidth = allocWidth;
     }
-
-    get moveOnClick() {
-        return false;
-    }
 });
 
 var officeTypes = [
diff --git a/src/viewers/text.js b/src/viewers/text.js
index 33ca22e..d1fce12 100644
--- a/src/viewers/text.js
+++ b/src/viewers/text.js
@@ -23,7 +23,7 @@
  *
  */
 
-const {Gdk, Gio, GLib, GObject, Gtk, GtkSource} = imports.gi;
+const {Adw, Gdk, Gio, GLib, GObject, Gtk, GtkSource} = imports.gi;
 
 const Renderer = imports.ui.renderer;
 
@@ -49,7 +49,7 @@ var Klass = GObject.registerClass({
     _init(file, fileInfo) {
         super._init();
 
-        this._cancellable = new Gio.Cancellable();
+        this.cancellable = new Gio.Cancellable();
 
         let buffer = this._createBuffer(file, fileInfo);
         this._view = new GtkSource.View({ buffer: buffer,
@@ -58,26 +58,29 @@ var Klass = GObject.registerClass({
                                           monospace: true,
                                           show_line_numbers: !!buffer.language });
 
-        this.add(this._view);
-        this.isReady();
+        this.set_child(this._view);
 
-        this.connect('destroy', this._onDestroy.bind(this));
+        this.isReady();
     }
 
-    _onDestroy() {
-        this._cancellable.cancel();
+    _setStyle(adwStyleManager, buffer) {
+        let sourceStyleManager = GtkSource.StyleSchemeManager.get_default();
+        let scheme;
+        if (adwStyleManager.dark)
+          scheme = sourceStyleManager.get_scheme('Adwaita-dark');
+        else
+          scheme = sourceStyleManager.get_scheme('Adwaita');
+        buffer.set_style_scheme(scheme);
     }
 
     _createBuffer(file, fileInfo) {
         let buffer = new GtkSource.Buffer();
-        let styleManager = GtkSource.StyleSchemeManager.get_default();
-        let stylePath = GLib.build_filenamev([pkg.pkgdatadir,
-                                              'gtksourceview-4',
-                                              'styles']);
-        styleManager.prepend_search_path(stylePath);
 
-        let scheme = styleManager.get_scheme('builder-dark');
-        buffer.set_style_scheme(scheme);
+        let adwStyleManager = Adw.StyleManager.get_default();
+        adwStyleManager.connect('notify::dark', () => {
+          this._setStyle(adwStyleManager, buffer);
+        });
+        this._setStyle(adwStyleManager, buffer);
 
         let langManager = GtkSource.LanguageManager.get_default();
         let language = langManager.guess_language(file.get_basename(),
@@ -88,7 +91,7 @@ var Klass = GObject.registerClass({
         let sourceFile = new GtkSource.File({ location: file });
         let loader = new GtkSource.FileLoader({ buffer: buffer,
                                                 file: sourceFile });
-        loader.load_async(0, this._cancellable, null, (loader, result) => {
+        loader.load_async(0, this.cancellable, null, (loader, result) => {
             try {
                 loader.load_finish(result);
             } catch (e) {
@@ -99,10 +102,6 @@ var Klass = GObject.registerClass({
 
         return buffer;
     }
-
-    get moveOnClick() {
-        return false;
-    }
 });
 
 // register for text/plain and let the mime handler call us for child types
diff --git a/src/viewers/gst.js b/src/viewers/video.js
similarity index 78%
rename from src/viewers/gst.js
rename to src/viewers/video.js
index 0c5654a..7eb09f8 100644
--- a/src/viewers/gst.js
+++ b/src/viewers/video.js
@@ -23,7 +23,7 @@
  *
  */
 
-const {GLib, GObject, Sushi} = imports.gi;
+const {GLib, GObject, Gtk, Sushi} = imports.gi;
 
 const Renderer = imports.ui.renderer;
 const TotemMimeTypes = imports.util.totemMimeTypes;
@@ -38,7 +38,7 @@ var Klass = GObject.registerClass({
                                          GObject.ParamFlags.READABLE,
                                          false)
     },
-}, class GstRenderer extends Sushi.MediaBin {
+}, class VideoRenderer extends Gtk.Video {
     get ready() {
         return !!this._ready;
     }
@@ -48,23 +48,10 @@ var Klass = GObject.registerClass({
     }
 
     _init(file) {
-        super._init({ uri: file.get_uri() });
+        super._init();
 
-        this._autoplayId = GLib.idle_add(0, () => {
-            this._autoplayId = 0;
-            this.play();
-            return false;
-        });
-
-        this.connect('destroy', this._onDestroy.bind(this));
-        this.connect('size-change', this.isReady.bind(this));
-    }
-
-    _onDestroy() {
-        if (this._autoplayId > 0) {
-            GLib.source_remove(this._autoplayId);
-            this._autoplayId = 0;
-        }
+        this.set_file(file);
+        this.autoplay = true;
     }
 
     get canFullscreen() {
diff --git a/sushi.doap b/sushi.doap
index 2b56388..7441ad9 100644
--- a/sushi.doap
+++ b/sushi.doap
@@ -25,9 +25,9 @@
   </author>
   <maintainer>
     <foaf:Person>
-      <foaf:name>Felipe Borges</foaf:name>
-      <foaf:mbox rdf:resource="mailto:felipeborges@gnome.org" />
-      <gnome:userid>felipeborges</gnome:userid>
+      <foaf:name>Peter Eisenmann</foaf:name>
+      <foaf:mbox rdf:resource="mailto:p3732@getgoogleoff.me" />
+      <gnome:userid>peisen</gnome:userid>
     </foaf:Person>
   </maintainer>
   <maintainer>
