Use libappindicator for tray icon

This commit is contained in:
Melon 2020-10-06 10:54:25 +01:00
parent 2573e3211a
commit 7c0b73ea8c
4 changed files with 266 additions and 210 deletions

View File

@ -3,7 +3,9 @@ using System.IO;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using AppIndicator3;
using Gtk; using Gtk;
using MelonVPNClient;
using MelonVPNCore; using MelonVPNCore;
using Notify; using Notify;
@ -17,7 +19,8 @@ public partial class MainWindow : Window
private TextView clientsListText; private TextView clientsListText;
private Thread startupCheckThread; private Thread startupCheckThread;
private ThreadWrapper wrapper; private ThreadWrapper wrapper;
private StatusIcon trayIcon; private Indicator trayIcon;
private PopupMenu trayMenu;
private bool ConnectedToVPN; private bool ConnectedToVPN;
public static readonly string MelonIconImg = "MiniMelonVPNIcon.png"; public static readonly string MelonIconImg = "MiniMelonVPNIcon.png";
public static readonly string MelonOnlineImg = "MiniMelonVPNOnline.png"; public static readonly string MelonOnlineImg = "MiniMelonVPNOnline.png";
@ -109,231 +112,236 @@ public partial class MainWindow : Window
}; };
startupCheckThread.Start(); startupCheckThread.Start();
trayIcon = new StatusIcon trayMenu = new PopupMenu(OnActivateTrayIcon, OnToggleMenuItem, QuitApp);
{ trayIcon = new Indicator("melonvpn", "MiniMelonVPNIcon", (int)IndicatorCategory.ApplicationStatus)
Visible = true, {
TooltipText = "Melon VPN" 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) void UpdateTrayMenu()
{ {
Menu popupMenu = new Menu(); if (trayMenu != null) trayMenu.Update(ConnectedToVPN, Visible);
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 OnToggleMenuItem(object sender, EventArgs args) void UpdateIcon(string file)
{
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)
{ {
bool oldState = ConnectedToVPN; DirectoryInfo ExeLocation = Directory.GetParent(Assembly.GetEntryAssembly().Location);
switch (s) string IconPath = System.IO.Path.Combine(ExeLocation.FullName, file);
{ Console.WriteLine("Trying to update icon to: " + IconPath);
case ClientResponseState.Error: if (File.Exists(IconPath))
statusLabel.Text = "Client Error"; {
ConnectedToVPN = false; Icon = new Gdk.Pixbuf(IconPath);
break; if (trayIcon != null) trayIcon.Icon = file.Replace(".png", "");
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);
} }
void NotifyStateUpdate(bool old, bool current) string GenerateTable(string[][] a)
{ {
if(old!=current) if (a.Length == 0) return "";
{ int maxlength = a[0][0].Length;
if(current==true) foreach (string b in a[0])
{ {
Notification pop = new Notification("Melon VPN", "Connected to the VPN"); if (b.Length > maxlength) maxlength = b.Length;
pop.Show(); }
} string[] c = new string[a.Length];
else for (int i = 0; i < a.Length; i++) c[i] = a[i][0].PadRight(maxlength, ' ') + a[i][1];
{ return string.Join("\n", c);
Notification pop = new Notification("Melon VPN", "Disconnected from the VPN"); }
pop.Show();
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 "";
}
protected void OnDeleteEvent(object sender, DeleteEventArgs a)
void StartupCheckMethod()
{ {
if (ConnectedToVPN) Visible = false; ClientResponseState s = Client.SendDataMessage(DataMessage.Status);
else QuitApp(); Console.WriteLine(s);
a.RetVal = true; Application.Invoke(delegate
} {
UpdateStatus(s);
protected void QuitApp() });
}
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(); Thread.Sleep(timeout);
wrapper.Join(); pop.Close();
} })
if (trayIcon != null) trayIcon.Dispose();
Application.Quit();
}
void OnStartClicked(object sender, EventArgs e)
{
ClientResponseState s = Client.SendDataMessage(DataMessage.Start);
switch (s)
{ {
case ClientResponseState.Error: IsBackground = true
case ClientResponseState.ServerError: };
case ClientResponseState.Offline: wait.Start();
UpdateStatus(s); }
MessageDialog md = new MessageDialog(this,
DialogFlags.DestroyWithParent, MessageType.Error, protected void OnDeleteEvent(object sender, DeleteEventArgs a)
ButtonsType.Ok, "Error starting VPN. Is the daemon running?"); {
md.Run(); if (ConnectedToVPN)
md.Destroy(); {
break; Visible = false;
} UpdateTrayMenu();
} }
else QuitApp();
void OnStopClicked(object sender, EventArgs e) a.RetVal = true;
{ }
ClientResponseState s = Client.SendDataMessage(DataMessage.Stop);
switch (s) protected void QuitApp()
{ {
case ClientResponseState.Error: if (wrapper != null)
case ClientResponseState.ServerError: {
case ClientResponseState.Online: wrapper.Kill();
UpdateStatus(s); wrapper.Join();
MessageDialog md = new MessageDialog(this, }
DialogFlags.DestroyWithParent, MessageType.Error, if (trayIcon != null) trayIcon.Dispose();
ButtonsType.Ok, "Error stopping VPN. Is the daemon running?"); Application.Quit();
md.Run(); }
md.Destroy();
break; void OnStartClicked(object sender, EventArgs e)
} {
} ClientResponseState s = Client.SendDataMessage(DataMessage.Start);
switch (s)
void OnRefreshClicked(object sender, EventArgs e) {
{ case ClientResponseState.Error:
Refresh(); case ClientResponseState.ServerError:
} case ClientResponseState.Offline:
UpdateStatus(s);
void OnViewClientsClicked(object sender, EventArgs e) MessageDialog md = new MessageDialog(this,
{ DialogFlags.DestroyWithParent, MessageType.Error,
Refresh(); 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();
} }
} }

View File

@ -63,6 +63,9 @@
<Reference Include="libnotify.net"> <Reference Include="libnotify.net">
<HintPath>..\..\..\net-libs\libnotify.net.dll</HintPath> <HintPath>..\..\..\net-libs\libnotify.net.dll</HintPath>
</Reference> </Reference>
<Reference Include="appindicator3-sharp">
<HintPath>..\..\..\..\..\lib\mono\gac\appindicator3-sharp\12.10.0.0__bf81553d61d0bd64\appindicator3-sharp.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="gtk-gui\gui.stetic"> <EmbeddedResource Include="gtk-gui\gui.stetic">
@ -75,6 +78,7 @@
<Compile Include="gtk-gui\MainWindow.cs" /> <Compile Include="gtk-gui\MainWindow.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PopupMenu.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MelonVPNCore\MelonVPNCore.csproj"> <ProjectReference Include="..\MelonVPNCore\MelonVPNCore.csproj">

View File

@ -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;
}
}

View File

@ -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/MiniMelonVPNOnline.png /usr/lib/melon-vpn/
sudo cp MelonVPNClient/bin/Release/MelonVPNDesktopIcon.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 # make all exe files in melon vpn projects executable
sudo chmod +x /usr/lib/melon-vpn/*.exe sudo chmod +x /usr/lib/melon-vpn/*.exe