using System; using System.IO; using System.Net.NetworkInformation; using System.Reflection; using System.Threading; using AppIndicator3; using Gtk; using MelonVPNClient; using MelonVPNCore; using Notify; public partial class MainWindow : Window { private Label statusLabel; private Button startBtn; private Button stopBtn; private Button refreshBtn; private ScrolledWindow clientsListScroller; private TextView clientsListText; private Thread startupCheckThread; private ThreadWrapper wrapper; private Indicator trayIcon; private PopupMenu trayMenu; private bool ConnectedToVPN; private bool IsHidden; public static readonly string MelonIconImg = "MiniMelonVPNIcon.png"; public static readonly string MelonOnlineImg = "MiniMelonVPNOnline.png"; public MainWindow() : base(WindowType.Toplevel) { Build(); Title = "Melon VPN"; UpdateIcon(MelonIconImg); SetSizeRequest(300, 300); SetDefaultSize(300, 300); SetPosition(WindowPosition.CenterAlways); Resizable = false; TypeHint = Gdk.WindowTypeHint.Dialog; ShowAll(); Present(); statusLabel = new Label { Visible = true, Text = "Loading..." }; fixed1.Put(statusLabel, 10, 10); startBtn = new Button { Visible = true, Label = "Start" }; startBtn.SetSizeRequest(80, 30); startBtn.Clicked += OnStartClicked; fixed1.Put(startBtn, 10, 40); stopBtn = new Button { Visible = true, Label = "Stop" }; stopBtn.SetSizeRequest(80, 30); stopBtn.Clicked += OnStopClicked; fixed1.Put(stopBtn, 100, 40); refreshBtn = new Button { Visible = true, Label = "Refresh" }; refreshBtn.SetSizeRequest(80, 30); refreshBtn.Clicked += OnRefreshClicked; fixed1.Put(refreshBtn, 190, 40); clientsListText = new TextView { Visible = true, Editable = false }; clientsListScroller = new ScrolledWindow { Visible = true }; clientsListScroller.SetSizeRequest(280, 200); clientsListScroller.Add(clientsListText); fixed1.Put(clientsListScroller, 10, 80); GUISocketServer.Receive += delegate (object sender, ClientResponseState s) { Console.WriteLine(s); Application.Invoke(delegate { UpdateStatus(s); }); }; GUISocketServer.ClientListUpdate += delegate (object sender, ConnectedClient[] clients) { Console.WriteLine(clients.Length); string[][] a = new string[clients.Length][]; for (int i = 0; i < clients.Length; i++) a[i] = new string[] { clients[i].name, clients[i].ip }; clientsListText.Buffer.Text = GenerateTable(a); }; wrapper = new ThreadWrapper(GUISocketServer.StartServer); startupCheckThread = new Thread(StartupCheckMethod) { IsBackground = true }; startupCheckThread.Start(); trayMenu = new PopupMenu(OnActivateTrayIcon, OnToggleMenuItem, QuitApp); trayIcon = new Indicator("melonvpn", "MiniMelonVPNIcon", (int)IndicatorCategory.ApplicationStatus) { Menu = trayMenu.GetMenu(), Status = (int)IndicatorStatus.Active }; } private void OnActivateTrayIcon() { if (IsHidden) JumpFromTray(); else SendToTray(); } void OnToggleMenuItem() { if (ConnectedToVPN) OnStopClicked(this, new EventArgs()); else OnStartClicked(this, new EventArgs()); } void UpdateTrayMenu() { if (trayMenu != null) trayMenu.Update(ConnectedToVPN, !IsHidden); } 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 = file.Replace(".png", ""); } } 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) { 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) { string msg = (current ? "Connected to" : "Disconnected from") + " the VPN"; string icon = "/usr/lib/melon-vpn/MiniMelonVPN" + (current ? "Online" : "Icon") + ".png"; Notification pop = new Notification("Melon VPN", msg, icon); pop.Show(); CloseNotificationAfterWait(pop, 1000); } } void CloseNotificationAfterWait(Notification pop, int timeout) { // Close the notification after a wait // so it doesn't hang around Thread wait = new Thread(() => { Thread.Sleep(timeout); pop.Close(); }) { IsBackground = true }; wait.Start(); } void SendToTray() { IsHidden = true; // Try hiding the window Iconify(); SkipPagerHint = true; SkipTaskbarHint = true; // Changing the visible property needs to be // delayed for the window to start minimizing Thread thread = new Thread(() => { Thread.Sleep(100); Visible = false; }) { IsBackground = true }; thread.Start(); UpdateTrayMenu(); } void JumpFromTray() { IsHidden = false; // Present triggers `Visible = true;` // it is then called again once the window is visible // so it triggers `Deiconify();` Present(); SkipPagerHint = false; SkipTaskbarHint = false; UpdateTrayMenu(); } protected void OnDeleteEvent(object sender, DeleteEventArgs a) { // Try to prevent destroying the tray icon // while connected to VPN if (ConnectedToVPN) SendToTray(); else QuitApp(); a.RetVal = true; } protected void QuitApp() { // Kill this thread if it exists if (wrapper != null) { wrapper.Kill(); wrapper.Join(); } // Destroy the tray icon first 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(); protected void OnVisibilityNotifyEvent(object o, VisibilityNotifyEventArgs args) { // Calling present again forces the window to `Deiconify();` Thread thread = new Thread(() => { Thread.Sleep(100); Present(); }) { IsBackground = true }; thread.Start(); } }