diff --git a/MelonVPNClient/MainWindow.cs b/MelonVPNClient/MainWindow.cs
index 744face..f4ec22e 100644
--- a/MelonVPNClient/MainWindow.cs
+++ b/MelonVPNClient/MainWindow.cs
@@ -3,7 +3,9 @@ using System.IO;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Threading;
+using AppIndicator3;
using Gtk;
+using MelonVPNClient;
using MelonVPNCore;
using Notify;
@@ -17,7 +19,8 @@ public partial class MainWindow : Window
private TextView clientsListText;
private Thread startupCheckThread;
private ThreadWrapper wrapper;
- private StatusIcon trayIcon;
+ private Indicator trayIcon;
+ private PopupMenu trayMenu;
private bool ConnectedToVPN;
public static readonly string MelonIconImg = "MiniMelonVPNIcon.png";
public static readonly string MelonOnlineImg = "MiniMelonVPNOnline.png";
@@ -109,231 +112,236 @@ public partial class MainWindow : Window
};
startupCheckThread.Start();
- trayIcon = new StatusIcon
- {
- Visible = true,
- TooltipText = "Melon VPN"
+ trayMenu = new PopupMenu(OnActivateTrayIcon, OnToggleMenuItem, QuitApp);
+ trayIcon = new Indicator("melonvpn", "MiniMelonVPNIcon", (int)IndicatorCategory.ApplicationStatus)
+ {
+ Menu = trayMenu.GetMenu(),
+ Status = (int)IndicatorStatus.Active
};
- trayIcon.Activate += OnActivateTrayIcon;
- trayIcon.PopupMenu += OnTrayIconPopup;
}
- private void OnActivateTrayIcon(object sender, EventArgs args)
+ private void OnActivateTrayIcon()
{
- Visible = !Visible;
+ Visible = !Visible;
+ UpdateTrayMenu();
+ }
+
+ void OnToggleMenuItem()
+ {
+ if (ConnectedToVPN) OnStopClicked(this, new EventArgs());
+ else OnStartClicked(this, new EventArgs());
}
- private void OnTrayIconPopup(object o, PopupMenuArgs args)
- {
- Menu popupMenu = new Menu();
-
- ImageMenuItem menuItemToggle = new ImageMenuItem(ConnectedToVPN ? "Disconnect" : "Connect");
- Image toggleimg = new Image(ConnectedToVPN ? Stock.MediaPause : Stock.MediaPlay);
- menuItemToggle.Image = toggleimg;
- popupMenu.Add(menuItemToggle);
- menuItemToggle.Activated += OnToggleMenuItem;
-
- ImageMenuItem menuItemQuit = new ImageMenuItem("Quit");
- Image appimg = new Image(Stock.Quit, IconSize.Menu);
- menuItemQuit.Image = appimg;
- popupMenu.Add(menuItemQuit);
- menuItemQuit.Activated += delegate { QuitApp(); };
-
- popupMenu.ShowAll();
- popupMenu.Popup();
+ void UpdateTrayMenu()
+ {
+ if (trayMenu != null) trayMenu.Update(ConnectedToVPN, Visible);
}
- void OnToggleMenuItem(object sender, EventArgs args)
- {
- if (ConnectedToVPN) OnStopClicked(this, new EventArgs());
- else OnStartClicked(this, new EventArgs());
- }
-
- void UpdateIcon(string file)
- {
- DirectoryInfo ExeLocation = Directory.GetParent(Assembly.GetEntryAssembly().Location);
- string IconPath = System.IO.Path.Combine(ExeLocation.FullName, file);
- Console.WriteLine("Trying to update icon to: " + IconPath);
- if (File.Exists(IconPath))
- {
- Icon = new Gdk.Pixbuf(IconPath);
- if (trayIcon != null) trayIcon.Icon = new Gdk.Pixbuf(IconPath);
- }
- }
-
- string GenerateTable(string[][] a)
- {
- if (a.Length == 0) return "";
- int maxlength = a[0][0].Length;
- foreach (string b in a[0])
- {
- if (b.Length > maxlength) maxlength = b.Length;
- }
- string[] c = new string[a.Length];
- for (int i = 0; i < a.Length; i++) c[i] = a[i][0].PadRight(maxlength, ' ') + a[i][1];
- return string.Join("\n", c);
- }
-
- string GetVpnInternalIp()
- {
- if (NetworkInterface.GetIsNetworkAvailable())
- {
- foreach (NetworkInterface f in NetworkInterface.GetAllNetworkInterfaces())
- {
- IPInterfaceProperties p = f.GetIPProperties();
- IPAddressInformationCollection addressesColl = p.AnycastAddresses;
- foreach (IPAddressInformation ip in addressesColl)
- {
- if (ip.Address.ToString().StartsWith("10.137.248.", StringComparison.CurrentCulture))
- {
- return ip.Address.ToString();
- }
- }
- }
- }
- return "";
- }
-
- void StartupCheckMethod()
- {
- ClientResponseState s = Client.SendDataMessage(DataMessage.Status);
- Console.WriteLine(s);
- Application.Invoke(delegate
- {
- UpdateStatus(s);
- });
- }
-
- void Refresh()
- {
- ClientResponseState s = Client.SendDataMessage(DataMessage.Status);
- switch (s)
- {
- case ClientResponseState.Error:
- case ClientResponseState.ServerError:
- UpdateStatus(s);
- MessageDialog md = new MessageDialog(this,
- DialogFlags.DestroyWithParent, MessageType.Error,
- ButtonsType.Ok, "Error stopping VPN. Is the daemon running?");
- md.Run();
- md.Destroy();
- break;
- case ClientResponseState.Online:
- case ClientResponseState.Offline:
- UpdateStatus(s);
- break;
- }
- }
-
- void UpdateStatus(ClientResponseState s)
+ void UpdateIcon(string file)
{
- bool oldState = ConnectedToVPN;
- switch (s)
- {
- case ClientResponseState.Error:
- statusLabel.Text = "Client Error";
- ConnectedToVPN = false;
- break;
- case ClientResponseState.ServerError:
- statusLabel.Text = "Server Error";
- ConnectedToVPN = false;
- break;
- case ClientResponseState.Blank:
- statusLabel.Text = "No reply";
- ConnectedToVPN = false;
- break;
- case ClientResponseState.Online:
- statusLabel.Text = "Online - " + GetVpnInternalIp();
- ConnectedToVPN = true;
- break;
- case ClientResponseState.Offline:
- statusLabel.Text = "Offline";
- ConnectedToVPN = false;
- break;
- }
- if (ConnectedToVPN) UpdateIcon(MelonOnlineImg);
- else UpdateIcon(MelonIconImg);
- NotifyStateUpdate(oldState,ConnectedToVPN);
+ DirectoryInfo ExeLocation = Directory.GetParent(Assembly.GetEntryAssembly().Location);
+ string IconPath = System.IO.Path.Combine(ExeLocation.FullName, file);
+ Console.WriteLine("Trying to update icon to: " + IconPath);
+ if (File.Exists(IconPath))
+ {
+ Icon = new Gdk.Pixbuf(IconPath);
+ if (trayIcon != null) trayIcon.Icon = file.Replace(".png", "");
+ }
}
- void NotifyStateUpdate(bool old, bool current)
+ string GenerateTable(string[][] a)
{
- if(old!=current)
- {
- if(current==true)
- {
- Notification pop = new Notification("Melon VPN", "Connected to the VPN");
- pop.Show();
- }
- else
- {
- Notification pop = new Notification("Melon VPN", "Disconnected from the VPN");
- pop.Show();
+ if (a.Length == 0) return "";
+ int maxlength = a[0][0].Length;
+ foreach (string b in a[0])
+ {
+ if (b.Length > maxlength) maxlength = b.Length;
+ }
+ string[] c = new string[a.Length];
+ for (int i = 0; i < a.Length; i++) c[i] = a[i][0].PadRight(maxlength, ' ') + a[i][1];
+ return string.Join("\n", c);
+ }
+
+ string GetVpnInternalIp()
+ {
+ if (NetworkInterface.GetIsNetworkAvailable())
+ {
+ foreach (NetworkInterface f in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ IPInterfaceProperties p = f.GetIPProperties();
+ IPAddressInformationCollection addressesColl = p.AnycastAddresses;
+ foreach (IPAddressInformation ip in addressesColl)
+ {
+ if (ip.Address.ToString().StartsWith("10.137.248.", StringComparison.CurrentCulture))
+ {
+ return ip.Address.ToString();
+ }
+ }
}
- }
- }
-
- protected void OnDeleteEvent(object sender, DeleteEventArgs a)
+ }
+ return "";
+ }
+
+ void StartupCheckMethod()
{
- if (ConnectedToVPN) Visible = false;
- else QuitApp();
- a.RetVal = true;
- }
-
- protected void QuitApp()
+ ClientResponseState s = Client.SendDataMessage(DataMessage.Status);
+ Console.WriteLine(s);
+ Application.Invoke(delegate
+ {
+ UpdateStatus(s);
+ });
+ }
+
+ void Refresh()
+ {
+ ClientResponseState s = Client.SendDataMessage(DataMessage.Status);
+ switch (s)
+ {
+ case ClientResponseState.Error:
+ case ClientResponseState.ServerError:
+ UpdateStatus(s);
+ MessageDialog md = new MessageDialog(this,
+ DialogFlags.DestroyWithParent, MessageType.Error,
+ ButtonsType.Ok, "Error stopping VPN. Is the daemon running?");
+ md.Run();
+ md.Destroy();
+ break;
+ case ClientResponseState.Online:
+ case ClientResponseState.Offline:
+ UpdateStatus(s);
+ break;
+ }
+ }
+
+ void UpdateStatus(ClientResponseState s)
+ {
+ bool oldState = ConnectedToVPN;
+ switch (s)
+ {
+ case ClientResponseState.Error:
+ statusLabel.Text = "Client Error";
+ ConnectedToVPN = false;
+ break;
+ case ClientResponseState.ServerError:
+ statusLabel.Text = "Server Error";
+ ConnectedToVPN = false;
+ break;
+ case ClientResponseState.Blank:
+ statusLabel.Text = "No reply";
+ ConnectedToVPN = false;
+ break;
+ case ClientResponseState.Online:
+ statusLabel.Text = "Online - " + GetVpnInternalIp();
+ ConnectedToVPN = true;
+ break;
+ case ClientResponseState.Offline:
+ statusLabel.Text = "Offline";
+ ConnectedToVPN = false;
+ break;
+ }
+ if (ConnectedToVPN) UpdateIcon(MelonOnlineImg);
+ else UpdateIcon(MelonIconImg);
+ UpdateTrayMenu();
+ NotifyStateUpdate(oldState, ConnectedToVPN);
+ }
+
+ void NotifyStateUpdate(bool old, bool current)
+ {
+ if (old != current)
+ {
+ if (current == true)
+ {
+ Notification pop = new Notification("Melon VPN", "Connected to the VPN");
+ pop.Show();
+ CloseNotificationAfterWait(pop, 1000);
+ }
+ else
+ {
+ Notification pop = new Notification("Melon VPN", "Disconnected from the VPN");
+ pop.Show();
+ CloseNotificationAfterWait(pop, 1000);
+ }
+ }
+ }
+
+ void CloseNotificationAfterWait(Notification pop, int timeout)
{
- if (wrapper != null)
+ Thread wait = new Thread(() =>
{
- wrapper.Kill();
- wrapper.Join();
- }
- if (trayIcon != null) trayIcon.Dispose();
- Application.Quit();
- }
-
- void OnStartClicked(object sender, EventArgs e)
- {
- ClientResponseState s = Client.SendDataMessage(DataMessage.Start);
- switch (s)
+ Thread.Sleep(timeout);
+ pop.Close();
+ })
{
- case ClientResponseState.Error:
- case ClientResponseState.ServerError:
- case ClientResponseState.Offline:
- UpdateStatus(s);
- MessageDialog md = new MessageDialog(this,
- DialogFlags.DestroyWithParent, MessageType.Error,
- ButtonsType.Ok, "Error starting VPN. Is the daemon running?");
- md.Run();
- md.Destroy();
- break;
- }
- }
-
- void OnStopClicked(object sender, EventArgs e)
- {
- ClientResponseState s = Client.SendDataMessage(DataMessage.Stop);
- switch (s)
- {
- case ClientResponseState.Error:
- case ClientResponseState.ServerError:
- case ClientResponseState.Online:
- UpdateStatus(s);
- MessageDialog md = new MessageDialog(this,
- DialogFlags.DestroyWithParent, MessageType.Error,
- ButtonsType.Ok, "Error stopping VPN. Is the daemon running?");
- md.Run();
- md.Destroy();
- break;
- }
- }
-
- void OnRefreshClicked(object sender, EventArgs e)
- {
- Refresh();
- }
-
- void OnViewClientsClicked(object sender, EventArgs e)
- {
- Refresh();
+ IsBackground = true
+ };
+ wait.Start();
+ }
+
+ protected void OnDeleteEvent(object sender, DeleteEventArgs a)
+ {
+ if (ConnectedToVPN)
+ {
+ Visible = false;
+ UpdateTrayMenu();
+ }
+ else QuitApp();
+ a.RetVal = true;
+ }
+
+ protected void QuitApp()
+ {
+ if (wrapper != null)
+ {
+ wrapper.Kill();
+ wrapper.Join();
+ }
+ if (trayIcon != null) trayIcon.Dispose();
+ Application.Quit();
+ }
+
+ void OnStartClicked(object sender, EventArgs e)
+ {
+ ClientResponseState s = Client.SendDataMessage(DataMessage.Start);
+ switch (s)
+ {
+ case ClientResponseState.Error:
+ case ClientResponseState.ServerError:
+ case ClientResponseState.Offline:
+ UpdateStatus(s);
+ MessageDialog md = new MessageDialog(this,
+ DialogFlags.DestroyWithParent, MessageType.Error,
+ ButtonsType.Ok, "Error starting VPN. Is the daemon running?");
+ md.Run();
+ md.Destroy();
+ break;
+ }
+ }
+
+ void OnStopClicked(object sender, EventArgs e)
+ {
+ ClientResponseState s = Client.SendDataMessage(DataMessage.Stop);
+ switch (s)
+ {
+ case ClientResponseState.Error:
+ case ClientResponseState.ServerError:
+ case ClientResponseState.Online:
+ UpdateStatus(s);
+ MessageDialog md = new MessageDialog(this,
+ DialogFlags.DestroyWithParent, MessageType.Error,
+ ButtonsType.Ok, "Error stopping VPN. Is the daemon running?");
+ md.Run();
+ md.Destroy();
+ break;
+ }
+ }
+
+ void OnRefreshClicked(object sender, EventArgs e)
+ {
+ Refresh();
+ }
+
+ void OnViewClientsClicked(object sender, EventArgs e)
+ {
+ Refresh();
}
}
diff --git a/MelonVPNClient/MelonVPNClient.csproj b/MelonVPNClient/MelonVPNClient.csproj
index 57600f4..1716df2 100644
--- a/MelonVPNClient/MelonVPNClient.csproj
+++ b/MelonVPNClient/MelonVPNClient.csproj
@@ -63,6 +63,9 @@
..\..\..\net-libs\libnotify.net.dll
+
+ ..\..\..\..\..\lib\mono\gac\appindicator3-sharp\12.10.0.0__bf81553d61d0bd64\appindicator3-sharp.dll
+
@@ -75,6 +78,7 @@
+
diff --git a/MelonVPNClient/PopupMenu.cs b/MelonVPNClient/PopupMenu.cs
new file mode 100644
index 0000000..a9a3cbe
--- /dev/null
+++ b/MelonVPNClient/PopupMenu.cs
@@ -0,0 +1,39 @@
+using Gtk;
+
+namespace MelonVPNClient
+{
+ public class PopupMenu
+ {
+ private Menu me;
+ private MenuItem openItem;
+ private MenuItem toggleItem;
+ private MenuItem quitItem;
+
+ public PopupMenu(System.Action open, System.Action toggle, System.Action quit)
+ {
+ me = new Menu();
+
+ openItem = new MenuItem("...");
+ me.Add(openItem);
+ openItem.Activated += delegate { open(); };
+
+ toggleItem = new MenuItem("...");
+ me.Add(toggleItem);
+ toggleItem.Activated += delegate { toggle(); };
+
+ quitItem = new MenuItem("Quit");
+ me.Add(quitItem);
+ quitItem.Activated += delegate { quit(); };
+
+ me.ShowAll();
+ }
+
+ public void Update(bool ConnectedToVPN, bool Visible)
+ {
+ openItem.Label = Visible ? "Hide" : "Show";
+ toggleItem.Label = ConnectedToVPN ? "Disconnect" : "Connect";
+ }
+
+ public Menu GetMenu() => me;
+ }
+}
diff --git a/install-components b/install-components
index b6ac914..a427b5f 100755
--- a/install-components
+++ b/install-components
@@ -43,6 +43,11 @@ sudo cp MelonVPNClient/bin/Release/MiniMelonVPNIcon.png /usr/lib/melon-vpn/
sudo cp MelonVPNClient/bin/Release/MiniMelonVPNOnline.png /usr/lib/melon-vpn/
sudo cp MelonVPNClient/bin/Release/MelonVPNDesktopIcon.png /usr/lib/melon-vpn/
+# copy app indicator icons
+mkdir -p ~/.local/share/icons/hicolor/128x128/apps/
+cp MelonVPNClient/bin/Release/MiniMelonVPNIcon.png ~/.local/share/icons/hicolor/128x128/apps/
+cp MelonVPNClient/bin/Release/MiniMelonVPNOnline.png ~/.local/share/icons/hicolor/128x128/apps/
+
# make all exe files in melon vpn projects executable
sudo chmod +x /usr/lib/melon-vpn/*.exe