From 7c0b73ea8c0c01dd1b4632a7583f5931ab90afad Mon Sep 17 00:00:00 2001 From: MrMelon Date: Tue, 6 Oct 2020 10:54:25 +0100 Subject: [PATCH] Use libappindicator for tray icon --- MelonVPNClient/MainWindow.cs | 428 ++++++++++++++------------- MelonVPNClient/MelonVPNClient.csproj | 4 + MelonVPNClient/PopupMenu.cs | 39 +++ install-components | 5 + 4 files changed, 266 insertions(+), 210 deletions(-) create mode 100644 MelonVPNClient/PopupMenu.cs 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