├── 10-service_status.rules ├── README.md ├── img └── services-systemd.png ├── org.freedesktop.policykit.pkexec.systemctl.policy └── services-systemd@abteil.org ├── extension.js ├── metadata.json ├── popupServiceItem.js ├── prefs.js ├── schemas ├── gschemas.compiled └── org.gnome.shell.extensions.services-systemd.gschema.xml └── stylesheet.css /10-service_status.rules: -------------------------------------------------------------------------------- 1 | polkit.addRule(function(action, subject) { 2 | if (action.id == "org.freedesktop.systemd1.manage-units" && subject.isInGroup("wheel") ) { 3 | var verb = action.lookup("verb"); 4 | if (verb == "start" || verb == "stop" || verb == "restart" || verb == "enable" || verb == "disable") { 5 | return polkit.Result.YES; 6 | } 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Services Systemd 2 | ## About 3 | Services Systemd is a [Gnome](https://www.gnome.org/) Shell Extension which 4 | allows to start and stop systemd services via a menu in the status area in the 5 | main menu panel. As there exists all a lot of irrelevant systemd services - in 6 | the sense of being displayed in this menu - the user can preselect which 7 | services should be shown in the preference dialog of the extension. 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/petres/gnome-shell-extension-services-systemd/master/img/services-systemd.png) 10 | 11 | ## Install 12 | 13 | ### Gnome Shell Extensions Page 14 | The easiest way to install this extension is via the official [Gnome Shell Extensions](https://extensions.gnome.org) resource page: https://extensions.gnome.org/extension/1034/services-systemd/ 15 | 16 | ### Arch Linux 17 | For Arch Linux the AUR package [gnome-shell-extension-services-systemd-git](https://aur4.archlinux.org/packages/gnome-shell-extension-services-systemd-git/) is provided. 18 | 19 | ## Authorization 20 | Done via a password prompt from the command `pkexec` of the polkit package. 21 | This command usually pops up a graphical password prompt. 22 | 23 | ### Without Password Prompt 24 | 25 | #### Using pkexec (default) 26 | In the recent versions of this extension the authorization is done by `pkexec` 27 | (before via `gksu`). Therefore if you would like to be able to start systemd 28 | services without getting prompted for a password, you will have to configure a 29 | polkit policy. The policy file [org.freedesktop.policykit.pkexec.systemctl.policy](org.freedesktop.policykit.pkexec.systemctl.policy) 30 | would allow the execution of `systemctl [start|stop]` without a password 31 | confirmation. Simple copy the file in your polkit policy folder (usually: 32 | `/usr/share/polkit-1/actions`). 33 | 34 | #### Using systemctl 35 | You can also choose to use `systemctl` natively and bypass a password prompt. 36 | 37 | To do this, add the policy file 38 | [10-service_status.rules](10-service_status.rules) to `/etc/polkit-1/rules.d`. 39 | 40 | Feel free to change the `wheel` group noted in the file to any other group that 41 | you see fit. 42 | 43 | ## Future 44 | **Planned additional functionality:** 45 | * Separators/Groups 46 | * Adjustable systemd folders 47 | * Other services 48 | 49 | ## Credits 50 | Some parts have been taken from the gnome extension [Services](https://github.com/hjr265/gnome-shell-extension-services). 51 | 52 | ## License 53 | [GPLv3](http://www.gnu.org/licenses/gpl-3.0.en.html) 54 | -------------------------------------------------------------------------------- /img/services-systemd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petres/gnome-shell-extension-services-systemd/86936eff0f6f87d1f8f3b0270472881234064aa2/img/services-systemd.png -------------------------------------------------------------------------------- /org.freedesktop.policykit.pkexec.systemctl.policy: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Start/Start/Restart Services Systemd 6 | http://www.freedesktop.org/wiki/Software/polkit/ 7 | 8 | Start systemd service 9 | Authentication is required to start a systemd service 10 | system-run-symbolic 11 | 12 | no 13 | no 14 | yes 15 | 16 | /usr/bin/systemctl 17 | start 18 | 19 | 20 | Stop systemd service 21 | Authentication is required to stop a systemd service 22 | system-run-symbolic 23 | 24 | no 25 | no 26 | yes 27 | 28 | /usr/bin/systemctl 29 | stop 30 | 31 | 32 | Restart systemd service 33 | Authentication is required to restart a systemd service 34 | system-run-symbolic 35 | 36 | no 37 | no 38 | yes 39 | 40 | /usr/bin/systemctl 41 | restart 42 | 43 | 44 | -------------------------------------------------------------------------------- /services-systemd@abteil.org/extension.js: -------------------------------------------------------------------------------- 1 | const GLib = imports.gi.GLib; 2 | const Lang = imports.lang; 3 | const Main = imports.ui.main; 4 | const PanelMenu = imports.ui.panelMenu; 5 | const PopupMenu = imports.ui.popupMenu; 6 | const St = imports.gi.St; 7 | const ExtensionUtils = imports.misc.extensionUtils; 8 | 9 | const Util = imports.misc.util; 10 | 11 | const Me = ExtensionUtils.getCurrentExtension(); 12 | //const ScrollablePopupMenu = Me.imports.scrollablePopupMenu.ScrollablePopupMenu; 13 | var PopupServiceItem = Me.imports.popupServiceItem.PopupServiceItem; 14 | 15 | const ServicesManager = new Lang.Class({ 16 | Name: 'ServicesManager', 17 | _entries: [], 18 | _containerType: -1, 19 | 20 | _init: function() { 21 | this._settings = ExtensionUtils.getSettings(); 22 | this._settings.connect('changed', Lang.bind(this, this._loadConfig)); 23 | 24 | this._createContainer(); 25 | this._loadConfig(); 26 | this._refresh(); 27 | }, 28 | _createContainer: function() { 29 | this._containerType = this._settings.get_enum('position'); 30 | 31 | if (this._containerType == 0) { 32 | this.container = new PanelMenu.Button(0.0); 33 | 34 | let hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); 35 | let icon = new St.Icon({icon_name: 'system-run-symbolic', style_class: 'system-status-icon'}); 36 | hbox.add_child(icon); 37 | 38 | this.container.add_actor(hbox); 39 | this.container.add_style_class_name('panel-status-button'); 40 | 41 | this.container.connect('button-press-event', Lang.bind(this, function() { 42 | this._refresh(); 43 | })); 44 | Main.panel.addToStatusArea('servicesManager', this.container); 45 | } else { 46 | this.container = new PopupMenu.PopupSubMenuMenuItem("Systemd Services", true); 47 | //this.container.icon.style_class = 'system-extensions-submenu-icon'; 48 | this.container.icon.icon_name = 'system-run-symbolic'; 49 | 50 | Main.panel.statusArea.aggregateMenu.menu.addMenuItem(this.container, 8); 51 | } 52 | 53 | this.container.connect('button-press-event', Lang.bind(this, function() { 54 | this._refresh(); 55 | })); 56 | }, 57 | _getCommand: function(service, action, type) { 58 | let command = `systemctl ${action} ${service} --${type}` 59 | if (type == "system" && action != 'is-active') { 60 | if (this._settings.get_enum("command-method") == 0) { 61 | command = `pkexec --user root ${command}` 62 | } else if (this._settings.get_enum("command-method") == 2) { 63 | command = `sudo ${command}` 64 | } 65 | } 66 | return `sh -c "${command}; exit;"` 67 | }, 68 | _refresh: function() { 69 | this.container.menu.removeAll(); 70 | 71 | let restartButton = this._settings.get_boolean('show-restart') 72 | 73 | this._entries.forEach(Lang.bind(this, function(service) { 74 | let active = false; 75 | let [_, out, err, stat] = GLib.spawn_command_line_sync( 76 | this._getCommand(service['service'], 'is-active', service["type"])); 77 | active = (stat == 0); 78 | 79 | let serviceItem = new PopupServiceItem(service['name'], active, {'restartButton': restartButton}); 80 | this.container.menu.addMenuItem(serviceItem); 81 | 82 | serviceItem.connect('toggled', Lang.bind(this, function() { 83 | GLib.spawn_command_line_async( 84 | this._getCommand(service['service'], (active ? 'stop' : 'start'), service["type"])); 85 | })); 86 | 87 | if (serviceItem.restartButton) 88 | serviceItem.restartButton.connect('clicked', Lang.bind(this, function() { 89 | GLib.spawn_command_line_async( 90 | this._getCommand(service['service'], 'restart', service["type"])); 91 | this.container.menu.close(); 92 | })); 93 | })); 94 | if(this._containerType == 0 && this._settings.get_boolean('show-add')) { 95 | if(this._entries.length > 0) 96 | this.container.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); 97 | 98 | let item = new PopupMenu.PopupMenuItem(_("Add systemd services ...")); 99 | item.connect('activate', Lang.bind(this, function() { 100 | Util.spawn(["gnome-shell-extension-prefs", "services-systemd@abteil.org"]); 101 | this.container.menu.close(); 102 | })); 103 | this.container.menu.addMenuItem(item); 104 | } 105 | return true; 106 | }, 107 | _loadConfig: function() { 108 | if (this._containerType != this._settings.get_enum('position')) { 109 | this.container.destroy(); 110 | this._createContainer(); 111 | } 112 | 113 | let entries = this._settings.get_strv("systemd"); 114 | this._entries = [] 115 | for (let i = 0; i < entries.length; i++) { 116 | let entry = JSON.parse(entries[i]); 117 | if (!("type" in entry)) 118 | entry["type"] = "system" 119 | this._entries.push(entry); 120 | } 121 | }, 122 | destroy: function() { 123 | this.container.destroy(); 124 | } 125 | }); 126 | 127 | let serviceManager; 128 | 129 | function enable() { 130 | serviceManager = new ServicesManager(); 131 | } 132 | 133 | function disable() { 134 | serviceManager.destroy(); 135 | } 136 | -------------------------------------------------------------------------------- /services-systemd@abteil.org/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "services-systemd@abteil.org", 3 | "name": "Services Systemd", 4 | "url": "https://github.com/petres/gnome-shell-extension-services-systemd", 5 | "description": "Toggle systemd services on/off from a popup menu in the top gnome panel. Can be used to start services like apache2, mysql, postgres. It uses `pkexec' to run `sytemctl'. If you want to start services without entering a password you have to polkit policy file. An example policy file can be found in the github repository.", 6 | "settings-schema": "org.gnome.shell.extensions.services-systemd", 7 | "shell-version": [ 8 | "3.34" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /services-systemd@abteil.org/popupServiceItem.js: -------------------------------------------------------------------------------- 1 | const Lang = imports.lang; 2 | const PopupMenu = imports.ui.popupMenu; 3 | const Util = imports.misc.util; 4 | const { GObject, Gtk, St, Clutter} = imports.gi; 5 | 6 | const ExtensionSystem = imports.ui.extensionSystem; 7 | const ExtensionUtils = imports.misc.extensionUtils; 8 | 9 | var PopupServiceItem = GObject.registerClass( 10 | class PopupServiceItem extends PopupMenu.PopupSwitchMenuItem { 11 | _init(text, active, params) { 12 | super._init(text, active); 13 | 14 | if (params.restartButton) { 15 | this.restartButton = new St.Button({ 16 | x_align: St.Align.END, 17 | x_expand: false, 18 | reactive: true, 19 | can_focus: true, 20 | track_hover: true, 21 | accessible_name: 'restart', 22 | style_class: 'system-menu-action services-systemd-button-reload' 23 | }); 24 | 25 | this.restartButton.child = new St.Icon({ icon_name: 'view-refresh-symbolic' }); 26 | this.add_child(this.restartButton); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /services-systemd@abteil.org/prefs.js: -------------------------------------------------------------------------------- 1 | const GLib = imports.gi.GLib; 2 | const Gio = imports.gi.Gio; 3 | const Gtk = imports.gi.Gtk; 4 | const GObject = imports.gi.GObject; 5 | const Lang = imports.lang; 6 | const ByteArray = imports.byteArray.ByteArray; 7 | 8 | const ExtensionUtils = imports.misc.extensionUtils; 9 | const Me = ExtensionUtils.getCurrentExtension(); 10 | 11 | 12 | const ServicesSystemdSettings = new GObject.Class({ 13 | Name: 'Services-Systemd-Settings', 14 | Extends: Gtk.Notebook, 15 | 16 | _init : function(params) { 17 | /*** Open Settings ***********************************************************************/ 18 | this._settings = ExtensionUtils.getSettings(); 19 | this._settings.connect('changed', Lang.bind(this, this._refresh)); 20 | 21 | this._changedPermitted = false; 22 | /*****************************************************************************************/ 23 | 24 | 25 | 26 | /*** GUI: General ************************************************************************/ 27 | this.parent(params); 28 | this.set_tab_pos(Gtk.PositionType.TOP); 29 | 30 | let servicesPage = new Gtk.Grid() 31 | servicesPage.set_orientation(Gtk.Orientation.VERTICAL); 32 | servicesPage.margin = 20; 33 | 34 | let otherPage = new Gtk.Grid() 35 | otherPage.set_orientation(Gtk.Orientation.VERTICAL); 36 | otherPage.margin = 20; 37 | otherPage.set_row_spacing(10); 38 | 39 | //this.insert_page(servicesPage, new Gtk.Label("test"), 0) 40 | this.append_page(servicesPage, new Gtk.Label({ label: "Services" })) 41 | this.append_page(otherPage, new Gtk.Label({ label: "Other" })) 42 | /*****************************************************************************************/ 43 | 44 | 45 | 46 | /*** GUI: Other Settings *****************************************************************/ 47 | let showAddLabel = new Gtk.Label({ 48 | label: "Show add services: ", 49 | xalign: 0, 50 | hexpand: true 51 | }); 52 | 53 | this._showAddCheckbox = new Gtk.Switch(); 54 | this._showAddCheckbox.set_halign(Gtk.Align.CENTER); 55 | this._showAddCheckbox.set_valign(Gtk.Align.CENTER); 56 | this._showAddCheckbox.connect('notify::active', Lang.bind(this, function(button) { 57 | this._changedPermitted = false; 58 | this._settings.set_boolean('show-add', button.active); 59 | this._changedPermitted = true; 60 | })); 61 | 62 | otherPage.attach(showAddLabel, 1, 1, 1, 1); 63 | otherPage.attach_next_to(this._showAddCheckbox, showAddLabel, 1, 1, 1); 64 | 65 | 66 | 67 | let showRestartLabel = new Gtk.Label({ 68 | label: "Show restart button: ", 69 | xalign: 0, 70 | hexpand: true 71 | }); 72 | 73 | this._showRestartCheckbox = new Gtk.Switch(); 74 | this._showRestartCheckbox.set_halign(Gtk.Align.CENTER); 75 | this._showRestartCheckbox.set_valign(Gtk.Align.CENTER); 76 | this._showRestartCheckbox.connect('notify::active', Lang.bind(this, function(button) { 77 | this._changedPermitted = false; 78 | this._settings.set_boolean('show-restart', button.active); 79 | this._changedPermitted = true; 80 | })); 81 | 82 | otherPage.attach(showRestartLabel, 1, 2, 1, 1); 83 | otherPage.attach_next_to(this._showRestartCheckbox, showRestartLabel, 1, 1, 1); 84 | 85 | 86 | let positionLabel = new Gtk.Label({ 87 | label: "Position: ", 88 | xalign: 0, 89 | hexpand: true 90 | }); 91 | 92 | let model = new Gtk.ListStore(); 93 | model.set_column_types([GObject.TYPE_INT, GObject.TYPE_STRING]); 94 | 95 | this._positionCombo = new Gtk.ComboBox({model: model}); 96 | this._positionCombo.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED); 97 | 98 | let renderer = new Gtk.CellRendererText(); 99 | this._positionCombo.pack_start(renderer, true); 100 | this._positionCombo.add_attribute(renderer, 'text', 1); 101 | 102 | let positionsItems = [ 103 | { id: 0, name: "Panel"}, 104 | { id: 1, name: "Menu"} 105 | ] 106 | for (let i = 0; i < positionsItems.length; i++) { 107 | let item = positionsItems[i]; 108 | let iter = model.append(); 109 | model.set(iter, [0, 1], [item.id, item.name]); 110 | } 111 | 112 | this._positionCombo.connect('changed', Lang.bind(this, function(entry) { 113 | let [success, iter] = this._positionCombo.get_active_iter() 114 | if (success) { 115 | this._changedPermitted = false; 116 | this._settings.set_enum('position', this._positionCombo.get_model().get_value(iter, 0)); 117 | this._changedPermitted = true; 118 | } 119 | })); 120 | 121 | otherPage.attach(positionLabel, 1, 3, 1, 1); 122 | otherPage.attach_next_to(this._positionCombo, positionLabel, 1, 1, 1); 123 | 124 | 125 | let commandMethodLabel = new Gtk.Label({ 126 | label: "Command Method: ", 127 | xalign: 0, 128 | hexpand: true 129 | }); 130 | 131 | let commandMethodModel = new Gtk.ListStore(); 132 | commandMethodModel.set_column_types([GObject.TYPE_INT, GObject.TYPE_STRING]); 133 | 134 | this._commandMethodCombo = new Gtk.ComboBox({model: commandMethodModel}); 135 | this._commandMethodCombo.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED); 136 | 137 | let commandMethodRenderer = new Gtk.CellRendererText(); 138 | this._commandMethodCombo.pack_start(commandMethodRenderer, true); 139 | this._commandMethodCombo.add_attribute(commandMethodRenderer, 'text', 1); 140 | 141 | let commandMethodsItems = [ 142 | { id: 0, name: "pkexec" }, 143 | { id: 1, name: "systemctl"}, 144 | { id: 2, name: "sudo"} 145 | ] 146 | 147 | for (let i = 0; i < commandMethodsItems.length; i++) { 148 | let item = commandMethodsItems[i]; 149 | let iter = commandMethodModel.append(); 150 | commandMethodModel.set(iter, [0, 1], [item.id, item.name]); 151 | } 152 | 153 | this._commandMethodCombo.connect('changed', Lang.bind(this, function(entry) { 154 | let [success, iter] = this._commandMethodCombo.get_active_iter() 155 | if (success) { 156 | this._changedPermitted = false; 157 | this._settings.set_enum('command-method', this._commandMethodCombo.get_model().get_value(iter, 0)); 158 | this._changedPermitted = true; 159 | } 160 | })); 161 | 162 | otherPage.attach(commandMethodLabel, 1, 4, 1, 1); 163 | otherPage.attach_next_to(this._commandMethodCombo, commandMethodLabel, 1, 1, 1); 164 | /*****************************************************************************************/ 165 | 166 | 167 | 168 | /*** GUI: Services Settings **************************************************************/ 169 | // Label 170 | let treeViewLabel = new Gtk.Label({ label: '' + "Listed systemd Services:" + '', 171 | use_markup: true, 172 | halign: Gtk.Align.START }) 173 | servicesPage.add(treeViewLabel); 174 | 175 | 176 | // TreeView 177 | this._store = new Gtk.ListStore(); 178 | this._store.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING]); 179 | 180 | this._treeView = new Gtk.TreeView({ model: this._store, 181 | hexpand: true, vexpand: true }); 182 | 183 | let selection = this._treeView.get_selection(); 184 | selection.set_mode(Gtk.SelectionMode.SINGLE); 185 | selection.connect ('changed', Lang.bind (this, this._onSelectionChanged)); 186 | 187 | 188 | let labelColumn = new Gtk.TreeViewColumn({ expand: true, 189 | title: "Label" }); 190 | 191 | let labelRenderer = new Gtk.CellRendererText; 192 | labelColumn.pack_start(labelRenderer, true); 193 | labelColumn.add_attribute(labelRenderer, "text", 0); 194 | this._treeView.append_column(labelColumn); 195 | 196 | let serviceColumn = new Gtk.TreeViewColumn({ expand: true, 197 | title: "Service" }); 198 | 199 | let serviceRenderer = new Gtk.CellRendererText; 200 | serviceColumn.pack_start(serviceRenderer, true); 201 | serviceColumn.add_attribute(serviceRenderer, "text", 1); 202 | this._treeView.append_column(serviceColumn); 203 | 204 | let typeColumn = new Gtk.TreeViewColumn({ expand: true, 205 | title: "Type" }); 206 | 207 | let typeRenderer = new Gtk.CellRendererText; 208 | typeColumn.pack_start(typeRenderer, true); 209 | typeColumn.add_attribute(typeRenderer, "text", 2); 210 | this._treeView.append_column(typeColumn); 211 | 212 | servicesPage.add(this._treeView); 213 | 214 | // Delete Toolbar 215 | let toolbar = new Gtk.Toolbar(); 216 | toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); 217 | toolbar.halign = 2; 218 | servicesPage.add(toolbar); 219 | 220 | let upButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_GO_UP }); 221 | upButton.connect('clicked', Lang.bind(this, this._up)); 222 | toolbar.add(upButton); 223 | 224 | let downButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_GO_DOWN }); 225 | downButton.connect('clicked', Lang.bind(this, this._down)); 226 | toolbar.add(downButton); 227 | 228 | let delButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_DELETE }); 229 | delButton.connect('clicked', Lang.bind(this, this._delete)); 230 | toolbar.add(delButton); 231 | 232 | this._selDepButtons = [upButton, downButton, delButton] 233 | 234 | // Add Grid 235 | let grid = new Gtk.Grid(); 236 | 237 | //// Label 238 | let labelName = new Gtk.Label({label: "Label: "}); 239 | labelName.halign = 2; 240 | 241 | this._displayName = new Gtk.Entry({ hexpand: true, 242 | margin_top: 5 }); 243 | this._displayName.set_placeholder_text("Name in menu"); 244 | 245 | let labelService = new Gtk.Label({label: "Service: "}); 246 | labelService.halign = 2; 247 | 248 | let sListStore = new Gtk.ListStore(); 249 | sListStore.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING]); 250 | 251 | let types = ['system', 'user'] 252 | 253 | this._availableSystemdServices = { 254 | 'all': [] 255 | } 256 | 257 | for (let t in types) { 258 | let type = types[t] 259 | this._availableSystemdServices[type] = this._getSystemdServicesList(type) 260 | this._availableSystemdServices['all'] = this._availableSystemdServices['all'].concat(this._availableSystemdServices[type]) 261 | for (let i in this._availableSystemdServices[type]) { 262 | let name = this._availableSystemdServices[type][i] + " " + type 263 | log(name) 264 | sListStore.set(sListStore.append(), [0, 1, 2], [name, this._availableSystemdServices[type][i], type]); 265 | } 266 | } 267 | 268 | this._systemName = new Gtk.Entry() 269 | this._systemName.set_placeholder_text("Systemd service name and type"); 270 | let completion = new Gtk.EntryCompletion() 271 | this._systemName.set_completion(completion) 272 | completion.set_model(sListStore) 273 | 274 | completion.set_text_column(0) 275 | 276 | grid.attach(labelName, 1, 1, 1, 1); 277 | grid.attach_next_to(this._displayName, labelName, 1, 1, 1); 278 | 279 | grid.attach(labelService, 1, 2, 1, 1); 280 | grid.attach_next_to(this._systemName, labelService, 1, 1, 1); 281 | 282 | servicesPage.add(grid); 283 | 284 | let addToolbar = new Gtk.Toolbar(); 285 | addToolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); 286 | addToolbar.halign = 2; 287 | servicesPage.add(addToolbar); 288 | 289 | let addButton = new Gtk.ToolButton({ 290 | stock_id: Gtk.STOCK_ADD, 291 | label: "Add", 292 | is_important: true 293 | }); 294 | 295 | addButton.connect('clicked', Lang.bind(this, this._add)); 296 | addToolbar.add(addButton); 297 | /*****************************************************************************************/ 298 | 299 | 300 | 301 | this._changedPermitted = true; 302 | this._refresh(); 303 | this._onSelectionChanged(); 304 | }, 305 | _getSystemdServicesList: function(type) { 306 | let [_u1, out_u1, err_u1, stat_u1] = GLib.spawn_command_line_sync('sh -c "systemctl --' + type + ' list-unit-files --type=service,timer --no-legend | awk \'{print $1}\'"'); 307 | let allFiltered = out_u1.toString().split("\n"); 308 | //let allFiltered = ByteArray.toString(out_u1).split("\n"); 309 | let [_u2, out_u2, err_u2, stat_u2] = GLib.spawn_command_line_sync('sh -c "systemctl --' + type + ' list-units --type=service,timer --no-legend | awk \'{print $1}\'"'); 310 | allFiltered = allFiltered.concat(out_u2.toString().split("\n")); 311 | //allFiltered = allFiltered.concat(ByteArray.toString(out_u2).split("\n")); 312 | return allFiltered.sort( 313 | function (a, b) { 314 | return a.toLowerCase().localeCompare(b.toLowerCase()); 315 | }) 316 | }, 317 | _getTypeOfService: function(service) { 318 | let type = "undefined" 319 | if (this._availableSystemdServices['system'].indexOf(service) != -1) 320 | type = "system" 321 | else if (this._availableSystemdServices['user'].indexOf(service) != -1) 322 | type = "user" 323 | return type 324 | }, 325 | _getIdFromIter: function(iter) { 326 | let displayName = this._store.get_value(iter, 0); 327 | let serviceName = this._store.get_value(iter, 1); 328 | let type = this._store.get_value(iter, 2); 329 | return JSON.stringify({"name": displayName, "service": serviceName, "type": type}); 330 | }, 331 | _isValidTemplateInstance: function(serviceName, type) { 332 | // is this a possible template instance 333 | let index = serviceName.indexOf("@"); 334 | let result = index != -1; 335 | 336 | if (result) { 337 | let templateName = serviceName.substr(0,index+1) + ".service"; 338 | // type is valid and template exists 339 | result = result && (type == "system" || type == "user") && (this._availableSystemdServices[type].indexOf(templateName) != -1); 340 | } 341 | return result; 342 | }, 343 | _add: function() { 344 | let displayName = this._displayName.text.trim() 345 | let serviceEntry = this._systemName.text.trim() 346 | if (displayName.length > 0 && serviceEntry.length > 0) { 347 | let serviceArray = serviceEntry.split(" ") 348 | let serviceName = "" 349 | let type = "" 350 | if (serviceArray.length > 1) { 351 | serviceName = serviceArray[0] 352 | type = serviceArray[1] 353 | } else { 354 | serviceName = serviceArray[0] 355 | type = this._getTypeOfService(serviceName) 356 | } 357 | 358 | if (!this._isValidTemplateInstance(serviceName, type) && ( !(type == "system" || type == "user") || this._availableSystemdServices[type].indexOf(serviceName) == -1)) { 359 | this._messageDialog = new Gtk.MessageDialog ({ 360 | title: "Warning", 361 | modal: true, 362 | buttons: Gtk.ButtonsType.OK, 363 | message_type: Gtk.MessageType.WARNING, 364 | text: "Service does not exist." 365 | }); 366 | this._messageDialog.connect('response', Lang.bind(this, function() { 367 | this._messageDialog.close(); 368 | })); 369 | this._messageDialog.show(); 370 | } else { 371 | let id = JSON.stringify({"name": displayName, "service": serviceName, "type": type}) 372 | let currentItems = this._settings.get_strv("systemd"); 373 | let index = currentItems.indexOf(id); 374 | if (index < 0) { 375 | this._changedPermitted = false; 376 | currentItems.push(id); 377 | this._settings.set_strv("systemd", currentItems); 378 | this._store.set(this._store.append(), [0, 1, 2], [displayName, serviceName, type]); 379 | this._changedPermitted = true; 380 | } 381 | this._displayName.text = "" 382 | this._systemName.text = "" 383 | } 384 | 385 | } else { 386 | this._messageDialog = new Gtk.MessageDialog ({ 387 | //parent: this.get_toplevel(), 388 | title: "Warning", 389 | modal: true, 390 | buttons: Gtk.ButtonsType.OK, 391 | message_type: Gtk.MessageType.WARNING, 392 | text: "No label and/or service specified." 393 | }); 394 | 395 | this._messageDialog.connect ('response', Lang.bind(this, function() { 396 | this._messageDialog.close(); 397 | })); 398 | this._messageDialog.show(); 399 | } 400 | }, 401 | _up: function() { 402 | let [any, model, iter] = this._treeView.get_selection().get_selected(); 403 | 404 | if (any) { 405 | let index = this._settings.get_strv("systemd").indexOf(this._getIdFromIter(iter)); 406 | this._move(index, index - 1) 407 | } 408 | }, 409 | _down: function() { 410 | let [any, model, iter] = this._treeView.get_selection().get_selected(); 411 | 412 | if (any) { 413 | let index = this._settings.get_strv("systemd").indexOf(this._getIdFromIter(iter)); 414 | this._move(index, index + 1) 415 | } 416 | }, 417 | _move: function(oldIndex, newIndex) { 418 | let currentItems = this._settings.get_strv("systemd"); 419 | 420 | if (oldIndex < 0 || oldIndex >= currentItems.length || 421 | newIndex < 0 || newIndex >= currentItems.length) 422 | return; 423 | 424 | currentItems.splice(newIndex, 0, currentItems.splice(oldIndex, 1)[0]); 425 | 426 | this._settings.set_strv("systemd", currentItems); 427 | 428 | this._treeView.get_selection().unselect_all(); 429 | this._treeView.get_selection().select_path(Gtk.TreePath.new_from_string(String(newIndex))); 430 | }, 431 | _delete: function() { 432 | let [any, model, iter] = this._treeView.get_selection().get_selected(); 433 | 434 | if (any) { 435 | let currentItems = this._settings.get_strv("systemd"); 436 | let index = currentItems.indexOf(this._getIdFromIter(iter)); 437 | 438 | if (index < 0) 439 | return; 440 | 441 | currentItems.splice(index, 1); 442 | this._settings.set_strv("systemd", currentItems); 443 | 444 | this._store.remove(iter); 445 | } 446 | }, 447 | _onSelectionChanged: function() { 448 | let [any, model, iter] = this._treeView.get_selection().get_selected(); 449 | if (any) { 450 | this._selDepButtons.forEach(function(value) { 451 | value.set_sensitive(true) 452 | }); 453 | } else { 454 | this._selDepButtons.forEach(function(value) { 455 | value.set_sensitive(false) 456 | }); 457 | } 458 | }, 459 | _refresh: function() { 460 | if (!this._changedPermitted) 461 | return; 462 | 463 | this._store.clear(); 464 | this._showAddCheckbox.set_active(this._settings.get_boolean('show-add')) 465 | this._showRestartCheckbox.set_active(this._settings.get_boolean('show-restart')) 466 | this._positionCombo.set_active(this._settings.get_enum('position')) 467 | this._commandMethodCombo.set_active(this._settings.get_enum('command-method')) 468 | 469 | let currentItems = this._settings.get_strv("systemd"); 470 | let validItems = [ ]; 471 | 472 | for (let i = 0; i < currentItems.length; i++) { 473 | let entry = JSON.parse(currentItems[i]); 474 | // REMOVE NOT EXISTING ENTRIES 475 | if (!this._isValidTemplateInstance(entry["service"],entry["type"]) && (this._availableSystemdServices["all"].indexOf(entry["service"]) < 0)) 476 | continue; 477 | 478 | // COMPATIBILITY 479 | if(!("type" in entry)) 480 | entry["type"] = this._getTypeOfService(entry["service"]) 481 | 482 | validItems.push(JSON.stringify(entry)); 483 | 484 | let iter = this._store.append(); 485 | this._store.set( 486 | iter, 487 | [0, 1, 2], 488 | [entry["name"], entry["service"], entry["type"]] 489 | ); 490 | } 491 | 492 | this._changedPermitted = false 493 | this._settings.set_strv("systemd", validItems); 494 | this._changedPermitted = true 495 | } 496 | }); 497 | 498 | function init() { 499 | } 500 | 501 | function buildPrefsWidget() { 502 | let widget = new ServicesSystemdSettings(); 503 | widget.show_all(); 504 | 505 | return widget; 506 | } 507 | -------------------------------------------------------------------------------- /services-systemd@abteil.org/schemas/gschemas.compiled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petres/gnome-shell-extension-services-systemd/86936eff0f6f87d1f8f3b0270472881234064aa2/services-systemd@abteil.org/schemas/gschemas.compiled -------------------------------------------------------------------------------- /services-systemd@abteil.org/schemas/org.gnome.shell.extensions.services-systemd.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | [ ] 15 | Systemd service list 16 | A list of serrvice which are shown 17 | 18 | 19 | true 20 | Show the add services button 21 | Whether to show the add services button as last entry in the extensions list. 22 | 23 | 24 | true 25 | Show the restart button for each service 26 | Whether to show the restart button for each service. 27 | 28 | 29 | 'panel' 30 | Where to display the extension indicator 31 | 32 | 33 | 'pkexec' 34 | The method to use to manage services 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /services-systemd@abteil.org/stylesheet.css: -------------------------------------------------------------------------------- 1 | .system-menu-action.services-systemd-button-reload { 2 | height: 18px; 3 | width: 18px; 4 | padding: 0px; 5 | border: 0px; 6 | } --------------------------------------------------------------------------------