diff --git a/.gitignore b/.gitignore index 2bd26c6..8eb34d8 100644 --- a/.gitignore +++ b/.gitignore @@ -322,4 +322,5 @@ ASALocalRun/ *.binlog # NVidia Nsight GPU debugger configuration file -*.nvuser \ No newline at end of file +*.nvuser +/.vs/ diff --git a/Myrtille.Capture.Services/App.config b/Myrtille.Capture.Services/App.config new file mode 100644 index 0000000..475eb3f --- /dev/null +++ b/Myrtille.Capture.Services/App.config @@ -0,0 +1,21 @@ + + + + +
+ + + + + + + + + D:\Recivedlog + + + D:\Videolog + + + + \ No newline at end of file diff --git a/Myrtille.Capture.Services/Library.cs b/Myrtille.Capture.Services/Library.cs new file mode 100644 index 0000000..e84c64e --- /dev/null +++ b/Myrtille.Capture.Services/Library.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + + +namespace Myrtille.Capture.Services +{ + public static class Library + { + public static void WriteErrorLog(Exception ex) + { + StreamWriter sw = null; + try + { + sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\LogFile.txt", true); + sw.WriteLine(DateTime.Now.ToString() + ": " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim()); + Console.WriteLine(DateTime.Now.ToString() + ": " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim()); + sw.Flush(); + sw.Close(); + } + catch + { + } + } + + public static void WriteErrorLog(string Message) + { + StreamWriter sw = null; + try + { + sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\LogFile.txt", true); + sw.WriteLine(DateTime.Now.ToString() + ": " + Message); + Console.WriteLine(DateTime.Now.ToString() + ": " + Message); + sw.Flush(); + sw.Close(); + } + catch + { + } + } + + } +} diff --git a/Myrtille.Capture.Services/Myrtille.Capture.Services.csproj b/Myrtille.Capture.Services/Myrtille.Capture.Services.csproj new file mode 100644 index 0000000..0f545f9 --- /dev/null +++ b/Myrtille.Capture.Services/Myrtille.Capture.Services.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3} + Exe + Myrtille.Capture.Services + Myrtille.Capture.Services + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + Component + + + ProjectInstaller.cs + + + True + True + Settings.settings + + + Component + + + SaveService.cs + + + + + + + Always + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + ProjectInstaller.cs + + + + + PreserveNewest + + + + + {0438D53A-9A57-423C-9E54-9612C4576257} + 1 + 0 + 0 + tlbimp + False + True + + + + + {37630774-1321-4E6A-8661-4430A8946E9E} + Myrtille.Common + + + + \ No newline at end of file diff --git a/Myrtille.Capture.Services/Program.cs b/Myrtille.Capture.Services/Program.cs new file mode 100644 index 0000000..84d3acf --- /dev/null +++ b/Myrtille.Capture.Services/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace Myrtille.Capture.Services +{ + static class Program + { + public static void Main(string[] args) + { + if (args.FirstOrDefault()?.ToUpper() == "/CONSOLE") + { + RunAsConsole(); + } + else + { + RunAsService(); + } + } + private static void RunAsConsole() + { + SaveService serv = new SaveService(); + serv.StartService(); + + Console.WriteLine("Running service as console. Press any key to stop."); + Console.ReadKey(); + + serv.Stop(); + } + private static void RunAsService() + { + /* Warning: Don't load the object graph or + * initialize anything in here. + * + * Initialize everything in TestService.StartService() instead + */ + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new SaveService() + }; + ServiceBase.Run(ServicesToRun); + } + } +} diff --git a/Myrtille.Capture.Services/ProjectInstaller.Designer.cs b/Myrtille.Capture.Services/ProjectInstaller.Designer.cs new file mode 100644 index 0000000..c1094d0 --- /dev/null +++ b/Myrtille.Capture.Services/ProjectInstaller.Designer.cs @@ -0,0 +1,59 @@ +namespace VideoSavingService +{ + partial class ProjectInstaller + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); + this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); + // + // serviceProcessInstaller1 + // + this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; + this.serviceProcessInstaller1.Password = null; + this.serviceProcessInstaller1.Username = null; + this.serviceProcessInstaller1.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.serviceProcessInstaller1_AfterInstall); + // + // serviceInstaller1 + // + this.serviceInstaller1.ServiceName = "Vide Save Service"; + this.serviceInstaller1.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.serviceInstaller1_AfterInstall); + // + // ProjectInstaller + // + this.Installers.AddRange(new System.Configuration.Install.Installer[] { + this.serviceProcessInstaller1, + this.serviceInstaller1}); + + } + + #endregion + + private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; + private System.ServiceProcess.ServiceInstaller serviceInstaller1; + } +} \ No newline at end of file diff --git a/Myrtille.Capture.Services/ProjectInstaller.cs b/Myrtille.Capture.Services/ProjectInstaller.cs new file mode 100644 index 0000000..fa75b9c --- /dev/null +++ b/Myrtille.Capture.Services/ProjectInstaller.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration.Install; +using System.Linq; +using System.Threading.Tasks; + +namespace VideoSavingService +{ + [RunInstaller(true)] + public partial class ProjectInstaller : System.Configuration.Install.Installer + { + public ProjectInstaller() + { + InitializeComponent(); + } + + private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e) + { + + } + + private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e) + { + + } + } +} diff --git a/Myrtille.Capture.Services/ProjectInstaller.resx b/Myrtille.Capture.Services/ProjectInstaller.resx new file mode 100644 index 0000000..5c8b468 --- /dev/null +++ b/Myrtille.Capture.Services/ProjectInstaller.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 196, 17 + + + False + + \ No newline at end of file diff --git a/Myrtille.Capture.Services/Properties/AssemblyInfo.cs b/Myrtille.Capture.Services/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8d4efd1 --- /dev/null +++ b/Myrtille.Capture.Services/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("VideoSavingService")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("VideoSavingService")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2dd3560c-99e7-418d-8250-98789bb9c2a3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Myrtille.Capture.Services/Properties/Settings.Designer.cs b/Myrtille.Capture.Services/Properties/Settings.Designer.cs new file mode 100644 index 0000000..8a28fa5 --- /dev/null +++ b/Myrtille.Capture.Services/Properties/Settings.Designer.cs @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Myrtille.Capture.Services.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.0.1.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("D:\\Recivedlog")] + public string ImageLogPath { + get { + return ((string)(this["ImageLogPath"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("D:\\Videolog")] + public string VideosavePath { + get { + return ((string)(this["VideosavePath"])); + } + } + } +} diff --git a/Myrtille.Capture.Services/Properties/Settings.settings b/Myrtille.Capture.Services/Properties/Settings.settings new file mode 100644 index 0000000..9a7a707 --- /dev/null +++ b/Myrtille.Capture.Services/Properties/Settings.settings @@ -0,0 +1,12 @@ + + + + + + D:\Recivedlog + + + D:\Videolog + + + \ No newline at end of file diff --git a/Myrtille.Capture.Services/SaveService.Designer.cs b/Myrtille.Capture.Services/SaveService.Designer.cs new file mode 100644 index 0000000..1c71485 --- /dev/null +++ b/Myrtille.Capture.Services/SaveService.Designer.cs @@ -0,0 +1,37 @@ +namespace Myrtille.Capture.Services +{ + partial class SaveService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + + #endregion + } +} diff --git a/Myrtille.Capture.Services/SaveService.cs b/Myrtille.Capture.Services/SaveService.cs new file mode 100644 index 0000000..ed448f8 --- /dev/null +++ b/Myrtille.Capture.Services/SaveService.cs @@ -0,0 +1,67 @@ +using Myrtille.Capture.Services; +using Myrtille.Common.Capture; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Timers; + +namespace Myrtille.Capture.Services +{ + public partial class SaveService : ServiceBase + { + VideoSaver saver = new VideoSaver(Properties.Settings.Default.ImageLogPath, Properties.Settings.Default.VideosavePath); + private Timer timer1 = null; + + public SaveService() + { + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + new System.Threading.Thread(StartService).Start(); + } + internal void StartService() + { + timer1 = new Timer(); + this.timer1.Interval = 5000; //every 30 secs + this.timer1.Elapsed += new System.Timers.ElapsedEventHandler(this.timer1_Tick); + timer1.Enabled = true; + Library.WriteErrorLog("Test window service started"); + /* + This is the true composition root for a service, + so initialize everything in here + */ + Console.WriteLine("Starting service"); + } + + private void timer1_Tick(object sender, ElapsedEventArgs e) + { + //Write code here to do some job depends on your requirement + Library.WriteErrorLog("Timer ticked and some job has been done successfully"); + + + // Get all subdirectories + + string[] subdirectoryEntries = Directory.GetDirectories(Properties.Settings.Default.ImageLogPath) + .Select(Path.GetFileName) + .ToArray(); + // Loop through them to see if they have any other subdirectories + + foreach (string subdirectory in subdirectoryEntries) + saver.InQueueForSave(subdirectory); + } + + protected override void OnStop() + { + timer1.Enabled = false; + Library.WriteErrorLog("Test window service stopped"); + } + } +} diff --git a/Myrtille.Capture.Services/ffmpeg.exe b/Myrtille.Capture.Services/ffmpeg.exe new file mode 100644 index 0000000..d5830fb Binary files /dev/null and b/Myrtille.Capture.Services/ffmpeg.exe differ diff --git a/Myrtille.Common/Capture/Picture.cs b/Myrtille.Common/Capture/Picture.cs new file mode 100644 index 0000000..48299a8 --- /dev/null +++ b/Myrtille.Common/Capture/Picture.cs @@ -0,0 +1,294 @@ +using System; +using System.Linq; +using System.Drawing; +using System.IO; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.Collections.Concurrent; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; + +namespace Myrtille.Common.Capture +{ + public enum PictureFormat + { + CUR = 0, + PNG = 1, + JPEG = 2, + WEBP = 3 + } + + [Serializable] + public class SessionPicture + { + public int Idx; + public int PosX; + public int PosY; + public int Width; + public int Height; + public PictureFormat Format; + public int Quality; + public bool Fullscreen; + public byte[] Data; + } + + public class frame + { + public Guid id; + public SessionPicture img; + public DateTime timestamp; + } + + public static class PictureHelper + { + public static void WriteErrorLog(Exception ex) + { + StreamWriter sw = null; + try + { + sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\LogFile.txt", true); + sw.WriteLine(DateTime.Now.ToString() + ": " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim()); + Console.WriteLine(DateTime.Now.ToString() + ": " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim()); + sw.Flush(); + sw.Close(); + } + catch + { + } + } + + public static void WriteErrorLog(string Message) + { + StreamWriter sw = null; + try + { + sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\LogFile.txt", true); + sw.WriteLine(DateTime.Now.ToString() + ": " + Message); + Console.WriteLine(DateTime.Now.ToString() + ": " + Message); + sw.Flush(); + sw.Close(); + } + catch + { + } + } + /// + /// Save image as jpeg + /// + /// path where to save + /// image to save + public static void SaveJpeg(string path, Image img) + { + var qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L); + var jpegCodec = GetEncoderInfo("image/jpeg"); + + var encoderParams = new EncoderParameters(1); + encoderParams.Param[0] = qualityParam; + img.Save(path, jpegCodec, encoderParams); + } + + /// + /// Save image + /// + /// path where to save + /// image to save + /// codec info + public static void Save(string path, Image img, ImageCodecInfo imageCodecInfo) + { + var qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L); + + var encoderParams = new EncoderParameters(1); + encoderParams.Param[0] = qualityParam; + img.Save(path, imageCodecInfo, encoderParams); + } + + /// + /// get codec info by mime type + /// + /// + /// + public static ImageCodecInfo GetEncoderInfo(string mimeType) + { + return ImageCodecInfo.GetImageEncoders().FirstOrDefault(t => t.MimeType == mimeType); + } + + /// + /// the image remains the same size, and it is placed in the middle of the new canvas + /// + /// image to put on canvas + /// canvas width + /// canvas height + /// canvas color + /// + public static Image PutOnCanvas(Image image, int width, int height, Color canvasColor) + { + var res = new Bitmap(width, height); + using (var g = Graphics.FromImage(res)) + { + g.Clear(canvasColor); + var x = (width - image.Width) / 2; + var y = (height - image.Height) / 2; + g.DrawImageUnscaled(image, x, y, image.Width, image.Height); + } + + return res; + } + + + public static Image MergeTwoImages(Image image1, Image image2, int x, int y) + { + //var res = new Bitmap(width, height); + using (var g = Graphics.FromImage(image1)) + { + g.DrawImageUnscaled(image2, x, y, image2.Width, image2.Height); + } + + return image1; + } + + + /// + /// the image remains the same size, and it is placed in the middle of the new canvas + /// + /// image to put on canvas + /// canvas width + /// canvas height + /// + public static Image PutOnWhiteCanvas(Image image, int width, int height) + { + return PutOnCanvas(image, width, height, Color.White); + } + + /// + /// resize an image and maintain aspect ratio + /// + /// image to resize + /// desired width + /// max height + /// if image width is smaller than newWidth use image width + /// resized image + public static Image Resize(Image image, int newWidth, int maxHeight, bool onlyResizeIfWider) + { + if (onlyResizeIfWider && image.Width <= newWidth) newWidth = image.Width; + + var newHeight = image.Height * newWidth / image.Width; + if (newHeight > maxHeight) + { + // Resize with height instead + newWidth = image.Width * maxHeight / image.Height; + newHeight = maxHeight; + } + + var res = new Bitmap(newWidth, newHeight); + + using (var graphic = Graphics.FromImage(res)) + { + graphic.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphic.SmoothingMode = SmoothingMode.HighQuality; + graphic.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphic.CompositingQuality = CompositingQuality.HighQuality; + graphic.DrawImage(image, 0, 0, newWidth, newHeight); + } + + return res; + } + + + public static SessionPicture BinaryDeserializeObject(string path) + { + using (StreamReader streamReader = new StreamReader(path)) + { + BinaryFormatter binaryFormatter = new BinaryFormatter(); + SessionPicture obj; + try + { + obj = (SessionPicture)binaryFormatter.Deserialize(streamReader.BaseStream); + } + catch (SerializationException ex) + { + throw new SerializationException(((object)ex).ToString() + "\n" + ex.Source); + } + return obj; + } + } + + + // Bitmap objects. This is static and only gets instantiated once. + private static readonly ImageConverter _imageConverter = new ImageConverter(); + + + //Converts Byte Array ino image + public static Bitmap GetImageFromByteArray(byte[] byteArray) + { + Bitmap bm = (Bitmap)_imageConverter.ConvertFrom(byteArray); + + if (bm != null && (bm.HorizontalResolution != (int)bm.HorizontalResolution || + bm.VerticalResolution != (int)bm.VerticalResolution)) + { + // Correct a strange glitch that has been observed in the test program when converting + // from a PNG file image created by CopyImageToByteArray() - the dpi value "drifts" + // slightly away from the nominal integer value + bm.SetResolution((int)(bm.HorizontalResolution + 0.5f), + (int)(bm.VerticalResolution + 0.5f)); + } + + return bm; + } + + + /// + /// Crop an image + /// + /// image to crop + /// rectangle to crop + /// resulting image + public static Image Crop(Image img, Rectangle cropArea) + { + var bmpImage = new Bitmap(img); + var bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat); + return bmpCrop; + } + + public static byte[] ImageToByteArray(System.Drawing.Image imageIn) + { + MemoryStream ms = new MemoryStream(); + imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); + return ms.ToArray(); + } + + public static Image ByteArrayToImage(byte[] byteArrayIn) + { + MemoryStream ms = new MemoryStream(byteArrayIn); + Image returnImage = Image.FromStream(ms); + return returnImage; + } + + //The actual converting function + public static string GetImage(object img) + { + return "data:image/jpg;base64," + Convert.ToBase64String((byte[])img); + } + + + public static void PerformImageResizeAndPutOnCanvas(string pFilePath, string pFileName, int pWidth, int pHeight, string pOutputFileName) + { + + System.Drawing.Image imgBef; + imgBef = System.Drawing.Image.FromFile(pFilePath + pFileName); + + + System.Drawing.Image _imgR; + _imgR = Resize(imgBef, pWidth, pHeight, true); + + + System.Drawing.Image _img2; + _img2 = PutOnCanvas(_imgR, pWidth, pHeight, System.Drawing.Color.White); + + //Save JPEG + SaveJpeg(pFilePath + pOutputFileName, _img2); + + } + } + +} diff --git a/Myrtille.Common/Capture/PictureSaver.cs b/Myrtille.Common/Capture/PictureSaver.cs new file mode 100644 index 0000000..9e894f4 --- /dev/null +++ b/Myrtille.Common/Capture/PictureSaver.cs @@ -0,0 +1,233 @@ +using System; +using System.Linq; +using System.Drawing; +using System.IO; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.Collections.Concurrent; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; + +namespace Myrtille.Common.Capture +{ + public static class PictureSaver + { + static ConcurrentQueue Queue = new ConcurrentQueue(); + static PictureSaver() + { + Thread doSaving = new Thread(DoSave); + + doSaving.Start(); + } + + static void DoSave() + { + frame item; + while (true) + { + if (Queue.TryDequeue(out item)) + { + var path = Directory.CreateDirectory(@"D:\Recivedlog\" + item.id.ToString()); + + BinaryFormatter bf = new BinaryFormatter(); + using (var ms = new MemoryStream()) + { + bf.Serialize(ms, item.img); + File.WriteAllBytes(path.FullName + "\\" + string.Format("{0:yyyy-MM-dd_HH-mm-ss-fff}", item.timestamp), ms.ToArray()); + } + + } + } + } + + + public static void InQueueForSave(SessionPicture img, Guid id, DateTime timestamp) + { + Queue.Enqueue(new frame() { img = img, id = id, timestamp = timestamp }); + } + + + + + // ImageConverter object used to convert byte arrays containing JPEG or PNG file images into + // Bitmap objects. This is static and only gets instantiated once. + private static readonly ImageConverter _imageConverter = new ImageConverter(); + + + + /// + /// Save image as jpeg + /// + /// path where to save + /// image to save + public static void SaveJpeg(string path, Image img) + { + var qualityParam = new EncoderParameter(Encoder.Quality, 100L); + var jpegCodec = GetEncoderInfo("image/jpeg"); + + var encoderParams = new EncoderParameters(1); + encoderParams.Param[0] = qualityParam; + img.Save(path, jpegCodec, encoderParams); + } + + /// + /// Save image + /// + /// path where to save + /// image to save + /// codec info + public static void Save(string path, Image img, ImageCodecInfo imageCodecInfo) + { + var qualityParam = new EncoderParameter(Encoder.Quality, 100L); + + var encoderParams = new EncoderParameters(1); + encoderParams.Param[0] = qualityParam; + img.Save(path, imageCodecInfo, encoderParams); + } + + /// + /// get codec info by mime type + /// + /// + /// + public static ImageCodecInfo GetEncoderInfo(string mimeType) + { + return ImageCodecInfo.GetImageEncoders().FirstOrDefault(t => t.MimeType == mimeType); + } + + /// + /// the image remains the same size, and it is placed in the middle of the new canvas + /// + /// image to put on canvas + /// canvas width + /// canvas height + /// canvas color + /// + public static Image PutOnCanvas(Image image, int width, int height, Color canvasColor) + { + var res = new Bitmap(width, height); + using (var g = Graphics.FromImage(res)) + { + g.Clear(canvasColor); + var x = (width - image.Width) / 2; + var y = (height - image.Height) / 2; + g.DrawImageUnscaled(image, x, y, image.Width, image.Height); + } + + return res; + } + + + public static Image mergeTwoImages(Image image1, Image image2, int x, int y) + { + //var res = new Bitmap(width, height); + using (var g = Graphics.FromImage(image1)) + { + g.DrawImageUnscaled(image2, x, y, image2.Width, image2.Height); + } + + return image1; + } + + + /// + /// the image remains the same size, and it is placed in the middle of the new canvas + /// + /// image to put on canvas + /// canvas width + /// canvas height + /// + public static Image PutOnWhiteCanvas(Image image, int width, int height) + { + return PutOnCanvas(image, width, height, Color.White); + } + + /// + /// resize an image and maintain aspect ratio + /// + /// image to resize + /// desired width + /// max height + /// if image width is smaller than newWidth use image width + /// resized image + public static Image Resize(Image image, int newWidth, int maxHeight, bool onlyResizeIfWider) + { + if (onlyResizeIfWider && image.Width <= newWidth) newWidth = image.Width; + + var newHeight = image.Height * newWidth / image.Width; + if (newHeight > maxHeight) + { + // Resize with height instead + newWidth = image.Width * maxHeight / image.Height; + newHeight = maxHeight; + } + + var res = new Bitmap(newWidth, newHeight); + + using (var graphic = Graphics.FromImage(res)) + { + graphic.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphic.SmoothingMode = SmoothingMode.HighQuality; + graphic.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphic.CompositingQuality = CompositingQuality.HighQuality; + graphic.DrawImage(image, 0, 0, newWidth, newHeight); + } + + return res; + } + + /// + /// Crop an image + /// + /// image to crop + /// rectangle to crop + /// resulting image + public static Image Crop(Image img, Rectangle cropArea) + { + var bmpImage = new Bitmap(img); + var bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat); + return bmpCrop; + } + + public static byte[] imageToByteArray(System.Drawing.Image imageIn) + { + MemoryStream ms = new MemoryStream(); + imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); + return ms.ToArray(); + } + + public static Image byteArrayToImage(byte[] byteArrayIn) + { + MemoryStream ms = new MemoryStream(byteArrayIn); + Image returnImage = Image.FromStream(ms); + return returnImage; + } + + //The actual converting function + public static string GetImage(object img) + { + return "data:image/jpg;base64," + Convert.ToBase64String((byte[])img); + } + + + public static void PerformImageResizeAndPutOnCanvas(string pFilePath, string pFileName, int pWidth, int pHeight, string pOutputFileName) + { + + System.Drawing.Image imgBef; + imgBef = System.Drawing.Image.FromFile(pFilePath + pFileName); + + + System.Drawing.Image _imgR; + _imgR = PictureHelper.Resize(imgBef, pWidth, pHeight, true); + + + System.Drawing.Image _img2; + _img2 = PictureHelper.PutOnCanvas(_imgR, pWidth, pHeight, System.Drawing.Color.White); + + //Save JPEG + PictureHelper.SaveJpeg(pFilePath + pOutputFileName, _img2); + + } + } +} \ No newline at end of file diff --git a/Myrtille.Common/Capture/Video.cs b/Myrtille.Common/Capture/Video.cs new file mode 100644 index 0000000..e45de04 --- /dev/null +++ b/Myrtille.Common/Capture/Video.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Threading; + +namespace Myrtille.Common.Capture +{ + + public class Frame + { + public string id; + } + public class VideoSaver + { + static Image bmp1; + static List filesName; + static ConcurrentQueue Queue = new ConcurrentQueue(); + static string ImageLogPath; + static string VideosavePath; + public VideoSaver(string imageLogPath, string videosavePath) + { + ImageLogPath = imageLogPath; + VideosavePath= videosavePath; + + Thread doSaving = new Thread(DoSave); + doSaving.Start(); + } + + void DoSave() + { + while (true) + { + + if (Queue.TryDequeue(out Frame item)) + { + string inputName = item.id; + + // Library.WriteErrorLog("start Converting: " + inputName); + + var ext = new List { "jpg", "gif", "png" }; + filesName = Directory + .EnumerateFiles(ImageLogPath + "\\" + inputName, "*.*", SearchOption.TopDirectoryOnly) + //.Where(s => ext.Contains(Path.GetExtension(s).TrimStart('.').ToLowerInvariant()) + .OrderBy(f => f).ToList(); + + //var firstFrame = "D:\\Recivedlog\\e74f905b-77c1-4463-97b1-0335d96740c2\\2021-08-14_14-10-59-986"; + var firstFrame = filesName.First(); + var img =PictureHelper.BinaryDeserializeObject(firstFrame); + bmp1 =PictureHelper.GetImageFromByteArray(img.Data); + + // bmp.Save("sdcfg.png",System.Drawing.Imaging.ImageFormat.Bmp); + try + { + GenerateVideo(item.id); + } + finally + { + string fullpath = ImageLogPath +"\\"+ item.id; + // If directory does not exist, don't even try + if (Directory.Exists(fullpath)) + { + var dir = new DirectoryInfo(fullpath); + dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly; + dir.Delete(true); + } + } + // Library.WriteErrorLog("finish Converting: " + inputName); + } + } + } + public void GenerateVideo(string OutputName) + { + //Process proc = new Process(); + using (var proc = new Process()) + { + proc.StartInfo.FileName = "ffmpeg.exe"; + String arg = "-f image2pipe -framerate " + "3" + " -i pipe:.bmp -pix_fmt yuv420p -qscale:v 5 -vcodec libx264 -bufsize 30000k -y " + VideosavePath + "\\" + OutputName + ".mp4"; + proc.StartInfo.Arguments = arg;// "-f image2pipe -i pipe:.jpg -vcodec libx264 -maxrate " + bitrate + "k -bt 10 -r " + fps + " -an -y test.mp4"; //+ rtmp; + proc.StartInfo.UseShellExecute = false; + proc.StartInfo.RedirectStandardInput = true; + proc.StartInfo.RedirectStandardOutput = true; + + proc.Start(); + + BinaryWriter writer = new BinaryWriter(proc.StandardInput.BaseStream); + + + foreach (string name in filesName) + { + //img = Image.FromFile(@"image1.jpg"); + //img.Save(writer.BaseStream, System.Drawing.Imaging.ImageFormat.Jpeg); + // img.Dispose(); + + //var imgg = BinaryDeserializeObject("D:\\Recivedlog\\e74f905b-77c1-4463-97b1-0335d96740c2\\2021-08-14_14-10-59-986"); + var imgg = PictureHelper.BinaryDeserializeObject(name); + Image bmp2 = PictureHelper.GetImageFromByteArray(imgg.Data); + + bmp1 = PictureHelper.MergeTwoImages(bmp1, bmp2, imgg.PosX, imgg.PosY); + Image frame = (Image)bmp1.Clone(); + frame.Save(writer.BaseStream, System.Drawing.Imaging.ImageFormat.Jpeg); + //bmp.Save("sdcfg.png", System.Drawing.Imaging.ImageFormat.Bmp); + + frame.Dispose(); + + }; + writer.Close(); + } + //Console.ReadKey(); + } + + + public void InQueueForSave(string path) + { + Queue.Enqueue(new Frame() { id = path }); + } + + } +} diff --git a/Myrtille.Common/Myrtille.Common.csproj b/Myrtille.Common/Myrtille.Common.csproj index a6b8176..ed28ca3 100644 --- a/Myrtille.Common/Myrtille.Common.csproj +++ b/Myrtille.Common/Myrtille.Common.csproj @@ -86,6 +86,9 @@ + + + @@ -107,6 +110,7 @@ + diff --git a/Myrtille.Common/Tocken/FormGenerator.cs b/Myrtille.Common/Tocken/FormGenerator.cs new file mode 100644 index 0000000..e90b8d9 --- /dev/null +++ b/Myrtille.Common/Tocken/FormGenerator.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Myrtille.Common.Tocken +{ + public class RemoteSessionInfo + { + + public string ServerAddress; // :port, if specified + public string UserName; + public string UserPassword; + public int ClientWidth; + public int ClientHeight; + public DateTime expires_at; + } + + public class FormGenerator + { + // This constant is used to determine the keysize of the encryption algorithm in bits. + // We divide this by 8 within the code below to get the equivalent number of bytes. + private const int Keysize = 256; + private static readonly int DerivationIterations=1000; + + public static string Decrypt(string cipherText, string passPhrase) + { + try + { + // Get the complete stream of bytes that represent: + // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] + var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); + // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. + var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); + // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. + var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); + // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. + var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); + + using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + { + var keyBytes = password.GetBytes(Keysize / 8); + using (var symmetricKey = new RijndaelManaged()) + { + symmetricKey.BlockSize = 256; + symmetricKey.Mode = CipherMode.CBC; + symmetricKey.Padding = PaddingMode.PKCS7; + using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) + { + using (var memoryStream = new MemoryStream(cipherTextBytes)) + { + using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + { + var plainTextBytes = new byte[cipherTextBytes.Length]; + var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); + memoryStream.Close(); + cryptoStream.Close(); + return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); + } + } + } + } + } + } + catch (Exception e) + { + throw new Exception("Can not Decrypt !"+e.Message); + } + + } + + public static byte[] Encrypt(byte[] plainData, string passPhrase) + { + // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text + // so that the same Salt and IV values can be used when decrypting. + var saltStringBytes = Generate256BitsOfRandomEntropy(); + var ivStringBytes = Generate256BitsOfRandomEntropy(); + var plainTextBytes = plainData; + using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + { + var keyBytes = password.GetBytes(Keysize / 8); + using (var symmetricKey = new RijndaelManaged()) + { + symmetricKey.BlockSize = 256; + symmetricKey.Mode = CipherMode.CBC; + symmetricKey.Padding = PaddingMode.PKCS7; + using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) + { + using (var memoryStream = new MemoryStream()) + { + using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); + cryptoStream.FlushFinalBlock(); + // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. + var cipherTextBytes = saltStringBytes; + cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); + cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); + memoryStream.Close(); + cryptoStream.Close(); + return (cipherTextBytes); + } + } + } + } + } + } + + public static string Encrypt(string plainText, string passPhrase) + { + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + byte[] enBtypes = Encrypt(plainTextBytes, passPhrase); + return Convert.ToBase64String(enBtypes); + } + + private static byte[] Generate256BitsOfRandomEntropy() + { + var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. + using (var rngCsp = new RNGCryptoServiceProvider()) + { + // Fill the array with cryptographically secure random bytes. + rngCsp.GetBytes(randomBytes); + } + return randomBytes; + } + private static byte[] Generate128BitsOfRandomEntropy() + { + var randomBytes = new byte[16]; // 16 Bytes will give us 128 bits. + using (var rngCsp = new RNGCryptoServiceProvider()) + { + // Fill the array with cryptographically secure random bytes. + rngCsp.GetBytes(randomBytes); + } + return randomBytes; + } + } +} diff --git a/Myrtille.Web/Default.aspx b/Myrtille.Web/Default.aspx index 807bac6..080282d 100644 --- a/Myrtille.Web/Default.aspx +++ b/Myrtille.Web/Default.aspx @@ -79,6 +79,44 @@ <%=(RemoteSession != null && !string.IsNullOrEmpty(RemoteSession.VMGuid) && !RemoteSession.VMEnhancedMode).ToString().ToLower()%>);"> + + + + + + + + + + + + + L O A D I N G + + + + + + + + + + + + + + + + +
@@ -275,7 +313,7 @@ - + @@ -344,20 +382,17 @@ // auto-connect / start program from url // if the display resolution isn't set, the remote session isn't able to start; redirect with the client resolution - if (window.location.href.indexOf('&connect=') != -1 && (window.location.href.indexOf('&width=') == -1 || window.location.href.indexOf('&height=') == -1)) - { + if (window.location.href.indexOf('&connect=') != -1 && (window.location.href.indexOf('&width=') == -1 || window.location.href.indexOf('&height=') == -1)) { var width = document.getElementById('<%=width.ClientID%>').value; var height = document.getElementById('<%=height.ClientID%>').value; var redirectUrl = window.location.href; - if (window.location.href.indexOf('&width=') == -1) - { + if (window.location.href.indexOf('&width=') == -1) { redirectUrl += '&width=' + width; } - if (window.location.href.indexOf('&height=') == -1) - { + if (window.location.href.indexOf('&height=') == -1) { redirectUrl += '&height=' + height; } @@ -366,10 +401,8 @@ window.location.href = redirectUrl; } - function initDisplay() - { - try - { + function initDisplay() { + try { var display = new Display(); // detect the browser width & height @@ -379,11 +412,9 @@ if (<%=(RemoteSession != null && (RemoteSession.State == RemoteSessionState.Connecting || RemoteSession.State == RemoteSessionState.Connected)).ToString(CultureInfo.InvariantCulture).ToLower()%>) { // the toolbar is enabled (web.config) - if (document.getElementById('<%=toolbar.ClientID%>') != null) - { + if (document.getElementById('<%=toolbar.ClientID%>') != null) { // resume the saved toolbar state - if (getToggleCookie((parent != null && window.name != '' ? window.name + '_' : '') + 'toolbar')) - { + if (getToggleCookie((parent != null && window.name != '' ? window.name + '_' : '') + 'toolbar')) { toggleToolbar(); } @@ -397,31 +428,26 @@ } } } - catch (exc) - { + catch (exc) { alert('myrtille initDisplay error: ' + exc.message); } } - function onHostTypeChange(hostType) - { + function onHostTypeChange(hostType) { var securityProtocolDiv = document.getElementById('securityProtocolDiv'); - if (securityProtocolDiv != null) - { + if (securityProtocolDiv != null) { securityProtocolDiv.style.visibility = (hostType.selectedIndex == 0 || hostType.selectedIndex == 1 ? 'visible' : 'hidden'); securityProtocolDiv.style.display = (hostType.selectedIndex == 0 || hostType.selectedIndex == 1 ? 'block' : 'none'); } var vmDiv = document.getElementById('vmDiv'); - if (vmDiv != null) - { + if (vmDiv != null) { vmDiv.style.visibility = (hostType.selectedIndex == 1 ? 'visible' : 'hidden'); vmDiv.style.display = (hostType.selectedIndex == 1 ? 'block' : 'none'); } } - function setClientResolution(display) - { + function setClientResolution(display) { // browser size. default 1024x768 var width = display.getBrowserWidth() - display.getHorizontalOffset(); var height = display.getBrowserHeight() - display.getVerticalOffset(); @@ -432,17 +458,14 @@ document.getElementById('<%=height.ClientID%>').value = height; } - function disableControl(controlId) - { + function disableControl(controlId) { var control = document.getElementById(controlId); - if (control != null) - { + if (control != null) { control.disabled = true; } } - function disableToolbar() - { + function disableToolbar() { disableControl('stat'); disableControl('debug'); disableControl('browser'); @@ -460,20 +483,17 @@ disableControl('<%=imageQuality.ClientID%>'); } - function toggleToolbar() - { + function toggleToolbar() { var toolbar = document.getElementById('<%=toolbar.ClientID%>'); if (toolbar == null) return; - if (toolbar.style.visibility == 'visible') - { + if (toolbar.style.visibility == 'visible') { toolbar.style.visibility = 'hidden'; toolbar.style.display = 'none'; } - else - { + else { toolbar.style.visibility = 'visible'; toolbar.style.display = 'block'; } @@ -481,8 +501,7 @@ setCookie((parent != null && window.name != '' ? window.name + '_' : '') + 'toolbar', toolbar.style.visibility == 'visible' ? 1 : 0); } - function getToggleCookie(name) - { + function getToggleCookie(name) { if (<%=(RemoteSession == null).ToString().ToLower()%>) return false; @@ -493,20 +512,17 @@ return (value == '1' ? true : false); } - function onDragMove(event) - { + function onDragMove(event) { var target = event.target, - x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx, - y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; + x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx, + y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; - if ('webkitTransform' in target.style || 'transform' in target.style) - { + if ('webkitTransform' in target.style || 'transform' in target.style) { target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'; } - else - { + else { target.style.left = x + 'px'; target.style.top = y + 'px'; } diff --git a/Myrtille.Web/Default.aspx.cs b/Myrtille.Web/Default.aspx.cs index 084ec86..7b75cf0 100644 --- a/Myrtille.Web/Default.aspx.cs +++ b/Myrtille.Web/Default.aspx.cs @@ -31,6 +31,8 @@ limitations under the License. using Myrtille.Helpers; using Myrtille.Services.Contracts; using Myrtille.Web.Properties; +using System.Xml.Serialization; +using Myrtille.Common.Tocken; namespace Myrtille.Web { @@ -89,7 +91,7 @@ protected void Page_Init( // session sharing if (!bool.TryParse(ConfigurationManager.AppSettings["AllowSessionSharing"], out _allowSessionSharing)) { - _allowSessionSharing = true; + _allowSessionSharing = false; } // audio playback @@ -202,6 +204,7 @@ protected void Page_Load( try { _enterpriseSession = (EnterpriseSession)Session[HttpSessionStateVariables.EnterpriseSession.ToString()]; + } catch (Exception exc) { @@ -234,10 +237,12 @@ protected void Page_Load( // redirect to login page if (!string.IsNullOrEmpty(_loginUrl)) { - script += string.Format("window.location.href = '{0}';", _loginUrl); + //script += string.Format("window.location.href = '{0}';", _loginUrl); } - ClientScript.RegisterClientScriptBlock(GetType(), Guid.NewGuid().ToString(), script, true); + //ClientScript.RegisterClientScriptBlock(GetType(), Guid.NewGuid().ToString(), script, true); + if(RemoteSession.ExitCode!=0) + Response.Redirect($"ErrorPage.aspx?ExitCode={RemoteSession.ExitCode}", true); } // cleanup @@ -317,6 +322,275 @@ protected void Page_Load( // disable the browser cache; in addition to a "noCache" dummy param, with current time, on long-polling and xhr requests Response.Cache.SetCacheability(HttpCacheability.NoCache); Response.Cache.SetNoStore(); + + if (!string.IsNullOrEmpty(Request.QueryString["Token"])) + { + string Token = Request.QueryString["Token"]; + + XmlSerializer serializer = new XmlSerializer(typeof(RemoteSessionInfo)); + //Token = HttpUtility.UrlDecode(HttpUtility.UrlEncode(Token)); + Token = Token.Replace(' ', '+'); + var Decrypted = FormGenerator.Decrypt(Token, "sdwefdsvrgdcfew"); + + Decrypted = Decrypted.Replace("\\r\\n", "\r\n"); + StringReader reader = new StringReader(Decrypted); + var Info = serializer.Deserialize(reader) as RemoteSessionInfo; + + Connect(Info); + // txtSearchTerm.Text = searchTerm; + // DoSearch(searchTerm); + } + } + + + protected void Connect(RemoteSessionInfo info) + { + + + ////// the display size is required to start a remote session + ////// if missing, the client will provide it automatically + ////if (string.IsNullOrEmpty(width.Value) || string.IsNullOrEmpty(height.Value)) + ////{ + //// return; + ////} + + // connect + if (ConnectPredefinedRemoteServer(info)) + { + // in enterprise mode from login, a new http session id was already generated (no need to do it each time an host is connected!) + // in standard mode or enterprise mode from url, a new http session id must be generated + ////if (_enterpriseSession == null || Request["SI"] != null) + ////{ + //// // session fixation protection + //// if (_httpSessionUseUri) + //// { + //// // generate a new http session id + //// RemoteSession.OwnerSessionID = HttpSessionHelper.RegenerateSessionId(); + //// } + ////} + RemoteSession.OwnerSessionID = HttpSessionHelper.RegenerateSessionId(); + try + { + // standard mode: switch to http get (standard login) or remove the connection params from url (auto-connect / start program from url) + // enterprise mode: remove the host id from url + Response.Redirect("~/", true); + } + catch (ThreadAbortException) + { + // occurs because the response is ended after redirect + } + } + // connection failed from the hosts list or from a one time session url + else if (_enterpriseSession != null && Request["SD"] != null) + { + try + { + // remove the host id from url + Response.Redirect("~/", true); + } + catch (ThreadAbortException) + { + // occurs because the response is ended after redirect + } + } + else + { + } + + } + + + /// + /// connect predefined remote server + /// + /// + /// authentication is delegated to the remote server or connection broker (if applicable) + /// + private bool ConnectPredefinedRemoteServer(RemoteSessionInfo info) + { + // connection parameters + string loginHostName = null; + var loginHostType = HostType.RDP; + var loginProtocol = SecurityProtocol.auto; + var loginServer = info.ServerAddress; + var loginVMGuid = ""; + var loginVMAddress = ""; + var loginVMEnhancedMode = false; + var loginDomain = ""; + var loginUser = info.UserName; + var loginPassword = info.UserPassword; + var startProgram = ""; + + // allowed features + var allowRemoteClipboard = _allowRemoteClipboard; + var allowFileTransfer = _allowFileTransfer; + var allowPrintDownload = _allowPrintDownload; + var allowSessionSharing = _allowSessionSharing; + var allowAudioPlayback = _allowAudioPlayback; + + // sharing parameters + int maxActiveGuests = int.MaxValue; + + var connectionId = Guid.NewGuid(); + + // connect an host from the hosts list or from a one time session url + if (_enterpriseSession != null && (!string.IsNullOrEmpty(Request["SD"]))) + { + long hostId; + if (!long.TryParse(Request["SD"], out hostId)) + { + hostId = 0; + } + + try + { + // retrieve the host connection details + var connection = _enterpriseClient.GetSessionConnectionDetails(_enterpriseSession.SessionID, hostId, _enterpriseSession.SessionKey); + if (connection == null) + { + System.Diagnostics.Trace.TraceInformation("Unable to retrieve host {0} connection details (invalid host or one time session url already used?)", hostId); + return false; + } + + loginHostName = connection.HostName; + loginHostType = connection.HostType; + loginProtocol = connection.Protocol; + loginServer = !string.IsNullOrEmpty(connection.HostAddress) ? connection.HostAddress : connection.HostName; + loginVMGuid = connection.VMGuid; + loginVMEnhancedMode = connection.VMEnhancedMode; + loginDomain = connection.Domain; + loginUser = connection.Username; + loginPassword = CryptoHelper.RDP_Decrypt(connection.Password); + startProgram = connection.StartRemoteProgram; + } + catch (Exception exc) + { + System.Diagnostics.Trace.TraceError("Failed to retrieve host {0} connection details ({1})", hostId, exc); + return false; + } + } + // by using a connection service on a backend (connection API), the connection details can be hidden from querystring and mapped to a connection identifier + else if (!string.IsNullOrEmpty(Request["cid"])) + { + if (!Guid.TryParse(Request["cid"], out connectionId)) + { + System.Diagnostics.Trace.TraceInformation("Invalid connection id {0}", Request["cid"]); + return false; + } + + try + { + // retrieve the connection details + var connection = _connectionClient.GetConnectionInfo(connectionId); + if (connection == null) + { + System.Diagnostics.Trace.TraceInformation("Unable to retrieve connection info {0}", connectionId); + return false; + } + + // ensure the user is allowed to connect the host + if (!_connectionClient.IsUserAllowedToConnectHost(connection.User.Domain, connection.User.UserName, connection.Host.IPAddress, connection.VM != null ? connection.VM.Guid : Guid.Empty)) + { + System.Diagnostics.Trace.TraceInformation("User: domain={0}, name={1} is not allowed to connect host {2}", connection.User.Domain, connection.User.UserName, connection.Host.IPAddress); + return false; + } + + loginHostType = connection.Host.HostType; + loginProtocol = connection.Host.SecurityProtocol; + loginServer = connection.Host.IPAddress; + loginVMGuid = connection.VM != null ? connection.VM.Guid.ToString() : string.Empty; + loginVMAddress = connection.VM != null ? connection.VM.IPAddress : string.Empty; + loginVMEnhancedMode = connection.VM != null ? connection.VM.EnhancedMode : false; + loginDomain = connection.User.Domain; + loginUser = connection.User.UserName; + loginPassword = connection.User.Password; + startProgram = connection.StartProgram; + + allowRemoteClipboard = allowRemoteClipboard && connection.AllowRemoteClipboard; + allowFileTransfer = allowFileTransfer && connection.AllowFileTransfer; + allowPrintDownload = allowPrintDownload && connection.AllowPrintDownload; + allowSessionSharing = allowSessionSharing && connection.MaxActiveGuests > 0; + allowAudioPlayback = allowAudioPlayback && connection.AllowAudioPlayback; + + maxActiveGuests = connection.MaxActiveGuests; + } + catch (Exception exc) + { + System.Diagnostics.Trace.TraceError("Failed to retrieve connection info {0} ({1})", connectionId, exc); + return false; + } + } + + + // remove any active remote session (disconnected?) + if (RemoteSession != null) + { + // unset the remote session for the current http session + Session[HttpSessionStateVariables.RemoteSession.ToString()] = null; + RemoteSession = null; + } + + // create a new remote session + try + { + Application.Lock(); + + // create the remote session + RemoteSession = new RemoteSession( + connectionId, + loginHostName, + loginHostType, + loginProtocol, + loginServer, + loginVMGuid, + loginVMAddress, + loginVMEnhancedMode, + !string.IsNullOrEmpty(loginDomain) ? loginDomain : AccountHelper.GetDomain(loginUser, loginPassword), + AccountHelper.GetUserName(loginUser), + loginPassword, + info.ClientWidth,//int.Parse(width.Value), + info.ClientHeight,//int.Parse(height.Value), + startProgram, + allowRemoteClipboard, + allowFileTransfer, + allowPrintDownload, + allowSessionSharing, + allowAudioPlayback, + maxActiveGuests, + Session.SessionID, + "hajjar",//(string)Session[HttpSessionStateVariables.ClientKey.ToString()], + false//Request["cid"] != null + ); + + // bind the remote session to the current http session + Session[HttpSessionStateVariables.RemoteSession.ToString()] = RemoteSession; + + // register the remote session at the application level + var remoteSessions = (IDictionary)Application[HttpApplicationStateVariables.RemoteSessions.ToString()]; + remoteSessions.Add(RemoteSession.Id, RemoteSession); + } + catch (Exception exc) + { + System.Diagnostics.Trace.TraceError("Failed to create remote session ({0})", exc); + RemoteSession = null; + } + finally + { + Application.UnLock(); + } + + // connect it + if (RemoteSession != null) + { + RemoteSession.State = RemoteSessionState.Connecting; + } + else + { + connectError.InnerText = "Failed to create remote session!"; + return false; + } + + return true; } /// @@ -467,7 +741,8 @@ protected void ConnectButtonClick( } // connect - if (ConnectRemoteServer()) + var Connected = ConnectRemoteServer(); + if (Connected) { // in enterprise mode from login, a new http session id was already generated (no need to do it each time an host is connected!) // in standard mode or enterprise mode from url, a new http session id must be generated @@ -507,6 +782,16 @@ protected void ConnectButtonClick( } } + private void localLogTest(string msg) + { + using(System.IO.StreamWriter sw = new StreamWriter(@"D:\Log\MyLog.log", true)) + { + sw.WriteLine("-----------------------------------------"); + sw.WriteLine(msg); + sw.WriteLine("-----------------------------------------"); + } + } + /// /// connect the remote server /// @@ -515,6 +800,7 @@ protected void ConnectButtonClick( /// private bool ConnectRemoteServer() { + localLogTest("Im Here"); // connection parameters string loginHostName = null; var loginHostType = (HostType)Convert.ToInt32(hostType.Value); @@ -555,6 +841,7 @@ private bool ConnectRemoteServer() var connection = _enterpriseClient.GetSessionConnectionDetails(_enterpriseSession.SessionID, hostId, _enterpriseSession.SessionKey); if (connection == null) { + localLogTest("Unable to retrieve host {0} connection details (invalid host or one time session url already used?)"); System.Diagnostics.Trace.TraceInformation("Unable to retrieve host {0} connection details (invalid host or one time session url already used?)", hostId); return false; } @@ -572,6 +859,7 @@ private bool ConnectRemoteServer() } catch (Exception exc) { + localLogTest($"Failed to retrieve host {hostId} connection details ({exc})"); System.Diagnostics.Trace.TraceError("Failed to retrieve host {0} connection details ({1})", hostId, exc); return false; } @@ -646,6 +934,7 @@ private bool ConnectRemoteServer() { Application.Lock(); + width.Value = width.Value; // create the remote session RemoteSession = new RemoteSession( connectionId, @@ -704,6 +993,8 @@ private bool ConnectRemoteServer() return true; } + + #region enterprise mode /// diff --git a/Myrtille.Web/ErrorPage.aspx b/Myrtille.Web/ErrorPage.aspx new file mode 100644 index 0000000..8cd4bf5 --- /dev/null +++ b/Myrtille.Web/ErrorPage.aspx @@ -0,0 +1,16 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ErrorPage.aspx.cs" Inherits="Myrtille.Web.ErrorPage" %> + + + + + + + + + +
+

