forked from linuxmint/cinnamon-spices-applets
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add shadowsocks-switch@Klavionik (linuxmint#5029)
- Loading branch information
Showing
12 changed files
with
1,096 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Shadowsocks Switch | ||
Shadowsocks Switch simplifies enabling and disabling Shadowsocks. | ||
|
||
## Requirements | ||
This applet acts as a wrapper around `ss-local` utility and therefore requires | ||
`shadowsocks-libev` to be present. Run `apt-get install shadowsocks-libev` to install | ||
it before using this applet. | ||
|
||
## Rationale | ||
Shadowsocks is a secure proxy that is made to bypass internet censorship, so the use | ||
case is a lot like we use VPN nowadays. While other VPN protocols have Network Manager | ||
plugins that add convenient GUI to quickly enable/disable the VPN connection, Shadowsocks | ||
doesn't have one. | ||
|
||
I'm using Shadowsocks myself, so I wanted a simple and convenient UI to quickly | ||
enable/disable the Shadowsocks connection. | ||
|
||
## How it works | ||
Upon installation this applet adds an icon with a pop-up toggle in the system tray. | ||
|
||
When you switch the proxy on, the applet does the following. | ||
|
||
1. Reads and saves your current system proxy settings (proxy mode, SOCKS5 host and port). | ||
2. Runs `ss-local -c <config>` in background (`<config>` defaults to | ||
`/etc/shadowsocks-libev/config.json`). | ||
3. Sets your system proxy mode to `Manual`, SOCKS5 host to `localhost` and SOCKS5 port | ||
to the `local_port` value from `<config>`. | ||
|
||
When you are done using Shadowsocks, you switch it off and the applet restores your | ||
previous system proxy settings and stops the proxy. | ||
|
||
## Settings | ||
The applet uses a Shadowsocks configuration file to discover the local proxy port. The | ||
default path to the configuration file is `/etc/shadowsocks-libev/config.json`, but | ||
you can point the applet to any other configuration file via applet settings (click on | ||
the cog icon in the Applets window). | ||
|
||
## Credits | ||
Shadowsocks tray icon is based on [Papirus Icon Theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme). |
674 changes: 674 additions & 0 deletions
674
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/LICENSE
Large diffs are not rendered by default.
Oops, something went wrong.
39 changes: 39 additions & 0 deletions
39
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Shadowsocks Switch | ||
Shadowsocks Switch simplifies enabling and disabling Shadowsocks. | ||
|
||
## Requirements | ||
This applet acts as a wrapper around `ss-local` utility and therefore requires | ||
`shadowsocks-libev` to be present. Run `apt-get install shadowsocks-libev` to install | ||
it before using this applet. | ||
|
||
## Rationale | ||
Shadowsocks is a secure proxy that is made to bypass internet censorship, so the use | ||
case is a lot like we use VPN nowadays. While other VPN protocols have Network Manager | ||
plugins that add convenient GUI to quickly enable/disable the VPN connection, Shadowsocks | ||
doesn't have one. | ||
|
||
I'm using Shadowsocks myself, so I wanted a simple and convenient UI to quickly | ||
enable/disable the Shadowsocks connection. | ||
|
||
## How it works | ||
Upon installation this applet adds an icon with a pop-up toggle in the system tray. | ||
|
||
When you switch the proxy on, the applet does the following. | ||
|
||
1. Reads and saves your current system proxy settings (proxy mode, SOCKS5 host and port). | ||
2. Runs `ss-local -c <config>` in background (`<config>` defaults to | ||
`/etc/shadowsocks-libev/config.json`). | ||
3. Sets your system proxy mode to `Manual`, SOCKS5 host to `localhost` and SOCKS5 port | ||
to the `local_port` value from `<config>`. | ||
|
||
When you are done using Shadowsocks, you switch it off and the applet restores your | ||
previous system proxy settings and stops the proxy. | ||
|
||
## Settings | ||
The applet uses a Shadowsocks configuration file to discover the local proxy port. The | ||
default path to the configuration file is `/etc/shadowsocks-libev/config.json`, but | ||
you can point the applet to any other configuration file via applet settings (click on | ||
the cog icon in the Applets window). | ||
|
||
## Credits | ||
Shadowsocks tray icon is based on [Papirus Icon Theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme). |
207 changes: 207 additions & 0 deletions
207
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/applet.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
const Applet = imports.ui.applet | ||
const Util = imports.misc.util | ||
const PopupMenu = imports.ui.popupMenu | ||
const Main = imports.ui.main | ||
const Settings = imports.ui.settings | ||
const GLib = imports.gi.GLib | ||
const Gettext = imports.gettext | ||
|
||
const UUID = "shadowsocks-switch@Klavionik" | ||
|
||
Gettext.bindtextdomain(UUID, GLib.get_home_dir() + "/.local/share/locale") | ||
|
||
function _(string) { | ||
return Gettext.dgettext(UUID, string) | ||
} | ||
|
||
function logError(error) { | ||
global.logError("\n[" + UUID + "]: " + error + "\n") | ||
} | ||
|
||
function fileURItoPath(uri) { | ||
return uri.slice(7) // Remove file:// scheme. | ||
} | ||
|
||
function notify(msg) { | ||
const title = _("Shadowsocks Switch") | ||
Main.notify(title, msg) | ||
} | ||
|
||
class ShadowsocksAppletMenu extends Applet.AppletPopupMenu { | ||
constructor(launcher, orientation) { | ||
super(launcher, orientation) | ||
this.toggleRef = this._makeToggle() | ||
this.addMenuItem(this.toggleRef) | ||
} | ||
|
||
_makeToggle() { | ||
const toggle = new PopupMenu.PopupSwitchMenuItem(_("Enable Shadowsocks"), false) | ||
toggle.connect("toggled", () => (this.emit("toggle-proxy"))) | ||
return toggle | ||
} | ||
} | ||
|
||
class ShadowsocksAppletError extends Error {} | ||
|
||
class ShadowsocksApplet extends Applet.IconApplet { | ||
constructor(metadata, orientation, panel_height, instance_id) { | ||
super(orientation, panel_height, instance_id) | ||
this._metadata = metadata | ||
this._ssConfigFileURI = "" | ||
this._host = "localhost" | ||
this._port = -1 | ||
this._savedMode = "" | ||
this._savedHost = "" | ||
this._savedPort = -1 | ||
this._clientPid = -1 | ||
this._connected = false | ||
this._settingsProvider = undefined | ||
|
||
this._configureApplet() | ||
this.menu = this._createMenu(orientation) | ||
} | ||
|
||
_configureApplet() { | ||
this.set_applet_icon_path(this._metadata.path + "/icon.png") | ||
this.set_applet_tooltip(_("Toggle Shadowsocks proxy")) | ||
|
||
this._settingsProvider = new Settings.AppletSettings(this, this._metadata.uuid) | ||
this._settingsProvider.bindProperty( | ||
Settings.BindingDirection.IN, | ||
"config", | ||
"_ssConfigFileURI", | ||
() => {} | ||
) | ||
} | ||
|
||
_configureConnection() { | ||
const {port} = this._loadConfig() | ||
this._port = port | ||
} | ||
|
||
_createMenu(orientation) { | ||
const menuManager = new PopupMenu.PopupMenuManager(this) | ||
const menu = new ShadowsocksAppletMenu(this, orientation) | ||
|
||
menu.connect("toggle-proxy", this._onToggleProxy.bind(this)) | ||
menuManager.addMenu(menu) | ||
return menu | ||
} | ||
|
||
_onToggleProxy() { | ||
this._connected ? this._disconnect() : this._connect() | ||
} | ||
|
||
_loadConfig() { | ||
let content | ||
|
||
try { | ||
const [_, buffer] = GLib.file_get_contents(fileURItoPath(this._ssConfigFileURI)) | ||
content = buffer | ||
} catch (e) { | ||
const msg = _(`Cannot read Shadowsocks config.`) | ||
notify(`${msg} ${String(e)}`) | ||
throw new ShadowsocksAppletError(msg) | ||
} | ||
|
||
const config = JSON.parse(content) | ||
|
||
if (!config.hasOwnProperty("local_port")) { | ||
const msg = _("Shadowsocks config is missing local port.") | ||
notify(msg) | ||
throw new ShadowsocksAppletError(msg) | ||
} | ||
|
||
return {port: config.local_port} | ||
} | ||
|
||
_connect() { | ||
try { | ||
this._configureConnection() | ||
this._saveProxySettings() | ||
this._clientPid = this._runClient() | ||
this._setupProxy() | ||
} catch (e) { | ||
this.menu.toggleRef.setToggleState(false) | ||
this._restoreProxySettings() | ||
|
||
if (this._clientPid !== -1) { | ||
this._stopClient() | ||
} | ||
|
||
logError(String(e)) | ||
throw e | ||
} | ||
|
||
this._connected = true | ||
this.set_applet_icon_path(this._metadata.path + "/icon-connected.png") | ||
notify(_("Connection established.")) | ||
} | ||
|
||
_runClient() { | ||
const cmd = `ss-local -c ${fileURItoPath(this._ssConfigFileURI)}` | ||
return Util.trySpawnCommandLine(cmd) | ||
} | ||
|
||
_stopClient() { | ||
const cmd = `kill -s SIGINT ${this._clientPid}` | ||
Util.spawnCommandLine(cmd) | ||
this._clientPid = -1 | ||
} | ||
|
||
_saveProxySettings() { | ||
Util.spawnCommandLineAsyncIO("gsettings get org.gnome.system.proxy mode", (stdout) => { | ||
this._savedMode = stdout | ||
}) | ||
|
||
Util.spawnCommandLineAsyncIO("gsettings get org.gnome.system.proxy.socks host", (stdout) => { | ||
this._savedHost = stdout | ||
}) | ||
|
||
Util.spawnCommandLineAsyncIO("gsettings get org.gnome.system.proxy.socks port", (stdout) => { | ||
this._savedPort = stdout | ||
}) | ||
} | ||
|
||
_restoreProxySettings() { | ||
const restoreMode = `gsettings set org.gnome.system.proxy mode ${this._savedMode}` | ||
const restoreHost = `gsettings set org.gnome.system.proxy.socks host ${this._savedHost}` | ||
const restorePort = `gsettings set org.gnome.system.proxy.socks port ${this._savedPort}` | ||
|
||
const cmd = `sh -c "${restoreMode} ${restoreHost} ${restorePort}"` | ||
Util.trySpawnCommandLine(cmd) | ||
} | ||
|
||
_setupProxy() { | ||
const setMode = "gsettings set org.gnome.system.proxy mode 'manual'" | ||
const setHost = `gsettings set org.gnome.system.proxy.socks host ${this._host}` | ||
const setPort = `gsettings set org.gnome.system.proxy.socks port ${this._port}` | ||
|
||
const cmd = `sh -c "${setMode}; ${setHost}; ${setPort}"` | ||
Util.trySpawnCommandLine(cmd) | ||
} | ||
|
||
_disconnect() { | ||
this._restoreProxySettings() | ||
this._stopClient() | ||
|
||
this._connected = false | ||
notify(_("Disconnected.")) | ||
this.set_applet_icon_path(this._metadata.path + "/icon.png") | ||
} | ||
|
||
on_applet_clicked() { | ||
this.menu.toggle() | ||
} | ||
|
||
on_applet_removed_from_panel(deleteConfig) { | ||
if (this._connected) { | ||
this._disconnect() | ||
} | ||
} | ||
} | ||
|
||
|
||
function main(metadata, orientation, panel_height, instance_id) { | ||
return new ShadowsocksApplet(metadata, orientation, panel_height, instance_id) | ||
} |
Binary file added
BIN
+4.28 KB
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/icon-connected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+3.6 KB
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions
5
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"uuid": "shadowsocks-switch@Klavionik", | ||
"name": "Shadowsocks Switch", | ||
"description": "Quickly toggle Shadowsocks proxy from the system tray" | ||
} |
59 changes: 59 additions & 0 deletions
59
shadowsocks-switch@Klavionik/files/shadowsocks-switch@Klavionik/po/ru.po
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# SOME DESCRIPTIVE TITLE. | ||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | ||
# This file is distributed under the same license as the PACKAGE package. | ||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | ||
# | ||
msgid "" | ||
msgstr "" | ||
"Project-Id-Version: \n" | ||
"Report-Msgid-Bugs-To: \n" | ||
"POT-Creation-Date: 2023-09-12 00:22+0300\n" | ||
"PO-Revision-Date: 2023-09-12 00:26+0300\n" | ||
"Last-Translator: \n" | ||
"Language-Team: \n" | ||
"Language: ru\n" | ||
"MIME-Version: 1.0\n" | ||
"Content-Type: text/plain; charset=UTF-8\n" | ||
"Content-Transfer-Encoding: 8bit\n" | ||
"X-Generator: Poedit 3.3.2\n" | ||
|
||
#. metadata.json->name | ||
#: applet.js:26 | ||
msgid "Shadowsocks Switch" | ||
msgstr "Переключатель Shadowsocks" | ||
|
||
#: applet.js:38 | ||
msgid "Enable Shadowsocks" | ||
msgstr "Включить Shadowsocks" | ||
|
||
#: applet.js:66 | ||
msgid "Toggle Shadowsocks proxy" | ||
msgstr "Управление Shadowsocks" | ||
|
||
#: applet.js:102 | ||
msgid "Cannot read Shadowsocks config." | ||
msgstr "Невозможно прочитать конфигурацию Shadowsocks." | ||
|
||
#: applet.js:110 | ||
msgid "Shadowsocks config is missing local port." | ||
msgstr "В конфигурации Shadowsocks отсутствует локальный порт." | ||
|
||
#: applet.js:138 | ||
msgid "Connection established." | ||
msgstr "Соединение установлено." | ||
|
||
#: applet.js:189 | ||
msgid "Disconnected." | ||
msgstr "Соединение разорвано." | ||
|
||
#. metadata.json->description | ||
msgid "Quickly toggle Shadowsocks proxy from the system tray" | ||
msgstr "Быстрое управление Shadowsocks из системной панели" | ||
|
||
#. settings-schema.json->section1->description | ||
msgid "General" | ||
msgstr "Общие" | ||
|
||
#. settings-schema.json->config->description | ||
msgid "Shadowsocks configuration" | ||
msgstr "Конфигурация Shadowsocks" |
Oops, something went wrong.