diff --git a/README.md b/README.md index b7204747..52f8409a 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,15 @@ The media player uses [GStreamer](https://gstreamer.freedesktop.org/) as a media ## Installation from Flatpak The `Flatpak` package includes all required dependencies and codecs. -Additionally it also has a few patches, thus some functionalities work better (or are only available) in `Flatpak` version (until my changes are accepted upstream). List of patches used in this version can be found [here](https://github.com/Rafostar/clapper/issues/35). +Additionally it also has a few patches, thus some functionalities work better (or are only available) in `Flatpak` version (until my changes are accepted upstream). +List of patches used in this version can be found [here](https://github.com/Rafostar/clapper/issues/35). -Download on Flathub - -**Important:** If you have been using the Flatpak package from my custom 3rd party repo, please remove it and replace your installation with version from Flathub. That repository will not be maintained any longer. Thank you for understanding. + + Download on Flathub + ## Packages -The [pkgs folder](https://github.com/Rafostar/clapper/tree/master/pkgs) in this repository contains build scripts for various package formats. You can use them to build package yourself or download one of pre-built packages: - -#### Debian, Fedora & openSUSE +#### Fedora & openSUSE Pre-built packages are available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).
Those are automatically built on each git commit, and are thus considered unstable. @@ -54,22 +53,12 @@ meson builddir --prefix=/usr/local sudo meson install -C builddir ``` -## Q&A -**Q:** Does using `GJS` negatively impact video performance?
-**A:** Absolutely not. `GJS` is only used to put together the GUI during startup. -It has nothing to do with video rendering. All `GTK4` and `GStreamer` libraries are in C. -Even the custom video widget that I prepared for this player (based on original `GTK3` implementation) is compiled C code. -All of these libs are acting "on their own" and no function calls from `GJS` related to video decoding and rendering are performed during playback. - -**Q:** What settings should I set to maximize performance?
-**A:** As of now, player works best on `Wayland` session. `Wayland` users can try enabling highly experimental `vah264dec` plugin for improved performance (this plugin does not work on `Xorg` right now) for standard (8-bit) `H.264` videos. -It can be enabled from inside the player preferences dialog inside `Advanced -> GStreamer` tab using the customizable `Plugin Ranking` feature. -Since the whole app is rendered using your GPU, users of VERY weak GPUs might want to disable the "render window shadows" option to have more GPU power available for non-fullscreen video rendering. - -## Other Questions? +## Questions? Feel free to ask me any questions. Come and talk on Matrix: [#clapper-player:matrix.org](https://matrix.to/#/#clapper-player:matrix.org) ## Special Thanks -Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files. Big thanks to [bridadan](https://github.com/bridadan) and [Uniformbuffer3](https://github.com/Uniformbuffer3) for helping with testing V4L2 and NVDEC hardware acceleration methods. +Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files. +Big thanks to [bridadan](https://github.com/bridadan) and [Uniformbuffer3](https://github.com/Uniformbuffer3) for helping +with testing V4L2 and NVDEC hardware acceleration methods. Thanks a lot to all the people who are supporting the development with their anonymous donations through [Liberapay](https://liberapay.com/Clapper/). I :heart: U. diff --git a/_service b/_service index 2818907d..6d7b4ee1 100644 --- a/_service +++ b/_service @@ -4,7 +4,6 @@ https://github.com/Rafostar/clapper.git pkgs/rpm/clapper.spec pkgs/rpm/clapper.rpmlintrc - pkgs/deb/clapper.dsc diff --git a/css/styles.css b/css/styles.css index 92fd33c4..2c84dbd0 100644 --- a/css/styles.css +++ b/css/styles.css @@ -6,18 +6,18 @@ radio { margin-left: -2px; } /* Adwaita is missing osd ListBox */ -.osd list { +.playlistrow { + border-radius: 5px; +} +.playlistrow { + color: @theme_fg_color; +} +.osd .playlist { background: none; } -.osd list row image { +.osd .playlist row image { -gtk-icon-shadow: none; } -.gtk402 trough highlight { - border-color: transparent; -} -.gtk402 .osd trough highlight { - border-color: inherit; -} .osdheaderbar { background: transparent; } @@ -34,18 +34,15 @@ radio { .linkedright image { margin-right: 2px; } -/* Non-osd style for popover menu */ -.menupopover label { - color: @theme_text_color; -} -.menupopover arrow { - background: @theme_base_color; - border-color: @insensitive_base_color; -} -.menupopover contents { - background: @theme_base_color; - border-color: @insensitive_base_color; + +/* Flat popovers */ +popover arrow, +popover contents { + border-color: transparent; + box-shadow: none; } + +/* Rounded corners */ .adwrounded.csd { border-radius: 8px; } @@ -61,8 +58,15 @@ radio { .roundedcorners { border-radius: 8px; } -.adwthemedark scale trough highlight { - filter: brightness(120%); + +/* Reduce sliders size */ +scale trough slider { + min-height: 18px; + min-width: 18px; +} +.fullscreen.tvmode scale trough slider { + min-height: 20px; + min-width: 20px; } .videowidget { @@ -166,9 +170,6 @@ radio { margin-right: -4px; } .positionscale trough highlight { - min-height: 4px; -} -.osd .positionscale trough highlight { min-height: 6px; } .fullscreen.tvmode .positionscale trough slider { @@ -176,6 +177,7 @@ radio { background: transparent; border-color: transparent; box-shadow: none; + outline: none; } .positionscale mark indicator { min-height: 6px; @@ -232,6 +234,9 @@ radio { margin-top: -4px; margin-bottom: -6px; } +.volumescale trough highlight { + min-width: 4px; +} .fullscreen.tvmode .volumescale trough highlight { min-width: 6px; } @@ -249,6 +254,9 @@ radio { .fullscreen.tvmode .elapsedpopoverbox { min-width: 360px; } +.speedscale trough highlight { + min-height: 4px; +} .fullscreen.tvmode .speedscale trough highlight { min-height: 6px; } @@ -279,23 +287,6 @@ radio { font-weight: 600; } -/* Preferences */ -.prefsnotebook grid { - margin: 10px; -} -.prefssubpage header { - background: none; -} -.prefssubpage header tabs tab { - box-shadow: none; - margin: 0px; - margin-right: 1px; -} -.prefssubpage header tabs tab:checked { - color: initial; - background: @theme_selected_bg_color; -} - /* Open URI Dialog */ .uridialogbox { margin: 12px; diff --git a/data/com.github.rafostar.Clapper.gschema.xml b/data/com.github.rafostar.Clapper.gschema.xml index ed1c57e2..f43b58f4 100644 --- a/data/com.github.rafostar.Clapper.gschema.xml +++ b/data/com.github.rafostar.Clapper.gschema.xml @@ -6,35 +6,31 @@ false Automatically enter fullscreen when first file is loaded - - "restore" - Mode used for startup volume value + + false + Set custom volume value at startup 100 Custom initial volume value in percentage after startup - - false - Keep showing last video frame after playback finishes - - - false - Automatically close the app after playback finishes + + 0 + What to do after playback finishes - - - "normal" + + + 0 Mode used for seeking 10 Time amount to seek with single press of arrow keys - - "second" - Unit to use with seeking value + + 0 + Unit ID to use with seeking value true @@ -50,13 +46,13 @@ - + 0 Offset time for audio tracks relative to video (milliseconds) - + 0 Offset time for subtitle tracks relative to video (milliseconds) @@ -95,7 +91,7 @@ - '[{"apply":false,"name":"vah264dec","rank":300}]' + '{}' Custom values for GStreamer plugin ranking @@ -108,8 +104,8 @@ false Enable to use adaptive streaming for YouTube - - "hfr" + + 1 Max YouTube video quality type diff --git a/data/com.github.rafostar.Clapper.metainfo.xml b/data/com.github.rafostar.Clapper.metainfo.xml index d29bc31f..8ee290ac 100644 --- a/data/com.github.rafostar.Clapper.metainfo.xml +++ b/data/com.github.rafostar.Clapper.metainfo.xml @@ -9,18 +9,18 @@ com.github.rafostar.Clapper.desktop

- Clapper is a GNOME media player build using GJS with GTK4 toolkit. + Clapper is a GNOME media player built using GJS with GTK4 toolkit. The media player is using GStreamer as a media backend and renders everything via OpenGL. Player works natively on both Xorg and Wayland. It also supports hardware acceleration through VA-API on AMD/Intel GPUs, - NVDEC for Nvidia and V4L2 for mobile devices. + NVDEC on Nvidia and V4L2 on mobile devices.

The media player has an adaptive GUI. When viewing videos in "Windowed Mode", Clapper will use mostly unmodified GTK widgets to match your OS look nicely. When player enters "Fullscreen Mode" all GUI elements will become darker, bigger and semi-transparent for your viewing comfort. It also has a "Floating Mode" which - displays video only on top of all other windows for a PiP-like viewing experience. + displays only video on top of all other windows for a PiP-like viewing experience. Mobile friendly transitions are also supported.

@@ -118,6 +118,14 @@ + + keyboard + pointing + touch + + + small + workstation mobile diff --git a/pkgs/arch/.SRCINFO b/pkgs/arch/.SRCINFO deleted file mode 100644 index add3f792..00000000 --- a/pkgs/arch/.SRCINFO +++ /dev/null @@ -1,30 +0,0 @@ -pkgbase = clapper-git - pkgdesc = A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering. - pkgver = r745.4738673 - pkgrel = 1 - url = https://github.com/Rafostar/clapper - arch = any - license = GPL-3.0 - makedepends = meson>=0.50 - makedepends = git - makedepends = gobject-introspection - depends = gtk4 - depends = gjs - depends = glib2>=2.56.0 - depends = wayland-protocols - depends = hicolor-icon-theme - depends = gstreamer>=1.18.0 - depends = gst-plugins-base>=1.18.0 - depends = gst-plugins-good>=1.18.0 - depends = gst-plugins-bad>=1.18.0 - optdepends = gst-libav>=1.18.0: Popular video decoders - optdepends = gstreamer-vaapi>=1.18.0: Intel/AMD video acceleration - optdepends = gst-plugins-ugly>=1.18.0: CD/DVD playback - provides = clapper - provides = libgstclapper-1.0 - conflicts = clapper - source = clapper::git+https://github.com/Rafostar/clapper.git - md5sums = SKIP - -pkgname = clapper-git - diff --git a/pkgs/arch/.gitignore b/pkgs/arch/.gitignore deleted file mode 100644 index 0e26cfd4..00000000 --- a/pkgs/arch/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -clapper-*/ -clapper-*.pkg.tar.* -pkg/ -src/ diff --git a/pkgs/arch/PKGBUILD b/pkgs/arch/PKGBUILD deleted file mode 100644 index 153279e4..00000000 --- a/pkgs/arch/PKGBUILD +++ /dev/null @@ -1,74 +0,0 @@ -# -# PKGBUILD file for package clapper -# -# Copyright (C) 2020/21 sp1rit -# Copyright (C) 2020 Rafostar -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Maintainer: sp1rit - -_basename=clapper -pkgname="${_basename}-git" -pkgver=r745.4738673 -pkgrel=1 -pkgdesc="A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering." -arch=(any) -url="https://github.com/Rafostar/clapper" -license=("GPL-3.0") -depends=( - "gtk4" - "gjs" - "glib2>=2.56.0" # glib-2.0, gmodule-2.0, gio-2.0 - "wayland-protocols" # gtk4 non-default runtime dep - "hicolor-icon-theme" - "gstreamer>=1.18.0" # gstreamer-1.0, gstreamer-base-1.0 - "gst-plugins-base>=1.18.0" - "gst-plugins-good>=1.18.0" - "gst-plugins-bad>=1.18.0" -) -makedepends=( - "meson>=0.50" - "git" - "gobject-introspection" # /usr/sbin/g-ir-scanner -) -optdepends=( - "gst-libav>=1.18.0: Popular video decoders" - "gstreamer-vaapi>=1.18.0: Intel/AMD video acceleration" - "gst-plugins-ugly>=1.18.0: CD/DVD playback" -) -source=("${_basename}::git+https://github.com/Rafostar/${_basename}.git") -provides=("${_basename}" "libgst${_basename}-1.0") -conflicts=("${_basename}") -md5sums=("SKIP") - -pkgver() { - cd "${srcdir}/${_basename}" - printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" -} - -prepare() { - cd "${srcdir}/${_basename}" - arch-meson . _build -} - -build() { - cd "${srcdir}/${_basename}" - ninja -C _build -} - -package() { - cd "${srcdir}/${_basename}" - DESTDIR="$pkgdir" meson install -C _build/ -} diff --git a/pkgs/deb/clapper.dsc b/pkgs/deb/clapper.dsc deleted file mode 100644 index f525ed9a..00000000 --- a/pkgs/deb/clapper.dsc +++ /dev/null @@ -1,28 +0,0 @@ -Format: 3.0 (quilt) -Source: clapper -Binary: clapper -Architecture: any -Version: 0.3.0 -Maintainer: Rafostar -Build-Depends: debhelper (>= 10), - meson (>= 0.50), - gjs, - gobject-introspection, - libgtk-4-dev (>= 4.0.0), - libgstreamer1.0-dev (>= 1.18), - libgstreamer-plugins-base1.0-dev (>= 1.18), - libgstreamer-gl1.0-0 (>= 1.18), - libgles-dev, - libglib2.0-dev, - libglib2.0-bin, - desktop-file-utils, - hicolor-icon-theme, - brz, - libfontconfig1-dev, - libpam-systemd -Package-List: - clapper deb gnome optional arch=any -Files: - 0 0 debian.tar.xz -Description: Simple and modern GNOME media player - A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering. diff --git a/pkgs/deb/debian/changelog b/pkgs/deb/debian/changelog deleted file mode 100644 index 8d41dd87..00000000 --- a/pkgs/deb/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -clapper (0.3.0) unstable; urgency=low - - * New version - - -- Rafostar Fri, 18 Jun 2021 09:39:00 +0100 diff --git a/pkgs/deb/debian/compat b/pkgs/deb/debian/compat deleted file mode 100644 index f599e28b..00000000 --- a/pkgs/deb/debian/compat +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/pkgs/deb/debian/control b/pkgs/deb/debian/control deleted file mode 100644 index fdca70b5..00000000 --- a/pkgs/deb/debian/control +++ /dev/null @@ -1,37 +0,0 @@ -Source: clapper -Section: gnome -Priority: optional -Maintainer: Rafostar -Standards-Version: 4.4.0 -Build-Depends: debhelper (>= 10), - meson (>= 0.50), - gjs, - gobject-introspection, - libgtk-4-dev (>= 4.0.0), - libgstreamer1.0-dev (>= 1.18), - libgstreamer-plugins-base1.0-dev (>= 1.18), - libgstreamer-gl1.0-0 (>= 1.18), - libgles-dev, - libglib2.0-dev, - libglib2.0-bin, - desktop-file-utils, - hicolor-icon-theme - -Package: clapper -Architecture: any -Depends: gjs, - gir1.2-gtk-4.0 (>= 4.0.0), - hicolor-icon-theme, - libgstreamer1.0-0 (>= 1.18), - gstreamer1.0-plugins-base (>= 1.18), - gstreamer1.0-plugins-good (>= 1.18), - gstreamer1.0-plugins-bad (>= 1.18), - gstreamer1.0-gl (>= 1.18) -Recommends: gstreamer1.0-libav, - gstreamer1.0-pulseaudio -Suggests: gstreamer1.0-plugins-ugly, - gstreamer1.0-vaapi -Description: Simple and modern GNOME media player - A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering. - . - More codecs/features and video acceleration can be enabled by installing the suggested packages. diff --git a/pkgs/deb/debian/copyright b/pkgs/deb/debian/copyright deleted file mode 100644 index 09d53546..00000000 --- a/pkgs/deb/debian/copyright +++ /dev/null @@ -1,27 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: clapper -Source: https://github.com/Rafostar/clapper - -Files: * -Copyright: 2020 Rafostar -License: GPL-3.0+ - -Files: debian/* -Copyright: 2020 Rafostar -License: GPL-3.0+ - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General Public License - Version 3 can be found in `/usr/share/common-licenses/GPL-3'. diff --git a/pkgs/deb/debian/rules b/pkgs/deb/debian/rules deleted file mode 100755 index 2d33f6ac..00000000 --- a/pkgs/deb/debian/rules +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/make -f - -%: - dh $@ diff --git a/pkgs/deb/debian/source/format b/pkgs/deb/debian/source/format deleted file mode 100644 index 163aaf8d..00000000 --- a/pkgs/deb/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/pkgs/flatpak/com.github.rafostar.Clapper-testing.json b/pkgs/flatpak/com.github.rafostar.Clapper-testing.json index 3038b812..6fbba79a 100644 --- a/pkgs/flatpak/com.github.rafostar.Clapper-testing.json +++ b/pkgs/flatpak/com.github.rafostar.Clapper-testing.json @@ -36,6 +36,7 @@ "testing/gstreamer-1.0/gst-plugins-ugly.json", "testing/gstreamer-1.0/gst-libav.json", "testing/gstreamer-1.0/gstreamer-vaapi.json", + "testing/libadwaita.json", { "name": "clapper", "buildsystem": "meson", diff --git a/pkgs/flatpak/com.github.rafostar.Clapper.json b/pkgs/flatpak/com.github.rafostar.Clapper.json index cd86663c..dc244780 100644 --- a/pkgs/flatpak/com.github.rafostar.Clapper.json +++ b/pkgs/flatpak/com.github.rafostar.Clapper.json @@ -42,6 +42,7 @@ "flathub/gstreamer-1.0/gst-libav.json", "flathub/gstreamer-1.0/gstreamer-vaapi.json", "flathub/lib/gtk4.json", + "testing/libadwaita.json", { "name": "clapper", "buildsystem": "meson", diff --git a/pkgs/flatpak/testing/libadwaita.json b/pkgs/flatpak/testing/libadwaita.json new file mode 100644 index 00000000..5d8e7b53 --- /dev/null +++ b/pkgs/flatpak/testing/libadwaita.json @@ -0,0 +1,21 @@ +{ + "name": "libadwaita", + "buildsystem": "meson", + "config-opts": [ + "--buildtype=release", + "--wrap-mode=nofallback", + "-Dintrospection=enabled", + "-Dvapi=false", + "-Dgtk_doc=false", + "-Dtests=false", + "-Dexamples=false" + ], + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/libadwaita.git", + "tag": "1.0.0-alpha.2", + "commit": "f5932ab4250c8e709958c6e75a1a4941a5f0f386" + } + ] +} diff --git a/pkgs/rpm/clapper.spec b/pkgs/rpm/clapper.spec index fd21185b..6b856886 100644 --- a/pkgs/rpm/clapper.spec +++ b/pkgs/rpm/clapper.spec @@ -46,6 +46,7 @@ BuildRequires: hicolor-icon-theme Requires: gjs Requires: gtk4 >= %{gtk4_version} +Requires: libadwaita Requires: hicolor-icon-theme %if 0%{?suse_version} @@ -126,6 +127,9 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop %{_libdir}/%{appname}/ %changelog +* Mon Aug 23 2021 Rafostar - 0.3.0-2 +- Require libadwaita + * Fri Jun 18 2021 Rafostar - 0.3.0-1 - New version diff --git a/src/actions.js b/src/actions.js index 53201880..1ac67648 100644 --- a/src/actions.js +++ b/src/actions.js @@ -1,5 +1,6 @@ const { Gtk } = imports.gi; const Dialogs = imports.src.dialogs; +const Prefs = imports.src.prefs; const Misc = imports.src.misc; var actions = { @@ -42,7 +43,7 @@ function handleAction(action, window) new Dialogs.UriDialog(window); break; case 'prefs': - new Dialogs.PrefsDialog(window); + new Prefs.PrefsWindow(window); break; case 'shortcuts': if(!window.get_help_overlay()) { diff --git a/src/appBase.js b/src/appBase.js index 3015e5d3..a3d61c81 100644 --- a/src/appBase.js +++ b/src/appBase.js @@ -29,9 +29,6 @@ class ClapperAppBase extends Gtk.Application title: Misc.appName, }); - if(Gtk.MINOR_VERSION > 0 || Gtk.MICRO_VERSION > 1) - window.add_css_class('gtk402'); - if(!settings.get_boolean('render-shadows')) window.add_css_class('gpufriendly'); @@ -111,7 +108,6 @@ class ClapperAppBase extends Gtk.Application { const theme = gtkSettings.gtk_theme_name; const window = this.active_window; - const hasAdwThemeDark = window.has_css_class('adwthemedark'); debug(`user selected theme: ${theme}`); @@ -119,27 +115,6 @@ class ClapperAppBase extends Gtk.Application Having 2/4 corners rounded in floating mode is not good. */ if(!window.has_css_class('adwrounded')) window.add_css_class('adwrounded'); - - if(theme.startsWith('Adwaita') || theme.startsWith('Default')) { - const isDarkTheme = settings.get_boolean('dark-theme'); - - if(isDarkTheme && !hasAdwThemeDark) - window.add_css_class('adwthemedark'); - else if(!isDarkTheme && hasAdwThemeDark) - window.remove_css_class('adwthemedark'); - } - else if(hasAdwThemeDark) - window.remove_css_class('adwthemedark'); - - if(!theme.endsWith('-dark')) - return; - - /* We need to request a default theme with optional dark variant - to make the "gtk_application_prefer_dark_theme" setting work */ - const parsedTheme = theme.substring(0, theme.lastIndexOf('-')); - - gtkSettings.gtk_theme_name = parsedTheme; - debug(`set theme: ${parsedTheme}`); } _onIconThemeChanged(gtkSettings) diff --git a/src/controls.js b/src/controls.js index afd539bd..356ac9d3 100644 --- a/src/controls.js +++ b/src/controls.js @@ -435,12 +435,19 @@ class ClapperControls extends Gtk.Box if(isVisible) { const [start, end] = this.positionScale.get_slider_range(); const controlsHeight = this.parent.get_height(); - const scaleHeight = this.positionScale.parent.get_height(); + const scaleBoxHeight = this.positionScale.parent.get_height(); + const [isShared, destX, destY] = this.positionScale.translate_coordinates( + this.positionScale.parent, 0, 0 + ); + + /* Half of slider width, values are defined in CSS */ + const sliderOffset = (this.isFullscreen && !this.isMobile) + ? 10 : 9; this.chapterPopover.set_pointing_to(new Gdk.Rectangle({ - x: -2, - y: -(controlsHeight - scaleHeight) / 2, - width: 2 * end, + x: destX + end - sliderOffset, + y: -(controlsHeight - scaleBoxHeight) / 2, + width: 0, height: 0, })); } @@ -469,7 +476,7 @@ class ClapperControls extends Gtk.Box scrollController.connect('scroll', clapperWidget._onScroll.bind(clapperWidget)); this.volumeButton.add_controller(scrollController); - const initialVolume = (settings.get_string('volume-initial') === 'custom') + const initialVolume = (settings.get_boolean('volume-custom')) ? settings.get_int('volume-value') / 100 : settings.get_double('volume-last'); diff --git a/src/dialogs.js b/src/dialogs.js index f7e16c9d..b5cefc63 100644 --- a/src/dialogs.js +++ b/src/dialogs.js @@ -3,8 +3,6 @@ const System = imports.system; const Debug = imports.src.debug; const FileOps = imports.src.fileOps; const Misc = imports.src.misc; -const Prefs = imports.src.prefs; -const PrefsBase = imports.src.prefsBase; const { debug } = Debug; @@ -247,88 +245,6 @@ class ClapperResumeDialog extends Gtk.MessageDialog } }); -var PrefsDialog = GObject.registerClass( -class ClapperPrefsDialog extends Gtk.Dialog -{ - _init(window) - { - super._init({ - transient_for: window, - destroy_with_parent: true, - modal: true, - title: 'Preferences', - default_width: 460, - default_height: 400, - }); - - const pages = [ - { - title: 'Player', - pages: [ - { - title: 'General', - widget: Prefs.GeneralPage, - }, - { - title: 'Behaviour', - widget: Prefs.BehaviourPage, - }, - { - title: 'Audio', - widget: Prefs.AudioPage, - }, - { - title: 'Subtitles', - widget: Prefs.SubtitlesPage, - }, - { - title: 'Network', - widget: Prefs.NetworkPage, - }, - { - title: 'YouTube', - widget: Prefs.YouTubePage, - } - ] - }, - { - title: 'Advanced', - pages: [ - { - title: 'GStreamer', - widget: Prefs.GStreamerPage, - }, - { - title: 'Tweaks', - widget: Prefs.TweaksPage, - } - ] - } - ]; - - const prefsNotebook = new PrefsBase.Notebook(pages); - prefsNotebook.add_css_class('prefsnotebook'); - - const area = this.get_content_area(); - area.append(prefsNotebook); - - this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this)); - this.show(); - } - - _onCloseRequest(dialog) - { - debug('closing prefs dialog'); - - dialog.disconnect(this.closeSignal); - this.closeSignal = null; - - const area = dialog.get_content_area(); - const notebook = area.get_first_child(); - notebook._onClose(); - } -}); - var AboutDialog = GObject.registerClass( class ClapperAboutDialog extends Gtk.AboutDialog { diff --git a/src/headerbarBase.js b/src/headerbarBase.js index a4ccd9e8..d9db3f30 100644 --- a/src/headerbarBase.js +++ b/src/headerbarBase.js @@ -17,7 +17,6 @@ class ClapperHeaderBarBase extends Gtk.Box margin_start: 6, margin_end: 6, }); - this.add_css_class('osd'); this.add_css_class('osdheaderbar'); this.isMaximized = false; @@ -40,9 +39,10 @@ class ClapperHeaderBarBase extends Gtk.Box valign: Gtk.Align.CENTER, can_focus: false, }); + const menuToggleButton = this.menuButton.get_first_child(); + menuToggleButton.add_css_class('osd'); const mainMenuModel = uiBuilder.get_object('mainMenu'); const mainMenuPopover = new HeaderBarPopover(mainMenuModel); - mainMenuPopover.add_css_class('menupopover'); this.menuButton.set_popover(mainMenuPopover); this.menuButton.add_css_class('circular'); this.menuWidget.append(this.menuButton); @@ -57,6 +57,7 @@ class ClapperHeaderBarBase extends Gtk.Box icon_name: 'go-bottom-symbolic', can_focus: false, }); + floatButton.add_css_class('osd'); floatButton.add_css_class('circular'); floatButton.add_css_class('linkedleft'); floatButton.connect('clicked', @@ -74,6 +75,7 @@ class ClapperHeaderBarBase extends Gtk.Box icon_name: 'view-fullscreen-symbolic', can_focus: false, }); + fullscreenButton.add_css_class('osd'); fullscreenButton.add_css_class('circular'); fullscreenButton.add_css_class('linkedright'); fullscreenButton.connect('clicked', @@ -202,6 +204,7 @@ class ClapperHeaderBarBase extends Gtk.Box valign: Gtk.Align.CENTER, can_focus: false, }); + button.add_css_class('osd'); button.add_css_class('circular'); if(name === 'maximize') diff --git a/src/main.js b/src/main.js index 6230f765..f933a725 100644 --- a/src/main.js +++ b/src/main.js @@ -2,8 +2,11 @@ imports.gi.versions.Gdk = '4.0'; imports.gi.versions.Gtk = '4.0'; imports.gi.versions.Soup = '2.4'; -const { Gst } = imports.gi; +const { Gst, Gtk, Adw } = imports.gi; + Gst.init(null); +Gtk.init(); +Adw.init(); const { App } = imports.src.app; diff --git a/src/mainRemote.js b/src/mainRemote.js index edc8c736..c0fe0284 100644 --- a/src/mainRemote.js +++ b/src/mainRemote.js @@ -2,6 +2,11 @@ imports.gi.versions.Gdk = '4.0'; imports.gi.versions.Gtk = '4.0'; imports.gi.versions.Soup = '2.4'; +const { Gtk, Adw } = imports.gi; + +Gtk.init(); +Adw.init(); + const { AppRemote } = imports.src.appRemote; const Misc = imports.src.misc; diff --git a/src/player.js b/src/player.js index d75b94d0..1d07d06d 100644 --- a/src/player.js +++ b/src/player.js @@ -44,7 +44,7 @@ class ClapperPlayer extends GstClapper.Clapper this.ytClient = null; this.playlistWidget = new PlaylistWidget(); - this.seek_done = true; + this.seekDone = true; this.needsFastSeekRestore = false; this.customVideoTitle = null; @@ -70,6 +70,7 @@ class ClapperPlayer extends GstClapper.Clapper set_and_bind_settings() { const settingsToSet = [ + 'after-playback', 'seeking-mode', 'audio-offset', 'subtitle-offset', @@ -81,7 +82,6 @@ class ClapperPlayer extends GstClapper.Clapper this._onSettingsKeyChanged(settings, key); const flag = Gio.SettingsBindFlags.GET; - settings.bind('keep-last-frame', this.widget, 'keep-last-frame', flag); settings.bind('subtitle-font', this.pipeline, 'subtitle-font-desc', flag); } @@ -96,25 +96,23 @@ class ClapperPlayer extends GstClapper.Clapper set_all_plugins_ranks() { - let data = []; + let data = {}; /* Set empty plugin list if someone messed it externally */ try { data = JSON.parse(settings.get_string('plugin-ranking')); - if(!Array.isArray(data)) - throw new Error('plugin ranking data is not an array!'); + if(Array.isArray(data)) { + data = {}; + throw new Error('plugin ranking data is not an object'); + } } catch(err) { debug(err); - settings.set_string('plugin-ranking', "[]"); + settings.set_string('plugin-ranking', "{}"); } - for(let plugin of data) { - if(!plugin.apply || !plugin.name) - continue; - - this.set_plugin_rank(plugin.name, plugin.rank); - } + for(let plugin of Object.keys(data)) + this.set_plugin_rank(plugin, data[plugin]); } set_plugin_rank(name, rank) @@ -284,7 +282,7 @@ class ClapperPlayer extends GstClapper.Clapper if(this.needsTocUpdate) return; - this.seek_done = false; + this.seekDone = false; if(this.state === GstClapper.ClapperState.STOPPED) this.pause(); @@ -292,8 +290,6 @@ class ClapperPlayer extends GstClapper.Clapper if(position < 0) position = 0; - debug(`${this.seekingMode} seeking to position: ${position}`); - super.seek(position); } @@ -304,13 +300,12 @@ class ClapperPlayer extends GstClapper.Clapper seek_chapter(seconds) { - if(this.seekingMode !== 'fast') { + if(this.seek_mode !== GstClapper.ClapperSeekMode.FAST) { this.seek_seconds(seconds); return; } this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); - this.seekingMode = 'normal'; this.needsFastSeekRestore = true; this.seek_seconds(seconds); @@ -318,20 +313,19 @@ class ClapperPlayer extends GstClapper.Clapper adjust_position(isIncrease) { - this.seek_done = false; + this.seekDone = false; const { controls } = this.widget.get_ancestor(Gtk.Grid); const max = controls.positionAdjustment.get_upper(); - const seekingUnit = settings.get_string('seeking-unit'); let seekingValue = settings.get_int('seeking-value'); - switch(seekingUnit) { - case 'minute': - seekingValue *= 60; + switch(settings.get_int('seeking-unit')) { + case 2: /* Percentage */ + seekingValue *= max / 100; break; - case 'percentage': - seekingValue = max * seekingValue / 100; + case 1: /* Minute */ + seekingValue *= 60; break; default: break; @@ -538,16 +532,15 @@ class ClapperPlayer extends GstClapper.Clapper const clapperWidget = player.widget.get_ancestor(Gtk.Grid); if(!clapperWidget) return; - if(!this.seek_done && state !== GstClapper.ClapperState.BUFFERING) { + if(!this.seekDone && state !== GstClapper.ClapperState.BUFFERING) { clapperWidget.updateTime(); if(this.needsFastSeekRestore) { this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); - this.seekingMode = 'fast'; this.needsFastSeekRestore = false; } - this.seek_done = true; + this.seekDone = true; debug('seeking finished'); clapperWidget._onPlayerPositionUpdated(this, this.position); @@ -566,7 +559,8 @@ class ClapperPlayer extends GstClapper.Clapper if(this.playlistWidget._handleStreamEnded(player)) return; - if(settings.get_boolean('close-auto')) { + /* After playback equal 2 means close the app */ + if(settings.get_int('after-playback') === 2) { /* Stop will be automatically called soon afterwards */ this.quitOnStop = true; this._performCloseCleanup(this.widget.get_root()); @@ -640,16 +634,18 @@ class ClapperPlayer extends GstClapper.Clapper let root, value, action; switch(key) { + case 'after-playback': + this.widget.keep_last_frame = (settings.get_int(key) === 1); + break; case 'seeking-mode': - this.seekingMode = settings.get_string('seeking-mode'); - switch(this.seekingMode) { - case 'fast': + switch(settings.get_int(key)) { + case 2: /* Fast */ this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); break; - case 'accurate': + case 1: /* Accurate */ this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE); break; - default: + default: /* Normal */ this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); break; } @@ -669,12 +665,12 @@ class ClapperPlayer extends GstClapper.Clapper root[action + '_css_class'](gpuClass); break; case 'audio-offset': - value = Math.round(settings.get_double(key) * -Gst.MSECOND); + value = Math.round(settings.get_int(key) * -Gst.MSECOND); this.set_audio_video_offset(value); debug(`set audio-video offset: ${value}`); break; case 'subtitle-offset': - value = Math.round(settings.get_double(key) * -Gst.MSECOND); + value = Math.round(settings.get_int(key) * -Gst.MSECOND); this.set_subtitle_video_offset(value); debug(`set subtitle-video offset: ${value}`); break; diff --git a/src/playlist.js b/src/playlist.js index b0d80e39..1c2bcbb9 100644 --- a/src/playlist.js +++ b/src/playlist.js @@ -28,6 +28,7 @@ class ClapperPlaylistWidget extends Gtk.ListBox }); this.activeRowId = -1; this.repeatMode = RepeatMode.NONE; + this.add_css_class('playlist'); this.connect('row-activated', this._onRowActivated.bind(this)); } @@ -254,6 +255,7 @@ class ClapperPlaylistItem extends Gtk.ListBoxRow } this.filename = filename || uri; this.set_tooltip_text(this.filename); + this.add_css_class('playlistrow'); const box = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, diff --git a/src/prefs.js b/src/prefs.js index 6d4047fb..d2a657d8 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -1,354 +1,475 @@ -const { GObject, Gst, Gtk, Pango } = imports.gi; +const { Adw, GObject, Gio, Gst, Gtk } = imports.gi; +const Debug = imports.src.debug; const Misc = imports.src.misc; -const PrefsBase = imports.src.prefsBase; +const { debug } = Debug; const { settings } = Misc; /* PlayFlags are not exported through GI */ Gst.PlayFlags = { - VIDEO: 1, - AUDIO: 2, - TEXT: 4, - VIS: 8, - SOFT_VOLUME: 16, - NATIVE_AUDIO: 32, - NATIVE_VIDEO: 64, - DOWNLOAD: 128, - BUFFERING: 256, - DEINTERLACE: 512, - SOFT_COLORBALANCE: 1024, - FORCE_FILTERS: 2048, - FORCE_SW_DECODERS: 4096, + VIDEO: 1, + AUDIO: 2, + TEXT: 4, + VIS: 8, + SOFT_VOLUME: 16, + NATIVE_AUDIO: 32, + NATIVE_VIDEO: 64, + DOWNLOAD: 128, + BUFFERING: 256, + DEINTERLACE: 512, + SOFT_COLORBALANCE: 1024, + FORCE_FILTERS: 2048, + FORCE_SW_DECODERS: 4096, }; -var GeneralPage = GObject.registerClass( -class ClapperGeneralPage extends PrefsBase.Grid +const widgetOpts = { + halign: Gtk.Align.CENTER, + valign: Gtk.Align.CENTER, +}; + +function getCommonProps() { - _init() + return { + 'schema-name': GObject.ParamSpec.string( + 'schema-name', + 'GSchema setting name', + 'Name of the setting to bind', + GObject.ParamFlags.WRITABLE, + null + ), + }; +} + +const flags = Gio.SettingsBindFlags.DEFAULT; + +let PrefsActionRow = GObject.registerClass({ + GTypeName: 'ClapperPrefsActionRow', + Properties: getCommonProps(), +}, +class ClapperPrefsActionRow extends Adw.ActionRow +{ + _init(widget) { super._init(); - this.addTitle('Startup'); - this.addCheckButton('Auto enter fullscreen', 'fullscreen-auto'); + this._schemaName = null; + this._bindProp = null; - this.addTitle('Volume'); - const comboBox = this.addComboBoxText('Initial value', [ - ['restore', "Restore"], - ['custom', "Custom"], - ], 'volume-initial'); - const spinButton = this.addSpinButton('Value (percentage)', 0, 200, 'volume-value'); - this._onVolumeInitialChanged(spinButton, comboBox); - comboBox.connect('changed', this._onVolumeInitialChanged.bind(this, spinButton)); - - this.addTitle('Finish'); - this.addCheckButton('Keep showing last frame', 'keep-last-frame'); - this.addCheckButton('Close after playback', 'close-auto'); + this.add_suffix(widget); + this.set_activatable_widget(widget); } - _onVolumeInitialChanged(spinButton, comboBox) + set schema_name(value) { - const value = comboBox.get_active_id(); - spinButton.set_visible(value === 'custom'); - } -}); - -var BehaviourPage = GObject.registerClass( -class ClapperBehaviourPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Seeking'); - this.addComboBoxText('Mode', [ - ['normal', "Normal"], - ['accurate', "Accurate"], - ['fast', "Fast"], - ], 'seeking-mode'); - this.addComboBoxText('Unit', [ - ['second', "Second"], - ['minute', "Minute"], - ['percentage', "Percentage"], - ], 'seeking-unit'); - this.addSpinButton('Value', 1, 99, 'seeking-value'); - - this.addTitle('Resume'); - this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled'); - - this.addTitle('Floating Mode'); - this.addCheckButton('Show on all workspaces', 'floating-stick'); - } -}); - -var AudioPage = GObject.registerClass( -class ClapperAudioPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Synchronization'); - this.addSpinButton('Offset (milliseconds)', -1000, 1000, 'audio-offset', 25); - - this.addTitle('Processing'); - this.addPlayFlagCheckButton('Only use native audio formats', Gst.PlayFlags.NATIVE_AUDIO); - } -}); - -var SubtitlesPage = GObject.registerClass( -class ClapperSubtitlesPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - /* FIXME: This should be moved to subtitles popup and displayed only when - external subtitles were added for easier customization per video. */ - //this.addTitle('Synchronization'); - //this.addSpinButton('Offset (milliseconds)', -5000, 5000, 'subtitle-offset', 25); - - this.addTitle('External Subtitles'); - this.addFontButton('Default font', 'subtitle-font'); - } -}); - -var NetworkPage = GObject.registerClass( -class ClapperNetworkPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Client'); - this.addPlayFlagCheckButton('Progressive download buffering', Gst.PlayFlags.DOWNLOAD); - - this.addTitle('Server'); - const webServer = this.addCheckButton('Control player remotely', 'webserver-enabled'); - const serverPort = this.addSpinButton('Listening port', 1024, 65535, 'webserver-port'); - webServer.bind_property('active', serverPort, 'visible', GObject.BindingFlags.SYNC_CREATE); - const webApp = this.addCheckButton('Start built-in web application', 'webapp-enabled'); - webServer.bind_property('active', webApp, 'visible', GObject.BindingFlags.SYNC_CREATE); - const webAppPort = this.addSpinButton('Web application port', 1024, 65535, 'webapp-port'); - webServer.bind_property('active', webAppPort, 'visible', GObject.BindingFlags.SYNC_CREATE); - } -}); - -var YouTubePage = GObject.registerClass( -class ClapperYouTubePage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('YouTube'); - this.addCheckButton('Prefer adaptive streaming', 'yt-adaptive-enabled'); - this.addComboBoxText('Max quality', [ - ['normal', "Normal"], - ['hfr', "HFR"], - ], 'yt-quality-type'); - } -}); - -var GStreamerPage = GObject.registerClass( -class ClapperGStreamerPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Plugin Ranking'); - const listStore = new Gtk.ListStore(); - listStore.set_column_types([ - GObject.TYPE_BOOLEAN, - GObject.TYPE_STRING, - GObject.TYPE_STRING, - ]); - const treeView = new Gtk.TreeView({ - hexpand: true, - vexpand: true, - enable_search: false, - model: listStore, - }); - const treeSelection = treeView.get_selection(); - - const apply = new Gtk.TreeViewColumn({ - title: "Apply", - }); - const name = new Gtk.TreeViewColumn({ - title: "Plugin", - expand: true, - }); - const rank = new Gtk.TreeViewColumn({ - title: "Rank", - min_width: 90, - }); - - const applyCell = new Gtk.CellRendererToggle(); - const nameCell = new Gtk.CellRendererText({ - editable: true, - placeholder_text: "Insert plugin name", - }); - const rankCell = new Gtk.CellRendererText({ - editable: true, - weight: Pango.Weight.BOLD, - placeholder_text: "Insert plugin rank", - }); - - apply.pack_start(applyCell, true); - name.pack_start(nameCell, true); - rank.pack_start(rankCell, true); - - apply.add_attribute(applyCell, 'active', 0); - name.add_attribute(nameCell, 'text', 1); - rank.add_attribute(rankCell, 'text', 2); - - treeView.insert_column(apply, 0); - treeView.insert_column(name, 1); - treeView.insert_column(rank, 2); - - const frame = new Gtk.Frame({ - child: treeView - }); - this.addToGrid(frame); - - const addButton = new Gtk.Button({ - icon_name: 'list-add-symbolic', - halign: Gtk.Align.END, - }); - const removeButton = new Gtk.Button({ - icon_name: 'list-remove-symbolic', - sensitive: false, - halign: Gtk.Align.END, - }); - const label = new Gtk.Label({ - label: 'Changes require player restart', - halign: Gtk.Align.START, - hexpand: true, - ellipsize: Pango.EllipsizeMode.END, - }); - const box = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - spacing: 6, - hexpand: true, - }); - box.append(label); - box.append(removeButton); - box.append(addButton); - this.addToGrid(box); - - applyCell.connect('toggled', this._onApplyCellEdited.bind(this)); - nameCell.connect('edited', this._onNameCellEdited.bind(this)); - rankCell.connect('edited', this._onRankCellEdited.bind(this)); - addButton.connect('clicked', this._onAddButtonClicked.bind(this, listStore)); - removeButton.connect('clicked', this._onRemoveButtonClicked.bind(this, listStore)); - treeSelection.connect('changed', this._onTreeSelectionChanged.bind(this, removeButton)); - - this.settingsChangedSignal = settings.connect( - 'changed::plugin-ranking', this.refreshListStore.bind(this, listStore) - ); - - this.refreshListStore(listStore); + this._schemaName = value; } - refreshListStore(listStore) + vfunc_realize() { - const data = JSON.parse(settings.get_string('plugin-ranking')); - listStore.clear(); + super.vfunc_realize(); - for(let plugin of data) { - listStore.set( - listStore.append(), - [0, 1, 2], [ - plugin.apply || false, - plugin.name || '', - plugin.rank || 0 - ] + if(this._schemaName && this._bindProp) { + settings.bind(this._schemaName, + this.activatable_widget, this._bindProp, flags ); } - } - - updatePlugin(index, prop, value) - { - const data = JSON.parse(settings.get_string('plugin-ranking')); - data[index][prop] = value; - settings.set_string('plugin-ranking', JSON.stringify(data)); - } - - _onTreeSelectionChanged(removeButton, treeSelection) - { - const [isSelected, model, iter] = treeSelection.get_selected(); - this.activeIndex = -1; - - if(isSelected) { - this.activeIndex = Number(model.get_string_from_iter(iter)); - } - - removeButton.set_sensitive(this.activeIndex >= 0); - } - - _onAddButtonClicked(listStore, button) - { - const data = JSON.parse(settings.get_string('plugin-ranking')); - data.push({ - apply: false, - name: '', - rank: 0, - }); - settings.set_string('plugin-ranking', JSON.stringify(data)); - } - - _onRemoveButtonClicked(listStore, button) - { - if(this.activeIndex < 0) - return; - - const data = JSON.parse(settings.get_string('plugin-ranking')); - data.splice(this.activeIndex, 1); - settings.set_string('plugin-ranking', JSON.stringify(data)); - } - - _onApplyCellEdited(cell, path) - { - const newState = !cell.active; - this.updatePlugin(path, 'apply', newState); - } - - _onNameCellEdited(cell, path, newText) - { - newText = newText.trim(); - this.updatePlugin(path, 'name', newText); - } - - _onRankCellEdited(cell, path, newText) - { - newText = newText.trim(); - - if(isNaN(newText)) - newText = 0; - - this.updatePlugin(path, 'rank', Number(newText)); - } - - _onClose() - { - super._onClose('gstreamer'); - - settings.disconnect(this.settingsChangedSignal); - this.settingsChangedSignal = null; + this._schemaName = null; } }); -var TweaksPage = GObject.registerClass( -class ClapperTweaksPage extends PrefsBase.Grid +let PrefsSubpageRow = GObject.registerClass({ + GTypeName: 'ClapperPrefsSubpageRow', + Properties: getCommonProps(), +}, +class ClapperPrefsSubpageRow extends Adw.ActionRow +{ + _init(widget) + { + super._init({ + activatable: true, + }); + + this._prefsSubpage = null; + + const icon = new Gtk.Image({ + icon_name: 'go-next-symbolic', + }); + this.add_suffix(icon); + } + + vfunc_activate() + { + super.vfunc_activate(); + + if(!this._prefsSubpage) + this._prefsSubpage = this._createSubpage(); + + const prefs = this.get_ancestor(PrefsWindow); + prefs.present_subpage(this._prefsSubpage); + } + + _createSubpage() + { + /* For override */ + return null; + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsSwitch', +}, +class ClapperPrefsSwitch extends PrefsActionRow +{ + _init() + { + super._init(new Gtk.Switch(widgetOpts)); + this._bindProp = 'active'; + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsPlayFlagSwitch', + Properties: { + 'play-flag': GObject.ParamSpec.int( + 'play-flag', + 'PlayFlag', + 'Value of the gstreamer play flag to toggle', + GObject.ParamFlags.WRITABLE, + 1, 4096, 1, + ), + }, +}, +class ClapperPrefsPlayFlagSwitch extends PrefsActionRow +{ + _init() + { + super._init(new Gtk.Switch(widgetOpts)); + + this._flag = 1; + this._doneRealize = false; + } + + set play_flag(value) + { + this._flag = value; + } + + vfunc_realize() + { + super.vfunc_realize(); + + if(!this._doneRealize) { + const playFlags = settings.get_int('play-flags'); + + this.activatable_widget.active = ( + (playFlags & this._flag) === this._flag + ); + this.activatable_widget.connect( + 'notify::active', this._onPlayFlagToggled.bind(this) + ); + } + this._doneRealize = true; + } + + _onPlayFlagToggled() + { + let playFlags = settings.get_int('play-flags'); + + if(this.activatable_widget.active) + playFlags |= this._flag; + else + playFlags &= ~this._flag; + + settings.set_int('play-flags', playFlags); + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsSpin', + Properties: { + 'spin-adjustment': GObject.ParamSpec.object( + 'spin-adjustment', + 'GtkAdjustment', + 'Custom GtkAdjustment for spin button', + GObject.ParamFlags.WRITABLE, + Gtk.Adjustment + ), + }, +}, +class ClapperPrefsSpin extends PrefsActionRow +{ + _init() + { + super._init(new Gtk.SpinButton(widgetOpts)); + this._bindProp = 'value'; + } + + set spin_adjustment(value) + { + this.activatable_widget.set_adjustment(value); + } +}); + +let PrefsPluginFeature = GObject.registerClass( +class PrefsPluginFeature extends Adw.ActionRow +{ + _init(featureObj) + { + super._init({ + title: featureObj.name, + }); + + const enableSwitch = new Gtk.Switch(widgetOpts); + const spinButton = new Gtk.SpinButton(widgetOpts); + + spinButton.set_range(0, 512); + spinButton.set_increments(1, 1); + + enableSwitch.active = featureObj.enabled; + spinButton.value = featureObj.rank; + this.currentRank = featureObj.rank; + + this.add_suffix(enableSwitch); + this.add_suffix(spinButton); + + enableSwitch.bind_property('active', spinButton, 'sensitive', + GObject.BindingFlags.SYNC_CREATE + ); + + enableSwitch.connect('notify::active', this._onSwitchActivate.bind(this)); + spinButton.connect('value-changed', this._onValueChanged.bind(this)); + } + + _updateRanking(data) + { + settings.set_string('plugin-ranking', JSON.stringify(data)); + } + + _onSwitchActivate(enableSwitch) + { + const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage); + + if(enableSwitch.active) + settingsData[this.title] = this.currentRank; + else if(settingsData[this.title]) + delete settingsData[this.title]; + + this._updateRanking(settingsData); + } + + _onValueChanged(spinButton) + { + const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage); + + this.currentRank = spinButton.value; + settingsData[this.title] = this.currentRank; + + this._updateRanking(settingsData); + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsFont', +}, +class ClapperPrefsFont extends PrefsActionRow +{ + _init() + { + const opts = { + use_font: true, + use_size: true, + }; + Object.assign(opts, widgetOpts); + + super._init(new Gtk.FontButton(opts)); + this._bindProp = 'font'; + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsCombo', + Properties: getCommonProps(), +}, +class ClapperPrefsCombo extends Adw.ComboRow +{ + _init() + { + super._init(); + this._schemaName = null; + } + + set schema_name(value) + { + this._schemaName = value; + } + + vfunc_realize() + { + super.vfunc_realize(); + + if(this._schemaName) + settings.bind(this._schemaName, this, 'selected', flags); + + this._schemaName = null; + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsExpander', + Properties: getCommonProps(), +}, +class ClapperPrefsExpander extends Adw.ExpanderRow +{ + _init() + { + super._init({ + show_enable_switch: true, + }); + } + + set schema_name(value) + { + settings.bind(value, this, 'enable-expansion', flags); + } +}); + +GObject.registerClass({ + GTypeName: 'ClapperPrefsPluginRankingSubpageRow', +}, +class ClapperPrefsPluginRankingSubpageRow extends PrefsSubpageRow +{ + _createSubpage() + { + return new PrefsPluginRankingSubpage(); + } +}); + +let PrefsPluginExpander = GObject.registerClass( +class ClapperPrefsPluginExpander extends Adw.ExpanderRow +{ + _init(plugin) + { + super._init({ + title: plugin, + show_enable_switch: false, + }); + + this.expandSignal = this.connect( + 'notify::expanded', this._onExpandedNotify.bind(this) + ); + } + + _onExpandedNotify() + { + if(!this.expanded) + return; + + this.disconnect(this.expandSignal); + this.expandSignal = null; + + const { pluginsData } = this.get_ancestor(PrefsPluginRankingSubpage); + + pluginsData[this.title].sort((a, b) => + (a.name > b.name) - (a.name < b.name) + ); + const featuresNames = Object.keys(pluginsData[this.title]); + debug(`Adding ${featuresNames.length} features to the list of plugin: ${this.title}`); + + for(let featureObj of pluginsData[this.title]) + this.add(new PrefsPluginFeature(featureObj)); + } +}); + +let PrefsPluginRankingSubpage = GObject.registerClass({ + GTypeName: 'ClapperPrefsPluginRankingSubpage', + Template: `file://${Misc.getClapperPath()}/ui/preferences-plugin-ranking-subpage.ui`, + InternalChildren: ['decoders_group'], +}, +class ClapperPrefsPluginRankingSubpage extends Gtk.Box { _init() { super._init(); - this.addTitle('Appearance'); - this.addCheckButton('Enable dark theme', 'dark-theme'); + if(!Gst.is_initialized()) + Gst.init(null); - this.addTitle('Performance'); - this.addCheckButton('Render window shadows', 'render-shadows'); + const gstRegistry = Gst.Registry.get(); + const decoders = gstRegistry.feature_filter(this._decodersFilterCb, false); + + const plugins = {}; + this.settingsData = {}; + + /* In case someone messed up gsettings values */ + try { + this.settingsData = JSON.parse(settings.get_string('plugin-ranking')); + /* Might be an array in older Clapper versions */ + if(Array.isArray(this.settingsData)) + this.settingsData = {}; + } + catch(err) { /* Ignore */ } + + for(let decoder of decoders) { + const pluginName = decoder.get_plugin_name(); + + /* Do not add unsupported plugins */ + switch(pluginName) { + case 'playback': + continue; + default: + break; + } + + if(!plugins[pluginName]) + plugins[pluginName] = []; + + const decName = decoder.get_name(); + + plugins[pluginName].push({ + name: decName, + rank: decoder.get_rank(), + enabled: this.settingsData[decName] != null, + }); + } + + const pluginsNames = Object.keys(plugins); + debug(`Adding ${pluginsNames.length} found plugins to the list`); + + this.pluginsData = pluginsNames.sort().reduce((res, key) => + (res[key] = plugins[key], res), {} + ); + + for(let plugin in this.pluginsData) + this._decoders_group.add(new PrefsPluginExpander(plugin)); + } + + _decodersFilterCb(feature) + { + return ( + feature.list_is_type + && feature.list_is_type(Gst.ELEMENT_FACTORY_TYPE_DECODER) + ); + } + + _onReturnClicked(button) + { + const prefs = this.get_ancestor(PrefsWindow); + prefs.close_subpage(); + } +}); + +var PrefsWindow = GObject.registerClass({ + GTypeName: 'ClapperPrefsWindow', + Template: `file://${Misc.getClapperPath()}/ui/preferences-window.ui`, +}, +class ClapperPrefsWindow extends Adw.PreferencesWindow +{ + _init(window) + { + super._init({ + transient_for: window, + }); + + this.show(); } }); diff --git a/src/prefsBase.js b/src/prefsBase.js deleted file mode 100644 index 2d12442a..00000000 --- a/src/prefsBase.js +++ /dev/null @@ -1,238 +0,0 @@ -const { Gio, GObject, Gtk } = imports.gi; -const Debug = imports.src.debug; -const Misc = imports.src.misc; - -const { debug } = Debug; -const { settings } = Misc; - -var Notebook = GObject.registerClass( -class ClapperPrefsNotebook extends Gtk.Notebook -{ - _init(pages, isSubpage) - { - super._init({ - show_border: false, - vexpand: true, - hexpand: true, - }); - - if(isSubpage) { - this.set_tab_pos(Gtk.PositionType.LEFT); - this.add_css_class('prefssubpage'); - } - - this.addArrayPages(pages); - } - - addArrayPages(array) - { - for(let obj of array) - this.addObjectPages(obj); - } - - addObjectPages(item) - { - const widget = (item.pages) - ? new Notebook(item.pages, true) - : new item.widget(); - - this.addToNotebook(widget, item.title); - } - - addToNotebook(widget, title) - { - const label = new Gtk.Label({ - label: title, - }); - this.append_page(widget, label); - } - - _onClose() - { - const totalPages = this.get_n_pages(); - let index = 0; - - while(index < totalPages) { - const page = this.get_nth_page(index); - page._onClose(); - index++; - } - } -}); - -var Grid = GObject.registerClass( -class ClapperPrefsGrid extends Gtk.Grid -{ - _init() - { - super._init({ - row_spacing: 6, - column_spacing: 20, - }); - - this.flag = Gio.SettingsBindFlags.DEFAULT; - this.gridIndex = 0; - this.widgetDefaults = { - width_request: 160, - halign: Gtk.Align.END, - valign: Gtk.Align.CENTER, - }; - } - - addToGrid(leftWidget, rightWidget) - { - let spanWidth = 2; - - if(rightWidget) { - spanWidth = 1; - rightWidget.bind_property('visible', leftWidget, 'visible', - GObject.BindingFlags.SYNC_CREATE - ); - this.attach(rightWidget, 1, this.gridIndex, 1, 1); - } - - this.attach(leftWidget, 0, this.gridIndex, spanWidth, 1); - this.gridIndex++; - - return rightWidget || leftWidget; - } - - addTitle(text) - { - const label = this.getLabel(text, true); - - return this.addToGrid(label); - } - - addComboBoxText(text, entries, setting) - { - const label = this.getLabel(text + ':'); - const widget = this.getComboBoxText(entries, setting); - - return this.addToGrid(label, widget); - } - - addSpinButton(text, min, max, setting, precision) - { - const label = this.getLabel(text + ':'); - const widget = this.getSpinButton(min, max, setting, precision); - - return this.addToGrid(label, widget); - } - - addCheckButton(text, setting) - { - const widget = this.getCheckButton(text, setting); - - return this.addToGrid(widget); - } - - addPlayFlagCheckButton(text, flag) - { - const checkButton = this.addCheckButton(text); - const playFlags = settings.get_int('play-flags'); - - checkButton.active = ((playFlags & flag) === flag); - checkButton.connect('toggled', this._onPlayFlagToggled.bind(this, flag)); - - return checkButton; - } - - addFontButton(text, setting) - { - const label = this.getLabel(text + ':'); - const widget = this.getFontButton(setting); - - return this.addToGrid(label, widget); - } - - getLabel(text, isTitle) - { - const marginTop = (isTitle && this.gridIndex > 0) ? 16 : 0; - const marginBottom = (isTitle) ? 2 : 0; - - let marginLR = 0; - - if(isTitle) - text = '' + text + ''; - else - marginLR = 12; - - return new Gtk.Label({ - label: text, - use_markup: true, - hexpand: true, - halign: Gtk.Align.START, - margin_top: marginTop, - margin_bottom: marginBottom, - margin_start: marginLR, - margin_end: marginLR, - }); - } - - getComboBoxText(entries, setting) - { - const comboBox = new Gtk.ComboBoxText(this.widgetDefaults); - - for(let entry of entries) - comboBox.append(entry[0], entry[1]); - - settings.bind(setting, comboBox, 'active-id', this.flag); - - return comboBox; - } - - getSpinButton(min, max, setting, precision) - { - precision = precision || 1; - - const spinButton = new Gtk.SpinButton(this.widgetDefaults); - spinButton.set_range(min, max); - spinButton.set_digits(precision % 1 === 0 ? 0 : 3); - spinButton.set_increments(precision, 1); - settings.bind(setting, spinButton, 'value', this.flag); - - return spinButton; - } - - getCheckButton(text, setting) - { - const checkButton = new Gtk.CheckButton({ - label: text || null, - }); - - if(setting) - settings.bind(setting, checkButton, 'active', this.flag); - - return checkButton; - } - - getFontButton(setting) - { - const fontButton = new Gtk.FontButton({ - use_font: true, - use_size: true, - }); - settings.bind(setting, fontButton, 'font', this.flag); - - return fontButton; - } - - _onPlayFlagToggled(flag, button) - { - let playFlags = settings.get_int('play-flags'); - - if(button.active) - playFlags |= flag; - else - playFlags &= ~flag; - - settings.set_int('play-flags', playFlags); - } - - _onClose(name) - { - if(name) - debug(`cleanup of prefs ${name} page`); - } -}); diff --git a/src/widget.js b/src/widget.js index dbe40443..900a9f13 100644 --- a/src/widget.js +++ b/src/widget.js @@ -492,7 +492,7 @@ class ClapperWidget extends Gtk.Grid if( !this.isSeekable || this.controls.isPositionDragging - || !player.seek_done + || !player.seekDone ) return; diff --git a/src/youtube.js b/src/youtube.js index b4e0fdbc..7cd7d8e1 100644 --- a/src/youtube.js +++ b/src/youtube.js @@ -317,7 +317,7 @@ var YouTubeClient = GObject.registerClass({ width: monitor.geometry.width * monitor.scale_factor, height: monitor.geometry.height * monitor.scale_factor, codec: 'h264', - type: settings.get_string('yt-quality-type'), + type: YTItags.QualityType[settings.get_int('yt-quality-type')], adaptive: settings.get_boolean('yt-adaptive-enabled'), }; diff --git a/src/youtubeItags.js b/src/youtubeItags.js index 374ea8a1..09b69326 100644 --- a/src/youtubeItags.js +++ b/src/youtubeItags.js @@ -1,3 +1,8 @@ +var QualityType = { + 0: 'normal', + 1: 'hfr', +}; + const Itags = { video: { h264: { diff --git a/ui/preferences-plugin-ranking-subpage.ui b/ui/preferences-plugin-ranking-subpage.ui new file mode 100644 index 00000000..6d98919e --- /dev/null +++ b/ui/preferences-plugin-ranking-subpage.ui @@ -0,0 +1,30 @@ + + + + diff --git a/ui/preferences-window.ui b/ui/preferences-window.ui new file mode 100644 index 00000000..8a71aa4b --- /dev/null +++ b/ui/preferences-window.ui @@ -0,0 +1,292 @@ + + + + + 0 + 99 + 1 + 1 + + + 0 + 150 + 1 + 1 + + + -1000 + 1000 + 25 + 1 + + + 1024 + 65535 + 1 + 1 + + + 1024 + 65535 + 1 + 1 + +