oops! somthin wrong happened. contact your admin.

+
+ + + diff --git a/Myrtille.Web/ErrorPage.aspx.cs b/Myrtille.Web/ErrorPage.aspx.cs new file mode 100644 index 0000000..e079b4b --- /dev/null +++ b/Myrtille.Web/ErrorPage.aspx.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace Myrtille.Web +{ + public partial class ErrorPage : System.Web.UI.Page + { + protected void Page_Load(object sender, EventArgs e) + { + + } + } +} \ No newline at end of file diff --git a/Myrtille.Web/ErrorPage.aspx.designer.cs b/Myrtille.Web/ErrorPage.aspx.designer.cs new file mode 100644 index 0000000..e5fea37 --- /dev/null +++ b/Myrtille.Web/ErrorPage.aspx.designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Myrtille.Web +{ + + + public partial class ErrorPage + { + + /// + /// form1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm form1; + } +} diff --git a/Myrtille.Web/Myrtille.Web.csproj b/Myrtille.Web/Myrtille.Web.csproj index 1958eb4..87f522c 100644 --- a/Myrtille.Web/Myrtille.Web.csproj +++ b/Myrtille.Web/Myrtille.Web.csproj @@ -126,6 +126,13 @@ + + ErrorPage.aspx + ASPXCodeBehind + + + ErrorPage.aspx + LongPollingHandler.ashx @@ -298,6 +305,7 @@ + @@ -438,9 +446,6 @@ - - Designer - @@ -496,21 +501,17 @@ + SettingsSingleFileGenerator Settings.Designer.cs - - Web.config - Designer - - - Web.config - Designer + + + web.config - - Web.config - Designer + + web.config diff --git a/Myrtille.Web/Web.Debug.config b/Myrtille.Web/Web.Debug.config index 5ab5365..c1a5642 100644 --- a/Myrtille.Web/Web.Debug.config +++ b/Myrtille.Web/Web.Debug.config @@ -1,6 +1,6 @@ - + - + - - - - \ No newline at end of file diff --git a/Myrtille.Web/Web.Release.config b/Myrtille.Web/Web.Release.config index 61e7540..19058ed 100644 --- a/Myrtille.Web/Web.Release.config +++ b/Myrtille.Web/Web.Release.config @@ -1,6 +1,6 @@ - + - + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://localhost:8008/MyrtilleAdmin/ConnectionService/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Myrtille.sln b/Myrtille.sln index 38f26af..88c3036 100644 --- a/Myrtille.sln +++ b/Myrtille.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27428.2015 +VisualStudioVersion = 15.0.26228.4 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Myrtille.Common", "Myrtille.Common\Myrtille.Common.csproj", "{37630774-1321-4E6A-8661-4430A8946E9E}" EndProject @@ -332,6 +332,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Myrtille.Docker", "Myrtille EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Myrtille.RDP", "Myrtille.RDP\Myrtille.RDP.csproj", "{01E631B8-2A75-4C16-BAE9-E7F0523D89B1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Myrtille.Capture.Services", "Myrtille.Capture.Services\Myrtille.Capture.Services.csproj", "{2DD3560C-99E7-418D-8250-98789BB9C2A3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|.NET = Debug|.NET @@ -1980,6 +1982,54 @@ Global {01E631B8-2A75-4C16-BAE9-E7F0523D89B1}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {01E631B8-2A75-4C16-BAE9-E7F0523D89B1}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {01E631B8-2A75-4C16-BAE9-E7F0523D89B1}.RelWithDebInfo|x86.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|.NET.ActiveCfg = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|.NET.Build.0 = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|Win32.ActiveCfg = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|Win32.Build.0 = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|x64.ActiveCfg = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|x64.Build.0 = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|x86.ActiveCfg = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Debug|x86.Build.0 = Debug|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|.NET.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|.NET.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|Any CPU.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|Mixed Platforms.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|Mixed Platforms.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|Win32.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|Win32.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|x64.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|x64.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|x86.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.MinSizeRel|x86.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|.NET.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|.NET.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|Any CPU.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|Win32.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|Win32.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|x64.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|x64.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|x86.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.Release|x86.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|.NET.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|.NET.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|Mixed Platforms.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|Mixed Platforms.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|Win32.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|Win32.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {2DD3560C-99E7-418D-8250-98789BB9C2A3}.RelWithDebInfo|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE