diff --git a/README.md b/README.md index 15121d7..2f1000d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +Connection Manager for GNOME 46 +========================= + +Connection Manager was written by [Stefano Ciancio](https://github.com/sciancio) as a copy of [sshmenu](http://sshmenu.sourceforge.net/) from GNOME 2. Sadly, he lost interest in maintaining the extension after GNOME 3.32 on May 2019. Other maintainers have continued to provide updates to this extension over the years. + +This repository includes branches supporting GNOME pre-45, 45, 46. + What is Connection Manager ======================== @@ -59,5 +66,7 @@ You can find other info: * [License](https://github.com/sciancio/connectionmanager/wiki/License) - +Testing +======================== +$ dbus-run-session -- gnome-shell --nested --wayland diff --git a/connmgr.py b/connmgr.py index e1874cb..7b2f193 100644 --- a/connmgr.py +++ b/connmgr.py @@ -29,7 +29,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, Gio -from StringIO import StringIO +from io import StringIO import os.path import shutil @@ -702,7 +702,7 @@ def item_dialog(self, row): profileName = profile.get_string("visible-name") entry3.append_text(profileName) - if profileName.decode('utf-8') == row[3]: + if profileName == row[3]: entry3.set_active(index) else: diff --git a/extension.js b/extension.js index a9a85f3..77a31d9 100644 --- a/extension.js +++ b/extension.js @@ -1,5 +1,5 @@ -// ConnectionManager 3 - Simple GUI app for Gnome 3 that provides a menu -// for initiating SSH/Telnet/Custom Apps connections. +// ConnectionManager 3 - Simple GUI app for Gnome 3 that provides a menu +// for initiating SSH/Telnet/Custom Apps connections. // Copyright (C) 2011 Stefano Ciancio // // This library is free software; you can redistribute it and/or @@ -17,100 +17,80 @@ // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -const St = imports.gi.St; -const Gdk = imports.gi.Gdk; -const GLib = imports.gi.GLib; -const Gio = imports.gi.Gio; -const Lang = imports.lang; -const Shell = imports.gi.Shell; +import St from 'gi://St'; +import Gdk from 'gi://Gdk'; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; -const Mainloop = imports.mainloop; -const Signals = imports.signals; +import * as Signals from 'resource:///org/gnome/shell/misc/signals.js'; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const Panel = imports.ui.panel; -const Util = imports.misc.util; -const ByteArray = imports.byteArray; -const Me = imports.misc.extensionUtils.getCurrentExtension(); +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; +import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; +import * as Panel from 'resource:///org/gnome/shell/ui/panel.js'; +import * as Util from 'resource:///org/gnome/shell/misc/util.js'; -const Gettext = imports.gettext.domain('gnome-shell-extensions'); -const _ = Gettext.gettext; +let ByteArrayReplacement = new TextDecoder('utf-8'); +import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; let extensionPath; +let extensionObject, extensionSettings; +let CM = Extension.lookupByURL(import.meta.url); +let Me = Extension.lookupByURL(import.meta.url); // Import Command Terminal Manager and Search class -const CM = imports.misc.extensionUtils.getCurrentExtension(); -const Search = CM.imports.search; -const Terminals = CM.imports.terminals; +import * as Search from './search.js' +import * as Terminals from './terminals.js'; +class ConnectionManager extends PanelMenu.Button { + static { + GObject.registerClass(this); + } -const ConnectionManager = new Lang.Class({ - Name: 'ConnectionManager', - Extends: PanelMenu.Button, + constructor() { - _init: function() { + super(1, "Connection Manager", false); - this.parent(1.0, "Connection Manager", false); + this.CM = Extension.lookupByURL(import.meta.url); - this._box = new St.BoxLayout(); - -// this._icon = new St.Icon({ -// style_class: 'system-status-icon' -// }); -// this._icon.gicon = Gio.icon_new_for_string(Me.path + '/icons/' + DisabledIcon +'.svg'); - - + let box = new St.BoxLayout(); - this._icon = new St.Icon({ gicon: Gio.icon_new_for_string(Me.path + '/emblem-cm-symbolic.svg'), + let icon = new St.Icon({ gicon: Gio.icon_new_for_string(this.CM.path + '/emblem-cm-symbolic.svg'), icon_size: 15 }); - this._bin = new St.Bin({child: this._icon}); + let bin = new St.Bin({child: icon}); + + box.add_child(bin); - this._box.add(this._bin); - this.actor.add_actor(this._box); - this.actor.add_style_class_name('panel-status-button'); + this.add_child(box); + this.add_style_class_name('panel-status-button'); - let CMPrefs = CM.metadata; + let CMPrefs = this.CM.metadata; this._configFile = GLib.build_filenamev([GLib.get_home_dir(), CMPrefs['sw_config']]); this._prefFile = GLib.build_filenamev([extensionPath, CMPrefs['sw_bin']]) + " " + extensionPath; - // Search provider - this._searchProvider = null; - this._sshList = []; - this._searchProvider = new Search.SshSearchProvider('CONNECTION MANAGER'); - - if( typeof Main.overview.viewSelector === "object" && - typeof Main.overview.viewSelector._searchResults === "object") { - if(typeof Main.overview.viewSelector._searchResults._registerProvider === "function") { //3.14 - Main.overview.viewSelector._searchResults._registerProvider(this._searchProvider); - } else if(typeof Main.overview.viewSelector._searchResults._searchSystem === "object" && - typeof Main.overview.viewSelector._searchResults._searchSystem.addProvider === "function") { //3.12 - Main.overview.viewSelector._searchResults._searchSystem.addProvider(this._searchProvider); - } - } - this._readConf(); - }, + } - _readConf: function () { + _readConf() { this.menu.removeAll(); // Rewrite _setOpenedSubMenu method to correctly open submenu - this.menu._setOpenedSubMenu = Lang.bind(this, function (submenu) { + this.menu._setOpenedSubMenu = submenu => { this._openedSubMenu = submenu; - }); - + } + this._sshList = []; if (GLib.file_test(this._configFile, GLib.FileTest.EXISTS) ) { let filedata = GLib.file_get_contents(this._configFile); - let jsondata = JSON.parse(ByteArray.toString(filedata[1])); + let jsondata = JSON.parse(ByteArrayReplacement.decode(filedata[1])); let root = jsondata['Root']; // Global Settings @@ -129,7 +109,7 @@ const ConnectionManager = new Lang.Class({ this._readTree(root, this, ""); } else { - global.logError("CONNMGR: Error reading config file " + this._configFile); + console.error("CONNMGR: Error reading config file " + this._configFile); let filedata = null } @@ -137,45 +117,38 @@ const ConnectionManager = new Lang.Class({ this.menu.addMenuItem(menuSepPref, this.menu.length); let menuPref = new PopupMenu.PopupMenuItem("Connection Manager Settings"); - menuPref.connect('activate', Lang.bind(this, function() { - try { - Util.trySpawnCommandLine('python2 ' + this._prefFile); - } catch (e) { - Util.trySpawnCommandLine('python ' + this._prefFile); - } - })); + menuPref.connect('activate', () => { + Util.trySpawnCommandLine('python ' + this._prefFile); + }); this.menu.addMenuItem(menuPref, this.menu.length+1); - - // Update ssh name list - this._searchProvider._update(this._sshList); - }, + } - _readTree: function(node, parent, ident) { + _readTree(node, parent, ident) { let child, menuItem, menuSep, menuSub, icon, label, menuItemAll, iconAll, menuSepAll, menuItemTabs, iconTabs, ident_prec; - let childHasItem = false, commandAll = new Array(), commandTab = new Array(), + let childHasItem = false, commandAll = new Array(), commandTab = new Array(), sshparamsTab = new Array(), itemnr = 0; - // For each child ... + // For each child ... for (let i = 0; i < node.length; i++) { child = node[i][0]; let command; - + if (child.hasOwnProperty('Type')) { // Simple Item if (child.Type == '__item__') { menuItem = new PopupMenu.PopupBaseMenuItem(); - + icon = new St.Icon({icon_name: 'terminal', style_class: 'connmgr-icon' }); - menuItem.actor.add_child(icon); - + menuItem.add_child(icon); + label = new St.Label({ text: ident+child.Name }); - menuItem.actor.add_child(label); + menuItem.add_child(label); // For each command ... this.TermCmd.resetEnv(); @@ -185,15 +158,15 @@ const ConnectionManager = new Lang.Class({ let [commandT, sshparamsT] = this.TermCmd.createTabCmd(); menuItem.connect('activate', function() { - Util.spawnCommandLine(command); + Util.spawnCommandLine(command); }); parent.menu.addMenuItem(menuItem, i); childHasItem = true; if (this._menu_open_windows) { commandAll[itemnr] = command; } - if (this._menu_open_tabs) { - commandTab[itemnr] = commandT; - sshparamsTab[itemnr] = sshparamsT; + if (this._menu_open_tabs) { + commandTab[itemnr] = commandT; + sshparamsTab[itemnr] = sshparamsT; } itemnr++; @@ -214,10 +187,10 @@ const ConnectionManager = new Lang.Class({ menuItem = new PopupMenu.PopupBaseMenuItem(); icon = new St.Icon({icon_name: 'gtk-execute', style_class: 'connmgr-icon' }); - menuItem.actor.add_child(icon); + menuItem.add_child(icon); label = new St.Label({ text: ident+child.Name }); - menuItem.actor.add_child(label); + menuItem.add_child(label); // For each command ... this.TermCmd.resetEnv(); @@ -227,7 +200,7 @@ const ConnectionManager = new Lang.Class({ let [commandT, sshparamsT] = this.TermCmd.createTabCmd(); menuItem.connect('activate', function() { - Util.spawnCommandLine(command); + Util.spawnCommandLine(command); }); parent.menu.addMenuItem(menuItem, i); @@ -235,7 +208,7 @@ const ConnectionManager = new Lang.Class({ if (this._menu_open_windows) { commandAll[itemnr] = command; } if (this._menu_open_tabs) { commandTab[itemnr] = commandT; - sshparamsTab[itemnr] = sshparamsT; + sshparamsTab[itemnr] = sshparamsT; } itemnr++; @@ -255,13 +228,13 @@ const ConnectionManager = new Lang.Class({ if (child.Type == '__sep__') { parent.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(), i); } - + // Folder if (child.Type == '__folder__') { menuSub = new PopupMenu.PopupSubMenuMenuItem(ident+child.Name); parent.menu.addMenuItem(menuSub); - + ident_prec = ident; this._readTree(child.Children, menuSub, ident+" "); @@ -276,11 +249,11 @@ const ConnectionManager = new Lang.Class({ menuItemAll = new PopupMenu.PopupBaseMenuItem(); iconAll = new St.Icon({icon_name: 'fileopen', style_class: 'connmgr-icon' }); - menuItemAll.actor.add_child(iconAll); + menuItemAll.add_child(iconAll); label = new St.Label({ text: ident+"Open all windows" }); - menuItemAll.actor.add_child(label); - + menuItemAll.add_child(label); + parent.menu.addMenuItem(menuItemAll, position); position += 1; menuItemAll.connect('activate', function() { @@ -294,10 +267,10 @@ const ConnectionManager = new Lang.Class({ menuItemTabs = new PopupMenu.PopupBaseMenuItem(); iconTabs = new St.Icon({icon_name: 'fileopen', style_class: 'connmgr-icon' }); - menuItemTabs.actor.add_child(iconTabs); + menuItemTabs.add_child(iconTabs); label = new St.Label({ text: ident+"Open all as tabs" }); - menuItemTabs.actor.add_child(label); + menuItemTabs.add_child(label); parent.menu.addMenuItem(menuItemTabs, position); position += 1; @@ -321,39 +294,43 @@ const ConnectionManager = new Lang.Class({ } ident = ident_prec; - }, + } -}); +} let cm; -function enable() { - cm = new ConnectionManager(); - - let _children_length = Main.panel._rightBox.get_n_children(); - Main.panel.addToStatusArea("connectionmanager", cm, _children_length - 2, "right"); - - let file = Gio.file_new_for_path(cm._configFile); - cm.monitor = file.monitor(Gio.FileMonitorFlags.NONE, null); - cm.monitor.connect('changed', Lang.bind(cm, cm._readConf)); -} +export default class ConnectionManagerExtension extends Extension { -function disable() { - if(cm._searchProvider!=null) { - Main.overview.removeSearchProvider(cm._searchProvider); - cm._searchProvider = null; - } + enable() { - cm.monitor.cancel(); - cm.destroy(); -} + // extensionPath = extensionMeta.path; + extensionObject = Extension.lookupByUUID('connectionmanager2@ciancio.net'); + // extensionSettings = extensionObject.getSettings(); + extensionPath = extensionObject.path; + + let theme = St.IconTheme.new(); + + this.cm = new ConnectionManager(); -function init(extensionMeta) { - extensionPath = extensionMeta.path; - - let theme = imports.gi.Gtk.IconTheme.get_default(); - theme.append_search_path(extensionPath); + let _children_length = Main.panel._rightBox.get_n_children(); + Main.panel.addToStatusArea("connectionmanager", this.cm, _children_length - 2, "right"); + let file = Gio.file_new_for_path(this.cm._configFile); + this.cm.monitor = file.monitor(Gio.FileMonitorFlags.NONE, null); + this.cm.monitor.connect('changed', () => this.cm._readConf()); + + this._searchProvider = new Search.SearchProvider(this); + Main.overview.searchController.addProvider(this._searchProvider); + } + + disable() { + Main.overview.searchController.removeProvider(this._searchProvider); + this._searchProvider = null; + + this.cm.monitor.cancel(); + this.cm.destroy(); + } } diff --git a/metadata.json b/metadata.json index 08e9f02..051af76 100644 --- a/metadata.json +++ b/metadata.json @@ -4,17 +4,7 @@ "name": "Connection Manager", "original-authors": "Stefano Ciancio", "shell-version": [ - "3.12", - "3.14", - "3.16", - "3.18", - "3.20", - "3.22", - "3.24", - "3.26", - "3.28", - "3.30", - "3.32" + "47.1" ], "sw_bin": "connmgr.py", "sw_config": ".connmgr", diff --git a/search.js b/search.js index f564551..66ae7ae 100644 --- a/search.js +++ b/search.js @@ -1,5 +1,5 @@ -// ConnectionManager 3 - Simple GUI app for Gnome 3 that provides a menu -// for initiating SSH/Telnet/Custom Apps connections. +// ConnectionManager 3 - Simple GUI app for Gnome 3 that provides a menu +// for initiating SSH/Telnet/Custom Apps connections. // Copyright (C) 2011 Stefano Ciancio // // This library is free software; you can redistribute it and/or @@ -16,39 +16,154 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -const St = imports.gi.St; -const Gio = imports.gi.Gio; +import St from 'gi://St'; +import Gio from 'gi://Gio'; -const Shell = imports.gi.Shell; -const Util = imports.misc.util; -const Lang = imports.lang; -const Me = imports.misc.extensionUtils.getCurrentExtension(); +import Shell from 'gi://Shell'; +import * as Util from 'resource:///org/gnome/shell/misc/util.js'; -const Config = imports.misc.config; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +// const Lang = imports.lang; +import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; +const Me = Extension.lookupByURL(import.meta.url); + + +import * as Config from 'resource:///org/gnome/shell/misc/config.js'; // SSH / Apps Search Provider -var SshSearchProvider = new Lang.Class({ - Name: 'SshSearchProvider', +export class SearchProvider { + constructor(extension) { + this._extension = extension; + } + + // The application of the provider. Extensions will usually return `null`. + get appInfo() { + return null; + } + + // Whether the provider offers detailed results. + // Extensions will usually return `false`. + get canLaunchSearch() { + return false; + } + + // The unique ID of the provider. + get id() { + return this._extension.uuid; + } + + // This method is called when a search provider result is activated. + activateResult(result, terms) { + console.debug(`activateResult(${result}, [${terms}])`); + Util.spawnCommandLine(this.sshNames[result-1].command); + } + + getResultMetas (resultIds, callback) { + let app = null; + + const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); + + return new Promise((resolve, reject) => { + const cancelledId = cancellable.connect( + () => reject(Error('Operation Cancelled'))); - _init: function(title) { - this.id = title; + const metas = []; + + for (let i=0; i