'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); } });