f2c1a416bf
There is currently no way to trigger an icon's right-click menu by keyboard. While there's a good chance that the icon will ignore <shift>F10 and similar shortcuts, passing on key events will at least make it work for some icons ... https://bugzilla.gnome.org/show_bug.cgi?id=746487
255 lines
10 KiB
JavaScript
255 lines
10 KiB
JavaScript
const Clutter = imports.gi.Clutter;
|
|
const GObject = imports.gi.GObject;
|
|
const Meta = imports.gi.Meta;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
|
|
const CtrlAltTab = imports.ui.ctrlAltTab;
|
|
const Lang = imports.lang;
|
|
const Layout = imports.ui.layout;
|
|
const Main = imports.ui.main;
|
|
const Overview = imports.ui.overview;
|
|
const OverviewControls = imports.ui.overviewControls;
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
|
|
'bluetooth-applet': 'bluetooth',
|
|
'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
|
|
// when moved to control center
|
|
'gnome-sound-applet': 'volume',
|
|
'nm-applet': 'network',
|
|
'gnome-power-manager': 'battery',
|
|
'keyboard': 'keyboard',
|
|
'a11y-keyboard': 'a11y',
|
|
'kbd-scrolllock': 'keyboard',
|
|
'kbd-numlock': 'keyboard',
|
|
'kbd-capslock': 'keyboard',
|
|
'ibus-ui-gtk': 'keyboard'
|
|
};
|
|
|
|
// Offset of the original position from the bottom-right corner
|
|
const CONCEALED_VISIBLE_FRACTION = 0.2;
|
|
const REVEAL_ANIMATION_TIME = 0.2;
|
|
|
|
const BARRIER_THRESHOLD = 70;
|
|
const BARRIER_TIMEOUT = 1000;
|
|
|
|
const LegacyTray = new Lang.Class({
|
|
Name: 'LegacyTray',
|
|
|
|
_init: function() {
|
|
this.actor = new St.Widget({ clip_to_allocation: true,
|
|
layout_manager: new Clutter.BinLayout() });
|
|
let constraint = new Layout.MonitorConstraint({ primary: true,
|
|
work_area: true });
|
|
this.actor.add_constraint(constraint);
|
|
|
|
this._slideLayout = new OverviewControls.SlideLayout();
|
|
this._slideLayout.translationX = 0;
|
|
this._slideLayout.slideDirection = OverviewControls.SlideDirection.LEFT;
|
|
|
|
this._slider = new St.Widget({ style_class: 'legacy-tray',
|
|
x_expand: true, y_expand: true,
|
|
x_align: Clutter.ActorAlign.START,
|
|
y_align: Clutter.ActorAlign.END,
|
|
layout_manager: this._slideLayout });
|
|
this.actor.add_actor(this._slider);
|
|
this._slider.connect('notify::allocation', Lang.bind(this, this._syncBarrier));
|
|
|
|
this._box = new St.BoxLayout();
|
|
this._slider.add_actor(this._box);
|
|
|
|
this._concealHandle = new St.Button({ style_class: 'legacy-tray-handle',
|
|
/* translators: 'Hide' is a verb */
|
|
accessible_name: _("Hide tray"),
|
|
can_focus: true });
|
|
this._concealHandle.child = new St.Icon({ icon_name: 'go-previous-symbolic' });
|
|
this._box.add_child(this._concealHandle);
|
|
|
|
this._iconBox = new St.BoxLayout({ style_class: 'legacy-tray-icon-box' });
|
|
this._box.add_actor(this._iconBox);
|
|
|
|
this._revealHandle = new St.Button({ style_class: 'legacy-tray-handle' });
|
|
this._revealHandle.child = new St.Icon({ icon_name: 'go-next-symbolic' });
|
|
this._box.add_child(this._revealHandle);
|
|
|
|
this._revealHandle.bind_property('visible',
|
|
this._concealHandle, 'visible',
|
|
GObject.BindingFlags.BIDIRECTIONAL |
|
|
GObject.BindingFlags.INVERT_BOOLEAN);
|
|
this._revealHandle.connect('notify::visible',
|
|
Lang.bind(this, this._sync));
|
|
this._revealHandle.connect('notify::hover',
|
|
Lang.bind(this ,this._sync));
|
|
this._revealHandle.connect('clicked', Lang.bind(this,
|
|
function() {
|
|
this._concealHandle.show();
|
|
}));
|
|
this._concealHandle.connect('clicked', Lang.bind(this,
|
|
function() {
|
|
this._revealHandle.show();
|
|
}));
|
|
|
|
this._horizontalBarrier = null;
|
|
this._pressureBarrier = new Layout.PressureBarrier(BARRIER_THRESHOLD,
|
|
BARRIER_TIMEOUT,
|
|
Shell.ActionMode.NORMAL);
|
|
this._pressureBarrier.connect('trigger', Lang.bind(this, function() {
|
|
this._concealHandle.show();
|
|
}));
|
|
|
|
Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false });
|
|
Main.layoutManager.trackChrome(this._slider, { affectsInputRegion: true });
|
|
Main.ctrlAltTabManager.addGroup(this.actor,
|
|
_("Status Icons"), 'focus-legacy-systray-symbolic',
|
|
{ sortGroup: CtrlAltTab.SortGroup.BOTTOM });
|
|
|
|
this._trayManager = new Shell.TrayManager();
|
|
this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded));
|
|
this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
|
|
this._trayManager.manage_screen(global.screen, this.actor);
|
|
|
|
Main.overview.connect('showing', Lang.bind(this,
|
|
function() {
|
|
Tweener.removeTweens(this._slider);
|
|
Tweener.addTween(this._slider, { opacity: 0,
|
|
time: Overview.ANIMATION_TIME,
|
|
transition: 'easeOutQuad' });
|
|
}));
|
|
Main.overview.connect('shown', Lang.bind(this, this._sync));
|
|
Main.overview.connect('hiding', Lang.bind(this,
|
|
function() {
|
|
this._sync();
|
|
Tweener.removeTweens(this._slider);
|
|
Tweener.addTween(this._slider, { opacity: 255,
|
|
time: Overview.ANIMATION_TIME,
|
|
transition: 'easeOutQuad' });
|
|
}));
|
|
|
|
Main.layoutManager.connect('monitors-changed',
|
|
Lang.bind(this, this._sync));
|
|
global.screen.connect('in-fullscreen-changed',
|
|
Lang.bind(this, this._sync));
|
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sync));
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_onTrayIconAdded: function(tm, icon) {
|
|
let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : '';
|
|
if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined)
|
|
return;
|
|
|
|
let button = new St.Button({ child: icon,
|
|
style_class: 'legacy-tray-icon',
|
|
button_mask: St.ButtonMask.ONE |
|
|
St.ButtonMask.TWO |
|
|
St.ButtonMask.THREE,
|
|
can_focus: true,
|
|
x_fill: true, y_fill: true });
|
|
|
|
let app = Shell.WindowTracker.get_default().get_app_from_pid(icon.pid);
|
|
if (!app)
|
|
app = Shell.AppSystem.get_default().lookup_startup_wmclass(wmClass);
|
|
if (!app)
|
|
app = Shell.AppSystem.get_default().lookup_desktop_wmclass(wmClass);
|
|
if (app)
|
|
button.accessible_name = app.get_name();
|
|
else
|
|
button.accessible_name = icon.title;
|
|
|
|
button.connect('clicked',
|
|
function() {
|
|
icon.click(Clutter.get_current_event());
|
|
});
|
|
button.connect('key-press-event',
|
|
function() {
|
|
icon.click(Clutter.get_current_event());
|
|
return Clutter.EVENT_PROPAGATE;
|
|
});
|
|
button.connect('key-focus-in', Lang.bind(this,
|
|
function() {
|
|
this._concealHandle.show();
|
|
}));
|
|
|
|
this._iconBox.add_actor(button);
|
|
this._sync();
|
|
},
|
|
|
|
_onTrayIconRemoved: function(tm, icon) {
|
|
if (!this.actor.contains(icon))
|
|
return;
|
|
|
|
icon.get_parent().destroy();
|
|
this._sync();
|
|
},
|
|
|
|
_syncBarrier: function() {
|
|
let rtl = (this._slider.get_text_direction() == Clutter.TextDirection.RTL);
|
|
let [x, y] = this._slider.get_transformed_position();
|
|
let [w, h] = this._slider.get_transformed_size();
|
|
|
|
let x1 = Math.round(x);
|
|
if (rtl)
|
|
x1 += Math.round(w);
|
|
|
|
let x2 = x1;
|
|
let y1 = Math.round(y);
|
|
let y2 = y1 + Math.round(h);
|
|
|
|
if (this._horizontalBarrier &&
|
|
this._horizontalBarrier.x1 == x1 &&
|
|
this._horizontalBarrier.y1 == y1 &&
|
|
this._horizontalBarrier.x2 == x2 &&
|
|
this._horizontalBarrier.y2 == y2)
|
|
return;
|
|
|
|
if (this._horizontalBarrier) {
|
|
this._pressureBarrier.removeBarrier(this._horizontalBarrier);
|
|
this._horizontalBarrier.destroy();
|
|
this._horizontalBarrier = null;
|
|
}
|
|
|
|
let directions = (rtl ? Meta.BarrierDirection.NEGATIVE_X : Meta.BarrierDirection.POSITIVE_X);
|
|
this._horizontalBarrier = new Meta.Barrier({ display: global.display,
|
|
x1: x1, x2: x2,
|
|
y1: y1, y2: y2,
|
|
directions: directions });
|
|
this._pressureBarrier.addBarrier(this._horizontalBarrier);
|
|
},
|
|
|
|
_sync: function() {
|
|
// FIXME: we no longer treat tray icons as notifications
|
|
let allowed = Main.sessionMode.hasNotifications;
|
|
let hasIcons = this._iconBox.get_n_children() > 0;
|
|
let inOverview = Main.overview.visible && !Main.overview.animationInProgress;
|
|
let inFullscreen = Main.layoutManager.primaryMonitor.inFullscreen;
|
|
this.actor.visible = allowed && hasIcons && !inOverview && !inFullscreen;
|
|
|
|
if (!hasIcons)
|
|
this._concealHandle.hide();
|
|
|
|
let targetSlide;
|
|
if (this._concealHandle.visible) {
|
|
targetSlide = 1.0;
|
|
} else if (!hasIcons) {
|
|
targetSlide = 0.0;
|
|
} else {
|
|
let [, boxWidth] = this._box.get_preferred_width(-1);
|
|
let [, handleWidth] = this._revealHandle.get_preferred_width(-1);
|
|
|
|
targetSlide = handleWidth / boxWidth;
|
|
if (!this._revealHandle.hover)
|
|
targetSlide *= CONCEALED_VISIBLE_FRACTION;
|
|
}
|
|
|
|
if (this.actor.visible)
|
|
Tweener.addTween(this._slideLayout,
|
|
{ slideX: targetSlide,
|
|
time: REVEAL_ANIMATION_TIME,
|
|
transition: 'easeOutQuad' });
|
|
else
|
|
this._slideLayout.slideX = targetSlide;
|
|
}
|
|
});
|