diff --git a/CHANGELOG.md b/CHANGELOG.md
index aeece42..9ebf99d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [0.75] - 2020-02-18
+
+### Added
+- when a firewall rule gets changed a new notification pupup gets displayed
+- notification window has now tabs
+-- added tab with rule changed notifications
+- notification window can now be opened/closed by single clicking on the tray icon, without discarding contents
+
+### Fixed
+- settings backups names dont longer contian ':'
## [0.74] - 2019-12-21
diff --git a/PrivateSetup/PrivateSetup.csproj b/PrivateSetup/PrivateSetup.csproj
index e3f1f8f..cdab7f6 100644
--- a/PrivateSetup/PrivateSetup.csproj
+++ b/PrivateSetup/PrivateSetup.csproj
@@ -33,6 +33,7 @@
TRACE
prompt
4
+ false
icon.ico
diff --git a/PrivateSetup/Resources/icon.ico b/PrivateSetup/Resources/icon.ico
new file mode 100644
index 0000000..9ffbc2a
Binary files /dev/null and b/PrivateSetup/Resources/icon.ico differ
diff --git a/PrivateSetup/SetupWorker.cs b/PrivateSetup/SetupWorker.cs
index 7b673fd..4444978 100644
--- a/PrivateSetup/SetupWorker.cs
+++ b/PrivateSetup/SetupWorker.cs
@@ -147,12 +147,14 @@ private List Extract(bool Install = false)
progData = @"C:\ProgramData";
IniPath = progData + "\\" + SetupData.AppKey;
- if (!Directory.Exists(IniPath))
- Directory.CreateDirectory(IniPath);
- MiscFunc.SetAnyDirSec(IniPath); // ensure access for non admins
}
else // Note: when the ini file ins inside the application directory the app starts in portable mode
- IniPath = Data.InstallationPath;
+ IniPath = Data.InstallationPath + @"\Data";
+
+ if (!Directory.Exists(IniPath))
+ Directory.CreateDirectory(IniPath);
+ MiscFunc.SetAnyDirSec(IniPath); // ensure access for non admins
+
IniPath += @"\" + SetupData.AppKey + ".ini";
App.IniWriteValue(IniPath, "Startup", "Usage", Data.Use.ToString());
diff --git a/PrivateWin10/App.xaml.cs b/PrivateWin10/App.xaml.cs
index f071e62..f4a5c54 100644
--- a/PrivateWin10/App.xaml.cs
+++ b/PrivateWin10/App.xaml.cs
@@ -262,11 +262,12 @@ public static void Main(string[] args)
InitLicense();
+ MainWnd = new MainWindow();
+
TrayIcon = new TrayIcon();
TrayIcon.Action += TrayAction;
TrayIcon.Visible = (GetConfigInt("Startup", "Tray", 0) != 0) || App.TestArg("-autorun");
- MainWnd = new MainWindow();
if (!App.TestArg("-autorun") || !TrayIcon.Visible)
MainWnd.Show();
@@ -607,6 +608,14 @@ static void TrayAction(object sender, TrayIcon.TrayEventArgs args)
MainWnd.Show();
break;
}
+ case TrayIcon.Actions.ToggleNotify:
+ {
+ if (MainWnd.notificationWnd.IsVisible)
+ MainWnd.notificationWnd.HideWnd();
+ else if (!MainWnd.notificationWnd.IsEmpty())
+ MainWnd.notificationWnd.ShowWnd();
+ break;
+ }
case TrayIcon.Actions.CloseApplication:
{
if (Priv10Service.IsInstalled() && AdminFunc.IsAdministrator())
diff --git a/PrivateWin10/Controls/ConnectionNotify.xaml b/PrivateWin10/Controls/ConnectionNotify.xaml
new file mode 100644
index 0000000..de6cd0c
--- /dev/null
+++ b/PrivateWin10/Controls/ConnectionNotify.xaml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PrivateWin10/Controls/ConnectionNotify.xaml.cs b/PrivateWin10/Controls/ConnectionNotify.xaml.cs
new file mode 100644
index 0000000..903dfe6
--- /dev/null
+++ b/PrivateWin10/Controls/ConnectionNotify.xaml.cs
@@ -0,0 +1,411 @@
+using PrivateWin10.Windows;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PrivateWin10.Controls
+{
+ ///
+ /// Interaction logic for ConnectionNotify.xaml
+ ///
+ public partial class ConnectionNotify : UserControl, INotificationTab
+ {
+ DataGridExt consGridExt;
+
+ public event EventHandler Emptied;
+
+ public ConnectionNotify()
+ {
+ InitializeComponent();
+
+ this.btnPrev.Content = Translate.fmt("lbl_prev");
+ this.btnNext.Content = Translate.fmt("lbl_next");
+ this.lblRemember.Content = Translate.fmt("lbl_remember");
+ this.btnIgnore.Content = Translate.fmt("lbl_ignore");
+ this.btnApply.Content = Translate.fmt("lbl_apply");
+ this.consGrid.Columns[0].Header = Translate.fmt("lbl_protocol");
+ this.consGrid.Columns[1].Header = Translate.fmt("lbl_ip_port");
+ this.consGrid.Columns[2].Header = Translate.fmt("lbl_remote_host");
+ this.consGrid.Columns[3].Header = Translate.fmt("lbl_time_stamp");
+ this.consGrid.Columns[4].Header = Translate.fmt("lbl_pid");
+
+ consGridExt = new DataGridExt(consGrid);
+ consGridExt.Restore(App.GetConfig("GUI", "consGrid_Columns", ""));
+
+
+ cmbAccess.Items.Add(new ComboBoxItem() { Content = Translate.fmt("acl_none"), Tag = ProgramSet.Config.AccessLevels.Unconfigured });
+ cmbAccess.Items.Add(new ComboBoxItem() { Content = Translate.fmt("acl_silence"), Tag = ProgramSet.Config.AccessLevels.StopNotify });
+ cmbAccess.Items.Add(new ComboBoxItem() { Content = Translate.fmt("acl_allow"), Tag = ProgramSet.Config.AccessLevels.FullAccess });
+ cmbAccess.Items.Add(new ComboBoxItem() { Content = Translate.fmt("acl_edit"), Tag = ProgramSet.Config.AccessLevels.CustomConfig });
+ cmbAccess.Items.Add(new ComboBoxItem() { Content = Translate.fmt("acl_lan"), Tag = ProgramSet.Config.AccessLevels.LocalOnly });
+ cmbAccess.Items.Add(new ComboBoxItem() { Content = Translate.fmt("acl_block"), Tag = ProgramSet.Config.AccessLevels.BlockAccess });
+ foreach (ComboBoxItem item in cmbAccess.Items)
+ item.Background = ProgramControl.GetAccessColor((ProgramSet.Config.AccessLevels)item.Tag);
+
+#if DEBUG
+ cmbRemember.Items.Add(new ComboBoxItem() { Content = Translate.fmt("lbl_temp", "1 min"), Tag = 60 });
+#endif
+ cmbRemember.Items.Add(new ComboBoxItem() { Content = Translate.fmt("lbl_temp", "5 min"), Tag = 5 * 60 });
+ cmbRemember.Items.Add(new ComboBoxItem() { Content = Translate.fmt("lbl_temp", "15 min"), Tag = 15 * 60 });
+ cmbRemember.Items.Add(new ComboBoxItem() { Content = Translate.fmt("lbl_temp", "1 h"), Tag = 60 * 60 });
+ cmbRemember.SelectedIndex = cmbRemember.Items.Count - 1; // default is 1h
+ cmbRemember.Items.Add(new ComboBoxItem() { Content = Translate.fmt("lbl_temp", "24 h"), Tag = 24 * 60 * 60 });
+ cmbRemember.Items.Add(new ComboBoxItem() { Content = Translate.fmt("lbl_permanent"), Tag = 0 });
+ }
+
+ public void Closing()
+ {
+ App.SetConfig("GUI", "consGrid_Columns", consGridExt.Save());
+ }
+
+ int curIndex = -1;
+ private SortedDictionary>> mEvents = new SortedDictionary>>();
+
+ private List mEventList = new List();
+
+ public bool IsEmpty()
+ {
+ return mEventList.Count == 0;
+ }
+
+ public bool Add(ProgramSet progs, Priv10Engine.FwEventArgs args)
+ {
+ ProgramID id = args.entry.ProgID;
+ Program prog = null;
+ if (!progs.Programs.TryGetValue(id, out prog))
+ return false;
+
+ Tuple> list;
+ if (!mEvents.TryGetValue(id, out list))
+ {
+ if (args.update)
+ return false;
+
+ list = new Tuple>(prog, new List());
+ mEvents.Add(id, list);
+ mEventList.Add(id);
+ }
+
+ if (args.update)
+ {
+ foreach (var oldEntry in list.Item2)
+ {
+ if (oldEntry.entry.guid.Equals(args.entry.guid))
+ {
+ oldEntry.entry.Update(args.entry);
+ break;
+ }
+ }
+ }
+ else
+ {
+ list.Item2.Add(args);
+ }
+
+ int oldIndex = curIndex;
+
+ if (curIndex < 0)
+ curIndex = 0;
+ else if (curIndex >= mEventList.Count)
+ curIndex = mEventList.Count - 1;
+
+ UpdateIndex();
+
+ // don't update if the event is for a different entry
+ int index = mEventList.FindIndex((x) => { return id.CompareTo(x) == 0; });
+ if (curIndex == index)
+ LoadCurrent(oldIndex == curIndex);
+
+ return true;
+ }
+
+ private void UpdateIndex()
+ {
+ lblIndex.Text = string.Format("{0}/{1}", curIndex + 1, mEventList.Count);
+ btnPrev.IsEnabled = curIndex + 1 > 1;
+ btnNext.IsEnabled = curIndex + 1 < mEventList.Count;
+ }
+
+ private void LoadCurrent(bool bUpdate = false)
+ {
+ if (!bUpdate)
+ {
+ ProgramSet.Config.AccessLevels NetAccess = ProgramSet.Config.AccessLevels.Unconfigured;
+
+ cmbAccess.Background = ProgramControl.GetAccessColor(NetAccess);
+ WpfFunc.CmbSelect(cmbAccess, NetAccess.ToString());
+
+ btnApply.IsEnabled = false;
+ }
+
+ ProgramID id = mEventList.ElementAt(curIndex);
+ Tuple> list = mEvents[id];
+
+ //int PID = list.Item2.Count > 0 ? list.Item2.First().FwEvent.ProcessId : 0;
+ string FilePath = list.Item2.Count > 0 ? list.Item2.First().entry.FwEvent.ProcessFileName : "";
+
+ imgIcon.Source = ImgFunc.GetIcon(FilePath, imgIcon.Width); // todo: use .progSet.GetIcon instead?
+ //lblName.Text = id.GetDisplayName(false);
+ grpBox.Header = list.Item1.Description;
+ //lblPID.Text = string.Format("{0} ({1})", System.IO.Path.GetFileName(id.Path), PID);
+ lblPID.Text = System.IO.Path.GetFileName(id.Path);
+
+ List services = new List();
+
+ btnIgnore.IsEnabled = true;
+ consGrid.Items.Clear();
+ foreach (Priv10Engine.FwEventArgs args in list.Item2)
+ {
+ consGrid.Items.Insert(0, new ConEntry(args.entry));
+
+ if (args.services != null)
+ {
+ foreach (var service in args.services)
+ {
+ if (!services.Contains(service))
+ services.Add(service);
+ }
+ }
+ }
+
+ if (services.Count > 0)
+ {
+ cmbService.Visibility = Visibility.Visible;
+ cmbService.Items.Clear();
+ foreach (var service in services)
+ cmbService.Items.Add(service);
+ cmbService.SelectedIndex = -1;
+ cmbService.Text = Translate.fmt("msg_pick_svc");
+ }
+ else
+ {
+ cmbService.Visibility = Visibility.Collapsed;
+ switch (id.Type)
+ {
+ //case ProgramList.Types.Program: lblSubName.Text = ""; break;
+ case ProgramID.Types.Service: lblSubName.Text = id.GetServiceId(); break;
+ case ProgramID.Types.App: lblSubName.Text = id.GetPackageName(); break;
+ default: lblSubName.Text = ""; break;
+ }
+ }
+ lblPath.Text = id.Path;
+ }
+
+ private void btnPrev_Click(object sender, RoutedEventArgs e)
+ {
+ if (curIndex > 0)
+ curIndex--;
+ UpdateIndex();
+ LoadCurrent();
+ }
+
+ private void btnNext_Click(object sender, RoutedEventArgs e)
+ {
+ if (curIndex + 1 < mEventList.Count)
+ curIndex++;
+ UpdateIndex();
+ LoadCurrent();
+ }
+
+ private void PopEntry()
+ {
+ ProgramID id = mEventList.ElementAt(curIndex);
+
+ mEventList.RemoveAt(curIndex);
+ mEvents.Remove(id);
+
+ if (curIndex >= mEventList.Count)
+ curIndex = mEventList.Count - 1;
+ if (curIndex < 0)
+ {
+ btnIgnore.IsEnabled = false;
+ consGrid.Items.Clear();
+ curIndex = -1;
+ Emptied?.Invoke(this, new EventArgs());
+ return;
+ }
+
+ UpdateIndex();
+ LoadCurrent();
+ }
+
+ private void btnIgnore_Click(object sender, RoutedEventArgs e)
+ {
+ PopEntry();
+ }
+
+ private void cmbAccess_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (cmbService.Visibility == Visibility.Visible && cmbService.SelectedIndex == -1)
+ {
+ btnApply.IsEnabled = false;
+ return;
+ }
+
+ ProgramSet.Config.AccessLevels NetAccess = (ProgramSet.Config.AccessLevels)(cmbAccess.SelectedItem as ComboBoxItem).Tag;
+ cmbAccess.Background = ProgramControl.GetAccessColor(NetAccess);
+ btnApply.IsEnabled = NetAccess != ProgramSet.Config.AccessLevels.Unconfigured;
+ }
+
+ private bool MakeCustom(Program prog, UInt64 expiration, ConEntry entry = null)
+ {
+ FirewallRule rule = new FirewallRule() { guid = null, Profile = (int)FirewallRule.Profiles.All, Interface = (int)FirewallRule.Interfaces.All, Enabled = true };
+ rule.SetProgID(prog.ID);
+ rule.Name = FirewallManager.MakeRuleName(FirewallManager.CustomName, expiration != 0, prog.Description);
+ rule.Grouping = FirewallManager.RuleGroup;
+
+ if (entry != null)
+ {
+ rule.Direction = entry.Entry.FwEvent.Direction;
+ rule.Protocol = (int)entry.Entry.FwEvent.Protocol;
+ switch (entry.Entry.FwEvent.Protocol)
+ {
+ /*case (int)FirewallRule.KnownProtocols.ICMP:
+ case (int)FirewallRule.KnownProtocols.ICMPv6:
+
+ break;*/
+ case (int)FirewallRule.KnownProtocols.TCP:
+ case (int)FirewallRule.KnownProtocols.UDP:
+ rule.LocalPorts = "*";
+ rule.RemotePorts = entry.Entry.FwEvent.RemotePort.ToString();
+ break;
+ }
+ rule.LocalAddresses = "*";
+ rule.RemoteAddresses = entry.Entry.FwEvent.RemoteAddress.ToString();
+ }
+ else
+ {
+ rule.Direction = FirewallRule.Directions.Bidirectiona;
+ }
+
+ RuleWindow ruleWnd = new RuleWindow(new List() { prog }, rule);
+ if (ruleWnd.ShowDialog() != true)
+ return false;
+
+ if (!App.client.UpdateRule(rule, expiration))
+ {
+ MessageBox.Show(Translate.fmt("msg_rule_failed"), App.Title, MessageBoxButton.OK, MessageBoxImage.Exclamation);
+ return false;
+ }
+
+ return true;
+ }
+
+ private UInt64 GetExpiration()
+ {
+ ComboBoxItem remember = (cmbRemember.SelectedItem as ComboBoxItem);
+ if (remember != null && (int)remember.Tag != 0)
+ return MiscFunc.GetUTCTime() + (UInt64)(int)remember.Tag;
+ return 0;
+ }
+
+ private void btnApply_Click(object sender, RoutedEventArgs e)
+ {
+ ProgramID id = mEventList.ElementAt(curIndex);
+ Tuple> list = mEvents[id];
+
+ UInt64 expiration = GetExpiration();
+
+ ProgramSet.Config.AccessLevels NetAccess = (ProgramSet.Config.AccessLevels)(cmbAccess.SelectedItem as ComboBoxItem).Tag;
+
+ if (NetAccess == ProgramSet.Config.AccessLevels.CustomConfig)
+ {
+ if (!MakeCustom(list.Item1, expiration))
+ return;
+ }
+ else
+ {
+ ProgramSet prog = App.client.GetProgram(id);
+
+ if (NetAccess == ProgramSet.Config.AccessLevels.StopNotify)
+ {
+ if (expiration != 0)
+ prog.config.SilenceUntill = expiration;
+ else
+ prog.config.Notify = false;
+
+ App.client.UpdateProgram(prog.guid, prog.config);
+ }
+ else
+ {
+ prog.config.NetAccess = NetAccess;
+
+ App.client.UpdateProgram(prog.guid, prog.config, expiration);
+ }
+ }
+ PopEntry();
+ }
+
+ private void consGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ ConEntry entry = (ConEntry)consGrid.SelectedItem;
+ if (entry == null)
+ return;
+
+ ProgramID id = mEventList.ElementAt(curIndex);
+ Tuple> list = mEvents[id];
+
+ UInt64 expiration = GetExpiration();
+ if (MakeCustom(list.Item1, expiration, entry))
+ PopEntry();
+ }
+
+ public class ConEntry : INotifyPropertyChanged
+ {
+ public Program.LogEntry Entry;
+
+ public ConEntry(Program.LogEntry entry)
+ {
+ Entry = entry;
+ }
+
+ public string Protocol { get { return Translate.fmt(Entry.FwEvent.Direction == FirewallRule.Directions.Inbound ? "str_in" : "str_out", NetFunc.Protocol2Str(Entry.FwEvent.Protocol)); } }
+
+ public string Address { get { if (Entry.FwEvent.RemoteAddress == null) return ""; return Entry.FwEvent.RemoteAddress.ToString() + ":" + Entry.FwEvent.RemotePort.ToString(); } }
+
+ public string RemoteHost { get { return Entry.RemoteHostName; } }
+
+ public string TimeStamp { get { return Entry.FwEvent.TimeStamp.ToString("HH:mm:ss"); } }
+
+ public string ProcessID { get { return Entry.FwEvent.ProcessId.ToString(); } }
+
+ #region INotifyPropertyChanged Members
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ #endregion
+
+ #region Private Helpers
+
+ private void NotifyPropertyChanged(string propertyName)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+ #endregion
+ }
+
+ private void LblPath_MouseDown(object sender, MouseButtonEventArgs e)
+ {
+ string path = lblPath.Text;
+ if (!string.IsNullOrEmpty(path))
+ Process.Start("Explorer.exe", "/select, " + path);
+ }
+ }
+}
diff --git a/PrivateWin10/Controls/Converters.cs b/PrivateWin10/Controls/Converters.cs
index f246dba..10ff429 100644
--- a/PrivateWin10/Controls/Converters.cs
+++ b/PrivateWin10/Controls/Converters.cs
@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using System.Windows;
using System.Windows.Data;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
namespace PrivateWin10.Controls
{
@@ -59,4 +64,26 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
throw new NotImplementedException();
}
}
+
+ [ValueConversion(typeof(Icon), typeof(ImageSource))]
+ public class IconToImageSourceConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var icon = value as Icon;
+ if (icon == null)
+ {
+ //Trace.TraceWarning("Attempted to convert {0} instead of Icon object in IconToImageSourceConverter", value);
+ return null;
+ }
+
+ ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
+ return imageSource;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
diff --git a/PrivateWin10/Controls/FirewallRuleList.xaml.cs b/PrivateWin10/Controls/FirewallRuleList.xaml.cs
index 1b844c7..edf12d7 100644
--- a/PrivateWin10/Controls/FirewallRuleList.xaml.cs
+++ b/PrivateWin10/Controls/FirewallRuleList.xaml.cs
@@ -508,10 +508,12 @@ private void RulesGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
public class RuleItem : INotifyPropertyChanged
{
public FirewallRuleEx Rule;
+ public Program Prog;
- public RuleItem(FirewallRuleEx rule)
+ public RuleItem(FirewallRuleEx rule, Program prog = null)
{
Rule = rule;
+ Prog = prog;
}
public bool TestFilter(string textFilter)
diff --git a/PrivateWin10/Controls/ProgramTreeControl.xaml.cs b/PrivateWin10/Controls/ProgramTreeControl.xaml.cs
index 36c868d..0fd992d 100644
--- a/PrivateWin10/Controls/ProgramTreeControl.xaml.cs
+++ b/PrivateWin10/Controls/ProgramTreeControl.xaml.cs
@@ -244,12 +244,12 @@ private void treeView_SelectionChanged(object sender, SelectionChangedEventArgs
SelectionChanged?.Invoke(this, new EventArgs());
}
- private async void treeView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+ private void treeView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
}
- private async void hdrSort_ClickAsync(object sender, RoutedEventArgs e)
+ private void hdrSort_ClickAsync(object sender, RoutedEventArgs e)
{
var header = sender as GridViewColumnHeader;
var Member = header.Tag?.ToString();
diff --git a/PrivateWin10/Controls/RuleNotify.xaml b/PrivateWin10/Controls/RuleNotify.xaml
new file mode 100644
index 0000000..3fcb998
--- /dev/null
+++ b/PrivateWin10/Controls/RuleNotify.xaml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PrivateWin10/Controls/RuleNotify.xaml.cs b/PrivateWin10/Controls/RuleNotify.xaml.cs
new file mode 100644
index 0000000..eafab9c
--- /dev/null
+++ b/PrivateWin10/Controls/RuleNotify.xaml.cs
@@ -0,0 +1,199 @@
+using PrivateWin10.Windows;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PrivateWin10.Controls
+{
+ ///
+ /// Interaction logic for RuleNotify.xaml
+ ///
+ public partial class RuleNotify : UserControl, INotificationTab
+ {
+ DataGridExt rulesGridExt;
+
+ TextBlock Ignore;
+ TextBlock Approve;
+ TextBlock Reject;
+
+ public event EventHandler Emptied;
+
+ public RuleNotify()
+ {
+ InitializeComponent();
+
+ rulesGridExt = new DataGridExt(rulesGrid);
+ rulesGridExt.Restore(App.GetConfig("GUI", "rulesGrid_Columns", ""));
+
+ //this.lblGuide.Text = Translate.fmt("lbl_rule_guide");
+
+ this.lblDetails.Header = Translate.fmt("lbl_rule_details");
+ this.lblProt.Text = Translate.fmt("lbl_protocol_");
+ this.lblLocal.Text = Translate.fmt("lbl_local");
+ this.lblRemote.Text = Translate.fmt("lbl_remote");
+ this.lblProg.Text = Translate.fmt("lbl_program_");
+
+ this.rulesGrid.Columns[1].Header = Translate.fmt("lbl_name");
+ this.rulesGrid.Columns[2].Header = Translate.fmt("lbl_enabled");
+ this.rulesGrid.Columns[3].Header = Translate.fmt("lbl_action");
+ this.rulesGrid.Columns[4].Header = Translate.fmt("lbl_direction");
+ this.rulesGrid.Columns[5].Header = Translate.fmt("lbl_program");
+
+ Ignore = this.IgnoreSB.Content as TextBlock;
+ Approve = this.ApproveSB.Content as TextBlock;
+ Reject = this.RejectSB.Content as TextBlock;
+
+ this.Ignore.Text = Translate.fmt("lbl_ignore");
+ (this.IgnoreSB.MenuItemsSource[0] as MenuItem).Header = Translate.fmt("lbl_ignore_all");
+ this.Approve.Text = Translate.fmt("lbl_approve");
+ (this.ApproveSB.MenuItemsSource[0] as MenuItem).Header = Translate.fmt("lbl_approve_all");
+ this.Reject.Text = Translate.fmt("lbl_reject");
+ (this.RejectSB.MenuItemsSource[0] as MenuItem).Header = Translate.fmt("lbl_reject_all");
+
+ UpdateState();
+ }
+
+ public void Closing()
+ {
+ App.SetConfig("GUI", "rulesGrid_Columns", rulesGridExt.Save());
+ }
+
+ public void RemoveCurrent()
+ {
+ var item = rulesGrid.SelectedItem as FirewallRuleList.RuleItem;
+ rulesGrid.SelectedIndex++;
+ if (item != null)
+ rulesGrid.Items.Remove(item);
+ UpdateState();
+ }
+
+ public void UpdateState()
+ {
+ this.IgnoreSB.IsEnabled = rulesGrid.Items.IsEmpty ? false : true;
+ this.ApproveSB.IsEnabled = rulesGrid.Items.IsEmpty ? false : true;
+ this.RejectSB.IsEnabled = rulesGrid.Items.IsEmpty ? false : true;
+
+ if (rulesGrid.Items.IsEmpty)
+ Emptied?.Invoke(this, new EventArgs());
+ else if (rulesGrid.SelectedItem == null)
+ rulesGrid.SelectedItem = rulesGrid.Items[0];
+ }
+
+ public bool Add(Priv10Engine.ChangeArgs args)
+ {
+ foreach (FirewallRuleList.RuleItem item in rulesGrid.Items)
+ {
+ if (item.Rule.guid == args.rule.guid)
+ {
+ item.Update(args.rule);
+ return false;
+ }
+ }
+
+ rulesGrid.Items.Add(new FirewallRuleList.RuleItem(args.rule, args.prog));
+ UpdateState();
+ return true;
+ }
+
+ public bool IsEmpty()
+ {
+ return rulesGrid.Items.IsEmpty;
+ }
+
+ private void RulesGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ var item = rulesGrid.SelectedItem as FirewallRuleList.RuleItem;
+ if (item == null)
+ return;
+
+ this.txtProt.Text = item.Protocol;
+ if (item.Rule.Protocol == (int)FirewallRule.KnownProtocols.ICMP || item.Rule.Protocol == (int)FirewallRule.KnownProtocols.ICMPv6)
+ this.txtProt.Text += ";" + item.ICMPOptions;
+ this.txtLocal.Text = item.SrcAddress + ":" + item.SrcPorts;
+ this.txtRemote.Text = item.DestPorts + ":" + item.DestPorts;
+ this.txtProg.Text = item.Program;
+ }
+
+ private void RulesGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ var item = rulesGrid.SelectedItem as FirewallRuleList.RuleItem;
+ if (item == null)
+ return;
+
+ RuleWindow ruleWnd = new RuleWindow(new List() { item.Prog }, item.Rule);
+ if (ruleWnd.ShowDialog() != true)
+ return;
+
+ if (!App.client.UpdateRule(item.Rule))
+ {
+ MessageBox.Show(Translate.fmt("msg_rule_failed"), App.Title, MessageBoxButton.OK, MessageBoxImage.Exclamation);
+ return;
+ }
+
+ RemoveCurrent();
+ }
+
+ private void BtnIgnoreAll_Click(object sender, RoutedEventArgs e)
+ {
+ rulesGrid.Items.Clear();
+ UpdateState();
+ }
+
+ private void BtnIgnore_Click(object sender, MouseButtonEventArgs e)
+ {
+ var item = rulesGrid.SelectedItem as FirewallRuleList.RuleItem;
+ if (item == null)
+ return;
+
+ RemoveCurrent();
+ }
+
+ private void BtnApproveAll_Click(object sender, RoutedEventArgs e)
+ {
+ foreach (FirewallRuleList.RuleItem item in rulesGrid.Items)
+ App.client.SetRuleApproval(Priv10Engine.ApprovalMode.ApproveChanges, item.Rule);
+
+ rulesGrid.Items.Clear();
+ UpdateState();
+ }
+
+ private void BtnApprove_Click(object sender, MouseButtonEventArgs e)
+ {
+ var item = rulesGrid.SelectedItem as FirewallRuleList.RuleItem;
+ if (item != null)
+ App.client.SetRuleApproval(Priv10Engine.ApprovalMode.ApproveChanges, item.Rule);
+
+ RemoveCurrent();
+ }
+
+ private void BtnRejectAll_Click(object sender, RoutedEventArgs e)
+ {
+ foreach (FirewallRuleList.RuleItem item in rulesGrid.Items)
+ App.client.SetRuleApproval(Priv10Engine.ApprovalMode.RestoreRules, item.Rule);
+
+ rulesGrid.Items.Clear();
+ UpdateState();
+ }
+
+ private void BtnReject_Click(object sender, MouseButtonEventArgs e)
+ {
+ var item = rulesGrid.SelectedItem as FirewallRuleList.RuleItem;
+ if (item != null)
+ App.client.SetRuleApproval(Priv10Engine.ApprovalMode.RestoreRules, item.Rule);
+
+ RemoveCurrent();
+ }
+ }
+}
diff --git a/PrivateWin10/Controls/SplitButton.xaml b/PrivateWin10/Controls/SplitButton.xaml
new file mode 100644
index 0000000..9e53c87
--- /dev/null
+++ b/PrivateWin10/Controls/SplitButton.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/PrivateWin10/Controls/SplitButton.xaml.cs b/PrivateWin10/Controls/SplitButton.xaml.cs
new file mode 100644
index 0000000..d8f0bf1
--- /dev/null
+++ b/PrivateWin10/Controls/SplitButton.xaml.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PrivateWin10.Controls
+{
+ public partial class SplitButton : UserControl
+ {
+ private Button button;
+
+ private ObservableCollection