123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
- //
- // SPDX-License-Identifier: GPL-2.0-or-later
- import Clutter from 'gi://Clutter';
- import GObject from 'gi://GObject';
- import St from 'gi://St';
- import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
- import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
- import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
- import {getIcon} from './utils.js';
- import * as GMenu from './gmenu.js';
- import Tooltip from './tooltip.js';
- /**
- * A battery widget with an icon, text percentage and time estimate tooltip
- */
- export const 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({
- parent: this,
- text: null,
- });
- // 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');
- // Cleanup on destroy
- this.connect('destroy', this._onDestroy);
- }
- _onActionChanged(action_group, action_name) {
- if (action_name !== 'battery')
- return;
- if (action_group.has_action('battery')) {
- const value = action_group.get_action_state('battery');
- const [charging, icon_name, level, time] = value.deepUnpack();
- this._state = {
- charging: charging,
- icon_name: icon_name,
- level: level,
- time: time,
- };
- } else {
- this._state = null;
- }
- this._sync();
- }
- _onStateChanged(action_group, action_name, value) {
- if (action_name !== 'battery')
- return;
- const [charging, icon_name, level, time] = value.deepUnpack();
- this._state = {
- charging: charging,
- icon_name: icon_name,
- level: level,
- time: time,
- };
- this._sync();
- }
- _getBatteryLabel() {
- if (!this._state)
- return null;
- const {charging, level, time} = this._state;
- if (level === 100)
- // TRANSLATORS: When the battery level is 100%
- return _('Fully Charged');
- if (time === 0)
- // TRANSLATORS: When no time estimate for the battery is available
- // EXAMPLE: 42% (Estimating…)
- return _('%d%% (Estimating…)').format(level);
- const total = time / 60;
- const minutes = Math.floor(total % 60);
- const hours = Math.floor(total / 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);
- }
- _sync() {
- this.visible = !!this._state;
- if (!this.visible)
- return;
- this.icon.icon_name = this._state.icon_name;
- this.label.text = (this._state.level > -1) ? `${this._state.level}%` : '';
- this.tooltip.text = this._getBatteryLabel();
- }
- });
- /**
- * A cell signal strength widget with two icons
- */
- export const SignalStrength = GObject.registerClass({
- GTypeName: 'GSConnectShellDeviceSignalStrength',
- }, class SignalStrength extends St.BoxLayout {
- _init(params) {
- super._init({
- reactive: true,
- style_class: 'gsconnect-device-signal-strength',
- track_hover: true,
- });
- Object.assign(this, params);
- // Network Type Icon
- this.networkTypeIcon = new St.Icon({
- fallback_icon_name: 'network-cellular-symbolic',
- icon_size: 16,
- });
- this.add_child(this.networkTypeIcon);
- // Signal Strength Icon
- this.signalStrengthIcon = new St.Icon({
- fallback_icon_name: 'network-cellular-offline-symbolic',
- icon_size: 16,
- });
- this.add_child(this.signalStrengthIcon);
- // Network Type Text
- this.tooltip = new Tooltip({
- parent: this,
- text: null,
- });
- // ConnectivityReport 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, 'connectivityReport');
- // Cleanup on destroy
- this.connect('destroy', this._onDestroy);
- }
- _onActionChanged(action_group, action_name) {
- if (action_name !== 'connectivityReport')
- return;
- if (action_group.has_action('connectivityReport')) {
- const value = action_group.get_action_state('connectivityReport');
- const [
- cellular_network_type,
- cellular_network_type_icon,
- cellular_network_strength,
- cellular_network_strength_icon,
- hotspot_name,
- hotspot_bssid,
- ] = value.deepUnpack();
- this._state = {
- cellular_network_type: cellular_network_type,
- cellular_network_type_icon: cellular_network_type_icon,
- cellular_network_strength: cellular_network_strength,
- cellular_network_strength_icon: cellular_network_strength_icon,
- hotspot_name: hotspot_name,
- hotspot_bssid: hotspot_bssid,
- };
- } else {
- this._state = null;
- }
- this._sync();
- }
- _onStateChanged(action_group, action_name, value) {
- if (action_name !== 'connectivityReport')
- return;
- const [
- cellular_network_type,
- cellular_network_type_icon,
- cellular_network_strength,
- cellular_network_strength_icon,
- hotspot_name,
- hotspot_bssid,
- ] = value.deepUnpack();
- this._state = {
- cellular_network_type: cellular_network_type,
- cellular_network_type_icon: cellular_network_type_icon,
- cellular_network_strength: cellular_network_strength,
- cellular_network_strength_icon: cellular_network_strength_icon,
- hotspot_name: hotspot_name,
- hotspot_bssid: hotspot_bssid,
- };
- this._sync();
- }
- _onDestroy(actor) {
- actor.device.action_group.disconnect(actor._actionAddedId);
- actor.device.action_group.disconnect(actor._actionRemovedId);
- actor.device.action_group.disconnect(actor._actionStateChangedId);
- }
- _sync() {
- this.visible = !!this._state;
- if (!this.visible)
- return;
- this.networkTypeIcon.icon_name = this._state.cellular_network_type_icon;
- this.signalStrengthIcon.icon_name = this._state.cellular_network_strength_icon;
- this.tooltip.text = this._state.cellular_network_type;
- }
- });
- /**
- * A PopupMenu used as an information and control center for a device
- */
- export 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 -> Cellular Signal Strength
- this._signalStrength = new SignalStrength({device: this.device});
- this._title.actor.add_child(this._signalStrength);
- // 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
- */
- export const 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: getIcon(this.device.icon_name),
- style_class: 'system-status-icon gsconnect-device-indicator',
- });
- this.add_child(this._icon);
- // Menu
- const menu = new Menu({
- device: this.device,
- menu_type: 'icon',
- });
- this.menu.addMenuItem(menu);
- }
- });
|