mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 08:21:59 +02:00
Merge pull request #8 from Rafostar/GTK4
We are moving to GTK4! Porting took a little longer than expected, cause I had to port GStreamer first. This change will be problematic for users of non-rolling linux distros, but its worth it. Both GTK4 and GStreamer 1.18+ have important GL changes. Clapper player takes full advantage of them.
This commit is contained in:
132
README.md
132
README.md
@@ -28,10 +28,11 @@ Using hardware acceleration is highly recommended. As stated in `GStreamer` wiki
|
||||
In the case of OpenGL based elements, the buffers have the GstVideoGLTextureUploadMeta meta, which
|
||||
efficiently copies the content of the VA-API surface into a GL texture.
|
||||
```
|
||||
Clapper uses `OpenGL` based sinks, so when `VA-API` is available, both CPU and RAM usage is much lower.
|
||||
Clapper uses `OpenGL` based sinks, so when `VA-API` is available, both CPU and RAM usage is much lower. Especially if you have `gst-plugins-bad` 1.18+ with new `vah264dec` decoder which shares a single GL context with Clapper and uses DRM connection. If you have an AMD/Intel GPU, I highly recommend this new decoder.
|
||||
|
||||
To use `VA-API` make sure you have `gstreamer1-vaapi` installed. Verify with:
|
||||
To use `VA-API` with H.264 videos, make sure you have `gst-plugins-bad` 1.18+. For other codecs additionally install `gstreamer1-vaapi`. Verify with:
|
||||
```shell
|
||||
gst-inspect-1.0 vah264dec
|
||||
gst-inspect-1.0 vaapi
|
||||
```
|
||||
On some older GPUs you might need to export `GST_VAAPI_ALL_DRIVERS=1` environment variable.
|
||||
@@ -40,11 +41,13 @@ Other acceleration methods (supported by `GStreamer`) should also work, but I ha
|
||||
</details>
|
||||
|
||||
## Requirements
|
||||
Clapper uses `GStreamer` bindings from `GI` repository, so if your distro ships them as separate package, they must be installed first.
|
||||
Clapper uses GTK4 along with `GStreamer` bindings from `GI` repository, so if your distro ships them as separate package, they must be installed first.
|
||||
Additionally Clapper requires these `GStreamer` elements:
|
||||
* [gtkglsink](https://gstreamer.freedesktop.org/documentation/gtk/gtkglsink.html)
|
||||
* [gtk4glsink](https://gstreamer.freedesktop.org/documentation/gtk/gtkglsink.html)
|
||||
* [glsinkbin](https://gstreamer.freedesktop.org/documentation/opengl/glsinkbin.html)
|
||||
|
||||
**Attention:** `gtk4glsink` is my own port of current GStreamer `gtkglsink` to GTK4. The element is not part of GStreamer yet (pending review). Fedora package is available in my OBS repository. It will be installed along with Clapper if you add my repo to `dnf` package manager. Otherwise you might want to build it yourself from [source code](https://gitlab.freedesktop.org/Rafostar/gst-plugins-good/-/tree/GTK4) of my gstreamer GTK4 branch.
|
||||
|
||||
Other required plugins (codecs) depend on video format.
|
||||
|
||||
Recommended additional packages you should install manually via package manager:
|
||||
@@ -53,69 +56,90 @@ Recommended additional packages you should install manually via package manager:
|
||||
|
||||
Please note that packages naming varies by distro.
|
||||
|
||||
## Installation
|
||||
Run in terminal:
|
||||
```shell
|
||||
meson builddir --prefix=/usr/local
|
||||
sudo meson install -C builddir
|
||||
```
|
||||
|
||||
Additional GStreamer elements installation:
|
||||
<details>
|
||||
<summary>Fedora</summary>
|
||||
|
||||
Enable RPM Fusion and run:
|
||||
```shell
|
||||
sudo dnf install \
|
||||
gstreamer1-plugins-base \
|
||||
gstreamer1-plugins-good-gtk \
|
||||
gstreamer1-libav \
|
||||
gstreamer1-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>openSUSE</summary>
|
||||
|
||||
```shell
|
||||
sudo zypper install \
|
||||
gstreamer-plugins-base \
|
||||
gstreamer-plugins-good \
|
||||
gstreamer-plugins-libav \
|
||||
gstreamer-plugins-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch Linux</summary>
|
||||
|
||||
```shell
|
||||
sudo pacman -S \
|
||||
gst-plugins-base \
|
||||
gst-plugin-gtk \
|
||||
gst-libav \
|
||||
gstreamer-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
## 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:
|
||||
<details>
|
||||
<summary>Fedora, openSUSE & SLE (rpm)</summary>
|
||||
<summary><b>Debian, Fedora, openSUSE & Ubuntu</b></summary>
|
||||
|
||||
Pre-built packages are available here:<br>
|
||||
[software.opensuse.org//download.html?project=home:sp1rit&package=clapper](https://software.opensuse.org//download.html?project=home%3Asp1rit&package=clapper) ([See status](https://build.opensuse.org/package/show/home:sp1rit/clapper))
|
||||
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))
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch Linux</summary>
|
||||
<summary><b>Arch Linux</b></summary>
|
||||
|
||||
You can get clapper from the AUR: [clapper-git](https://aur.archlinux.org/packages/clapper-git), or
|
||||
You can get Clapper from the AUR: [clapper-git](https://aur.archlinux.org/packages/clapper-git), or
|
||||
```shell
|
||||
cd pkgs/arch
|
||||
makepkg -si
|
||||
```
|
||||
</details>
|
||||
|
||||
## Installation from source code
|
||||
Run in terminal:
|
||||
```shell
|
||||
meson builddir --prefix=/usr/local
|
||||
sudo meson install -C builddir
|
||||
```
|
||||
|
||||
GStreamer elements installation:
|
||||
<details>
|
||||
<summary><b>Debian/Ubuntu</b></summary>
|
||||
|
||||
```shell
|
||||
sudo apt install \
|
||||
gstreamer1.0-plugins-base \
|
||||
gstreamer1.0-plugins-good \
|
||||
gstreamer1.0-plugins-bad \
|
||||
gstreamer1.0-gl \
|
||||
gstreamer1.0-gtk4 \
|
||||
gstreamer1.0-libav \
|
||||
gstreamer-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Fedora</b></summary>
|
||||
|
||||
Enable RPM Fusion and run:
|
||||
```shell
|
||||
sudo dnf install \
|
||||
gstreamer1-plugins-base \
|
||||
gstreamer1-plugins-good \
|
||||
gstreamer1-plugins-good-gtk4 \
|
||||
gstreamer1-plugins-bad-free \
|
||||
gstreamer1-plugins-bad-free-extras \
|
||||
gstreamer1-libav \
|
||||
gstreamer1-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>openSUSE</b></summary>
|
||||
|
||||
```shell
|
||||
sudo zypper install \
|
||||
gstreamer-plugins-base \
|
||||
gstreamer-plugins-good \
|
||||
gstreamer-plugins-good-gtk4 \
|
||||
gstreamer-plugins-bad \
|
||||
gstreamer-plugins-libav \
|
||||
gstreamer-plugins-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Arch Linux</b></summary>
|
||||
|
||||
```shell
|
||||
sudo pacman -S \
|
||||
gst-plugins-base \
|
||||
gst-plugins-good \
|
||||
gst-plugin-gtk4 \
|
||||
gst-plugins-bad-libs \
|
||||
gst-libav \
|
||||
gstreamer-vaapi
|
||||
```
|
||||
</details>
|
||||
|
||||
## Special Thanks
|
||||
Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files.
|
||||
|
@@ -1,5 +1,6 @@
|
||||
const { Gdk, GLib, GObject, Gtk, GstPlayer } = imports.gi;
|
||||
const Debug = imports.clapper_src.debug;
|
||||
const { HeaderBar } = imports.clapper_src.headerbar;
|
||||
const { Interface } = imports.clapper_src.interface;
|
||||
const { Player } = imports.clapper_src.player;
|
||||
const { Window } = imports.clapper_src.window;
|
||||
@@ -20,8 +21,6 @@ var App = GObject.registerClass({
|
||||
{
|
||||
_init(opts)
|
||||
{
|
||||
GLib.set_prgname(APP_NAME);
|
||||
|
||||
super._init({
|
||||
application_id: pkg.name
|
||||
});
|
||||
@@ -39,7 +38,10 @@ var App = GObject.registerClass({
|
||||
this.window = null;
|
||||
this.interface = null;
|
||||
this.player = null;
|
||||
this.dragStartReady = false;
|
||||
this.dragAllowed = false;
|
||||
|
||||
this.posX = 0;
|
||||
this.posY = 0;
|
||||
}
|
||||
|
||||
vfunc_startup()
|
||||
@@ -51,35 +53,43 @@ var App = GObject.registerClass({
|
||||
'realize', this._onWindowRealize.bind(this)
|
||||
);
|
||||
this.window.connect(
|
||||
'key-press-event', this._onWindowKeyPressEvent.bind(this)
|
||||
'fullscreen-changed', this._onWindowFullscreenChanged.bind(this)
|
||||
);
|
||||
this.window.connect(
|
||||
'fullscreen-changed', this._onWindowFullscreenChanged.bind(this)
|
||||
'close-request', this._onWindowCloseRequest.bind(this)
|
||||
);
|
||||
|
||||
this.interface = new Interface();
|
||||
let headerBar = new Gtk.HeaderBar({
|
||||
title: APP_NAME,
|
||||
show_close_button: true,
|
||||
});
|
||||
headerBar.pack_end(this.interface.controls.openMenuButton);
|
||||
headerBar.pack_end(this.interface.controls.fullscreenButton);
|
||||
|
||||
let headerStart = [];
|
||||
let headerEnd = [
|
||||
this.interface.controls.openMenuButton,
|
||||
this.interface.controls.fullscreenButton
|
||||
];
|
||||
let headerBar = new HeaderBar(this.window, headerStart, headerEnd);
|
||||
this.interface.addHeaderBar(headerBar, APP_NAME);
|
||||
|
||||
this.interface.controls.fullscreenButton.connect(
|
||||
'clicked', () => this._onInterfaceToggleFullscreenClicked(true)
|
||||
'clicked', () => this.activeWindow.fullscreen()
|
||||
);
|
||||
this.interface.controls.unfullscreenButton.connect(
|
||||
'clicked', () => this._onInterfaceToggleFullscreenClicked(false)
|
||||
'clicked', () => this.activeWindow.unfullscreen()
|
||||
);
|
||||
|
||||
this.window.set_titlebar(this.interface.headerBar);
|
||||
this.window.add(this.interface);
|
||||
this.window.set_child(this.interface);
|
||||
}
|
||||
|
||||
vfunc_activate()
|
||||
{
|
||||
super.vfunc_activate();
|
||||
this.window.show_all();
|
||||
|
||||
this.window.present();
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
Gdk.Display.get_default(),
|
||||
this.cssProvider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
}
|
||||
|
||||
run(arr)
|
||||
@@ -94,8 +104,8 @@ var App = GObject.registerClass({
|
||||
this.hideCursorTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
|
||||
this.hideCursorTimeout = null;
|
||||
|
||||
if(this.isCursorInPlayer)
|
||||
this.playerWindow.set_cursor(this.blankCursor);
|
||||
if(this.player.motionController.is_pointer)
|
||||
this.player.widget.set_cursor(this.blankCursor);
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
@@ -107,7 +117,7 @@ var App = GObject.registerClass({
|
||||
this.hideControlsTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => {
|
||||
this.hideControlsTimeout = null;
|
||||
|
||||
if(this.window.isFullscreen && this.isCursorInPlayer) {
|
||||
if(this.window.isFullscreen && this.player.motionController.is_pointer) {
|
||||
this.clearTimeout('updateTime');
|
||||
this.interface.revealControls(false);
|
||||
}
|
||||
@@ -146,80 +156,63 @@ var App = GObject.registerClass({
|
||||
{
|
||||
this.window.disconnect(this.windowRealizeSignal);
|
||||
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
this.cssProvider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
|
||||
this.player = new Player();
|
||||
this.player.widget.add_events(
|
||||
Gdk.EventMask.SCROLL_MASK
|
||||
| Gdk.EventMask.ENTER_NOTIFY_MASK
|
||||
| Gdk.EventMask.LEAVE_NOTIFY_MASK
|
||||
);
|
||||
this.interface.addPlayer(this.player);
|
||||
|
||||
this.player.connect('warning', this._onPlayerWarning.bind(this));
|
||||
this.player.connect('error', this._onPlayerError.bind(this));
|
||||
if(!this.player.widget)
|
||||
return this.quit();
|
||||
|
||||
this.player.widget.width_request = 960;
|
||||
this.player.widget.height_request = 540;
|
||||
|
||||
this.interface.addPlayer(this.player);
|
||||
this.player.connect('state-changed', this._onPlayerStateChanged.bind(this));
|
||||
|
||||
this.player.connectWidget(
|
||||
'button-press-event', this._onPlayerButtonPressEvent.bind(this)
|
||||
this.player.clickGesture.connect(
|
||||
'pressed', this._onPlayerPressed.bind(this)
|
||||
);
|
||||
this.player.connectWidget(
|
||||
'enter-notify-event', this._onPlayerEnterNotifyEvent.bind(this)
|
||||
this.player.keyController.connect(
|
||||
'key-pressed', this._onPlayerKeyPressed.bind(this)
|
||||
);
|
||||
this.player.connectWidget(
|
||||
'leave-notify-event', this._onPlayerLeaveNotifyEvent.bind(this)
|
||||
this.player.motionController.connect(
|
||||
'enter', this._onPlayerEnter.bind(this)
|
||||
);
|
||||
this.player.connectWidget(
|
||||
'motion-notify-event', this._onPlayerMotionNotifyEvent.bind(this)
|
||||
this.player.motionController.connect(
|
||||
'leave', this._onPlayerLeave.bind(this)
|
||||
);
|
||||
this.player.motionController.connect(
|
||||
'motion', this._onPlayerMotion.bind(this)
|
||||
);
|
||||
this.player.dragGesture.connect(
|
||||
'drag-update', this._onPlayerDragUpdate.bind(this)
|
||||
);
|
||||
|
||||
/* Widget signals that are disconnected after first run */
|
||||
this._playerRealizeSignal = this.player.widget.connect(
|
||||
'realize', this._onPlayerRealize.bind(this)
|
||||
);
|
||||
this._playerDrawSignal = this.player.widget.connect(
|
||||
'draw', this._onPlayerDraw.bind(this)
|
||||
this._playerMapSignal = this.player.widget.connect(
|
||||
'map', this._onPlayerMap.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
_onWindowFullscreenChanged(window, isFullscreen)
|
||||
{
|
||||
// when changing fullscreen pango layout of popup is lost
|
||||
// and we need to re-add marks to the new layout
|
||||
this.interface.controls.setVolumeMarks(false);
|
||||
|
||||
if(isFullscreen) {
|
||||
this.setUpdateTimeInterval();
|
||||
this.setHideControlsTimeout();
|
||||
this.interface.controls.unfullscreenButton.set_sensitive(true);
|
||||
this.interface.controls.unfullscreenButton.show();
|
||||
this.interface.showControls(true);
|
||||
}
|
||||
else {
|
||||
this.clearTimeout('updateTime');
|
||||
this.interface.controls.unfullscreenButton.set_sensitive(false);
|
||||
this.interface.controls.unfullscreenButton.hide();
|
||||
this.interface.showControls(false);
|
||||
}
|
||||
|
||||
this.interface.setControlsOnVideo(isFullscreen);
|
||||
this.interface.controls.setVolumeMarks(true);
|
||||
this.interface.controls.setFullscreenMode(isFullscreen);
|
||||
this.interface.setFullscreenMode(isFullscreen);
|
||||
}
|
||||
|
||||
_onWindowKeyPressEvent(self, event)
|
||||
_onPlayerKeyPressed(self, keyval, keycode, state)
|
||||
{
|
||||
let [res, key] = event.get_keyval();
|
||||
if(!res) return;
|
||||
|
||||
//let keyName = Gdk.keyval_name(key);
|
||||
let bool = false;
|
||||
|
||||
switch(key) {
|
||||
switch(keyval) {
|
||||
case Gdk.KEY_space:
|
||||
case Gdk.KEY_Return:
|
||||
this.player.toggle_play();
|
||||
@@ -244,42 +237,27 @@ var App = GObject.registerClass({
|
||||
break;
|
||||
case Gdk.KEY_q:
|
||||
case Gdk.KEY_Q:
|
||||
this.window.destroy();
|
||||
this._onWindowCloseRequest();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onInterfaceToggleFullscreenClicked(isFsRequested)
|
||||
{
|
||||
if(this.window.isFullscreen === isFsRequested)
|
||||
return;
|
||||
|
||||
this.window.toggleFullscreen();
|
||||
}
|
||||
|
||||
_onPlayerRealize()
|
||||
{
|
||||
this.player.widget.disconnect(this._playerRealizeSignal);
|
||||
this.player.renderer.expose();
|
||||
|
||||
let display = this.player.widget.get_display();
|
||||
this.defaultCursor = Gdk.Cursor.new_from_name('default', null);
|
||||
this.blankCursor = Gdk.Cursor.new_from_name('none', null);
|
||||
|
||||
this.defaultCursor = Gdk.Cursor.new_from_name(
|
||||
display, 'default'
|
||||
);
|
||||
this.blankCursor = Gdk.Cursor.new_for_display(
|
||||
display, Gdk.CursorType.BLANK_CURSOR
|
||||
);
|
||||
|
||||
this.playerWindow = this.player.widget.get_window();
|
||||
this.setHideCursorTimeout();
|
||||
}
|
||||
|
||||
_onPlayerDraw(self, data)
|
||||
_onPlayerMap(self, data)
|
||||
{
|
||||
this.player.widget.disconnect(this._playerDrawSignal);
|
||||
this.player.widget.disconnect(this._playerMapSignal);
|
||||
this.emit('ready', true);
|
||||
|
||||
if(this.playlist.length)
|
||||
@@ -291,6 +269,7 @@ var App = GObject.registerClass({
|
||||
if(state === GstPlayer.PlayerState.BUFFERING)
|
||||
return;
|
||||
|
||||
let isInhibited = false;
|
||||
let flags = Gtk.ApplicationInhibitFlags.SUSPEND
|
||||
| Gtk.ApplicationInhibitFlags.IDLE;
|
||||
|
||||
@@ -303,79 +282,66 @@ var App = GObject.registerClass({
|
||||
flags,
|
||||
'video is playing'
|
||||
);
|
||||
if(!this.inhibitCookie)
|
||||
debug(new Error('could not inhibit session!'));
|
||||
|
||||
isInhibited = (this.inhibitCookie > 0);
|
||||
}
|
||||
else {
|
||||
if(!this.inhibitCookie)
|
||||
//if(!this.inhibitCookie)
|
||||
return;
|
||||
|
||||
/* Uninhibit seems to be broken as of GTK 3.99.2
|
||||
this.uninhibit(this.inhibitCookie);
|
||||
this.inhibitCookie = null;
|
||||
*/
|
||||
}
|
||||
|
||||
debug('set prevent suspend to: ' + this.is_inhibited(flags));
|
||||
debug(`set prevent suspend to: ${isInhibited}`);
|
||||
}
|
||||
|
||||
_onPlayerButtonPressEvent(self, event)
|
||||
_onPlayerPressed(gesture, nPress, x, y)
|
||||
{
|
||||
let [res, button] = event.get_button();
|
||||
if(!res) return;
|
||||
|
||||
this.dragStartReady = false;
|
||||
let button = gesture.get_current_button();
|
||||
let isDouble = (nPress % 2 == 0);
|
||||
this.dragAllowed = !isDouble;
|
||||
|
||||
switch(button) {
|
||||
case Gdk.BUTTON_PRIMARY:
|
||||
this._handlePrimaryButtonPress(event, button);
|
||||
if(isDouble)
|
||||
this.window.toggleFullscreen();
|
||||
break;
|
||||
case Gdk.BUTTON_SECONDARY:
|
||||
if(event.get_event_type() === Gdk.EventType.BUTTON_PRESS)
|
||||
this.player.toggle_play();
|
||||
this.player.toggle_play();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_handlePrimaryButtonPress(event, button)
|
||||
_onPlayerEnter(controller, x, y)
|
||||
{
|
||||
let eventType = event.get_event_type();
|
||||
|
||||
switch(eventType) {
|
||||
case Gdk.EventType.BUTTON_PRESS:
|
||||
let [res, x, y] = event.get_root_coords();
|
||||
if(!res)
|
||||
break;
|
||||
this.dragStartX = x;
|
||||
this.dragStartY = y;
|
||||
this.dragStartReady = true;
|
||||
break;
|
||||
case Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
this.window.toggleFullscreen();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onPlayerEnterNotifyEvent(self, event)
|
||||
{
|
||||
this.isCursorInPlayer = true;
|
||||
|
||||
this.setHideCursorTimeout();
|
||||
if(this.window.isFullscreen)
|
||||
this.setHideControlsTimeout();
|
||||
}
|
||||
|
||||
_onPlayerLeaveNotifyEvent(self, event)
|
||||
_onPlayerLeave(controller)
|
||||
{
|
||||
this.isCursorInPlayer = false;
|
||||
|
||||
this.clearTimeout('hideCursor');
|
||||
this.clearTimeout('hideControls');
|
||||
}
|
||||
|
||||
_onPlayerMotionNotifyEvent(self, event)
|
||||
_onPlayerMotion(self, posX, posY)
|
||||
{
|
||||
this.playerWindow.set_cursor(this.defaultCursor);
|
||||
/* GTK4 sometimes generates motions with same coords */
|
||||
if(this.posX === posX && this.posY === posY)
|
||||
return;
|
||||
|
||||
this.posX = posX;
|
||||
this.posY = posY;
|
||||
|
||||
this.player.widget.set_cursor(this.defaultCursor);
|
||||
this.setHideCursorTimeout();
|
||||
|
||||
if(this.window.isFullscreen) {
|
||||
@@ -388,36 +354,40 @@ var App = GObject.registerClass({
|
||||
else if(this.hideControlsTimeout) {
|
||||
this.clearTimeout('hideControls');
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.dragStartReady || this.window.isFullscreen)
|
||||
_onPlayerDragUpdate(gesture, offsetX, offsetY)
|
||||
{
|
||||
if(!this.dragAllowed || this.activeWindow.isFullscreen)
|
||||
return;
|
||||
|
||||
let [res, x, y] = event.get_root_coords();
|
||||
if(!res) return;
|
||||
let { gtk_double_click_distance } = this.player.widget.get_settings();
|
||||
|
||||
let startDrag = this.player.widget.drag_check_threshold(
|
||||
this.dragStartX, this.dragStartY, x, y
|
||||
);
|
||||
if(!startDrag) return;
|
||||
if (
|
||||
Math.abs(offsetX) > gtk_double_click_distance
|
||||
|| Math.abs(offsetY) > gtk_double_click_distance
|
||||
) {
|
||||
let [isActive, startX, startY] = gesture.get_start_point();
|
||||
if(!isActive) return;
|
||||
|
||||
this.dragStartReady = false;
|
||||
let timestamp = event.get_time();
|
||||
this.activeWindow.get_surface().begin_move(
|
||||
gesture.get_device(),
|
||||
gesture.get_current_button(),
|
||||
startX,
|
||||
startY,
|
||||
gesture.get_current_event_time()
|
||||
);
|
||||
|
||||
this.window.begin_move_drag(
|
||||
Gdk.BUTTON_PRIMARY,
|
||||
this.dragStartX,
|
||||
this.dragStartY,
|
||||
timestamp
|
||||
);
|
||||
gesture.reset();
|
||||
}
|
||||
}
|
||||
|
||||
_onPlayerWarning(self, error)
|
||||
_onWindowCloseRequest()
|
||||
{
|
||||
debug(error.message, 'LEVEL_WARNING');
|
||||
}
|
||||
this.window.destroy();
|
||||
this.player.widget.emit('destroy');
|
||||
this.interface.emit('destroy');
|
||||
|
||||
_onPlayerError(self, error)
|
||||
{
|
||||
debug(error);
|
||||
this.quit();
|
||||
}
|
||||
});
|
||||
|
@@ -1,41 +1,24 @@
|
||||
const { GObject, Gtk } = imports.gi;
|
||||
|
||||
var BoxedIconButton = GObject.registerClass(
|
||||
class BoxedIconButton extends Gtk.Button
|
||||
var CustomButton = GObject.registerClass(
|
||||
class ClapperCustomButton extends Gtk.Button
|
||||
{
|
||||
_init(icon, size, isFullscreen)
|
||||
_init(opts)
|
||||
{
|
||||
super._init({
|
||||
opts = opts || {};
|
||||
|
||||
let defaults = {
|
||||
margin_top: 4,
|
||||
margin_bottom: 4,
|
||||
can_focus: false,
|
||||
can_default: false,
|
||||
});
|
||||
margin_start: 1,
|
||||
margin_end: 1,
|
||||
};
|
||||
Object.assign(opts, defaults);
|
||||
|
||||
this.isFullscreen = isFullscreen || false;
|
||||
super._init(opts);
|
||||
|
||||
size = size || Gtk.IconSize.SMALL_TOOLBAR;
|
||||
let image = Gtk.Image.new_from_icon_name(icon, size);
|
||||
|
||||
if(image)
|
||||
this.set_image(image);
|
||||
|
||||
this.image.defaultSize = size;
|
||||
this.image.fullscreenSize = (size === Gtk.IconSize.SMALL_TOOLBAR)
|
||||
? Gtk.IconSize.LARGE_TOOLBAR
|
||||
: Gtk.IconSize.DND;
|
||||
|
||||
this.get_style_context().add_class('flat');
|
||||
|
||||
this.box = new Gtk.Box();
|
||||
this.box.pack_start(this, false, false, 0);
|
||||
|
||||
super.show();
|
||||
}
|
||||
|
||||
get visible()
|
||||
{
|
||||
return this.box.visible;
|
||||
this.isFullscreen = false;
|
||||
this.add_css_class('flat');
|
||||
}
|
||||
|
||||
setFullscreenMode(isFullscreen)
|
||||
@@ -43,62 +26,126 @@ class BoxedIconButton extends Gtk.Button
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
return;
|
||||
|
||||
this.image.icon_size = (isFullscreen)
|
||||
? this.image.fullscreenSize
|
||||
: this.image.defaultSize;
|
||||
|
||||
this.margin_top = (isFullscreen) ? 6 : 4;
|
||||
this.isFullscreen = isFullscreen;
|
||||
}
|
||||
|
||||
show_all()
|
||||
{
|
||||
this.box.show_all();
|
||||
}
|
||||
|
||||
show()
|
||||
{
|
||||
this.box.show();
|
||||
}
|
||||
|
||||
hide()
|
||||
{
|
||||
this.box.hide();
|
||||
}
|
||||
});
|
||||
|
||||
var BoxedPopoverButton = GObject.registerClass(
|
||||
class BoxedPopoverButton extends BoxedIconButton
|
||||
var IconButton = GObject.registerClass(
|
||||
class ClapperIconButton extends CustomButton
|
||||
{
|
||||
_init(icon, size, isFullscreen)
|
||||
_init(icon)
|
||||
{
|
||||
super._init(icon, size, isFullscreen);
|
||||
|
||||
this.popover = new Gtk.Popover({
|
||||
relative_to: this.box
|
||||
super._init({
|
||||
icon_name: icon,
|
||||
});
|
||||
this.popoverBox = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL
|
||||
});
|
||||
this.popover.add(this.popoverBox);
|
||||
this.popoverBox.show();
|
||||
}
|
||||
});
|
||||
|
||||
if(this.isFullscreen)
|
||||
this.popover.get_style_context().add_class('osd');
|
||||
var IconToggleButton = GObject.registerClass(
|
||||
class ClapperIconToggleButton extends IconButton
|
||||
{
|
||||
_init(primaryIcon, secondaryIcon)
|
||||
{
|
||||
super._init(primaryIcon);
|
||||
|
||||
this.primaryIcon = primaryIcon;
|
||||
this.secondaryIcon = secondaryIcon;
|
||||
}
|
||||
|
||||
setFullscreenMode(isEnabled)
|
||||
setPrimaryIcon()
|
||||
{
|
||||
if(this.isFullscreen === isEnabled)
|
||||
this.icon_name = this.primaryIcon;
|
||||
}
|
||||
|
||||
setSecondaryIcon()
|
||||
{
|
||||
this.icon_name = this.secondaryIcon;
|
||||
}
|
||||
});
|
||||
|
||||
var LabelButton = GObject.registerClass(
|
||||
class ClapperLabelButton extends CustomButton
|
||||
{
|
||||
_init(text)
|
||||
{
|
||||
super._init({
|
||||
margin_start: 0,
|
||||
margin_end: 0,
|
||||
});
|
||||
|
||||
this.customLabel = new Gtk.Label({
|
||||
label: text,
|
||||
single_line_mode: true,
|
||||
});
|
||||
|
||||
this.customLabel.add_css_class('labelbutton');
|
||||
this.set_child(this.customLabel);
|
||||
}
|
||||
|
||||
set_label(text)
|
||||
{
|
||||
this.customLabel.set_text(text);
|
||||
}
|
||||
});
|
||||
|
||||
var PopoverButton = GObject.registerClass(
|
||||
class ClapperPopoverButton extends IconButton
|
||||
{
|
||||
_init(icon)
|
||||
{
|
||||
super._init(icon);
|
||||
|
||||
this.popover = new Gtk.Popover({
|
||||
position: Gtk.PositionType.TOP,
|
||||
});
|
||||
this.popoverBox = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
});
|
||||
|
||||
this.popover.set_parent(this);
|
||||
this.popover.set_child(this.popoverBox);
|
||||
this.popover.set_offset(0, -this.margin_top);
|
||||
|
||||
if(this.isFullscreen)
|
||||
this.popover.add_css_class('osd');
|
||||
|
||||
this.changeStateSignal = this.popover.connect('closed', () =>
|
||||
this.unset_state_flags(Gtk.StateFlags.CHECKED)
|
||||
);
|
||||
this.destroySignal = this.connect('destroy', this._onDestroy.bind(this));
|
||||
}
|
||||
|
||||
setFullscreenMode(isFullscreen)
|
||||
{
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
return;
|
||||
|
||||
let action = (isEnabled) ? 'add_class' : 'remove_class';
|
||||
this.popover.get_style_context()[action]('osd');
|
||||
super.setFullscreenMode(isFullscreen);
|
||||
|
||||
super.setFullscreenMode(isEnabled);
|
||||
this.popover.set_offset(0, -this.margin_top);
|
||||
|
||||
let cssClass = 'osd';
|
||||
if(isFullscreen == this.popover.has_css_class(cssClass))
|
||||
return;
|
||||
|
||||
let action = (isFullscreen) ? 'add' : 'remove';
|
||||
this.popover[action + '_css_class'](cssClass);
|
||||
}
|
||||
|
||||
vfunc_clicked()
|
||||
{
|
||||
this.set_state_flags(Gtk.StateFlags.CHECKED, false);
|
||||
this.popover.popup();
|
||||
}
|
||||
|
||||
_onDestroy()
|
||||
{
|
||||
this.disconnect(this.destroySignal);
|
||||
|
||||
this.popover.disconnect(this.changeStateSignal);
|
||||
this.popover.unparent();
|
||||
this.popoverBox.emit('destroy');
|
||||
this.popover.emit('destroy');
|
||||
}
|
||||
});
|
||||
|
285
clapper_src/controls.js
vendored
285
clapper_src/controls.js
vendored
@@ -31,8 +31,8 @@ var Controls = GObject.registerClass({
|
||||
valign: Gtk.Align.END,
|
||||
});
|
||||
|
||||
this.fullscreenMode = false;
|
||||
this.durationFormated = '00:00:00';
|
||||
this.elapsedInitial = '00:00:00/00:00:00';
|
||||
this.buttonsArr = [];
|
||||
|
||||
this._addTogglePlayButton();
|
||||
@@ -52,142 +52,110 @@ var Controls = GObject.registerClass({
|
||||
this._addVolumeButton();
|
||||
this.unfullscreenButton = this.addButton(
|
||||
'view-restore-symbolic',
|
||||
Gtk.IconSize.SMALL_TOOLBAR,
|
||||
true
|
||||
);
|
||||
this.unfullscreenButton.set_visible(false);
|
||||
this.fullscreenButton = Gtk.Button.new_from_icon_name(
|
||||
'view-fullscreen-symbolic',
|
||||
Gtk.IconSize.SMALL_TOOLBAR
|
||||
);
|
||||
this.setDefaultWidgetBehaviour(this.fullscreenButton);
|
||||
this.openMenuButton = Gtk.Button.new_from_icon_name(
|
||||
'open-menu-symbolic',
|
||||
Gtk.IconSize.SMALL_TOOLBAR
|
||||
);
|
||||
this.setDefaultWidgetBehaviour(this.openMenuButton);
|
||||
this.forall(this.setDefaultWidgetBehaviour);
|
||||
|
||||
this.realizeSignal = this.connect(
|
||||
'realize', this._onControlsRealize.bind(this)
|
||||
);
|
||||
}
|
||||
this.add_css_class('playercontrols');
|
||||
|
||||
pack_start(widget, expand, fill, padding)
|
||||
{
|
||||
if(
|
||||
widget.box
|
||||
&& widget.box.constructor
|
||||
&& widget.box.constructor === Gtk.Box
|
||||
)
|
||||
widget = widget.box;
|
||||
|
||||
super.pack_start(widget, expand, fill, padding);
|
||||
this.realizeSignal = this.connect('realize', this._onRealize.bind(this));
|
||||
this.destroySignal = this.connect('destroy', this._onDestroy.bind(this));
|
||||
}
|
||||
|
||||
setFullscreenMode(isFullscreen)
|
||||
{
|
||||
if(isFullscreen === this.fullscreenMode)
|
||||
return;
|
||||
|
||||
for(let button of this.buttonsArr)
|
||||
button.setFullscreenMode(isFullscreen);
|
||||
|
||||
this.fullscreenMode = isFullscreen;
|
||||
this.unfullscreenButton.set_visible(isFullscreen);
|
||||
}
|
||||
|
||||
addButton(iconName, size, noPack)
|
||||
addButton(buttonIcon)
|
||||
{
|
||||
let button = new Buttons.BoxedIconButton(
|
||||
iconName, size, this.fullscreenMode
|
||||
);
|
||||
let button = (buttonIcon instanceof Gtk.Button)
|
||||
? buttonIcon
|
||||
: new Buttons.IconButton(buttonIcon);
|
||||
|
||||
if(!noPack)
|
||||
this.pack_start(button, false, false, 0);
|
||||
|
||||
this.buttonsArr.push(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
addPopoverButton(iconName, size)
|
||||
{
|
||||
let button = new Buttons.BoxedPopoverButton(
|
||||
iconName, size, this.fullscreenMode
|
||||
);
|
||||
this.pack_start(button, false, false, 0);
|
||||
this.append(button);
|
||||
this.buttonsArr.push(button);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
addRadioButtons(box, array, activeId)
|
||||
addLabelButton(text)
|
||||
{
|
||||
text = text || '';
|
||||
let button = new Buttons.LabelButton(text);
|
||||
|
||||
return this.addButton(button);
|
||||
}
|
||||
|
||||
addPopoverButton(iconName)
|
||||
{
|
||||
let button = new Buttons.PopoverButton(iconName);
|
||||
|
||||
return this.addButton(button);
|
||||
}
|
||||
|
||||
addCheckButtons(box, array, activeId)
|
||||
{
|
||||
let group = null;
|
||||
let children = box.get_children();
|
||||
let lastEl = (children.length > array.length)
|
||||
? children.length
|
||||
: array.length;
|
||||
let child = box.get_first_child();
|
||||
let i = 0;
|
||||
|
||||
for(let i = 0; i < lastEl; i++) {
|
||||
while(child || i < array.length) {
|
||||
if(i >= array.length) {
|
||||
children[i].hide();
|
||||
debug(`hiding unused ${children[i].type} radioButton nr: ${i}`);
|
||||
child.hide();
|
||||
debug(`hiding unused ${child.type} checkButton nr: ${i}`);
|
||||
i++;
|
||||
child = child.get_next_sibling();
|
||||
continue;
|
||||
}
|
||||
|
||||
let el = array[i];
|
||||
let radioButton;
|
||||
let checkButton;
|
||||
|
||||
if(i < children.length) {
|
||||
radioButton = children[i];
|
||||
debug(`reusing ${el.type} radioButton nr: ${i}`);
|
||||
if(child) {
|
||||
checkButton = child;
|
||||
debug(`reusing ${el.type} checkButton nr: ${i}`);
|
||||
}
|
||||
else {
|
||||
debug(`creating new ${el.type} radioButton nr: ${i}`);
|
||||
radioButton = new Gtk.RadioButton({
|
||||
debug(`creating new ${el.type} checkButton nr: ${i}`);
|
||||
checkButton = new Gtk.CheckButton({
|
||||
group: group,
|
||||
});
|
||||
radioButton.connect(
|
||||
checkButton.connect(
|
||||
'toggled',
|
||||
this._onRadioButtonToggled.bind(this, radioButton)
|
||||
this._onCheckButtonToggled.bind(this, checkButton)
|
||||
);
|
||||
this.setDefaultWidgetBehaviour(radioButton);
|
||||
box.add(radioButton);
|
||||
box.append(checkButton);
|
||||
}
|
||||
|
||||
radioButton.label = el.label;
|
||||
debug(`radioButton label: ${radioButton.label}`);
|
||||
radioButton.type = el.type;
|
||||
debug(`radioButton type: ${radioButton.type}`);
|
||||
radioButton.activeId = el.activeId;
|
||||
debug(`radioButton id: ${radioButton.activeId}`);
|
||||
checkButton.label = el.label;
|
||||
debug(`checkButton label: ${checkButton.label}`);
|
||||
checkButton.type = el.type;
|
||||
debug(`checkButton type: ${checkButton.type}`);
|
||||
checkButton.activeId = el.activeId;
|
||||
debug(`checkButton id: ${checkButton.activeId}`);
|
||||
|
||||
if(radioButton.activeId === activeId) {
|
||||
radioButton.set_active(true);
|
||||
debug(`activated ${el.type} radioButton nr: ${i}`);
|
||||
if(checkButton.activeId === activeId) {
|
||||
checkButton.set_active(true);
|
||||
debug(`activated ${el.type} checkButton nr: ${i}`);
|
||||
}
|
||||
if(!group)
|
||||
group = radioButton;
|
||||
group = checkButton;
|
||||
|
||||
radioButton.show();
|
||||
i++;
|
||||
if(child)
|
||||
child = child.get_next_sibling();
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultWidgetBehaviour(widget)
|
||||
{
|
||||
widget.can_focus = false;
|
||||
widget.can_default = false;
|
||||
}
|
||||
|
||||
setVolumeMarks(isAdded)
|
||||
{
|
||||
if(!isAdded)
|
||||
return this.volumeScale.clear_marks();
|
||||
|
||||
this.volumeScale.add_mark(0, Gtk.PositionType.LEFT, '0%');
|
||||
this.volumeScale.add_mark(1, Gtk.PositionType.LEFT, '100%');
|
||||
this.volumeScale.add_mark(2, Gtk.PositionType.LEFT, '200%');
|
||||
}
|
||||
|
||||
handleScaleIncrement(type, isUp)
|
||||
{
|
||||
let value = this[`${type}Scale`].get_value();
|
||||
@@ -206,49 +174,45 @@ var Controls = GObject.registerClass({
|
||||
|
||||
_addTogglePlayButton()
|
||||
{
|
||||
this.togglePlayButton = this.addButton(
|
||||
this.togglePlayButton = new Buttons.IconToggleButton(
|
||||
'media-playback-start-symbolic',
|
||||
Gtk.IconSize.LARGE_TOOLBAR
|
||||
'media-playback-pause-symbolic'
|
||||
);
|
||||
this.togglePlayButton.setPlayImage = () =>
|
||||
{
|
||||
this.togglePlayButton.image.set_from_icon_name(
|
||||
'media-playback-start-symbolic',
|
||||
this.togglePlayButton.image.icon_size
|
||||
);
|
||||
}
|
||||
this.togglePlayButton.setPauseImage = () =>
|
||||
{
|
||||
this.togglePlayButton.image.set_from_icon_name(
|
||||
'media-playback-pause-symbolic',
|
||||
this.togglePlayButton.image.icon_size
|
||||
);
|
||||
}
|
||||
this.togglePlayButton.add_css_class('playbackicon');
|
||||
this.addButton(this.togglePlayButton);
|
||||
}
|
||||
|
||||
_addPositionScale()
|
||||
{
|
||||
this.elapsedButton = this.addLabelButton(this.elapsedInitial);
|
||||
this.positionScale = new Gtk.Scale({
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
value_pos: Gtk.PositionType.LEFT,
|
||||
draw_value: true,
|
||||
draw_value: false,
|
||||
hexpand: true,
|
||||
valign: Gtk.Align.CENTER,
|
||||
can_focus: false,
|
||||
});
|
||||
let style = this.positionScale.get_style_context();
|
||||
style.add_class('positionscale');
|
||||
|
||||
this.positionScale.connect(
|
||||
'format-value', this._onPositionScaleFormatValue.bind(this)
|
||||
this.togglePlayButton.bind_property('margin_top',
|
||||
this.positionScale, 'margin_top', GObject.BindingFlags.SYNC_CREATE
|
||||
);
|
||||
this.positionScale.connect(
|
||||
'button-press-event', this._onPositionScaleButtonPressEvent.bind(this)
|
||||
this.togglePlayButton.bind_property('margin_bottom',
|
||||
this.positionScale, 'margin_bottom', GObject.BindingFlags.SYNC_CREATE
|
||||
);
|
||||
|
||||
this.positionScale.add_css_class('positionscale');
|
||||
this.positionScale.connect('value-changed', this._onPositionScaleValueChanged.bind(this));
|
||||
|
||||
/* GTK4 is missing pressed/released signals for GtkRange/GtkScale.
|
||||
* We cannot add controllers, cause it already has them, so we
|
||||
* workaround this by observing css classes it currently has */
|
||||
this.positionScale.connect(
|
||||
'button-release-event', this._onPositionScaleButtonReleaseEvent.bind(this)
|
||||
'notify::css-classes', this._onPositionScaleDragging.bind(this)
|
||||
);
|
||||
|
||||
this.positionAdjustment = this.positionScale.get_adjustment();
|
||||
this.pack_start(this.positionScale, true, true, 0);
|
||||
this.append(this.positionScale);
|
||||
}
|
||||
|
||||
_addVolumeButton()
|
||||
@@ -256,10 +220,14 @@ var Controls = GObject.registerClass({
|
||||
this.volumeButton = this.addPopoverButton(
|
||||
'audio-volume-muted-symbolic'
|
||||
);
|
||||
this.volumeButton.add_events(Gdk.EventMask.SCROLL_MASK);
|
||||
this.volumeButton.connect(
|
||||
'scroll-event', (self, event) => this._onScrollEvent(event)
|
||||
let scrollController = new Gtk.EventControllerScroll();
|
||||
scrollController.set_flags(
|
||||
Gtk.EventControllerScrollFlags.VERTICAL
|
||||
| Gtk.EventControllerScrollFlags.DISCRETE
|
||||
);
|
||||
scrollController.connect('scroll', this._onScroll.bind(this));
|
||||
this.volumeButton.add_controller(scrollController);
|
||||
|
||||
this.volumeScale = new Gtk.Scale({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
inverted: true,
|
||||
@@ -268,18 +236,18 @@ var Controls = GObject.registerClass({
|
||||
round_digits: 2,
|
||||
vexpand: true,
|
||||
});
|
||||
this.volumeScale.get_style_context().add_class('volumescale');
|
||||
this.volumeScale.add_css_class('volumescale');
|
||||
this.volumeAdjustment = this.volumeScale.get_adjustment();
|
||||
|
||||
this.volumeAdjustment.set_upper(2);
|
||||
this.volumeAdjustment.set_step_increment(0.05);
|
||||
this.volumeAdjustment.set_page_increment(0.05);
|
||||
this.setDefaultWidgetBehaviour(this.volumeScale);
|
||||
|
||||
this.volumeButton.popoverBox.add(this.volumeScale);
|
||||
this.volumeButton.popoverBox.show_all();
|
||||
|
||||
this.setVolumeMarks(true);
|
||||
for(let i = 0; i <= 2; i++) {
|
||||
let text = (i) ? `${i}00%` : '0%';
|
||||
this.volumeScale.add_mark(i, Gtk.PositionType.LEFT, text);
|
||||
}
|
||||
this.volumeButton.popoverBox.append(this.volumeScale);
|
||||
}
|
||||
|
||||
_getFormatedTime(time)
|
||||
@@ -293,25 +261,25 @@ var Controls = GObject.registerClass({
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
_onRadioButtonToggled(self, radioButton)
|
||||
_onCheckButtonToggled(self, checkButton)
|
||||
{
|
||||
if(!radioButton.get_active())
|
||||
if(!checkButton.get_active())
|
||||
return;
|
||||
|
||||
switch(radioButton.type) {
|
||||
switch(checkButton.type) {
|
||||
case 'video':
|
||||
case 'audio':
|
||||
case 'subtitle':
|
||||
this.emit(
|
||||
'track-change-requested',
|
||||
radioButton.type,
|
||||
radioButton.activeId
|
||||
checkButton.type,
|
||||
checkButton.activeId
|
||||
);
|
||||
break;
|
||||
case 'visualization':
|
||||
this.emit(
|
||||
`${radioButton.type}-change-requested`,
|
||||
radioButton.activeId
|
||||
`${checkButton.type}-change-requested`,
|
||||
checkButton.activeId
|
||||
);
|
||||
break;
|
||||
default:
|
||||
@@ -319,25 +287,26 @@ var Controls = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
_onPositionScaleFormatValue(self, value)
|
||||
_onPositionScaleValueChanged()
|
||||
{
|
||||
return this._getFormatedTime(value)
|
||||
let elapsed = this._getFormatedTime(this.positionScale.get_value())
|
||||
+ '/' + this.durationFormated;
|
||||
|
||||
this.elapsedButton.set_label(elapsed);
|
||||
}
|
||||
|
||||
_onPositionScaleButtonPressEvent()
|
||||
_onPositionScaleDragging(scale)
|
||||
{
|
||||
this.isPositionSeeking = true;
|
||||
let isPositionSeeking = scale.has_css_class('dragging');
|
||||
|
||||
if(this.isPositionSeeking === isPositionSeeking)
|
||||
return;
|
||||
|
||||
this.isPositionSeeking = isPositionSeeking;
|
||||
this.emit('position-seeking-changed', this.isPositionSeeking);
|
||||
}
|
||||
|
||||
_onPositionScaleButtonReleaseEvent()
|
||||
{
|
||||
this.isPositionSeeking = false;
|
||||
this.emit('position-seeking-changed', this.isPositionSeeking);
|
||||
}
|
||||
|
||||
_onControlsRealize()
|
||||
_onRealize()
|
||||
{
|
||||
this.disconnect(this.realizeSignal);
|
||||
|
||||
@@ -352,27 +321,25 @@ var Controls = GObject.registerClass({
|
||||
this[`${name}Button`].hide();
|
||||
}
|
||||
|
||||
_onScrollEvent(event)
|
||||
_onScroll(controller, dx, dy)
|
||||
{
|
||||
let [res, direction] = event.get_scroll_direction();
|
||||
if(!res) return;
|
||||
let isVertical = Math.abs(dy) >= Math.abs(dx);
|
||||
let isIncrease = (isVertical) ? dy < 0 : dx < 0;
|
||||
let type = (isVertical) ? 'volume' : 'position';
|
||||
|
||||
let type = 'volume';
|
||||
this.handleScaleIncrement(type, isIncrease);
|
||||
|
||||
switch(direction) {
|
||||
case Gdk.ScrollDirection.RIGHT:
|
||||
case Gdk.ScrollDirection.LEFT:
|
||||
type = 'position';
|
||||
case Gdk.ScrollDirection.UP:
|
||||
case Gdk.ScrollDirection.DOWN:
|
||||
let isUp = (
|
||||
direction === Gdk.ScrollDirection.UP
|
||||
|| direction === Gdk.ScrollDirection.RIGHT
|
||||
);
|
||||
this.handleScaleIncrement(type, isUp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_onDestroy()
|
||||
{
|
||||
this.disconnect(this.destroySignal);
|
||||
|
||||
this.visualizationsButton.emit('destroy');
|
||||
this.videoTracksButton.emit('destroy');
|
||||
this.audioTracksButton.emit('destroy');
|
||||
this.subtitleTracksButton.emit('destroy');
|
||||
this.volumeButton.emit('destroy');
|
||||
}
|
||||
});
|
||||
|
76
clapper_src/headerbar.js
Normal file
76
clapper_src/headerbar.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const { GLib, GObject, Gtk, Pango } = imports.gi;
|
||||
|
||||
var HeaderBar = GObject.registerClass(
|
||||
class ClapperHeaderBar extends Gtk.HeaderBar
|
||||
{
|
||||
_init(window, startButtons, endButtons)
|
||||
{
|
||||
super._init({
|
||||
can_focus: false,
|
||||
});
|
||||
|
||||
this.set_title_widget(this._createWidgetForWindow(window));
|
||||
startButtons.forEach(btn => this.pack_start(btn));
|
||||
endButtons.forEach(btn => this.pack_end(btn));
|
||||
}
|
||||
|
||||
updateHeaderBar(mediaInfo)
|
||||
{
|
||||
let title = mediaInfo.get_title();
|
||||
let subtitle = mediaInfo.get_uri() || null;
|
||||
|
||||
if(subtitle && subtitle.startsWith('file://')) {
|
||||
subtitle = GLib.path_get_basename(
|
||||
GLib.filename_from_uri(subtitle)[0]
|
||||
);
|
||||
}
|
||||
|
||||
if(!title) {
|
||||
title = (!subtitle)
|
||||
? this.defaultTitle
|
||||
: (subtitle.includes('.'))
|
||||
? subtitle.split('.').slice(0, -1).join('.')
|
||||
: subtitle;
|
||||
|
||||
subtitle = null;
|
||||
}
|
||||
|
||||
this.titleLabel.label = title;
|
||||
this.subtitleLabel.visible = (subtitle !== null);
|
||||
|
||||
if(subtitle)
|
||||
this.subtitleLabel.label = subtitle;
|
||||
}
|
||||
|
||||
_createWidgetForWindow(window)
|
||||
{
|
||||
let box = new Gtk.Box ({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
|
||||
this.titleLabel = new Gtk.Label({
|
||||
halign: Gtk.Align.CENTER,
|
||||
single_line_mode: true,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
width_chars: 5,
|
||||
});
|
||||
this.titleLabel.add_css_class('title');
|
||||
this.titleLabel.set_parent(box);
|
||||
|
||||
window.bind_property('title', this.titleLabel, 'label',
|
||||
GObject.BindingFlags.SYNC_CREATE
|
||||
);
|
||||
|
||||
this.subtitleLabel = new Gtk.Label({
|
||||
halign: Gtk.Align.CENTER,
|
||||
single_line_mode: true,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
});
|
||||
this.subtitleLabel.add_css_class('subtitle');
|
||||
this.subtitleLabel.set_parent(box);
|
||||
this.subtitleLabel.visible = false;
|
||||
|
||||
return box;
|
||||
}
|
||||
});
|
@@ -19,7 +19,7 @@ class ClapperInterface extends Gtk.Grid
|
||||
};
|
||||
Object.assign(this, defaults, opts);
|
||||
|
||||
this.controlsInVideo = false;
|
||||
this.fullscreenMode = false;
|
||||
this.lastVolumeValue = null;
|
||||
this.lastPositionValue = 0;
|
||||
this.lastRevealerEventTime = 0;
|
||||
@@ -33,29 +33,32 @@ class ClapperInterface extends Gtk.Grid
|
||||
this.revealerBottom = new Revealers.RevealerBottom();
|
||||
this.controls = new Controls();
|
||||
|
||||
this.videoBox.get_style_context().add_class('videobox');
|
||||
this.videoBox.pack_start(this.overlay, true, true, 0);
|
||||
this.videoBox.add_css_class('videobox');
|
||||
this.videoBox.append(this.overlay);
|
||||
this.attach(this.videoBox, 0, 0, 1, 1);
|
||||
this.attach(this.controls, 0, 1, 1, 1);
|
||||
|
||||
this.destroySignal = this.connect('destroy', this._onDestroy.bind(this));
|
||||
}
|
||||
|
||||
addPlayer(player)
|
||||
{
|
||||
this._player = player;
|
||||
this._player.widget.expand = true;
|
||||
this._player.widget.vexpand = true;
|
||||
this._player.widget.hexpand = true;
|
||||
|
||||
this._player.connect('state-changed', this._onPlayerStateChanged.bind(this));
|
||||
this._player.connect('volume-changed', this._onPlayerVolumeChanged.bind(this));
|
||||
this._player.connect('duration-changed', this._onPlayerDurationChanged.bind(this));
|
||||
this._player.connect('position-updated', this._onPlayerPositionUpdated.bind(this));
|
||||
|
||||
this._player.connectWidget(
|
||||
'scroll-event', (self, event) => this.controls._onScrollEvent(event)
|
||||
this._player.scrollController.connect(
|
||||
'scroll', (ctl, dx, dy) => this.controls._onScroll(ctl, dx, dy)
|
||||
);
|
||||
this.controls.togglePlayButton.connect(
|
||||
'clicked', this._onControlsTogglePlayClicked.bind(this)
|
||||
);
|
||||
this.controls.positionScale.connect(
|
||||
this.scaleSig = this.controls.positionScale.connect(
|
||||
'value-changed', this._onControlsPositionChanged.bind(this)
|
||||
);
|
||||
this.controls.volumeScale.connect(
|
||||
@@ -70,9 +73,9 @@ class ClapperInterface extends Gtk.Grid
|
||||
this.controls.connect(
|
||||
'visualization-change-requested', this._onVisualizationChangeRequested.bind(this)
|
||||
);
|
||||
this.revealerTop.connect('event-after', (self, event) => this._player.widget.event(event));
|
||||
//this.revealerTop.connect('event-after', (self, event) => this._player.widget.event(event));
|
||||
|
||||
this.overlay.add(this._player.widget);
|
||||
this.overlay.set_child(this._player.widget);
|
||||
this.overlay.add_overlay(this.revealerTop);
|
||||
this.overlay.add_overlay(this.revealerBottom);
|
||||
|
||||
@@ -98,32 +101,33 @@ class ClapperInterface extends Gtk.Grid
|
||||
this[`revealer${pos}`].showChild(isShow);
|
||||
}
|
||||
|
||||
setControlsOnVideo(isOnVideo)
|
||||
setFullscreenMode(isFullscreen)
|
||||
{
|
||||
if(this.controlsInVideo === isOnVideo)
|
||||
if(this.fullscreenMode === isFullscreen)
|
||||
return;
|
||||
|
||||
if(isOnVideo) {
|
||||
if(isFullscreen) {
|
||||
this.remove(this.controls);
|
||||
this.controls.pack_start(this.controls.unfullscreenButton.box, false, false, 0);
|
||||
this.revealerBottom.addWidget(this.controls);
|
||||
this.revealerBottom.append(this.controls);
|
||||
}
|
||||
else {
|
||||
this.revealerBottom.removeWidget(this.controls);
|
||||
this.controls.remove(this.controls.unfullscreenButton.box);
|
||||
this.revealerBottom.remove(this.controls);
|
||||
this.attach(this.controls, 0, 1, 1, 1);
|
||||
}
|
||||
|
||||
this.controlsInVideo = isOnVideo;
|
||||
debug(`placed controls in overlay: ${isOnVideo}`);
|
||||
this.controls.setFullscreenMode(isFullscreen);
|
||||
this.showControls(isFullscreen);
|
||||
|
||||
this.fullscreenMode = isFullscreen;
|
||||
debug(`interface in fullscreen mode: ${isFullscreen}`);
|
||||
}
|
||||
|
||||
updateMediaTracks()
|
||||
{
|
||||
let mediaInfo = this._player.get_media_info();
|
||||
|
||||
// set titlebar media title and path
|
||||
this.updateHeaderBar(mediaInfo);
|
||||
/* Set titlebar media title and path */
|
||||
this.updateTitles(mediaInfo);
|
||||
|
||||
// we can also check if video is "live" or "seekable" (right now unused)
|
||||
// it might be a good idea to hide position seek bar and disable seeking
|
||||
@@ -208,7 +212,7 @@ class ClapperInterface extends Gtk.Grid
|
||||
}
|
||||
continue;
|
||||
}
|
||||
this.controls.addRadioButtons(
|
||||
this.controls.addCheckButtons(
|
||||
this.controls[`${type}TracksButton`].popoverBox,
|
||||
parsedInfo[`${type}Tracks`],
|
||||
activeId
|
||||
@@ -220,32 +224,12 @@ class ClapperInterface extends Gtk.Grid
|
||||
}
|
||||
}
|
||||
|
||||
updateHeaderBar(mediaInfo)
|
||||
updateTitles(mediaInfo)
|
||||
{
|
||||
if(!this.headerBar)
|
||||
return;
|
||||
if(this.headerBar)
|
||||
this.headerBar.updateHeaderBar(mediaInfo);
|
||||
|
||||
let title = mediaInfo.get_title();
|
||||
let subtitle = mediaInfo.get_uri() || null;
|
||||
|
||||
if(subtitle.startsWith('file://')) {
|
||||
subtitle = GLib.filename_from_uri(subtitle)[0];
|
||||
subtitle = GLib.path_get_basename(subtitle);
|
||||
}
|
||||
|
||||
if(!title) {
|
||||
title = (!subtitle)
|
||||
? this.defaultTitle
|
||||
: (subtitle.includes('.'))
|
||||
? subtitle.split('.').slice(0, -1).join('.')
|
||||
: subtitle;
|
||||
|
||||
subtitle = null;
|
||||
}
|
||||
|
||||
this.headerBar.set_title(title);
|
||||
this.headerBar.set_subtitle(subtitle);
|
||||
this.revealerTop.setMediaTitle(title);
|
||||
this.revealerTop.setMediaTitle(this.headerBar.titleLabel.label);
|
||||
}
|
||||
|
||||
updateTime()
|
||||
@@ -281,7 +265,7 @@ class ClapperInterface extends Gtk.Grid
|
||||
});
|
||||
});
|
||||
|
||||
this.controls.addRadioButtons(
|
||||
this.controls.addCheckButtons(
|
||||
this.controls.visualizationsButton.popoverBox,
|
||||
parsedVisArr,
|
||||
null
|
||||
@@ -300,8 +284,8 @@ class ClapperInterface extends Gtk.Grid
|
||||
|
||||
_onTrackChangeRequested(self, type, activeId)
|
||||
{
|
||||
// reenabling audio is slow (as expected),
|
||||
// so it is better to toggle mute instead
|
||||
/* Reenabling audio is slow (as expected),
|
||||
* so it is better to toggle mute instead */
|
||||
if(type === 'audio') {
|
||||
if(activeId < 0)
|
||||
return this._player.set_mute(true);
|
||||
@@ -313,8 +297,8 @@ class ClapperInterface extends Gtk.Grid
|
||||
}
|
||||
|
||||
if(activeId < 0) {
|
||||
// disabling video leaves last frame frozen,
|
||||
// so we hide it by making it transparent
|
||||
/* Disabling video leaves last frame frozen,
|
||||
* so we hide it by making it transparent */
|
||||
if(type === 'video')
|
||||
this._player.widget.set_opacity(0);
|
||||
|
||||
@@ -365,10 +349,10 @@ class ClapperInterface extends Gtk.Grid
|
||||
case GstPlayer.PlayerState.STOPPED:
|
||||
this.needsTracksUpdate = true;
|
||||
case GstPlayer.PlayerState.PAUSED:
|
||||
this.controls.togglePlayButton.setPlayImage();
|
||||
this.controls.togglePlayButton.setPrimaryIcon();
|
||||
break;
|
||||
case GstPlayer.PlayerState.PLAYING:
|
||||
this.controls.togglePlayButton.setPauseImage();
|
||||
this.controls.togglePlayButton.setSecondaryIcon();
|
||||
if(this.needsTracksUpdate) {
|
||||
this.needsTracksUpdate = false;
|
||||
this.updateMediaTracks();
|
||||
@@ -449,7 +433,7 @@ class ClapperInterface extends Gtk.Grid
|
||||
this.lastPositionValue = positionSeconds;
|
||||
this._player.seek_seconds(positionSeconds);
|
||||
|
||||
if(this.controls.fullscreenMode)
|
||||
if(this.fullscreenMode)
|
||||
this.updateTime();
|
||||
}
|
||||
|
||||
@@ -468,14 +452,10 @@ class ClapperInterface extends Gtk.Grid
|
||||
: 'overamplified';
|
||||
|
||||
let iconName = `audio-volume-${icon}-symbolic`;
|
||||
|
||||
if(this.controls.volumeButton.image.icon_name !== iconName)
|
||||
if(this.controls.volumeButton.icon_name !== iconName)
|
||||
{
|
||||
debug(`set volume icon: ${icon}`);
|
||||
this.controls.volumeButton.image.set_from_icon_name(
|
||||
iconName,
|
||||
this.controls.volumeButton.image.icon_size
|
||||
);
|
||||
this.controls.volumeButton.set_icon_name(iconName);
|
||||
}
|
||||
|
||||
if(volume === this.lastVolumeValue)
|
||||
@@ -484,4 +464,10 @@ class ClapperInterface extends Gtk.Grid
|
||||
this.lastVolumeValue = volume;
|
||||
this._player.set_volume(volume);
|
||||
}
|
||||
|
||||
_onDestroy()
|
||||
{
|
||||
this.disconnect(this.destroySignal);
|
||||
this.controls.emit('destroy');
|
||||
}
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { Gio, GLib, GObject, Gst, GstPlayer } = imports.gi;
|
||||
const { Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi;
|
||||
const ByteArray = imports.byteArray;
|
||||
const Debug = imports.clapper_src.debug;
|
||||
|
||||
@@ -21,7 +21,16 @@ class ClapperPlayer extends GstPlayer.Player
|
||||
if(!Gst.is_initialized())
|
||||
Gst.init(null);
|
||||
|
||||
let gtkglsink = Gst.ElementFactory.make('gtkglsink', null);
|
||||
let plugin = 'gtk4glsink';
|
||||
let gtkglsink = Gst.ElementFactory.make(plugin, null);
|
||||
|
||||
if(!gtkglsink) {
|
||||
return debug(new Error(
|
||||
`Could not load "${plugin}".`
|
||||
+ ' Do you have gstreamer-plugins-good-gtk4 installed?'
|
||||
));
|
||||
}
|
||||
|
||||
let glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
|
||||
glsinkbin.sink = gtkglsink;
|
||||
|
||||
@@ -35,8 +44,7 @@ class ClapperPlayer extends GstPlayer.Player
|
||||
video_renderer: renderer
|
||||
});
|
||||
|
||||
// assign elements to player for later access
|
||||
// and make sure that GJS will not free them early
|
||||
/* Assign elements to player for later access */
|
||||
this.gtkglsink = gtkglsink;
|
||||
this.glsinkbin = glsinkbin;
|
||||
this.dispatcher = dispatcher;
|
||||
@@ -60,6 +68,7 @@ class ClapperPlayer extends GstPlayer.Player
|
||||
|
||||
this.set_config(config);
|
||||
this.set_mute(false);
|
||||
this.set_plugin_rank('vah264dec', 300);
|
||||
|
||||
this.loop = GLib.MainLoop.new(null, false);
|
||||
this.run_loop = opts.run_loop || false;
|
||||
@@ -71,9 +80,28 @@ class ClapperPlayer extends GstPlayer.Player
|
||||
this._trackId = 0;
|
||||
this.playlist_ext = opts.playlist_ext || 'claps';
|
||||
|
||||
this.keyController = new Gtk.EventControllerKey();
|
||||
this.motionController = new Gtk.EventControllerMotion();
|
||||
this.scrollController = new Gtk.EventControllerScroll();
|
||||
this.clickGesture = new Gtk.GestureClick();
|
||||
this.dragGesture = new Gtk.GestureDrag();
|
||||
|
||||
this.scrollController.set_flags(
|
||||
Gtk.EventControllerScrollFlags.BOTH_AXES
|
||||
);
|
||||
this.clickGesture.set_button(0);
|
||||
|
||||
this.widget.add_controller(this.keyController);
|
||||
this.widget.add_controller(this.motionController);
|
||||
this.widget.add_controller(this.scrollController);
|
||||
this.widget.add_controller(this.clickGesture);
|
||||
this.widget.add_controller(this.dragGesture);
|
||||
|
||||
this.connect('state-changed', this._onStateChanged.bind(this));
|
||||
this.connect('uri-loaded', this._onUriLoaded.bind(this));
|
||||
this.connect('end-of-stream', this._onStreamEnded.bind(this));
|
||||
this.connect('warning', this._onPlayerWarning.bind(this));
|
||||
this.connect('error', this._onPlayerError.bind(this));
|
||||
this.connectWidget('destroy', this._onWidgetDestroy.bind(this));
|
||||
}
|
||||
|
||||
@@ -241,6 +269,16 @@ class ClapperPlayer extends GstPlayer.Player
|
||||
this.loop.run();
|
||||
}
|
||||
|
||||
_onPlayerWarning(self, error)
|
||||
{
|
||||
debug(error.message, 'LEVEL_WARNING');
|
||||
}
|
||||
|
||||
_onPlayerError(self, error)
|
||||
{
|
||||
debug(error);
|
||||
}
|
||||
|
||||
_onWidgetDestroy()
|
||||
{
|
||||
while(this._widgetSignals.length)
|
||||
|
@@ -10,6 +10,13 @@ class ClapperCustomRevealer extends Gtk.Revealer
|
||||
{
|
||||
_init(opts)
|
||||
{
|
||||
opts = opts || {};
|
||||
|
||||
let defaults = {
|
||||
visible: false,
|
||||
};
|
||||
Object.assign(opts, defaults);
|
||||
|
||||
super._init(opts);
|
||||
|
||||
this.revealerName = '';
|
||||
@@ -19,10 +26,7 @@ class ClapperCustomRevealer extends Gtk.Revealer
|
||||
{
|
||||
if(isReveal) {
|
||||
this._clearTimeout();
|
||||
if(!this.visible)
|
||||
this.show();
|
||||
|
||||
this._setShowTimeout();
|
||||
this.set_visible(isReveal);
|
||||
}
|
||||
else
|
||||
this._setHideTimeout();
|
||||
@@ -30,68 +34,39 @@ class ClapperCustomRevealer extends Gtk.Revealer
|
||||
this._timedReveal(isReveal, REVEAL_TIME);
|
||||
}
|
||||
|
||||
show()
|
||||
{
|
||||
if(this.visible)
|
||||
return;
|
||||
|
||||
// Decreased size = lower CPU usage
|
||||
this._setTopAlign('START');
|
||||
|
||||
super.show();
|
||||
debug(`showing ${this.revealerName} revealer in drawing area`);
|
||||
}
|
||||
|
||||
hide()
|
||||
{
|
||||
if(!this.visible)
|
||||
return;
|
||||
|
||||
super.hide();
|
||||
debug(`removed ${this.revealerName} revealer from drawing area`);
|
||||
}
|
||||
|
||||
showChild(isReveal)
|
||||
{
|
||||
this._clearTimeout();
|
||||
|
||||
if(isReveal)
|
||||
this.show();
|
||||
else if(!isReveal)
|
||||
this.hide();
|
||||
|
||||
this.set_visible(isReveal);
|
||||
this._timedReveal(isReveal, 0);
|
||||
}
|
||||
|
||||
set_visible(isVisible)
|
||||
{
|
||||
if(this.visible === isVisible)
|
||||
return false;
|
||||
|
||||
super.set_visible(isVisible);
|
||||
debug(`${this.revealerName} revealer visible: ${isVisible}`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_timedReveal(isReveal, time)
|
||||
{
|
||||
this.set_transition_duration(time);
|
||||
this.set_reveal_child(isReveal);
|
||||
}
|
||||
|
||||
// Drawing revealers on top of video frames
|
||||
// increases CPU usage, so we hide them
|
||||
/* Drawing revealers on top of video frames
|
||||
* increases CPU usage, so we hide them */
|
||||
_setHideTimeout()
|
||||
{
|
||||
this._clearTimeout();
|
||||
this._setTopAlign('FILL');
|
||||
|
||||
this._revealerTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, REVEAL_TIME + 20, () => {
|
||||
this._revealerTimeout = null;
|
||||
this.hide();
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
_setShowTimeout()
|
||||
{
|
||||
this._clearTimeout();
|
||||
this._setTopAlign('FILL');
|
||||
|
||||
this._revealerTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, REVEAL_TIME + 20, () => {
|
||||
this._revealerTimeout = null;
|
||||
this._setTopAlign('START');
|
||||
this.set_visible(false);
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
@@ -105,17 +80,6 @@ class ClapperCustomRevealer extends Gtk.Revealer
|
||||
GLib.source_remove(this._revealerTimeout);
|
||||
this._revealerTimeout = null;
|
||||
}
|
||||
|
||||
_setTopAlign(align)
|
||||
{
|
||||
if(
|
||||
this.revealerName !== 'top'
|
||||
|| this.valign === Gtk.Align[align]
|
||||
)
|
||||
return;
|
||||
|
||||
this.valign = Gtk.Align[align];
|
||||
}
|
||||
});
|
||||
|
||||
var RevealerTop = GObject.registerClass(
|
||||
@@ -130,7 +94,7 @@ class ClapperRevealerTop extends CustomRevealer
|
||||
});
|
||||
|
||||
this.revealerName = 'top';
|
||||
|
||||
/*
|
||||
this.set_events(
|
||||
Gdk.EventMask.BUTTON_PRESS_MASK
|
||||
| Gdk.EventMask.BUTTON_RELEASE_MASK
|
||||
@@ -141,7 +105,7 @@ class ClapperRevealerTop extends CustomRevealer
|
||||
| Gdk.EventMask.ENTER_NOTIFY_MASK
|
||||
| Gdk.EventMask.LEAVE_NOTIFY_MASK
|
||||
);
|
||||
|
||||
*/
|
||||
let initTime = GLib.DateTime.new_now_local().format('%X');
|
||||
this.timeFormat = (initTime.length > 8)
|
||||
? '%I:%M %p'
|
||||
@@ -150,13 +114,13 @@ class ClapperRevealerTop extends CustomRevealer
|
||||
this.revealerGrid = new Gtk.Grid({
|
||||
column_spacing: 8
|
||||
});
|
||||
let gridContext = this.revealerGrid.get_style_context();
|
||||
gridContext.add_class('osd');
|
||||
gridContext.add_class('reavealertop');
|
||||
this.revealerGrid.add_css_class('osd');
|
||||
this.revealerGrid.add_css_class('reavealertop');
|
||||
|
||||
this.mediaTitle = new Gtk.Label({
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
expand: true,
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
margin_top: 14,
|
||||
margin_start: 12,
|
||||
xalign: 0,
|
||||
@@ -169,17 +133,16 @@ class ClapperRevealerTop extends CustomRevealer
|
||||
yalign: 0,
|
||||
};
|
||||
this.currentTime = new Gtk.Label(timeLabelOpts);
|
||||
this.currentTime.get_style_context().add_class('osdtime');
|
||||
this.currentTime.add_css_class('osdtime');
|
||||
|
||||
this.endTime = new Gtk.Label(timeLabelOpts);
|
||||
this.endTime.get_style_context().add_class('osdendtime');
|
||||
this.endTime.add_css_class('osdendtime');
|
||||
|
||||
this.revealerGrid.attach(this.mediaTitle, 0, 0, 1, 1);
|
||||
this.revealerGrid.attach(this.currentTime, 1, 0, 1, 1);
|
||||
this.revealerGrid.attach(this.endTime, 1, 0, 1, 1);
|
||||
|
||||
this.add(this.revealerGrid);
|
||||
this.revealerGrid.show_all();
|
||||
this.set_child(this.revealerGrid);
|
||||
}
|
||||
|
||||
setMediaTitle(title)
|
||||
@@ -195,8 +158,8 @@ class ClapperRevealerTop extends CustomRevealer
|
||||
this.currentTime.set_label(now);
|
||||
this.endTime.set_label(end);
|
||||
|
||||
// Make sure that next timeout is always run after clock changes,
|
||||
// by delaying it for additional few milliseconds
|
||||
/* Make sure that next timeout is always run after clock changes,
|
||||
* by delaying it for additional few milliseconds */
|
||||
let nextUpdate = 60002 - parseInt(currTime.get_seconds() * 1000);
|
||||
debug(`updated current time: ${now}`);
|
||||
|
||||
@@ -217,19 +180,48 @@ class ClapperRevealerBottom extends CustomRevealer
|
||||
|
||||
this.revealerName = 'bottom';
|
||||
this.revealerBox = new Gtk.Box();
|
||||
this.revealerBox.get_style_context().add_class('osd');
|
||||
this.revealerBox.add_css_class('osd');
|
||||
|
||||
this.add(this.revealerBox);
|
||||
this.revealerBox.show_all();
|
||||
this.set_child(this.revealerBox);
|
||||
}
|
||||
|
||||
addWidget(widget)
|
||||
append(widget)
|
||||
{
|
||||
this.revealerBox.pack_start(widget, false, true, 0);
|
||||
this.revealerBox.append(widget);
|
||||
}
|
||||
|
||||
removeWidget(widget)
|
||||
remove(widget)
|
||||
{
|
||||
this.revealerBox.remove(widget);
|
||||
}
|
||||
|
||||
set_visible(isVisible)
|
||||
{
|
||||
let isChange = super.set_visible(isVisible);
|
||||
if(!isChange) return;
|
||||
|
||||
let parent = this.get_parent();
|
||||
let playerWidget = parent.get_first_child();
|
||||
if(!playerWidget) return;
|
||||
|
||||
if(isVisible) {
|
||||
let box = this.get_first_child();
|
||||
if(!box) return;
|
||||
|
||||
let controls = box.get_first_child();
|
||||
if(!controls) return;
|
||||
|
||||
let togglePlayButton = controls.get_first_child();
|
||||
if(togglePlayButton) {
|
||||
togglePlayButton.grab_focus();
|
||||
debug('focus moved to toggle play button');
|
||||
}
|
||||
playerWidget.set_can_focus(false);
|
||||
}
|
||||
else {
|
||||
playerWidget.set_can_focus(true);
|
||||
playerWidget.grab_focus();
|
||||
debug('focus moved to player widget');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -13,13 +13,11 @@ var Window = GObject.registerClass({
|
||||
super._init({
|
||||
application: application,
|
||||
title: title || 'Clapper',
|
||||
border_width: 0,
|
||||
resizable: true,
|
||||
window_position: Gtk.WindowPosition.CENTER,
|
||||
width_request: 960,
|
||||
height_request: 642
|
||||
destroy_with_parent: true,
|
||||
});
|
||||
this.isFullscreen = false;
|
||||
this.mapSignal = this.connect('map', this._onMap.bind(this));
|
||||
}
|
||||
|
||||
toggleFullscreen()
|
||||
@@ -28,13 +26,10 @@ var Window = GObject.registerClass({
|
||||
this[`${un}fullscreen`]();
|
||||
}
|
||||
|
||||
vfunc_window_state_event(event)
|
||||
_onStateNotify(toplevel)
|
||||
{
|
||||
super.vfunc_window_state_event(event);
|
||||
|
||||
let isFullscreen = Boolean(
|
||||
event.new_window_state
|
||||
& Gdk.WindowState.FULLSCREEN
|
||||
toplevel.state & Gdk.ToplevelState.FULLSCREEN
|
||||
);
|
||||
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
@@ -43,4 +38,12 @@ var Window = GObject.registerClass({
|
||||
this.isFullscreen = isFullscreen;
|
||||
this.emit('fullscreen-changed', this.isFullscreen);
|
||||
}
|
||||
|
||||
_onMap()
|
||||
{
|
||||
this.disconnect(this.mapSignal);
|
||||
|
||||
let surface = this.get_surface();
|
||||
surface.connect('notify::state', this._onStateNotify.bind(this));
|
||||
}
|
||||
});
|
||||
|
@@ -7,13 +7,15 @@ scale marks {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.osd .playercontrols {
|
||||
-gtk-icon-size: 24px;
|
||||
}
|
||||
.osd button {
|
||||
margin: 2px;
|
||||
min-width: 36px;
|
||||
min-height: 36px;
|
||||
}
|
||||
.osd scale trough highlight {
|
||||
min-width: 6px;
|
||||
min-width: 0px;
|
||||
min-height: 6px;
|
||||
}
|
||||
.osd radio {
|
||||
@@ -22,12 +24,23 @@ scale marks {
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
}
|
||||
.playbackicon {
|
||||
-gtk-icon-size: 20px;
|
||||
}
|
||||
.osd .playbackicon {
|
||||
-gtk-icon-size: 28px;
|
||||
}
|
||||
.labelbutton {
|
||||
font-family: 'Cantarell', 'Noto Sans', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
.videobox {
|
||||
background: black;
|
||||
}
|
||||
.reavealertop {
|
||||
min-height: 100px;
|
||||
box-shadow: inset 0px 200px 10px -124px rgba(0,0,0,0.4);
|
||||
min-height: 90px;
|
||||
box-shadow: inset 0px 200px 10px -124px rgba(0,0,0,0.3);
|
||||
font-family: 'Cantarell', 'Noto Sans', sans-serif;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
@@ -43,25 +56,13 @@ scale marks {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Position Scale */
|
||||
.positionscale value {
|
||||
font-weight: 600;
|
||||
color: currentColor;
|
||||
}
|
||||
.positionscale trough highlight {
|
||||
min-height: 4px;
|
||||
}
|
||||
.osd .positionscale value {
|
||||
.osd .labelbutton {
|
||||
font-size: 24px;
|
||||
}
|
||||
.positionscale contents {
|
||||
margin-left: 4px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.osd .positionscale contents {
|
||||
margin-left: 8px;
|
||||
margin-right: 2px;
|
||||
|
||||
/* Position Scale */
|
||||
.positionscale trough highlight {
|
||||
min-height: 4px;
|
||||
}
|
||||
.osd .positionscale trough slider {
|
||||
color: transparent;
|
||||
@@ -75,12 +76,10 @@ scale marks {
|
||||
|
||||
/* Volume Scale */
|
||||
.volumescale {
|
||||
margin-left: 4px;
|
||||
min-height: 180px;
|
||||
}
|
||||
.osd .volumescale {
|
||||
margin: 6px;
|
||||
margin-left: 10px;
|
||||
min-height: 280px;
|
||||
}
|
||||
.volumescale marks label {
|
||||
@@ -91,3 +90,6 @@ scale marks {
|
||||
.osd .volumescale marks label {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
.osd .volumescale trough highlight {
|
||||
min-width: 6px;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
imports.gi.versions.Gdk = '3.0';
|
||||
imports.gi.versions.Gtk = '3.0';
|
||||
imports.gi.versions.Gdk = '4.0';
|
||||
imports.gi.versions.Gtk = '4.0';
|
||||
imports.searchPath.unshift('@importspath@');
|
||||
|
||||
const ClapperSrc = imports.clapper_src;
|
||||
|
4
main.js
4
main.js
@@ -1,5 +1,5 @@
|
||||
imports.gi.versions.Gdk = '3.0';
|
||||
imports.gi.versions.Gtk = '3.0';
|
||||
imports.gi.versions.Gdk = '4.0';
|
||||
imports.gi.versions.Gtk = '4.0';
|
||||
|
||||
const { Gst } = imports.gi;
|
||||
const { App } = imports.clapper_src.app;
|
||||
|
@@ -27,13 +27,13 @@ arch=(any)
|
||||
url="https://github.com/Rafostar/clapper"
|
||||
license=("GPL-3.0")
|
||||
depends=(
|
||||
"gtk3>=3.19.4"
|
||||
"gtk4>=3.99.2"
|
||||
"hicolor-icon-theme"
|
||||
"gjs"
|
||||
"gst-plugins-base-libs"
|
||||
"gst-plugins-good"
|
||||
"gst-plugins-bad-libs>=1.16.0"
|
||||
"gst-plugin-gtk"
|
||||
"gst-plugins-bad-libs>=1.18.0"
|
||||
"gst-plugin-gtk4"
|
||||
)
|
||||
makedepends=(
|
||||
"meson>=0.50"
|
||||
@@ -44,7 +44,7 @@ optdepends=(
|
||||
"gst-libav: Popular video decoders"
|
||||
"gstreamer-vaapi: Intel/AMD video acceleration"
|
||||
)
|
||||
source=("${pkgname%-git}::git+https://github.com/Rafostar/${pkgname%-git}.git#branch=master")
|
||||
source=("${pkgname%-git}::git+https://github.com/Rafostar/${pkgname%-git}.git")
|
||||
provides=("${pkgname%-git}")
|
||||
replaces=("${pkgname%-git}")
|
||||
conflicts=("${pkgname%-git}")
|
||||
|
@@ -14,14 +14,14 @@ Build-Depends: debhelper (>= 10),
|
||||
Package: clapper
|
||||
Architecture: all
|
||||
Depends: gjs (>= 1.50),
|
||||
gir1.2-gtk-3.0 (>= 3.19),
|
||||
gir1.2-gtk-4.0 (>= 3.99.2),
|
||||
hicolor-icon-theme,
|
||||
libgstreamer1.0-0,
|
||||
gstreamer1.0-plugins-base,
|
||||
gstreamer1.0-plugins-good,
|
||||
gstreamer1.0-plugins-bad (>= 1.16),
|
||||
gstreamer1.0-plugins-bad (>= 1.18),
|
||||
gstreamer1.0-gl,
|
||||
gstreamer1.0-gtk3
|
||||
gstreamer1.0-gtk4
|
||||
Recommends: gstreamer1.0-libav,
|
||||
gstreamer1.0-pulseaudio
|
||||
Suggests: gstreamer-plugins-ugly,
|
||||
|
@@ -19,8 +19,8 @@
|
||||
|
||||
|
||||
%global appname com.github.rafostar.Clapper
|
||||
%global gst_version 1.16.0
|
||||
%global gtk3_version 3.19.4
|
||||
%global gst_version 1.18.0
|
||||
%global gtk4_version 3.99.2
|
||||
|
||||
Name: clapper
|
||||
Version: 0.0.0
|
||||
@@ -39,7 +39,7 @@ BuildRequires: desktop-file-utils
|
||||
BuildRequires: hicolor-icon-theme
|
||||
|
||||
Requires: gjs
|
||||
Requires: gtk3 >= %{gtk3_version}
|
||||
Requires: gtk4 >= %{gtk4_version}
|
||||
Requires: hicolor-icon-theme
|
||||
|
||||
%if 0%{?suse_version}
|
||||
@@ -51,7 +51,7 @@ BuildRequires: update-desktop-files
|
||||
Requires: gstreamer
|
||||
Requires: gstreamer-plugins-base
|
||||
Requires: gstreamer-plugins-good
|
||||
Requires: gstreamer-plugins-good-gtk
|
||||
Requires: gstreamer-plugins-good-gtk4
|
||||
Requires: gstreamer-plugins-bad
|
||||
Requires: libgstplayer-1_0-0 >= %{gst_version}
|
||||
|
||||
@@ -62,14 +62,12 @@ Recommends: gstreamer-plugins-libav
|
||||
Suggests: gstreamer-plugins-ugly
|
||||
# Intel/AMD video acceleration
|
||||
Suggests: gstreamer-plugins-vaapi
|
||||
%endif
|
||||
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos_version}
|
||||
%else
|
||||
BuildRequires: glibc-all-langpacks
|
||||
Requires: gstreamer1
|
||||
Requires: gstreamer1-plugins-base
|
||||
Requires: gstreamer1-plugins-good
|
||||
Requires: gstreamer1-plugins-good-gtk
|
||||
Requires: gstreamer1-plugins-good-gtk4
|
||||
# Contains GstPlayer lib
|
||||
Requires: gstreamer1-plugins-bad-free >= %{gst_version}
|
||||
|
||||
@@ -113,7 +111,11 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
|
||||
%{_datadir}/mime/packages/%{appname}.xml
|
||||
|
||||
%changelog
|
||||
* Wed Oct 14 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-3
|
||||
- Update to GTK4
|
||||
|
||||
* Sat Sep 19 22:02:00 CEST 2020 sp1rit - 0.0.0-2
|
||||
- Added suse_update_desktop_file macro for SuSE packages
|
||||
|
||||
* Fri Sep 18 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-1
|
||||
- Initial package
|
||||
|
Reference in New Issue
Block a user