250 lines
7.0 KiB
JavaScript
250 lines
7.0 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const Clutter = imports.gi.Clutter;
|
||
|
const Gio = imports.gi.Gio;
|
||
|
const GObject = imports.gi.GObject;
|
||
|
const St = imports.gi.St;
|
||
|
|
||
|
const PanelMenu = imports.ui.panelMenu;
|
||
|
const PopupMenu = imports.ui.popupMenu;
|
||
|
|
||
|
const Extension = imports.misc.extensionUtils.getCurrentExtension();
|
||
|
|
||
|
// eslint-disable-next-line no-redeclare
|
||
|
const _ = gsconnect._;
|
||
|
const GMenu = Extension.imports.shell.gmenu;
|
||
|
const Tooltip = Extension.imports.shell.tooltip;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* A battery widget with an icon, text percentage and time estimate tooltip
|
||
|
*/
|
||
|
var Battery = GObject.registerClass({
|
||
|
GTypeName: 'GSConnectShellDeviceBattery'
|
||
|
}, class Battery extends St.BoxLayout {
|
||
|
|
||
|
_init(params) {
|
||
|
super._init({
|
||
|
reactive: true,
|
||
|
style_class: 'gsconnect-device-battery',
|
||
|
track_hover: true
|
||
|
});
|
||
|
Object.assign(this, params);
|
||
|
|
||
|
// Percent Label
|
||
|
this.label = new St.Label({
|
||
|
y_align: Clutter.ActorAlign.CENTER
|
||
|
});
|
||
|
this.label.clutter_text.ellipsize = 0;
|
||
|
this.add_child(this.label);
|
||
|
|
||
|
// Battery Icon
|
||
|
this.icon = new St.Icon({
|
||
|
fallback_icon_name: 'battery-missing-symbolic',
|
||
|
icon_size: 16
|
||
|
});
|
||
|
this.add_child(this.icon);
|
||
|
|
||
|
// Battery Estimate
|
||
|
this.tooltip = new Tooltip.Tooltip({
|
||
|
parent: this,
|
||
|
text: this.battery_label
|
||
|
});
|
||
|
|
||
|
// Battery GAction
|
||
|
this._actionAddedId = this.device.action_group.connect(
|
||
|
'action-added',
|
||
|
this._onActionChanged.bind(this)
|
||
|
);
|
||
|
this._actionRemovedId = this.device.action_group.connect(
|
||
|
'action-removed',
|
||
|
this._onActionChanged.bind(this)
|
||
|
);
|
||
|
this._actionStateChangedId = this.device.action_group.connect(
|
||
|
'action-state-changed',
|
||
|
this._onStateChanged.bind(this)
|
||
|
);
|
||
|
|
||
|
this._onActionChanged(this.device.action_group, 'battery');
|
||
|
|
||
|
// Refresh when mapped
|
||
|
this._mappedId = this.connect('notify::mapped', this._sync.bind(this));
|
||
|
|
||
|
// Cleanup
|
||
|
this.connect('destroy', this._onDestroy);
|
||
|
}
|
||
|
|
||
|
_onActionChanged(action_group, action_name) {
|
||
|
if (action_name === 'battery') {
|
||
|
if (action_group.has_action('battery')) {
|
||
|
let value = action_group.get_action_state('battery');
|
||
|
let [charging, icon_name, level, time] = value.deepUnpack();
|
||
|
|
||
|
this.battery = {
|
||
|
Charging: charging,
|
||
|
IconName: icon_name,
|
||
|
Level: level,
|
||
|
Time: time
|
||
|
};
|
||
|
} else {
|
||
|
this.battery = null;
|
||
|
}
|
||
|
|
||
|
this._sync();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_onStateChanged(action_group, action_name, value) {
|
||
|
if (action_name === 'battery') {
|
||
|
let [charging, icon_name, level, time] = value.deepUnpack();
|
||
|
|
||
|
this.battery = {
|
||
|
Charging: charging,
|
||
|
IconName: icon_name,
|
||
|
Level: level,
|
||
|
Time: time
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
get battery_label() {
|
||
|
if (!this.battery) return null;
|
||
|
|
||
|
let {Charging, Level, Time} = this.battery;
|
||
|
|
||
|
if (Level === 100) {
|
||
|
// TRANSLATORS: When the battery level is 100%
|
||
|
return _('Fully Charged');
|
||
|
} else if (Time === 0) {
|
||
|
// TRANSLATORS: When no time estimate for the battery is available
|
||
|
// EXAMPLE: 42% (Estimating…)
|
||
|
return _('%d%% (Estimating…)').format(Level);
|
||
|
}
|
||
|
|
||
|
Time = Time / 60;
|
||
|
let minutes = Math.floor(Time % 60);
|
||
|
let hours = Math.floor(Time / 60);
|
||
|
|
||
|
if (Charging) {
|
||
|
// TRANSLATORS: Estimated time until battery is charged
|
||
|
// EXAMPLE: 42% (1:15 Until Full)
|
||
|
return _('%d%% (%d\u2236%02d Until Full)').format(
|
||
|
Level,
|
||
|
hours,
|
||
|
minutes
|
||
|
);
|
||
|
} else {
|
||
|
// TRANSLATORS: Estimated time until battery is empty
|
||
|
// EXAMPLE: 42% (12:15 Remaining)
|
||
|
return _('%d%% (%d\u2236%02d Remaining)').format(
|
||
|
Level,
|
||
|
hours,
|
||
|
minutes
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_onDestroy(actor) {
|
||
|
actor.device.action_group.disconnect(actor._actionAddedId);
|
||
|
actor.device.action_group.disconnect(actor._actionRemovedId);
|
||
|
actor.device.action_group.disconnect(actor._actionStateChangedId);
|
||
|
actor.disconnect(actor._mappedId);
|
||
|
}
|
||
|
|
||
|
_sync() {
|
||
|
this.visible = (this.battery);
|
||
|
|
||
|
if (this.visible && this.mapped) {
|
||
|
this.icon.icon_name = this.battery.IconName;
|
||
|
this.label.text = (this.battery.Level > -1) ? `${this.battery.Level}%` : '';
|
||
|
this.tooltip.text = this.battery_label;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
|
||
|
/**
|
||
|
* A PopupMenu used as an information and control center for a device
|
||
|
*/
|
||
|
var Menu = class Menu extends PopupMenu.PopupMenuSection {
|
||
|
|
||
|
constructor(params) {
|
||
|
super();
|
||
|
Object.assign(this, params);
|
||
|
|
||
|
this.actor.add_style_class_name('gsconnect-device-menu');
|
||
|
|
||
|
// Title
|
||
|
this._title = new PopupMenu.PopupSeparatorMenuItem(this.device.name);
|
||
|
this.addMenuItem(this._title);
|
||
|
|
||
|
// Title -> Name
|
||
|
this._title.label.style_class = 'gsconnect-device-name';
|
||
|
this._title.label.clutter_text.ellipsize = 0;
|
||
|
this.device.bind_property(
|
||
|
'name',
|
||
|
this._title.label,
|
||
|
'text',
|
||
|
GObject.BindingFlags.SYNC_CREATE
|
||
|
);
|
||
|
|
||
|
// Title -> Battery
|
||
|
this._battery = new Battery({device: this.device});
|
||
|
this._title.actor.add_child(this._battery);
|
||
|
|
||
|
// Actions
|
||
|
let actions;
|
||
|
|
||
|
if (this.menu_type === 'icon') {
|
||
|
actions = new GMenu.IconBox({
|
||
|
action_group: this.device.action_group,
|
||
|
model: this.device.menu
|
||
|
});
|
||
|
} else if (this.menu_type === 'list') {
|
||
|
actions = new GMenu.ListBox({
|
||
|
action_group: this.device.action_group,
|
||
|
model: this.device.menu
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.addMenuItem(actions);
|
||
|
}
|
||
|
|
||
|
isEmpty() {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* An indicator representing a Device in the Status Area
|
||
|
*/
|
||
|
var Indicator = GObject.registerClass({
|
||
|
GTypeName: 'GSConnectDeviceIndicator'
|
||
|
}, class Indicator extends PanelMenu.Button {
|
||
|
|
||
|
_init(params) {
|
||
|
super._init(0.0, `${params.device.name} Indicator`, false);
|
||
|
Object.assign(this, params);
|
||
|
|
||
|
// Device Icon
|
||
|
this._icon = new St.Icon({
|
||
|
gicon: gsconnect.getIcon(this.device.icon_name),
|
||
|
style_class: 'system-status-icon gsconnect-device-indicator'
|
||
|
});
|
||
|
this.add_child(this._icon);
|
||
|
|
||
|
// Menu
|
||
|
let menu = new Menu({
|
||
|
device: this.device,
|
||
|
menu_type: 'icon'
|
||
|
});
|
||
|
this.menu.addMenuItem(menu);
|
||
|
}
|
||
|
|
||
|
update_icon(icon_name) {
|
||
|
this._icon.gicon = gsconnect.getIcon(icon_name);
|
||
|
}
|
||
|
});
|
||
|
|