mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 07:42:23 +02:00
@@ -6,6 +6,8 @@ Categories=GTK;GNOME;AudioVideo;Player;Video;TV;
|
|||||||
MimeType=application/claps;application/mpeg4-iod;application/mpeg4-muxcodetable;application/mxf;application/ogg;application/ram;application/sdp;application/streamingmedia;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;application/x-extension-m4a;application/x-extension-mp4;application/x-flac;application/x-flash-video;application/x-matroska;application/x-ogg;application/x-streamingmedia;audio/3gpp;audio/3gpp2;audio/aac;audio/ac3;audio/amr;audio/amr-wb;audio/basic;audio/dv;audio/eac3;audio/flac;audio/m4a;audio/midi;audio/mp1;audio/mp2;audio/mp3;audio/mp4;audio/mpeg;audio/mpegurl;audio/mpg;audio/ogg;audio/opus;audio/scpls;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/vnd.dolby.mlp;audio/vnd.dts;audio/vnd.dts.hd;audio/vnd.rn-realaudio;audio/wav;audio/webm;audio/x-aac;audio/x-aiff;audio/x-ape;audio/x-flac;audio/x-gsm;audio/x-it;audio/x-m4a;audio/x-matroska;audio/x-mod;audio/x-mp1;audio/x-mp2;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-mpg;audio/x-ms-asf;audio/x-ms-wma;audio/x-musepack;audio/x-pn-aiff;audio/x-pn-au;audio/x-pn-realaudio;audio/x-pn-wav;audio/x-real-audio;audio/x-realaudio;audio/x-s3m;audio/x-scpls;audio/x-shorten;audio/x-speex;audio/x-tta;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-wav;audio/x-wavpack;audio/x-xm;video/3gp;video/3gpp;video/3gpp2;video/divx;video/dv;video/fli;video/flv;video/mp2t;video/mp4;video/mp4v-es;video/mpeg;video/mpeg-system;video/msvideo;video/ogg;video/quicktime;video/vnd.mpegurl;video/vnd.rn-realvideo;video/webm;video/x-avi;video/x-flc;video/x-fli;video/x-flv;video/x-m4v;video/x-matroska;video/x-mpeg;video/x-mpeg-system;video/x-mpeg2;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-msvideo;video/x-nsv;video/x-ogm+ogg;video/x-theora;video/x-theora+ogg;x-content/audio-cdda;x-content/audio-player;x-content/video-dvd;x-scheme-handler/mms;x-scheme-handler/mmsh;x-scheme-handler/rtmp;x-scheme-handler/rtp;x-scheme-handler/rtsp;
|
MimeType=application/claps;application/mpeg4-iod;application/mpeg4-muxcodetable;application/mxf;application/ogg;application/ram;application/sdp;application/streamingmedia;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;application/x-extension-m4a;application/x-extension-mp4;application/x-flac;application/x-flash-video;application/x-matroska;application/x-ogg;application/x-streamingmedia;audio/3gpp;audio/3gpp2;audio/aac;audio/ac3;audio/amr;audio/amr-wb;audio/basic;audio/dv;audio/eac3;audio/flac;audio/m4a;audio/midi;audio/mp1;audio/mp2;audio/mp3;audio/mp4;audio/mpeg;audio/mpegurl;audio/mpg;audio/ogg;audio/opus;audio/scpls;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/vnd.dolby.mlp;audio/vnd.dts;audio/vnd.dts.hd;audio/vnd.rn-realaudio;audio/wav;audio/webm;audio/x-aac;audio/x-aiff;audio/x-ape;audio/x-flac;audio/x-gsm;audio/x-it;audio/x-m4a;audio/x-matroska;audio/x-mod;audio/x-mp1;audio/x-mp2;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-mpg;audio/x-ms-asf;audio/x-ms-wma;audio/x-musepack;audio/x-pn-aiff;audio/x-pn-au;audio/x-pn-realaudio;audio/x-pn-wav;audio/x-real-audio;audio/x-realaudio;audio/x-s3m;audio/x-scpls;audio/x-shorten;audio/x-speex;audio/x-tta;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-wav;audio/x-wavpack;audio/x-xm;video/3gp;video/3gpp;video/3gpp2;video/divx;video/dv;video/fli;video/flv;video/mp2t;video/mp4;video/mp4v-es;video/mpeg;video/mpeg-system;video/msvideo;video/ogg;video/quicktime;video/vnd.mpegurl;video/vnd.rn-realvideo;video/webm;video/x-avi;video/x-flc;video/x-fli;video/x-flv;video/x-m4v;video/x-matroska;video/x-mpeg;video/x-mpeg-system;video/x-mpeg2;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-msvideo;video/x-nsv;video/x-ogm+ogg;video/x-theora;video/x-theora+ogg;x-content/audio-cdda;x-content/audio-player;x-content/video-dvd;x-scheme-handler/mms;x-scheme-handler/mmsh;x-scheme-handler/rtmp;x-scheme-handler/rtp;x-scheme-handler/rtsp;
|
||||||
Exec=com.github.rafostar.Clapper %U
|
Exec=com.github.rafostar.Clapper %U
|
||||||
Icon=com.github.rafostar.Clapper
|
Icon=com.github.rafostar.Clapper
|
||||||
|
DBusActivatable=true
|
||||||
|
StartupNotify=true
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
# Translators: Search terms to find this application. Do NOT translate the semicolons!
|
# Translators: Search terms to find this application. Do NOT translate the semicolons!
|
||||||
|
3
data/com.github.rafostar.Clapper.service.in
Normal file
3
data/com.github.rafostar.Clapper.service.in
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[D-BUS Service]
|
||||||
|
Name=@app_id@
|
||||||
|
Exec=@bindir@/@app_id@ --gapplication-service
|
@@ -32,3 +32,15 @@ gnome.compile_resources('com.github.rafostar.Clapper.data',
|
|||||||
install: true,
|
install: true,
|
||||||
install_dir: pkgdatadir,
|
install_dir: pkgdatadir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dbus_conf = configuration_data()
|
||||||
|
dbus_conf.set('app_id', meson.project_name())
|
||||||
|
dbus_conf.set('bindir', bindir)
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
input: 'com.github.rafostar.Clapper.service.in',
|
||||||
|
output: 'com.github.rafostar.Clapper.service',
|
||||||
|
configuration: dbus_conf,
|
||||||
|
install: true,
|
||||||
|
install_dir: join_paths(datadir, 'dbus-1', 'services'),
|
||||||
|
)
|
||||||
|
5
lib/gst/plugin/gstclapperpaintable.c
vendored
5
lib/gst/plugin/gstclapperpaintable.c
vendored
@@ -84,6 +84,10 @@ gst_clapper_paintable_dispose (GObject *object)
|
|||||||
|
|
||||||
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
||||||
|
|
||||||
|
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
|
||||||
|
gst_clear_object (&self->importer);
|
||||||
|
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
|
||||||
|
|
||||||
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
|
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +99,6 @@ gst_clapper_paintable_finalize (GObject *object)
|
|||||||
GST_TRACE ("Finalize");
|
GST_TRACE ("Finalize");
|
||||||
|
|
||||||
g_weak_ref_clear (&self->widget);
|
g_weak_ref_clear (&self->widget);
|
||||||
gst_clear_object (&self->importer);
|
|
||||||
|
|
||||||
g_mutex_clear (&self->lock);
|
g_mutex_clear (&self->lock);
|
||||||
g_mutex_clear (&self->importer_lock);
|
g_mutex_clear (&self->importer_lock);
|
||||||
|
@@ -109,6 +109,11 @@ retrieve_gl_context_on_main (GstClapperGLBaseImporter *self)
|
|||||||
|
|
||||||
gdk_display = gdk_display_get_default ();
|
gdk_display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
if (G_UNLIKELY (!gdk_display)) {
|
||||||
|
GST_ERROR_OBJECT (self, "Could not retrieve Gdk display");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(gdk_context = gdk_display_create_gl_context (gdk_display, &error))) {
|
if (!(gdk_context = gdk_display_create_gl_context (gdk_display, &error))) {
|
||||||
GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s",
|
GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s",
|
||||||
error ? error->message : "No error set by Gdk");
|
error ? error->message : "No error set by Gdk");
|
||||||
@@ -465,7 +470,8 @@ _realize_gdk_context_with_api (GdkGLContext *gdk_context, GdkGLAPI api)
|
|||||||
static gboolean
|
static gboolean
|
||||||
gst_clapper_gl_base_importer_gdk_context_realize (GstClapperGLBaseImporter *self, GdkGLContext *gdk_context)
|
gst_clapper_gl_base_importer_gdk_context_realize (GstClapperGLBaseImporter *self, GdkGLContext *gdk_context)
|
||||||
{
|
{
|
||||||
GdkGLAPI preferred_api;
|
GdkGLAPI preferred_api = GDK_GL_API_GL;
|
||||||
|
GdkDisplay *gdk_display;
|
||||||
const gchar *gl_env;
|
const gchar *gl_env;
|
||||||
gboolean success;
|
gboolean success;
|
||||||
|
|
||||||
@@ -483,13 +489,34 @@ gst_clapper_gl_base_importer_gdk_context_realize (GstClapperGLBaseImporter *self
|
|||||||
return _realize_gdk_context_with_api (gdk_context, preferred_api);
|
return _realize_gdk_context_with_api (gdk_context, preferred_api);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gdk_display = gdk_gl_context_get_display (gdk_context);
|
||||||
|
GST_DEBUG_OBJECT (self, "Auto selecting GL API for display: %s",
|
||||||
|
gdk_display_get_name (gdk_display));
|
||||||
|
|
||||||
/* Apple decoder uses rectangle texture-target, which GLES does not support.
|
/* Apple decoder uses rectangle texture-target, which GLES does not support.
|
||||||
* For Linux we prefer GLES in order to get HW colorspace conversion.
|
* For Linux we prefer EGL + GLES in order to get direct HW colorspace conversion.
|
||||||
* Windows will try EGL + GLES setup first and auto fallback to WGL. */
|
* Windows will try EGL + GLES setup first and auto fallback to WGL. */
|
||||||
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_MACOS
|
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WAYLAND
|
||||||
|
if (GDK_IS_WAYLAND_DISPLAY (gdk_display))
|
||||||
|
preferred_api = GDK_GL_API_GLES;
|
||||||
|
#endif
|
||||||
|
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_EGL
|
||||||
|
if (GDK_IS_X11_DISPLAY (gdk_display) && gdk_x11_display_get_egl_display (gdk_display))
|
||||||
|
preferred_api = GDK_GL_API_GLES;
|
||||||
|
#endif
|
||||||
|
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WIN32_EGL
|
||||||
|
if (GDK_IS_WIN32_DISPLAY (gdk_display) && gdk_win32_display_get_egl_display (gdk_display))
|
||||||
|
preferred_api = GDK_GL_API_GLES;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* FIXME: Remove once GStreamer can handle DRM modifiers. This tries to avoid
|
||||||
|
* "scrambled" image on Linux with Intel GPUs that are mostly used together with
|
||||||
|
* x86 CPUs at the expense of using slightly slower non-direct DMABuf import.
|
||||||
|
* See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1236 */
|
||||||
|
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WAYLAND || GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_EGL
|
||||||
|
#if !defined(HAVE_GST_PATCHES) && (defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64))
|
||||||
preferred_api = GDK_GL_API_GL;
|
preferred_api = GDK_GL_API_GL;
|
||||||
#else
|
#endif
|
||||||
preferred_api = GDK_GL_API_GLES;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!(success = _realize_gdk_context_with_api (gdk_context, preferred_api))) {
|
if (!(success = _realize_gdk_context_with_api (gdk_context, preferred_api))) {
|
||||||
|
101
src/app.js
101
src/app.js
@@ -1,27 +1,92 @@
|
|||||||
const { Gio, GObject, Gdk, Gtk } = imports.gi;
|
const { Gio, GObject, Gtk } = imports.gi;
|
||||||
const { AppBase } = imports.src.appBase;
|
|
||||||
const { Widget } = imports.src.widget;
|
const { Widget } = imports.src.widget;
|
||||||
const Debug = imports.src.debug;
|
const Debug = imports.src.debug;
|
||||||
|
const FileOps = imports.src.fileOps;
|
||||||
|
const Misc = imports.src.misc;
|
||||||
|
const Actions = imports.src.actions;
|
||||||
|
|
||||||
const { debug } = Debug;
|
const { debug } = Debug;
|
||||||
|
const { settings } = Misc;
|
||||||
|
|
||||||
var App = GObject.registerClass({
|
var App = GObject.registerClass({
|
||||||
GTypeName: 'ClapperApp',
|
GTypeName: 'ClapperApp',
|
||||||
},
|
},
|
||||||
class ClapperApp extends AppBase
|
class ClapperApp extends Gtk.Application
|
||||||
{
|
{
|
||||||
_init()
|
_init()
|
||||||
{
|
{
|
||||||
super._init();
|
super._init({
|
||||||
|
application_id: Misc.appId,
|
||||||
|
flags: Gio.ApplicationFlags.HANDLES_OPEN,
|
||||||
|
});
|
||||||
|
|
||||||
this.flags |= Gio.ApplicationFlags.HANDLES_OPEN;
|
this.doneFirstActivate = false;
|
||||||
|
this.isFileAppend = false;
|
||||||
|
this.mapSignal = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_startup()
|
vfunc_open(files, hint)
|
||||||
{
|
{
|
||||||
super.vfunc_startup();
|
super.vfunc_open(files, hint);
|
||||||
|
|
||||||
|
this.activate();
|
||||||
|
this._openFilesAsync(files).catch(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
vfunc_activate()
|
||||||
|
{
|
||||||
|
super.vfunc_activate();
|
||||||
|
|
||||||
|
if(!this.doneFirstActivate)
|
||||||
|
this._onFirstActivate();
|
||||||
|
|
||||||
|
this.active_window.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _openFilesAsync(files)
|
||||||
|
{
|
||||||
|
const urisArr = [];
|
||||||
|
|
||||||
|
for(let file of files) {
|
||||||
|
const uri = file.get_uri();
|
||||||
|
if(!uri.startsWith('file:')) {
|
||||||
|
urisArr.push(uri);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If file is not a dir its URI will be returned in an array */
|
||||||
|
const uris = await FileOps.getDirFilesUrisPromise(file).catch(debug);
|
||||||
|
if(uris && uris.length)
|
||||||
|
urisArr.push(...uris);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [playlist, subs] = Misc.parsePlaylistFiles(urisArr);
|
||||||
|
const { player } = this.active_window.get_child();
|
||||||
|
const action = (this.isFileAppend) ? 'append' : 'set';
|
||||||
|
|
||||||
|
if(playlist && playlist.length)
|
||||||
|
player[`${action}_playlist`](playlist);
|
||||||
|
if(subs)
|
||||||
|
player.set_subtitles(subs);
|
||||||
|
|
||||||
|
/* Restore default behavior */
|
||||||
|
this.isFileAppend = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onFirstActivate()
|
||||||
|
{
|
||||||
|
const window = new Gtk.ApplicationWindow({
|
||||||
|
application: this,
|
||||||
|
title: Misc.appName,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.add_css_class('adwrounded');
|
||||||
|
|
||||||
|
if(!settings.get_boolean('render-shadows'))
|
||||||
|
window.add_css_class('gpufriendly');
|
||||||
|
|
||||||
|
window.add_css_class('gpufriendlyfs');
|
||||||
|
|
||||||
const window = this.active_window;
|
|
||||||
const clapperWidget = new Widget();
|
const clapperWidget = new Widget();
|
||||||
const dummyHeaderbar = new Gtk.Box({
|
const dummyHeaderbar = new Gtk.Box({
|
||||||
can_focus: false,
|
can_focus: false,
|
||||||
@@ -33,14 +98,20 @@ class ClapperApp extends AppBase
|
|||||||
window.set_child(clapperWidget);
|
window.set_child(clapperWidget);
|
||||||
window.set_titlebar(dummyHeaderbar);
|
window.set_titlebar(dummyHeaderbar);
|
||||||
|
|
||||||
|
for(let name in Actions.actions) {
|
||||||
|
const simpleAction = new Gio.SimpleAction({ name });
|
||||||
|
simpleAction.connect('activate', (action) =>
|
||||||
|
Actions.handleAction(action, window)
|
||||||
|
);
|
||||||
|
this.add_action(simpleAction);
|
||||||
|
|
||||||
|
const accels = Actions.actions[name];
|
||||||
|
if(accels)
|
||||||
|
this.set_accels_for_action(`app.${name}`, accels);
|
||||||
|
}
|
||||||
|
|
||||||
this.mapSignal = window.connect('map', this._onWindowMap.bind(this));
|
this.mapSignal = window.connect('map', this._onWindowMap.bind(this));
|
||||||
}
|
this.doneFirstActivate = true;
|
||||||
|
|
||||||
vfunc_open(files, hint)
|
|
||||||
{
|
|
||||||
super.vfunc_open(files, hint);
|
|
||||||
|
|
||||||
this._openFilesAsync(files).then(() => this.activate()).catch(debug);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWindowMap(window)
|
_onWindowMap(window)
|
||||||
|
101
src/appBase.js
101
src/appBase.js
@@ -1,101 +0,0 @@
|
|||||||
const { Gio, GLib, GObject, Gtk } = imports.gi;
|
|
||||||
const Debug = imports.src.debug;
|
|
||||||
const FileOps = imports.src.fileOps;
|
|
||||||
const Misc = imports.src.misc;
|
|
||||||
const Actions = imports.src.actions;
|
|
||||||
|
|
||||||
const { debug } = Debug;
|
|
||||||
const { settings } = Misc;
|
|
||||||
|
|
||||||
var AppBase = GObject.registerClass({
|
|
||||||
GTypeName: 'ClapperAppBase',
|
|
||||||
},
|
|
||||||
class ClapperAppBase extends Gtk.Application
|
|
||||||
{
|
|
||||||
_init()
|
|
||||||
{
|
|
||||||
super._init({
|
|
||||||
application_id: Misc.appId,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.doneFirstActivate = false;
|
|
||||||
this.isFileAppend = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
vfunc_startup()
|
|
||||||
{
|
|
||||||
super.vfunc_startup();
|
|
||||||
|
|
||||||
const window = new Gtk.ApplicationWindow({
|
|
||||||
application: this,
|
|
||||||
title: Misc.appName,
|
|
||||||
});
|
|
||||||
|
|
||||||
/* FIXME: AFAIK there is no way to detect theme rounded corners.
|
|
||||||
* Having 2/4 corners rounded in floating mode is not good. */
|
|
||||||
window.add_css_class('adwrounded');
|
|
||||||
|
|
||||||
if(!settings.get_boolean('render-shadows'))
|
|
||||||
window.add_css_class('gpufriendly');
|
|
||||||
|
|
||||||
window.add_css_class('gpufriendlyfs');
|
|
||||||
}
|
|
||||||
|
|
||||||
vfunc_activate()
|
|
||||||
{
|
|
||||||
super.vfunc_activate();
|
|
||||||
|
|
||||||
if(!this.doneFirstActivate)
|
|
||||||
this._onFirstActivate();
|
|
||||||
|
|
||||||
this.active_window.present_with_time(
|
|
||||||
Math.floor(GLib.get_monotonic_time() / 1000)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _openFilesAsync(files)
|
|
||||||
{
|
|
||||||
const urisArr = [];
|
|
||||||
|
|
||||||
for(let file of files) {
|
|
||||||
const uri = file.get_uri();
|
|
||||||
if(!uri.startsWith('file:')) {
|
|
||||||
urisArr.push(uri);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If file is not a dir its URI will be returned in an array */
|
|
||||||
const uris = await FileOps.getDirFilesUrisPromise(file).catch(debug);
|
|
||||||
if(uris && uris.length)
|
|
||||||
urisArr.push(...uris);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [playlist, subs] = Misc.parsePlaylistFiles(urisArr);
|
|
||||||
const { player } = this.active_window.get_child();
|
|
||||||
const action = (this.isFileAppend) ? 'append' : 'set';
|
|
||||||
|
|
||||||
if(playlist && playlist.length)
|
|
||||||
player[`${action}_playlist`](playlist);
|
|
||||||
if(subs)
|
|
||||||
player.set_subtitles(subs);
|
|
||||||
|
|
||||||
/* Restore default behavior */
|
|
||||||
this.isFileAppend = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFirstActivate()
|
|
||||||
{
|
|
||||||
for(let name in Actions.actions) {
|
|
||||||
const simpleAction = new Gio.SimpleAction({ name });
|
|
||||||
simpleAction.connect('activate', (action) =>
|
|
||||||
Actions.handleAction(action, this.active_window)
|
|
||||||
);
|
|
||||||
this.add_action(simpleAction);
|
|
||||||
|
|
||||||
const accels = Actions.actions[name];
|
|
||||||
if(accels)
|
|
||||||
this.set_accels_for_action(`app.${name}`, accels);
|
|
||||||
}
|
|
||||||
this.doneFirstActivate = true;
|
|
||||||
}
|
|
||||||
});
|
|
274
src/headerbar.js
274
src/headerbar.js
@@ -1,11 +1,233 @@
|
|||||||
const { GObject } = imports.gi;
|
const { GObject, Gtk } = imports.gi;
|
||||||
const { HeaderBarBase } = imports.src.headerbarBase;
|
const Debug = imports.src.debug;
|
||||||
|
const Misc = imports.src.misc;
|
||||||
|
|
||||||
|
const { debug } = Debug;
|
||||||
|
|
||||||
var HeaderBar = GObject.registerClass({
|
var HeaderBar = GObject.registerClass({
|
||||||
GTypeName: 'ClapperHeaderBar',
|
GTypeName: 'ClapperHeaderBar',
|
||||||
},
|
},
|
||||||
class ClapperHeaderBar extends HeaderBarBase
|
class ClapperHeaderBar extends Gtk.Box
|
||||||
{
|
{
|
||||||
|
_init()
|
||||||
|
{
|
||||||
|
super._init({
|
||||||
|
can_focus: false,
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
spacing: 6,
|
||||||
|
margin_top: 6,
|
||||||
|
margin_start: 6,
|
||||||
|
margin_end: 6,
|
||||||
|
});
|
||||||
|
this.add_css_class('osdheaderbar');
|
||||||
|
|
||||||
|
this.isMaximized = false;
|
||||||
|
this.isMenuOnLeft = true;
|
||||||
|
|
||||||
|
const uiBuilder = Misc.getBuilderForName('clapper.ui');
|
||||||
|
|
||||||
|
this.menuWidget = new Gtk.Box({
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
spacing: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.menuButton = new Gtk.MenuButton({
|
||||||
|
icon_name: 'open-menu-symbolic',
|
||||||
|
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);
|
||||||
|
this.menuButton.set_popover(mainMenuPopover);
|
||||||
|
this.menuButton.add_css_class('circular');
|
||||||
|
this.menuWidget.append(this.menuButton);
|
||||||
|
|
||||||
|
this.extraButtonsBox = new Gtk.Box({
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
});
|
||||||
|
this.extraButtonsBox.add_css_class('linked');
|
||||||
|
|
||||||
|
const floatButton = new Gtk.Button({
|
||||||
|
icon_name: 'pip-in-symbolic',
|
||||||
|
can_focus: false,
|
||||||
|
});
|
||||||
|
floatButton.add_css_class('osd');
|
||||||
|
floatButton.add_css_class('circular');
|
||||||
|
floatButton.add_css_class('linkedleft');
|
||||||
|
floatButton.connect('clicked',
|
||||||
|
this._onFloatButtonClicked.bind(this)
|
||||||
|
);
|
||||||
|
this.extraButtonsBox.append(floatButton);
|
||||||
|
|
||||||
|
const separator = new Gtk.Separator({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
});
|
||||||
|
separator.add_css_class('linkseparator');
|
||||||
|
this.extraButtonsBox.append(separator);
|
||||||
|
|
||||||
|
const fullscreenButton = new Gtk.Button({
|
||||||
|
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',
|
||||||
|
this._onFullscreenButtonClicked.bind(this)
|
||||||
|
);
|
||||||
|
this.extraButtonsBox.append(fullscreenButton);
|
||||||
|
this.menuWidget.append(this.extraButtonsBox);
|
||||||
|
|
||||||
|
this.spacerWidget = new Gtk.Box({
|
||||||
|
hexpand: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.minimizeWidget = this._getWindowButton('minimize');
|
||||||
|
this.maximizeWidget = this._getWindowButton('maximize');
|
||||||
|
this.closeWidget = this._getWindowButton('close');
|
||||||
|
|
||||||
|
const gtkSettings = Gtk.Settings.get_default();
|
||||||
|
this._onLayoutUpdate(gtkSettings);
|
||||||
|
|
||||||
|
gtkSettings.connect(
|
||||||
|
'notify::gtk-decoration-layout',
|
||||||
|
this._onLayoutUpdate.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenuOnLeft(isOnLeft)
|
||||||
|
{
|
||||||
|
if(this.isMenuOnLeft === isOnLeft)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(isOnLeft) {
|
||||||
|
this.menuWidget.reorder_child_after(
|
||||||
|
this.extraButtonsBox, this.menuButton
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.menuWidget.reorder_child_after(
|
||||||
|
this.menuButton, this.extraButtonsBox
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isMenuOnLeft = isOnLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMaximized(isMaximized)
|
||||||
|
{
|
||||||
|
if(this.isMaximized === isMaximized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.maximizeWidget.icon_name = (isMaximized)
|
||||||
|
? 'window-restore-symbolic'
|
||||||
|
: 'window-maximize-symbolic';
|
||||||
|
|
||||||
|
this.isMaximized = isMaximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLayoutUpdate(gtkSettings)
|
||||||
|
{
|
||||||
|
const gtkLayout = gtkSettings.gtk_decoration_layout;
|
||||||
|
|
||||||
|
this._replaceButtons(gtkLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
_replaceButtons(gtkLayout)
|
||||||
|
{
|
||||||
|
const modLayout = gtkLayout.replace(':', ',spacer,');
|
||||||
|
const layoutArr = modLayout.split(',');
|
||||||
|
|
||||||
|
let lastWidget = null;
|
||||||
|
|
||||||
|
let showMinimize = false;
|
||||||
|
let showMaximize = false;
|
||||||
|
let showClose = false;
|
||||||
|
|
||||||
|
let menuAdded = false;
|
||||||
|
let spacerAdded = false;
|
||||||
|
|
||||||
|
debug(`headerbar layout: ${modLayout}`);
|
||||||
|
|
||||||
|
for(let name of layoutArr) {
|
||||||
|
/* Menu might be named "appmenu" */
|
||||||
|
if(!menuAdded && (!name || name === 'appmenu' || name === 'icon'))
|
||||||
|
name = 'menu';
|
||||||
|
|
||||||
|
const widget = this[`${name}Widget`];
|
||||||
|
if(!widget) continue;
|
||||||
|
|
||||||
|
if(!widget.parent)
|
||||||
|
this.append(widget);
|
||||||
|
else
|
||||||
|
this.reorder_child_after(widget, lastWidget);
|
||||||
|
|
||||||
|
switch(name) {
|
||||||
|
case 'spacer':
|
||||||
|
spacerAdded = true;
|
||||||
|
break;
|
||||||
|
case 'minimize':
|
||||||
|
showMinimize = true;
|
||||||
|
break;
|
||||||
|
case 'maximize':
|
||||||
|
showMaximize = true;
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
showClose = true;
|
||||||
|
break;
|
||||||
|
case 'menu':
|
||||||
|
this.setMenuOnLeft(!spacerAdded);
|
||||||
|
menuAdded = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastWidget = widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minimizeWidget.visible = showMinimize;
|
||||||
|
this.maximizeWidget.visible = showMaximize;
|
||||||
|
this.closeWidget.visible = showClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getWindowButton(name)
|
||||||
|
{
|
||||||
|
const button = new Gtk.Button({
|
||||||
|
icon_name: `window-${name}-symbolic`,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
can_focus: false,
|
||||||
|
});
|
||||||
|
button.add_css_class('osd');
|
||||||
|
button.add_css_class('circular');
|
||||||
|
|
||||||
|
if(name === 'maximize')
|
||||||
|
name = 'toggle-maximized';
|
||||||
|
|
||||||
|
button.connect('clicked',
|
||||||
|
this._onWindowButtonActivate.bind(this, name)
|
||||||
|
);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateFloatIcon(isFloating)
|
||||||
|
{
|
||||||
|
const floatButton = this.extraButtonsBox.get_first_child();
|
||||||
|
if(!floatButton) return;
|
||||||
|
|
||||||
|
const iconName = (isFloating)
|
||||||
|
? 'pip-out-symbolic'
|
||||||
|
: 'pip-in-symbolic';
|
||||||
|
|
||||||
|
if(floatButton.icon_name !== iconName)
|
||||||
|
floatButton.icon_name = iconName;
|
||||||
|
}
|
||||||
|
|
||||||
_onWindowButtonActivate(action)
|
_onWindowButtonActivate(action)
|
||||||
{
|
{
|
||||||
this.activate_action(`window.${action}`, null);
|
this.activate_action(`window.${action}`, null);
|
||||||
@@ -29,3 +251,49 @@ class ClapperHeaderBar extends HeaderBarBase
|
|||||||
this.root.fullscreen();
|
this.root.fullscreen();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var HeaderBarPopover = GObject.registerClass({
|
||||||
|
GTypeName: 'ClapperHeaderBarPopover',
|
||||||
|
},
|
||||||
|
class ClapperHeaderBarPopover extends Gtk.PopoverMenu
|
||||||
|
{
|
||||||
|
_init(model)
|
||||||
|
{
|
||||||
|
super._init({
|
||||||
|
menu_model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connect('map', this._onMap.bind(this));
|
||||||
|
this.connect('closed', this._onClosed.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMap()
|
||||||
|
{
|
||||||
|
const { child } = this.root;
|
||||||
|
|
||||||
|
if(
|
||||||
|
!child
|
||||||
|
|| !child.player
|
||||||
|
|| !child.player.widget
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
child.revealControls();
|
||||||
|
child.isPopoverOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onClosed()
|
||||||
|
{
|
||||||
|
const { child } = this.root;
|
||||||
|
|
||||||
|
if(
|
||||||
|
!child
|
||||||
|
|| !child.player
|
||||||
|
|| !child.player.widget
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
child.revealControls();
|
||||||
|
child.isPopoverOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@@ -1,288 +0,0 @@
|
|||||||
const { GObject, Gtk } = imports.gi;
|
|
||||||
const Debug = imports.src.debug;
|
|
||||||
const Misc = imports.src.misc;
|
|
||||||
|
|
||||||
const { debug } = Debug;
|
|
||||||
|
|
||||||
var HeaderBarBase = GObject.registerClass({
|
|
||||||
GTypeName: 'ClapperHeaderBarBase',
|
|
||||||
},
|
|
||||||
class ClapperHeaderBarBase extends Gtk.Box
|
|
||||||
{
|
|
||||||
_init()
|
|
||||||
{
|
|
||||||
super._init({
|
|
||||||
can_focus: false,
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
spacing: 6,
|
|
||||||
margin_top: 6,
|
|
||||||
margin_start: 6,
|
|
||||||
margin_end: 6,
|
|
||||||
});
|
|
||||||
this.add_css_class('osdheaderbar');
|
|
||||||
|
|
||||||
this.isMaximized = false;
|
|
||||||
this.isMenuOnLeft = true;
|
|
||||||
|
|
||||||
const uiBuilder = Misc.getBuilderForName('clapper.ui');
|
|
||||||
|
|
||||||
this.menuWidget = new Gtk.Box({
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
spacing: 6,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.menuButton = new Gtk.MenuButton({
|
|
||||||
icon_name: 'open-menu-symbolic',
|
|
||||||
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);
|
|
||||||
this.menuButton.set_popover(mainMenuPopover);
|
|
||||||
this.menuButton.add_css_class('circular');
|
|
||||||
this.menuWidget.append(this.menuButton);
|
|
||||||
|
|
||||||
this.extraButtonsBox = new Gtk.Box({
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
});
|
|
||||||
this.extraButtonsBox.add_css_class('linked');
|
|
||||||
|
|
||||||
const floatButton = new Gtk.Button({
|
|
||||||
icon_name: 'pip-in-symbolic',
|
|
||||||
can_focus: false,
|
|
||||||
});
|
|
||||||
floatButton.add_css_class('osd');
|
|
||||||
floatButton.add_css_class('circular');
|
|
||||||
floatButton.add_css_class('linkedleft');
|
|
||||||
floatButton.connect('clicked',
|
|
||||||
this._onFloatButtonClicked.bind(this)
|
|
||||||
);
|
|
||||||
this.extraButtonsBox.append(floatButton);
|
|
||||||
|
|
||||||
const separator = new Gtk.Separator({
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
});
|
|
||||||
separator.add_css_class('linkseparator');
|
|
||||||
this.extraButtonsBox.append(separator);
|
|
||||||
|
|
||||||
const fullscreenButton = new Gtk.Button({
|
|
||||||
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',
|
|
||||||
this._onFullscreenButtonClicked.bind(this)
|
|
||||||
);
|
|
||||||
this.extraButtonsBox.append(fullscreenButton);
|
|
||||||
this.menuWidget.append(this.extraButtonsBox);
|
|
||||||
|
|
||||||
this.spacerWidget = new Gtk.Box({
|
|
||||||
hexpand: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.minimizeWidget = this._getWindowButton('minimize');
|
|
||||||
this.maximizeWidget = this._getWindowButton('maximize');
|
|
||||||
this.closeWidget = this._getWindowButton('close');
|
|
||||||
|
|
||||||
const gtkSettings = Gtk.Settings.get_default();
|
|
||||||
this._onLayoutUpdate(gtkSettings);
|
|
||||||
|
|
||||||
gtkSettings.connect(
|
|
||||||
'notify::gtk-decoration-layout',
|
|
||||||
this._onLayoutUpdate.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setMenuOnLeft(isOnLeft)
|
|
||||||
{
|
|
||||||
if(this.isMenuOnLeft === isOnLeft)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(isOnLeft) {
|
|
||||||
this.menuWidget.reorder_child_after(
|
|
||||||
this.extraButtonsBox, this.menuButton
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.menuWidget.reorder_child_after(
|
|
||||||
this.menuButton, this.extraButtonsBox
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isMenuOnLeft = isOnLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMaximized(isMaximized)
|
|
||||||
{
|
|
||||||
if(this.isMaximized === isMaximized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.maximizeWidget.icon_name = (isMaximized)
|
|
||||||
? 'window-restore-symbolic'
|
|
||||||
: 'window-maximize-symbolic';
|
|
||||||
|
|
||||||
this.isMaximized = isMaximized;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLayoutUpdate(gtkSettings)
|
|
||||||
{
|
|
||||||
const gtkLayout = gtkSettings.gtk_decoration_layout;
|
|
||||||
|
|
||||||
this._replaceButtons(gtkLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
_replaceButtons(gtkLayout)
|
|
||||||
{
|
|
||||||
const modLayout = gtkLayout.replace(':', ',spacer,');
|
|
||||||
const layoutArr = modLayout.split(',');
|
|
||||||
|
|
||||||
let lastWidget = null;
|
|
||||||
|
|
||||||
let showMinimize = false;
|
|
||||||
let showMaximize = false;
|
|
||||||
let showClose = false;
|
|
||||||
|
|
||||||
let menuAdded = false;
|
|
||||||
let spacerAdded = false;
|
|
||||||
|
|
||||||
debug(`headerbar layout: ${modLayout}`);
|
|
||||||
|
|
||||||
for(let name of layoutArr) {
|
|
||||||
/* Menu might be named "appmenu" */
|
|
||||||
if(!menuAdded && (!name || name === 'appmenu' || name === 'icon'))
|
|
||||||
name = 'menu';
|
|
||||||
|
|
||||||
const widget = this[`${name}Widget`];
|
|
||||||
if(!widget) continue;
|
|
||||||
|
|
||||||
if(!widget.parent)
|
|
||||||
this.append(widget);
|
|
||||||
else
|
|
||||||
this.reorder_child_after(widget, lastWidget);
|
|
||||||
|
|
||||||
switch(name) {
|
|
||||||
case 'spacer':
|
|
||||||
spacerAdded = true;
|
|
||||||
break;
|
|
||||||
case 'minimize':
|
|
||||||
showMinimize = true;
|
|
||||||
break;
|
|
||||||
case 'maximize':
|
|
||||||
showMaximize = true;
|
|
||||||
break;
|
|
||||||
case 'close':
|
|
||||||
showClose = true;
|
|
||||||
break;
|
|
||||||
case 'menu':
|
|
||||||
this.setMenuOnLeft(!spacerAdded);
|
|
||||||
menuAdded = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastWidget = widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.minimizeWidget.visible = showMinimize;
|
|
||||||
this.maximizeWidget.visible = showMaximize;
|
|
||||||
this.closeWidget.visible = showClose;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getWindowButton(name)
|
|
||||||
{
|
|
||||||
const button = new Gtk.Button({
|
|
||||||
icon_name: `window-${name}-symbolic`,
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
can_focus: false,
|
|
||||||
});
|
|
||||||
button.add_css_class('osd');
|
|
||||||
button.add_css_class('circular');
|
|
||||||
|
|
||||||
if(name === 'maximize')
|
|
||||||
name = 'toggle-maximized';
|
|
||||||
|
|
||||||
button.connect('clicked',
|
|
||||||
this._onWindowButtonActivate.bind(this, name)
|
|
||||||
);
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateFloatIcon(isFloating)
|
|
||||||
{
|
|
||||||
const floatButton = this.extraButtonsBox.get_first_child();
|
|
||||||
if(!floatButton) return;
|
|
||||||
|
|
||||||
const iconName = (isFloating)
|
|
||||||
? 'pip-out-symbolic'
|
|
||||||
: 'pip-in-symbolic';
|
|
||||||
|
|
||||||
if(floatButton.icon_name !== iconName)
|
|
||||||
floatButton.icon_name = iconName;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onWindowButtonActivate(action)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFloatButtonClicked(button)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFullscreenButtonClicked(button)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var HeaderBarPopover = GObject.registerClass({
|
|
||||||
GTypeName: 'ClapperHeaderBarPopover',
|
|
||||||
},
|
|
||||||
class ClapperHeaderBarPopover extends Gtk.PopoverMenu
|
|
||||||
{
|
|
||||||
_init(model)
|
|
||||||
{
|
|
||||||
super._init({
|
|
||||||
menu_model: model,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connect('map', this._onMap.bind(this));
|
|
||||||
this.connect('closed', this._onClosed.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMap()
|
|
||||||
{
|
|
||||||
const { child } = this.root;
|
|
||||||
|
|
||||||
if(
|
|
||||||
!child
|
|
||||||
|| !child.player
|
|
||||||
|| !child.player.widget
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
child.revealControls();
|
|
||||||
child.isPopoverOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onClosed()
|
|
||||||
{
|
|
||||||
const { child } = this.root;
|
|
||||||
|
|
||||||
if(
|
|
||||||
!child
|
|
||||||
|| !child.player
|
|
||||||
|| !child.player.widget
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
child.revealControls();
|
|
||||||
child.isPopoverOpen = false;
|
|
||||||
}
|
|
||||||
});
|
|
Reference in New Issue
Block a user