437 lines
13 KiB
C#
437 lines
13 KiB
C#
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 Button restartToggleBtn;
|
|
private bool ConnectedToVPN;
|
|
private bool IsHidden;
|
|
private bool RestartMode = false;
|
|
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);
|
|
|
|
restartToggleBtn = new Button
|
|
{
|
|
Visible = true,
|
|
Label = "-"
|
|
};
|
|
restartToggleBtn.SetSizeRequest(80, 30);
|
|
restartToggleBtn.Clicked += RestartToggleBtn_Clicked;
|
|
fixed1.Put(restartToggleBtn, 190, 5);
|
|
|
|
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;
|
|
case ClientResponseState.Starting:
|
|
statusLabel.Text = "Starting";
|
|
ConnectedToVPN = false;
|
|
break;
|
|
case ClientResponseState.Restarting:
|
|
statusLabel.Text = "Restarting";
|
|
ConnectedToVPN = true;
|
|
break;
|
|
case ClientResponseState.RestartOn:
|
|
RestartMode = true;
|
|
restartToggleBtn.Label = "Restart [#]";
|
|
break;
|
|
case ClientResponseState.RestartOff:
|
|
RestartMode = false;
|
|
restartToggleBtn.Label = "Restart [ ]";
|
|
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();
|
|
}
|
|
|
|
void RestartToggleBtn_Clicked(object sender, EventArgs e)
|
|
{
|
|
|
|
ClientResponseState s = (RestartMode) ? Client.SendDataMessage(DataMessage.RestartOff) : Client.SendDataMessage(DataMessage.RestartOn);
|
|
switch (s)
|
|
{
|
|
case ClientResponseState.Error:
|
|
case ClientResponseState.ServerError:
|
|
case ClientResponseState.RestartOn:
|
|
RestartMode = true;
|
|
restartToggleBtn.Label = "Restart [#]";
|
|
break;
|
|
case ClientResponseState.RestartOff:
|
|
RestartMode = false;
|
|
restartToggleBtn.Label = "Restart [ ]";
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|