diff --git a/.gitignore b/.gitignore
index 27736ea..d66fcab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@ dist/
node_modules/
yarn.lock
package-lock.json
-docs-lock.json
\ No newline at end of file
+docs-lock.json
+src/schemas/gschemas.compiled
+src/po/*~
\ No newline at end of file
diff --git a/Makefile b/Makefile
index c3f6316..7e03e73 100644
--- a/Makefile
+++ b/Makefile
@@ -1,26 +1,56 @@
#!/usr/bin/make -f
-.PHONY : build clean install uninstall
-.DEFAULT_GOAL := help
+.PHONY : build clean install uninstall open-prefs spawn-gnome-shell translations
+.DEFAULT_GOAL := build
UUID = runcat@kolesnikov.se
+DIST_ARCHIVE = $(UUID).shell-extension.zip
LOCAL = $(HOME)/.local/share/gnome-shell/extensions
-js_sources = $(shell find src -maxdepth 1 -type f -name '*.js')
+all_sources = $(shell find src -type f)
-build: clean
+js_sources = $(shell cd src && find . -maxdepth 1 -type f -name '*.js')
+
+translations_sources = src/panelMenuButton.js src/prefs.js
+translations_sources += $(shell find src/resources/ui -maxdepth 1 -type f -name '*.ui')
+translations = $(shell find src/po -maxdepth 1 -type f -name '*.po')
+
+
+build: dist/$(DIST_ARCHIVE)
+
+dist:
mkdir -p dist/
+
+dist/$(DIST_ARCHIVE): dist $(all_sources)
gnome-extensions pack -f src/ \
- $(addprefix --extra-source=../, $(js_sources)) \
+ $(addprefix --extra-source=, $(js_sources)) \
+ --extra-source=./dataProviders \
+ --extra-source=./resources \
--extra-source=../assets \
--extra-source=../LICENSE \
+ --podir=./po \
-o dist/
+
+src/po/%.po: src/po/messages.pot
+ msgmerge --update $@ src/po/messages.pot
+
+src/po/messages.pot: $(translations_sources)
+ touch src/po/messages.pot && \
+ xgettext \
+ --package-name gnome-runcat-extension \
+ --package-version 20 \
+ --from-code=UTF-8 \
+ --output=src/po/messages.pot \
+ $^
+
+translations: src/po/messages.pot $(translations)
+
clean:
rm -rf dist
install: uninstall build
- gnome-extensions install dist/$(UUID).shell-extension.zip --force
+ gnome-extensions install dist/$(DIST_ARCHIVE) --force
gnome-extensions enable $(UUID) || true
echo "You need to restart GNOME Shell to apply changes"
@@ -31,8 +61,8 @@ uninstall:
open-prefs:
gnome-extensions prefs $(UUID)
-spawn-gnome-shell: install
- env MUTTER_DEBUG_DUMMY_MODE_SPECS=1280x720 \
+spawn-gnome-shell:
+ env MUTTER_DEBUG_DUMMY_MODE_SPECS=1600x800 \
MUTTER_DEBUG_DUMMY_MONITOR_SCALES=1 \
dbus-run-session -- gnome-shell --nested --wayland
diff --git a/README.md b/README.md
index e98437e..46f6c5b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
# RunCat for GNOME Shell
@@ -15,7 +15,7 @@ This is the recommended method for installation, as it doesn't require the build
[](https://extensions.gnome.org/extension/2986/runcat/)
-### Manual installation
+### Manual installation
#### From source code
If you want to install the extension from sources, clone [the RunCat repository](https://github.com/win0err/gnome-runcat), navigate to the cloned directory and run:
@@ -24,23 +24,24 @@ $ make install
```
#### Release ZIP-archive
-If you want to install the extension from release zip-archive,
-download `runcat@kolesnikov.se.zip` from [the releases section](https://github.com/win0err/gnome-runcat/releases) and run:
+If you want to install the extension from release zip-archive,
+download `runcat@kolesnikov.se.shell-extension.zip` from [the releases section](https://github.com/win0err/gnome-runcat/releases) and run:
```bash
-$ gnome-extensions install path/to/runcat@kolesnikov.se.zip --force
+$ gnome-extensions install path/to/runcat@kolesnikov.se.shell-extension.zip --force
```
#### After installation:
-1. Restart the GNOME Shell:
+1. Restart the GNOME Shell:
- ALT+F2 to open the command prompt, and enter r to restart the GNOME Shell;
- or Log Out, then Log In, if GNOME Shell won't restart;
-2. Enable the extension:
+2. Enable the extension:
- Open GNOME Tweaks → Extensions → RunCat → On;
- or Run in terminal: `gnome-extensions enable runcat@kolesnikov.se`.
### Manage RunCat preferences
-- Open GNOME Tweaks → Extensions → RunCat → ⚙️;
+- Right-click on the extension button on the top bar → Settings;
+- or Open GNOME Tweaks → Extensions → RunCat → ⚙️;
- or Open [RunCat on GNOME Extensions portal](https://extensions.gnome.org/extension/2986/runcat/) → ⚙️;
- or Manage directly in `dconf`: `dconf list /org/gnome/shell/extensions/runcat/`.
diff --git a/package.json b/package.json
index 0b547f7..78f66f4 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "runcat",
"private": true,
"scripts": {
- "generate:types": "ts-for-gir generate Gtk-4.0 Adw-1 -e gjs",
+ "generate:types": "ts-for-gir generate Gtk-4.0 Adw-1 Clutter-1.0 -e gjs",
"lint": "eslint src/"
},
"repository": "win0err/gnome-runcat",
diff --git a/src/constants.js b/src/constants.js
new file mode 100644
index 0000000..92b8f0c
--- /dev/null
+++ b/src/constants.js
@@ -0,0 +1,21 @@
+/* eslint-disable no-var, no-unused-vars */
+
+var SCHEMA_PATH = 'org.gnome.shell.extensions.runcat';
+var LOG_ERROR_PREFIX = 'RuncatExtensionError';
+
+const CHARACTER_AND_PERCENTAGE = 0;
+const PERCENTAGE_ONLY = 1;
+const CHARACTER_ONLY = 2;
+
+var PanelMenuButtonVisibility = {
+ [CHARACTER_AND_PERCENTAGE]: { character: true, percentage: true },
+ [PERCENTAGE_ONLY]: { character: false, percentage: true },
+ [CHARACTER_ONLY]: { character: true, percentage: false },
+};
+
+var Settings = {
+ IDLE_THRESHOLD: 'idle-threshold',
+ DISPLAYING_ITEMS: 'displaying-items',
+};
+
+var SYSTEM_MONITOR_COMMAND = 'gnome-system-monitor -r';
\ No newline at end of file
diff --git a/src/cpu.js b/src/cpu.js
deleted file mode 100644
index c341bdf..0000000
--- a/src/cpu.js
+++ /dev/null
@@ -1,77 +0,0 @@
-const { Gio } = imports.gi;
-const ByteArray = imports.byteArray;
-const Config = imports.misc.config;
-
-const [major] = Config.PACKAGE_VERSION.split('.');
-const shellVersion = Number.parseInt(major, 10);
-
-// eslint-disable-next-line
-var Cpu = class Cpu {
- constructor() {
- this.lastActive = 0;
- this.lastTotal = 0;
-
- this.utilization = 0;
-
- this.procStatFile = Gio.File.new_for_path('/proc/stat');
-
- this.refresh();
- }
-
- refresh() {
- try {
- const [success, contents] = this.procStatFile.load_contents(null);
- if (!success) {
- throw new Error('Can\'t load contents of stat file');
- }
-
- const procTextData = shellVersion >= 41
- ? new TextDecoder('utf-8').decode(contents)
- : ByteArray.toString(contents);
-
- const cpuInfo = procTextData
- .split('\n')
- .shift()
- .trim()
- .split(/[\s]+/)
- .map(n => parseInt(n, 10));
-
- const [
- , // eslint-disable-line
- user,
- nice,
- system,
- idle,
- iowait,
- irq, // eslint-disable-line
- softirq,
- steal,
- guest, // eslint-disable-line
- ] = cpuInfo;
-
- const active = user + system + nice + softirq + steal;
- const total = user + system + nice + softirq + steal + idle + iowait;
-
- const utilization = 100 * ((active - this.lastActive) / (total - this.lastTotal));
-
- this.lastActive = active;
- this.lastTotal = total;
-
- if (Number.isNaN(utilization)) {
- const utilizationData = JSON.stringify({
- active,
- lastActive: this.lastActive,
- total,
- lastTotal: this.lastTotal,
- });
- throw new RangeError(`CPU utilization is NaN: ${utilizationData}`);
- }
-
- this.utilization = utilization;
- } catch (e) {
- logError(e, 'RuncatExtensionError'); // eslint-disable-line no-undef
- }
-
- return this.utilization;
- }
-};
\ No newline at end of file
diff --git a/src/dataProviders/cpu.js b/src/dataProviders/cpu.js
new file mode 100644
index 0000000..fc4f26b
--- /dev/null
+++ b/src/dataProviders/cpu.js
@@ -0,0 +1,59 @@
+const { Gio } = imports.gi;
+const Extension = imports.misc.extensionUtils.getCurrentExtension();
+
+const { readFile } = Extension.imports.utils;
+
+// eslint-disable-next-line func-names, no-unused-vars, no-var
+var createGenerator = async function* () {
+ const procStatFile = Gio.File.new_for_path('/proc/stat');
+
+ let prevActive = 0;
+ let prevTotal = 0;
+
+ while (true) {
+ // eslint-disable-next-line no-await-in-loop
+ const procStatContents = await readFile(procStatFile);
+
+ const cpuInfo = procStatContents
+ .split('\n')[0].trim()
+ .split(/[\s]+/)
+ .map(n => parseInt(n, 10));
+
+ const [
+ , // eslint-disable-line
+ user,
+ nice,
+ system,
+ idle,
+ iowait,
+ irq, // eslint-disable-line
+ softirq,
+ steal,
+ guest, // eslint-disable-line
+ ] = cpuInfo;
+
+ const active = user + system + nice + softirq + steal;
+ const total = user + system + nice + softirq + steal + idle + iowait;
+
+ // eslint-disable-next-line object-curly-newline
+ const data = JSON.stringify({ total, active, prevTotal, prevActive });
+
+ let utilization = 100 * ((active - prevActive) / (total - prevTotal));
+ if (Number.isNaN(utilization) || !Number.isFinite(utilization)) {
+ log(`cpu utilization is ${utilization}, data: ${data}`);
+
+ utilization = 0;
+ }
+
+ if (utilization > 100) {
+ log(`cpu utilization is ${utilization}, data: ${data}`);
+
+ utilization = 100;
+ }
+
+ prevActive = active;
+ prevTotal = total;
+
+ yield utilization;
+ }
+};
\ No newline at end of file
diff --git a/src/extension.js b/src/extension.js
index 5504117..20bbc53 100644
--- a/src/extension.js
+++ b/src/extension.js
@@ -1,7 +1,10 @@
'use strict';
+imports.gi.versions.Gtk = '4.0';
+
const Main = imports.ui.main;
-const Extension = imports.misc.extensionUtils.getCurrentExtension();
+const ExtensionUtils = imports.misc.extensionUtils;
+const Extension = ExtensionUtils.getCurrentExtension();
const { PanelMenuButton } = Extension.imports.panelMenuButton;
@@ -22,5 +25,7 @@ class RunCatExtension {
}
function init() {
+ ExtensionUtils.initTranslations(Extension.metadata.uuid);
+
return new RunCatExtension();
}
\ No newline at end of file
diff --git a/src/iconProvider.js b/src/iconProvider.js
deleted file mode 100644
index 7c4ad0b..0000000
--- a/src/iconProvider.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const { Gio } = imports.gi;
-const ExtensionUtils = imports.misc.extensionUtils;
-
-const Extension = ExtensionUtils.getCurrentExtension();
-
-const getGIcon = (name, pack = 'cat') => Gio.icon_new_for_string(
- `${Extension.path}/icons/${pack}/my-${name}-symbolic.svg`,
-);
-
-// eslint-disable-next-line
-var IconProvider = class IconProvider {
- constructor(spritesCount = 5) {
- this.spritesCount = spritesCount;
- this.currentSprite = 0;
-
- this._sleeping = getGIcon('sleeping');
-
- this.sprites = [...Array(spritesCount).keys()]
- .map(i => getGIcon(`running-${i}`));
- }
-
- get sleeping() {
- this.reset();
-
- return this._sleeping;
- }
-
- get nextSprite() {
- this.currentSprite++;
-
- if (this.currentSprite === this.spritesCount) {
- this.reset();
- }
-
- return this.sprites[this.currentSprite];
- }
-
- reset() {
- this.currentSprite = 0;
- }
-};
\ No newline at end of file
diff --git a/src/metadata.json b/src/metadata.json
index 7830aa7..b442f4a 100644
--- a/src/metadata.json
+++ b/src/metadata.json
@@ -2,13 +2,12 @@
"name": "RunCat",
"description": "The cat tells you the CPU usage by running speed",
"uuid": "runcat@kolesnikov.se",
+ "gettext-domain": "runcat@kolesnikov.se",
"shell-version": [
- "3.38",
- "40",
- "41",
"42",
"43"
],
+ "session-modes": ["user"],
"url": "https://github.com/win0err/gnome-runcat",
- "version": 19
+ "version": 20
}
diff --git a/src/panelMenuButton.js b/src/panelMenuButton.js
index 33373c3..086ff4d 100644
--- a/src/panelMenuButton.js
+++ b/src/panelMenuButton.js
@@ -1,12 +1,47 @@
const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+const { trySpawnCommandLine } = imports.misc.util;
+
const ExtensionUtils = imports.misc.extensionUtils;
const Extension = ExtensionUtils.getCurrentExtension();
-const { St, Clutter, GObject } = imports.gi;
+const {
+ Gio,
+ GObject,
+ Gtk,
+ GLib,
+} = imports.gi;
+
+const _ = imports.gettext.domain(Extension.metadata.uuid).gettext;
+
+const {
+ SYSTEM_MONITOR_COMMAND,
+ SCHEMA_PATH,
+ PanelMenuButtonVisibility,
+ Settings,
+} = Extension.imports.constants;
+const { createGenerator: createCpuGenerator } = Extension.imports.dataProviders.cpu;
+
+const getGIcon = name => Gio.icon_new_for_string(
+ `${Extension.path}/resources/icons/cat/my-${name}-symbolic.svg`,
+);
+
+// eslint-disable-next-line func-names
+const spritesGenerator = function* () {
+ const SPRITES_COUNT = 5;
+
+ const sprites = [...Array(SPRITES_COUNT).keys()]
+ .map(i => getGIcon(`active-${i}`));
+
+ let i;
+ while (true) {
+ for (i = 0; i < SPRITES_COUNT; i++) {
+ yield sprites[i];
+ }
+ }
+};
-const { Settings } = Extension.imports.settings;
-const { Timer } = Extension.imports.timer;
-const { Cpu } = Extension.imports.cpu;
-const { IconProvider } = Extension.imports.iconProvider;
+// y = 5000/sqrt(x+30) - 400
+const getAnimationInterval = cpuUtilization => Math.ceil(5000 / Math.sqrt(cpuUtilization + 30) - 400);
// eslint-disable-next-line
var PanelMenuButton = GObject.registerClass(
@@ -15,131 +50,132 @@ var PanelMenuButton = GObject.registerClass(
_init() {
super._init(null, Extension.metadata.name);
- this.cpu = new Cpu();
- this.iconProvider = new IconProvider();
+ this.dataProviders = {
+ cpu: createCpuGenerator(),
+ };
- this.ui = new Map();
- this.timers = new Map();
+ this.initSettingsListeners();
+ this.initUi();
+ this.initSources();
+ }
- this.currentSprite = 0;
+ initUi() {
+ this.ui = {
+ builder: Gtk.Builder.new(),
+ icons: {
+ idle: getGIcon('idle'),
+ runningGenerator: spritesGenerator(),
+ },
+ };
+ this.ui.builder.set_translation_domain(Extension.metadata.uuid);
+
+ const itemsVisibility = PanelMenuButtonVisibility[this.settings.displayingItems];
+
+ this.ui.builder.add_from_file(`${Extension.path}/resources/ui/extension.ui`);
+
+ const icon = this.ui.builder.get_object('icon');
+ icon.set_property('gicon', this.ui.icons.idle);
+ if (!itemsVisibility.character) {
+ icon.hide();
+ }
+
+ const labelBox = this.ui.builder.get_object('labelBox');
+ labelBox.add_child(this.ui.builder.get_object('label'));
+ if (!itemsVisibility.percentage) {
+ labelBox.hide();
+ }
+
+ const box = this.ui.builder.get_object('box');
+ box.add_child(icon);
+ box.add_child(labelBox);
- this._initSettings();
- this._initUi();
- this._initListeners();
- this._initTimers();
- }
+ this.add_child(box);
- get animationInterval() {
- const utilizationCoefficient = this.cpu.utilization > 100 ? 100 : this.cpu.utilization;
+ this.menu.addAction(
+ _('Open System Monitor'),
+ () => trySpawnCommandLine(SYSTEM_MONITOR_COMMAND),
+ );
+ this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this.menu.addAction(_('Settings'), () => ExtensionUtils.openPrefs());
+ }
- // y = 5000/sqrt(x+30) - 400
- return Math.ceil(5000 / Math.sqrt(utilizationCoefficient + 30) - 400);
+ destroyUi() {
+ this.ui.builder.get_object('icon').destroy();
+ this.ui.builder.get_object('label').destroy();
+ this.ui.builder.get_object('labelBox').destroy();
+ this.ui.builder.get_object('box').destroy();
}
- _initSettings() {
- this.settings = new Settings();
+ updateItemsVisibility() {
+ const itemsVisibility = PanelMenuButtonVisibility[this.settings.displayingItems];
+
+ const characterAction = itemsVisibility.character ? 'show' : 'hide';
+ const percentageAction = itemsVisibility.percentage ? 'show' : 'hide';
- this.sleepingThreshold = this.settings.sleepingThreshold.get();
- this.isRunnerHidden = this.settings.hideRunner.get();
- this.isPercentageHidden = this.settings.hidePercentage.get();
+ this.ui.builder.get_object('icon')[characterAction]();
+ this.ui.builder.get_object('labelBox')[percentageAction]();
}
- _initUi() {
- const box = new St.BoxLayout({
- style_class: 'panel-status-menu-box runcat-menu',
- });
+ initSettingsListeners() {
+ this.gioSettings = ExtensionUtils.getSettings(SCHEMA_PATH);
+ this.settings = {
+ idleThreshold: this.gioSettings.get_int(Settings.IDLE_THRESHOLD),
+ displayingItems: this.gioSettings.get_enum(Settings.DISPLAYING_ITEMS),
+ };
- const icon = new St.Icon({
- style_class: 'system-status-icon runcat-menu__icon',
- gicon: this.iconProvider.sleeping,
- });
- this.ui.set('icon', icon);
- this._manageUiElementVisibility('icon', this.isRunnerHidden);
-
- const labelBox = new St.BoxLayout({});
- this.ui.set('labelBox', labelBox);
-
- const label = new St.Label({
- style_class: 'runcat-menu__label',
- y_expand: true,
- y_align: Clutter.ActorAlign.CENTER,
- x_align: Clutter.ActorAlign.FILL,
- x_expand: true,
+ this.gioSettings.connect(`changed::${Settings.IDLE_THRESHOLD}`, () => {
+ this.settings.idleThreshold = this.gioSettings.get_int(Settings.IDLE_THRESHOLD);
});
- this.ui.set('label', label);
- labelBox.add_child(label);
- this._manageUiElementVisibility('labelBox', this.isPercentageHidden);
- box.add_child(icon);
- box.add_child(labelBox);
- this.ui.set('box', box);
+ this.gioSettings.connect(`changed::${Settings.DISPLAYING_ITEMS}`, () => {
+ this.settings.displayingItems = this.gioSettings.get_enum(Settings.DISPLAYING_ITEMS);
- this.add_child(box);
+ const itemsVisibility = PanelMenuButtonVisibility[this.settings.displayingItems];
+
+ const characterAction = itemsVisibility.character ? 'show' : 'hide';
+ const percentageAction = itemsVisibility.percentage ? 'show' : 'hide';
+
+ this.ui.builder.get_object('icon')[characterAction]();
+ this.ui.builder.get_object('labelBox')[percentageAction]();
+ });
}
- _manageUiElementVisibility(elementName, isHidden) {
- const action = isHidden ? 'hide' : 'show';
- this.ui.get(elementName)[action]();
+ async initSources() {
+ this.refreshDataSourceId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3000, () => this.refreshData());
+ await this.refreshData();
+
+ this.repaintUiSourceId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 0, () => this.repaintUi());
}
- _initListeners() {
- this.settings.hideRunner.addListener(() => {
- this.isRunnerHidden = this.settings.hideRunner.get();
- this._manageUiElementVisibility('icon', this.isRunnerHidden);
- });
+ destroySources() {
+ GLib.source_remove(this.refreshDataSourceId);
+ GLib.source_remove(this.repaintUiSourceId);
+ }
- this.settings.hidePercentage.addListener(() => {
- this.isPercentageHidden = this.settings.hidePercentage.get();
- this._manageUiElementVisibility('labelBox', this.isPercentageHidden);
- });
+ async refreshData() {
+ const { value: cpu } = await this.dataProviders.cpu.next();
+ this.data = { cpu };
- this.settings.sleepingThreshold.addListener(() => {
- this.sleepingThreshold = this.settings.sleepingThreshold.get();
- });
+ return GLib.SOURCE_CONTINUE;
}
- _initTimers() {
- this.timers.set('cpu', new Timer(() => {
- try {
- this.cpu.refresh();
- } catch (e) {
- logError(e, 'RuncatExtensionError'); // eslint-disable-line no-undef
- }
- }, 3000));
-
- this.timers.set(
- 'ui',
- new Timer(() => {
- try {
- if (this.timers.has('ui')) {
- this.timers.get('ui').interval = this.animationInterval;
- }
-
- if (!this.isRunnerHidden) {
- const isRunningSpriteShown = this.cpu.utilization > this.sleepingThreshold;
- this.ui.get('icon').set_gicon(
- isRunningSpriteShown ? this.iconProvider.nextSprite : this.iconProvider.sleeping,
- );
- }
-
- if (!this.isPercentageHidden) {
- const utilization = Math.ceil(this.cpu.utilization || 0);
- this.ui.get('label').set_text(`${utilization}%`);
- }
- } catch (e) {
- logError(e, 'RuncatExtensionError'); // eslint-disable-line no-undef
- }
- }, 250),
- );
+ repaintUi() {
+ const isRunningSpriteShown = this.data?.cpu > this.settings.idleThreshold;
+ const gicon = isRunningSpriteShown ? this.ui.icons.runningGenerator.next().value : this.ui.icons.idle;
+
+ this.ui.builder.get_object('icon').set_gicon(gicon);
+ this.ui.builder.get_object('label').set_text(`${Math.round(this.data.cpu)}%`);
+
+ const animationInterval = getAnimationInterval(this.data.cpu);
+
+ this.repaintUiSourceId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, animationInterval, () => this.repaintUi());
+
+ return GLib.SOURCE_REMOVE;
}
destroy() {
- this.timers.forEach(timer => timer.stop());
- this.ui.forEach(element => element.destroy());
-
- this.settings.hideRunner.removeAllListeners();
- this.settings.hidePercentage.removeAllListeners();
- this.settings.sleepingThreshold.removeAllListeners();
+ this.destroySources();
+ this.destroyUi();
super.destroy();
}
diff --git a/src/po/messages.pot b/src/po/messages.pot
new file mode 100644
index 0000000..521d214
--- /dev/null
+++ b/src/po/messages.pot
@@ -0,0 +1,90 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gnome-runcat-extension package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: gnome-runcat-extension 20\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-10-01 15:52+0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/panelMenuButton.js:95
+msgid "Open System Monitor"
+msgstr ""
+
+#: src/panelMenuButton.js:99
+msgid "Settings"
+msgstr ""
+
+#: src/prefs.js:48
+msgid "RunCat Settings"
+msgstr ""
+
+#: src/prefs.js:59
+msgid "Version"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:6
+msgid "General"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:10
+msgid "General Preferences"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:14
+msgid "Idle threshold"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:34
+msgid "Displaying items"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:38
+msgid "Character and percentage"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:39
+msgid "Percentage only"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:40
+msgid "Character only"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:51
+msgid "Reset preferences"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:52
+msgid "Reset RunCat preferences to defaults"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:65
+msgid "Reset"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:84
+msgid "The cat tells you the CPU usage by running speed"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:88
+msgid "Visit RunCat's GitHub page"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:98
+msgid "Visit Homepage"
+msgstr ""
+
+#: src/resources/ui/preferences.ui:102
+msgid "About RunCat"
+msgstr ""
diff --git a/src/po/ru.po b/src/po/ru.po
new file mode 100644
index 0000000..d872c6a
--- /dev/null
+++ b/src/po/ru.po
@@ -0,0 +1,93 @@
+# Russian translation for the GNOME RunCat Extension.
+# Copyright (C) 2022 Sergei Kolesnikov
+# This file is distributed under the same license as the GNOME RunCat package.
+# Sergei Kolesnikov , 2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gnome-runcat-extension 20\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-10-01 15:50+0300\n"
+"PO-Revision-Date: 2022-09-30 19:16+0300\n"
+"Last-Translator: Sergei Kolesnikov \n"
+"Language-Team: Russian\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+#: src/panelMenuButton.js:95
+msgid "Open System Monitor"
+msgstr "Открыть Системный монитор"
+
+#: src/panelMenuButton.js:99
+msgid "Settings"
+msgstr "Настройки"
+
+#: src/prefs.js:48
+msgid "RunCat Settings"
+msgstr "Настройки RunCat"
+
+#: src/prefs.js:59
+msgid "Version"
+msgstr "Версия"
+
+#: src/resources/ui/preferences.ui:6
+msgid "General"
+msgstr "Общие"
+
+#: src/resources/ui/preferences.ui:10
+msgid "General Preferences"
+msgstr "Общие настройки"
+
+#: src/resources/ui/preferences.ui:14
+msgid "Idle threshold"
+msgstr "Порог бездействия"
+
+#: src/resources/ui/preferences.ui:34
+msgid "Displaying items"
+msgstr "Отображаемые элементы"
+
+#: src/resources/ui/preferences.ui:38
+msgid "Character and percentage"
+msgstr "Персонаж и проценты"
+
+#: src/resources/ui/preferences.ui:39
+msgid "Percentage only"
+msgstr "Только проценты"
+
+#: src/resources/ui/preferences.ui:40
+msgid "Character only"
+msgstr "Только персонаж"
+
+#: src/resources/ui/preferences.ui:51
+msgid "Reset preferences"
+msgstr "Сбросить настройки"
+
+#: src/resources/ui/preferences.ui:52
+msgid "Reset RunCat preferences to defaults"
+msgstr "Сбросить настройки RunCat к значениям по умолчанию"
+
+#: src/resources/ui/preferences.ui:65
+msgid "Reset"
+msgstr "Сбрость"
+
+#: src/resources/ui/preferences.ui:84
+msgid "The cat tells you the CPU usage by running speed"
+msgstr ""
+"Котик, который показывает загрузку\n"
+" процессора скоростью бега"
+
+#: src/resources/ui/preferences.ui:88
+msgid "Visit RunCat's GitHub page"
+msgstr "Посетить страницу RunCat на GitHub"
+
+#: src/resources/ui/preferences.ui:98
+msgid "Visit Homepage"
+msgstr "Открыть домашнюю страницу"
+
+#: src/resources/ui/preferences.ui:102
+msgid "About RunCat"
+msgstr "О RunCat"
diff --git a/src/prefs.js b/src/prefs.js
index 431cf34..3ed90e2 100644
--- a/src/prefs.js
+++ b/src/prefs.js
@@ -1,220 +1,77 @@
-const { GObject, Gtk } = imports.gi;
-const Extension = imports.misc.extensionUtils.getCurrentExtension();
-
-const Config = imports.misc.config;
-const [major] = Config.PACKAGE_VERSION.split('.');
-const shellVersion = Number.parseInt(major, 10);
-
-const isGtk4 = shellVersion >= 40;
-
-const { Settings } = Extension.imports.settings;
-
-const BaseComponent = isGtk4 ? Gtk.ScrolledWindow : Gtk.Box;
-
-const RuncatSettingsWidget = GObject.registerClass(
- { GTypeName: 'RuncatSettingsWidget' },
- class RuncatSettingsWidget extends BaseComponent {
- _init() {
- if (isGtk4) {
- super._init({
- hscrollbar_policy: Gtk.PolicyType.NEVER,
- vexpand: true,
- });
- } else {
- super._init({
- orientation: Gtk.Orientation.VERTICAL,
- border_width: 20,
- spacing: 20,
- });
- }
-
- this._settings = new Settings();
- if (isGtk4) {
- this._box = new Gtk.Box({
- orientation: Gtk.Orientation.VERTICAL,
- halign: Gtk.Align.CENTER,
- spacing: 20,
- margin_top: 20,
- margin_bottom: 20,
- margin_start: 20,
- margin_end: 20,
- });
-
- this.set_child(this._box);
- }
-
- this._initSleepingThreshold();
- this._initShowComboBox();
- this._initBottomButtons();
-
- if (!isGtk4) {
- this.show_all();
- }
- }
-
- _initSleepingThreshold() {
- const hbox = new Gtk.Box({
- orientation: Gtk.Orientation.HORIZONTAL,
- spacing: 20,
- });
-
- const label = new Gtk.Label({
- label: 'Sleeping Threshold',
- use_markup: true,
- });
-
- const scaleConfig = {
- digits: 0,
- adjustment: new Gtk.Adjustment({
- lower: 0,
- upper: 100,
- }),
- hexpand: true,
- halign: Gtk.Align.END,
- };
-
- let scale;
- if (isGtk4) {
- scale = new Gtk.Scale({
- ...scaleConfig,
- draw_value: true,
- });
- } else {
- scale = new Gtk.HScale({
- ...scaleConfig,
- value_pos: Gtk.PositionType.RIGHT,
- });
- }
- scale.set_size_request(400, 15);
- scale.set_value(this._settings.sleepingThreshold.get());
-
- this._settings.sleepingThreshold.addListener(() => {
- const updatedValue = this._settings.sleepingThreshold.get();
- if (updatedValue !== scale.get_value()) {
- scale.set_value(updatedValue);
- }
- });
- scale.connect('value-changed', () => {
- const updatedValue = scale.get_value();
- if (updatedValue !== this._settings.sleepingThreshold.get()) {
- this._settings.sleepingThreshold.set(scale.get_value());
- }
- });
- this.connect('destroy', () => this._settings.sleepingThreshold.removeAllListeners());
-
- if (isGtk4) {
- hbox.append(label);
- hbox.append(scale);
-
- this._box.append(hbox);
- } else {
- hbox.add(label);
- hbox.add(scale);
-
- this.add(hbox);
- }
- }
-
- _initShowComboBox() {
- const hbox = new Gtk.Box({
- orientation: Gtk.Orientation.HORIZONTAL,
- });
-
- const label = new Gtk.Label({
- label: 'Show',
- use_markup: true,
- margin_end: 20,
- });
-
- const combo = new Gtk.ComboBoxText({
- halign: Gtk.Align.END,
- visible: true,
- });
-
- const options = ['Runner and percentage', 'Percentage only', 'Runner only'];
- options.forEach(opt => combo.append(opt, opt));
-
- combo.set_active(this._getActiveShowIndex());
-
- this._settings.hideRunner.addListener(() => {
- combo.set_active(this._getActiveShowIndex());
- });
- this._settings.hidePercentage.addListener(() => {
- combo.set_active(this._getActiveShowIndex());
- });
- combo.connect('changed', widget => {
- switch (widget.get_active()) {
- case 0: // show runner & percentage
- this._settings.hideRunner.set(false);
- this._settings.hidePercentage.set(false);
- break;
- case 1: // show percentage only
- this._settings.hideRunner.set(true);
- this._settings.hidePercentage.set(false);
- break;
- case 2: // show runner only
- this._settings.hideRunner.set(false);
- this._settings.hidePercentage.set(true);
- break;
- default:
- break;
- }
- });
-
- this.connect('destroy', () => {
- this._settings.hideRunner.removeAllListeners();
- this._settings.hidePercentage.removeAllListeners();
- });
-
- if (isGtk4) {
- hbox.append(label);
- hbox.append(combo);
-
- this._box.append(hbox);
- } else {
- hbox.add(label);
- hbox.pack_end(combo, false, false, 0);
-
- this.add(hbox);
- }
- }
-
- _getActiveShowIndex() {
- const hideRunner = this._settings.hideRunner.get();
- const hidePercentage = this._settings.hidePercentage.get();
-
- switch (true) {
- case !hideRunner && !hidePercentage:
- return 0;
- case hideRunner && !hidePercentage:
- return 1;
- case !hideRunner && hidePercentage:
- return 2;
- default:
- return 0; // default show both
- }
- }
-
- _initBottomButtons() {
- const resetButton = new Gtk.Button({ label: 'Reset to default' });
-
- resetButton.connect('clicked', () => {
- this._settings.sleepingThreshold.set(0);
- this._settings.hideRunner.set(false);
- this._settings.hidePercentage.set(false);
- });
-
- if (isGtk4) {
- this._box.append(resetButton);
- } else {
- this.pack_end(resetButton, false, false, 0);
- }
- }
- },
-);
-
-function buildPrefsWidget() {
- return new RuncatSettingsWidget();
+const {
+ Adw,
+ Gio,
+ Gdk,
+ Gtk,
+} = imports.gi;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Extension = ExtensionUtils.getCurrentExtension();
+
+const _ = imports.gettext.domain(Extension.metadata.uuid).gettext;
+
+const { SCHEMA_PATH, Settings } = Extension.imports.constants;
+const { findWidgetByType } = Extension.imports.utils;
+
+// eslint-disable-next-line no-unused-vars
+function fillPreferencesWindow(window) {
+ const settings = ExtensionUtils.getSettings(SCHEMA_PATH);
+
+ const builder = Gtk.Builder.new();
+ builder.set_translation_domain(Extension.metadata.uuid);
+ builder.add_from_file(`${Extension.path}/resources/ui/preferences.ui`);
+
+ settings.bind(
+ Settings.IDLE_THRESHOLD,
+ builder.get_object(Settings.IDLE_THRESHOLD),
+ 'value',
+ Gio.SettingsBindFlags.DEFAULT,
+ );
+
+ const combo = builder.get_object(Settings.DISPLAYING_ITEMS);
+ combo.set_selected(settings.get_enum(Settings.DISPLAYING_ITEMS));
+ combo.connect('notify::selected', widget => {
+ settings.set_enum(Settings.DISPLAYING_ITEMS, widget.selected);
+ });
+
+ builder.get_object('reset').connect('clicked', () => {
+ settings.reset(Settings.IDLE_THRESHOLD);
+
+ settings.reset(Settings.DISPLAYING_ITEMS);
+ combo.set_selected(settings.get_enum(Settings.DISPLAYING_ITEMS));
+ });
+
+ const page = builder.get_object('preferences-general');
+ window.add(page);
+
+ // eslint-disable-next-line no-param-reassign
+ window.title = _('RunCat Settings');
+
+ const homepageAction = Gio.SimpleAction.new('homepage', null);
+ homepageAction.connect('activate', () => Gtk.show_uri(window, Extension.metadata.url, Gdk.CURRENT_TIME));
+
+ const aboutAction = Gio.SimpleAction.new('about', null);
+ aboutAction.connect('activate', () => {
+ const logo = Gtk.Image.new_from_file(`${Extension.path}/resources/se.kolesnikov.runcat.svg`);
+
+ const aboutDialog = builder.get_object('about-dialog');
+ aboutDialog.set_property('logo', logo.get_paintable());
+ const versionText = _('Version');
+ aboutDialog.set_property('version', `${versionText} ${Extension.metadata.version}`);
+ aboutDialog.set_property('transient_for', window);
+
+ aboutDialog.show();
+ });
+
+ const group = Gio.SimpleActionGroup.new();
+ group.add_action(homepageAction);
+ group.add_action(aboutAction);
+
+ const menu = builder.get_object('menu-button');
+ menu.insert_action_group('prefs', group);
+
+ const header = findWidgetByType(window.get_content(), Adw.HeaderBar);
+ header.pack_end(menu);
}
function init() {
diff --git a/src/icons/cat/my-running-0-symbolic.svg b/src/resources/icons/cat/my-active-0-symbolic.svg
similarity index 100%
rename from src/icons/cat/my-running-0-symbolic.svg
rename to src/resources/icons/cat/my-active-0-symbolic.svg
diff --git a/src/icons/cat/my-running-1-symbolic.svg b/src/resources/icons/cat/my-active-1-symbolic.svg
similarity index 100%
rename from src/icons/cat/my-running-1-symbolic.svg
rename to src/resources/icons/cat/my-active-1-symbolic.svg
diff --git a/src/icons/cat/my-running-2-symbolic.svg b/src/resources/icons/cat/my-active-2-symbolic.svg
similarity index 100%
rename from src/icons/cat/my-running-2-symbolic.svg
rename to src/resources/icons/cat/my-active-2-symbolic.svg
diff --git a/src/icons/cat/my-running-3-symbolic.svg b/src/resources/icons/cat/my-active-3-symbolic.svg
similarity index 100%
rename from src/icons/cat/my-running-3-symbolic.svg
rename to src/resources/icons/cat/my-active-3-symbolic.svg
diff --git a/src/icons/cat/my-running-4-symbolic.svg b/src/resources/icons/cat/my-active-4-symbolic.svg
similarity index 100%
rename from src/icons/cat/my-running-4-symbolic.svg
rename to src/resources/icons/cat/my-active-4-symbolic.svg
diff --git a/src/icons/cat/my-sleeping-symbolic.svg b/src/resources/icons/cat/my-idle-symbolic.svg
similarity index 100%
rename from src/icons/cat/my-sleeping-symbolic.svg
rename to src/resources/icons/cat/my-idle-symbolic.svg
diff --git a/assets/se.kolesnikov.runcat.svg b/src/resources/se.kolesnikov.runcat.svg
similarity index 100%
rename from assets/se.kolesnikov.runcat.svg
rename to src/resources/se.kolesnikov.runcat.svg
diff --git a/src/resources/ui/extension.ui b/src/resources/ui/extension.ui
new file mode 100644
index 0000000..183a237
--- /dev/null
+++ b/src/resources/ui/extension.ui
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/resources/ui/preferences.ui b/src/resources/ui/preferences.ui
new file mode 100644
index 0000000..50bf420
--- /dev/null
+++ b/src/resources/ui/preferences.ui
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+ GNOME RunCat
+ Sergei Kolesnikov https://kolesnikov.se/
+ Sergei Kolesnikov, Takuto Nakamura
+ The cat tells you the CPU usage by running speed
+ © 2020-2022 Sergei Kolesnikov
+ gpl-3-0
+ https://github.com/win0err/gnome-runcat
+ Visit RunCat's GitHub page
+ False
+ True
+ True
+ True
+
+
+
+
+
+
+
+
+ 0
+ 100
+ 1
+
+
\ No newline at end of file
diff --git a/src/schemas/org.gnome.shell.extensions.runcat.gschema.xml b/src/schemas/org.gnome.shell.extensions.runcat.gschema.xml
index bddcd97..e15598f 100644
--- a/src/schemas/org.gnome.shell.extensions.runcat.gschema.xml
+++ b/src/schemas/org.gnome.shell.extensions.runcat.gschema.xml
@@ -1,27 +1,26 @@
+
+
+
+
+
+
-
+
0
- Sleeping Threshold
+ Idle threshold
-
- false
- Hide Runner
+
+ 'character-and-percentage'
+ Displaying items
-
-
- false
- Hide Percentage
-
-
-
diff --git a/src/settings.js b/src/settings.js
deleted file mode 100644
index 07ce4df..0000000
--- a/src/settings.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/* eslint-disable max-classes-per-file */
-const { Gio } = imports.gi;
-const GioSSS = Gio.SettingsSchemaSource;
-const ExtensionUtils = imports.misc.extensionUtils;
-const Extension = ExtensionUtils.getCurrentExtension();
-
-const SCHEMA_PATH = 'org.gnome.shell.extensions.runcat';
-
-const valueTypes = {
- INTEGER: 'int',
- BOOLEAN: 'boolean',
- STRING: 'string',
- DOUBLE: 'double',
-};
-
-// eslint-disable-next-line
-var Settings = class Settings {
- constructor(schemaPath = SCHEMA_PATH) {
- const schemaDir = Extension.dir.get_child('schemas');
-
- let schemaSource = GioSSS.get_default();
- if (schemaDir.query_exists(null)) {
- schemaSource = GioSSS.new_from_directory(
- schemaDir.get_path(),
- schemaSource,
- false,
- );
- }
-
- const schemaObj = schemaSource.lookup(schemaPath, true);
- if (!schemaObj) {
- throw new Error(`Schema ${schemaPath} could not be found for extension ${Extension.metadata.uuid}`);
- }
-
- this._gioSettings = new Gio.Settings({ settings_schema: schemaObj });
- }
-
- get sleepingThreshold() {
- if (!this._sleepingThreshold) {
- // eslint-disable-next-line no-use-before-define
- this._sleepingThreshold = new Value(
- this._gioSettings,
- 'sleeping-threshold',
- valueTypes.INTEGER,
- );
- }
-
- return this._sleepingThreshold;
- }
-
- get hideRunner() {
- if (!this._hideRunner) {
- // eslint-disable-next-line no-use-before-define
- this._hideRunner = new Value(
- this._gioSettings,
- 'hide-runner',
- valueTypes.BOOLEAN,
- );
- }
-
- return this._hideRunner;
- }
-
- get hidePercentage() {
- if (!this._hidePercentage) {
- // eslint-disable-next-line no-use-before-define
- this._hidePercentage = new Value(
- this._gioSettings,
- 'hide-percentage',
- valueTypes.BOOLEAN,
- );
- }
-
- return this._hidePercentage;
- }
-};
-
-class Value {
- constructor(gioSettings, key, type) {
- this._gioSettings = gioSettings;
- this._key = key;
- this._type = type;
- this._connectedCallbacks = [];
- }
-
- set(v) {
- return this._gioSettings[`set_${this._type}`](this._key, v);
- }
-
- get() {
- return this._gioSettings[`get_${this._type}`](this._key);
- }
-
- addListener(fn) {
- const id = this._gioSettings.connect(`changed::${this._key}`, fn);
- this._connectedCallbacks = [...this._connectedCallbacks, id];
- return id;
- }
-
- removeListener(id) {
- this._gioSettings.disconnect(id);
- this._connectedCallbacks = this._connectedCallbacks.filter(item => item !== id);
- }
-
- removeAllListeners() {
- this._connectedCallbacks.forEach(id => this._gioSettings.disconnect(id));
- this._connectedCallbacks = [];
- }
-}
\ No newline at end of file
diff --git a/src/timer.js b/src/timer.js
deleted file mode 100644
index 0e43ca4..0000000
--- a/src/timer.js
+++ /dev/null
@@ -1,64 +0,0 @@
-const Main = imports.ui.main;
-const Mainloop = imports.mainloop;
-
-const MAX_INTERVAL = 0xFFFFFFFF;
-
-// eslint-disable-next-line
-var Timer = class Timer {
- constructor(fn, interval = 1000, autostart = true) {
- this.callback = fn;
- this._interval = interval;
- this.isStarted = false;
- this.timeout = null;
-
- if (autostart) {
- this.start();
- }
- }
-
- get interval() {
- return this._interval;
- }
-
- set interval(newInterval) {
- if (newInterval < 0 || newInterval > MAX_INTERVAL || Number.isNaN(newInterval)) {
- throw new RangeError(`Interval ${newInterval} is out of range`);
- }
-
- this._interval = newInterval;
- }
-
- start() {
- this.isStarted = true;
- this._tick();
- }
-
- stop() {
- this._clearTimeout();
- this.isStarted = false;
- }
-
- _tick() {
- this._clearTimeout();
-
- const shouldTick = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
- if (shouldTick) {
- this.callback();
- }
-
- this._addTimeout();
- }
-
- _addTimeout() {
- if (this.isStarted) {
- this.timeout = Mainloop.timeout_add(this.interval, () => this._tick());
- }
- }
-
- _clearTimeout() {
- if (this.timeout) {
- Mainloop.source_remove(this.timeout);
- this.timeout = null;
- }
- }
-};
\ No newline at end of file
diff --git a/src/utils.js b/src/utils.js
new file mode 100644
index 0000000..1384877
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,31 @@
+// eslint-disable-next-line no-unused-vars, no-var
+var readFile = gioFile => new Promise((resolve, reject) => {
+ gioFile.load_contents_async(
+ null,
+ (f, result) => {
+ try {
+ const contents = f.load_contents_finish(result)[1];
+ const decodedContents = new TextDecoder('utf-8').decode(contents);
+
+ resolve(decodedContents);
+ } catch (e) {
+ reject(e);
+ }
+ },
+ );
+});
+
+// modified version of desktop cube's helper
+// https://github.com/Schneegans/Desktop-Cube/blob/main/prefs.js#L238
+// eslint-disable-next-line no-unused-vars, no-var
+var findWidgetByType = (parent, type) => {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const child of parent) {
+ if (child instanceof type) return child;
+
+ const match = findWidgetByType(child, type);
+ if (match) return match;
+ }
+
+ return null;
+};
\ No newline at end of file