diff --git a/data/com.github.rafostar.Clapper.desktop b/data/com.github.rafostar.Clapper.desktop index 4dc7e654..d780a30f 100644 --- a/data/com.github.rafostar.Clapper.desktop +++ b/data/com.github.rafostar.Clapper.desktop @@ -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; Exec=com.github.rafostar.Clapper %U Icon=com.github.rafostar.Clapper +DBusActivatable=true +StartupNotify=true Terminal=false Type=Application # Translators: Search terms to find this application. Do NOT translate the semicolons! diff --git a/data/com.github.rafostar.Clapper.service.in b/data/com.github.rafostar.Clapper.service.in new file mode 100644 index 00000000..d8057116 --- /dev/null +++ b/data/com.github.rafostar.Clapper.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=@app_id@ +Exec=@bindir@/@app_id@ --gapplication-service diff --git a/data/meson.build b/data/meson.build index 44adfd86..e436efe5 100644 --- a/data/meson.build +++ b/data/meson.build @@ -32,3 +32,15 @@ gnome.compile_resources('com.github.rafostar.Clapper.data', install: true, 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'), +) diff --git a/lib/gst/plugin/gstclapperpaintable.c b/lib/gst/plugin/gstclapperpaintable.c index 289a07a4..ebd756a5 100644 --- a/lib/gst/plugin/gstclapperpaintable.c +++ b/lib/gst/plugin/gstclapperpaintable.c @@ -84,6 +84,10 @@ gst_clapper_paintable_dispose (GObject *object) 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)); } @@ -95,7 +99,6 @@ gst_clapper_paintable_finalize (GObject *object) GST_TRACE ("Finalize"); g_weak_ref_clear (&self->widget); - gst_clear_object (&self->importer); g_mutex_clear (&self->lock); g_mutex_clear (&self->importer_lock); diff --git a/lib/gst/plugin/importers/gstclapperglbaseimporter.c b/lib/gst/plugin/importers/gstclapperglbaseimporter.c index 497ed19a..cb558030 100644 --- a/lib/gst/plugin/importers/gstclapperglbaseimporter.c +++ b/lib/gst/plugin/importers/gstclapperglbaseimporter.c @@ -109,6 +109,11 @@ retrieve_gl_context_on_main (GstClapperGLBaseImporter *self) 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))) { GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s", error ? error->message : "No error set by Gdk"); @@ -465,7 +470,8 @@ _realize_gdk_context_with_api (GdkGLContext *gdk_context, GdkGLAPI api) static gboolean 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; 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); } + 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. - * 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. */ -#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; -#else - preferred_api = GDK_GL_API_GLES; +#endif #endif if (!(success = _realize_gdk_context_with_api (gdk_context, preferred_api))) { diff --git a/src/app.js b/src/app.js index e888cc83..4f9e572a 100644 --- a/src/app.js +++ b/src/app.js @@ -1,27 +1,92 @@ -const { Gio, GObject, Gdk, Gtk } = imports.gi; -const { AppBase } = imports.src.appBase; +const { Gio, GObject, Gtk } = imports.gi; const { Widget } = imports.src.widget; 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 App = GObject.registerClass({ GTypeName: 'ClapperApp', }, -class ClapperApp extends AppBase +class ClapperApp extends Gtk.Application { _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 dummyHeaderbar = new Gtk.Box({ can_focus: false, @@ -33,14 +98,20 @@ class ClapperApp extends AppBase window.set_child(clapperWidget); 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)); - } - - vfunc_open(files, hint) - { - super.vfunc_open(files, hint); - - this._openFilesAsync(files).then(() => this.activate()).catch(debug); + this.doneFirstActivate = true; } _onWindowMap(window) diff --git a/src/appBase.js b/src/appBase.js deleted file mode 100644 index be72d61d..00000000 --- a/src/appBase.js +++ /dev/null @@ -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; - } -}); diff --git a/src/headerbar.js b/src/headerbar.js index d41a519f..b1551ccf 100644 --- a/src/headerbar.js +++ b/src/headerbar.js @@ -1,11 +1,233 @@ -const { GObject } = imports.gi; -const { HeaderBarBase } = imports.src.headerbarBase; +const { GObject, Gtk } = imports.gi; +const Debug = imports.src.debug; +const Misc = imports.src.misc; + +const { debug } = Debug; var HeaderBar = GObject.registerClass({ 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) { this.activate_action(`window.${action}`, null); @@ -29,3 +251,49 @@ class ClapperHeaderBar extends HeaderBarBase 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; + } +}); diff --git a/src/headerbarBase.js b/src/headerbarBase.js deleted file mode 100644 index 46950070..00000000 --- a/src/headerbarBase.js +++ /dev/null @@ -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; - } -});