diff --git a/Greenshot/Forms/CaptureForm.cs b/Greenshot/Forms/CaptureForm.cs index 1f4c962a9..550a30a13 100644 --- a/Greenshot/Forms/CaptureForm.cs +++ b/Greenshot/Forms/CaptureForm.cs @@ -57,7 +57,6 @@ namespace Greenshot.Forms { private Rectangle captureRect = Rectangle.Empty; private ICapture capture = null; private AppConfig conf = AppConfig.GetInstance(); - private CopyData copyData = null; private ILanguage lang = Language.GetInstance(); public CaptureForm() { @@ -65,56 +64,11 @@ namespace Greenshot.Forms { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - // Create a new instance of the class: copyData = new CopyData(); - copyData = new CopyData(); - // Assign the handle: - copyData.AssignHandle(this.Handle); - // Create the channel to send on: - copyData.Channels.Add("Greenshot"); - // Hook up received event: - copyData.DataReceived += new DataReceivedEventHandler(CopyDataDataReceived); - // Make sure the form is hidden (might be overdoing it...) this.Hide(); } - /// - /// DataReceivedEventHandler - /// - /// - /// - private void CopyDataDataReceived(object sender, DataReceivedEventArgs dataReceivedEventArgs) { - // Cast the data to the type of object we sent: - DataTransport dataTransport = (DataTransport)dataReceivedEventArgs.Data; - HandleDataTransport(dataTransport); - } - - public void HandleDataTransport(DataTransport dataTransport) { - LOG.Debug("Data received, Command = " + dataTransport.Command + ", Data: " + dataTransport.CommandData); - switch(dataTransport.Command) { - case CommandEnum.Exit: - MainForm.instance.exit(); - break; - case CommandEnum.ReloadConfig: - AppConfig.Reload(); - // Even update language when needed - MainForm.instance.UpdateUI(); - break; - case CommandEnum.OpenFile: - string filename = dataTransport.CommandData; - if (File.Exists(filename)) { - MakeCapture(filename); - } else { - LOG.Warn("No such file: " + filename); - } - break; - default: - LOG.Error("Unknown command!"); - break; - } - } - void DoCaptureFeedback() { if((bool)conf.Ui_Effects_CameraSound) { SoundHelper.Play(); diff --git a/Greenshot/Forms/MainForm.cs b/Greenshot/Forms/MainForm.cs index eb6e20543..f78df2d37 100644 --- a/Greenshot/Forms/MainForm.cs +++ b/Greenshot/Forms/MainForm.cs @@ -49,11 +49,13 @@ namespace Greenshot { private static log4net.ILog LOG = null; private static AppConfig conf; - static Mutex applicationMutex = null; + private static Mutex applicationMutex = null; [STAThread] public static void Main(string[] args) { - DataTransport dataTransport = null; + bool isAlreadyRunning = false; + List filesToOpen = new List(); + // Set the Thread name, is better than "1" Thread.CurrentThread.Name = Application.ProductName; @@ -74,6 +76,16 @@ namespace Greenshot { // Log the startup LOG.Info("Starting: " + EnvironmentInfo.EnvironmentToString(false)); try { + // Fix for Bug 2495900, Multi-user Environment + // check whether there's an local instance running already + + // 1) Create Mutex + applicationMutex = new Mutex(false, @"Local\F48E86D3-E34C-4DB7-8F8F-9A0EA55F0D08"); + // 2) Get the right to it, this returns false if it's already locked + if (!applicationMutex.WaitOne(0, false)) { + isAlreadyRunning = true; + } + for(int argumentNr = 0; argumentNr < args.Length; argumentNr++) { string argument = args[argumentNr]; @@ -106,8 +118,8 @@ namespace Greenshot { helpOutput.AppendLine("\t\tA detailed listing of available settings for the configure command."); helpOutput.AppendLine(); helpOutput.AppendLine(); - helpOutput.AppendLine("\t--uninstall"); - helpOutput.AppendLine("\t\tUnstall is called from the unstaller and tries to close all running instances."); + helpOutput.AppendLine("\t--exit"); + helpOutput.AppendLine("\t\tTries to close all running instances."); helpOutput.AppendLine(); helpOutput.AppendLine(); helpOutput.AppendLine("\t--configure [property=value] ..."); @@ -120,7 +132,7 @@ namespace Greenshot { helpOutput.AppendLine("\t\tOpen the bitmap file in the running Greenshot instance or start a new instance"); helpOutput.AppendLine(); helpOutput.AppendLine(); - helpOutput.AppendLine("\t--exit"); + helpOutput.AppendLine("\t--norun"); helpOutput.AppendLine("\t\tCan be used if someone only wants to change the configuration."); helpOutput.AppendLine("\t\tAs soon as this option is found Greenshot exits if not and there is no running instance it will stay running."); helpOutput.AppendLine("\t\tExample: greenshot.exe --configure Output_File_Path=\"C:\\Documents and Settings\\\" --exit"); @@ -131,19 +143,20 @@ namespace Greenshot { if (!attachedToConsole) { Console.ReadKey(); } + FreeMutex(); return; } - // unregister application on uninstall (allow uninstall) - if (argument.Equals("--uninstall") || argument.Equals("uninstall")) { + // exit application + if (argument.Equals("--exit")) { try { LOG.Info("Sending all instances the exit command."); // Pass Exit to running instance, if any - dataTransport = new DataTransport(CommandEnum.Exit, args[0]); - SendData(dataTransport); + SendData(new CopyDataTransport(CommandEnum.Exit)); } catch (Exception e) { LOG.Warn("Exception by exit.", e); } + FreeMutex(); return; } @@ -165,7 +178,7 @@ namespace Greenshot { conf.SetProperties(properties); conf.Store(); // Update running instances - SendData(new DataTransport(CommandEnum.ReloadConfig)); + SendData(new CopyDataTransport(CommandEnum.ReloadConfig)); LOG.Debug("Configuration modified!"); } else { LOG.Debug("Configuration NOT modified!"); @@ -173,33 +186,38 @@ namespace Greenshot { } // Make an exit possible - if (argument.Equals("--exit")) { + if (argument.Equals("--norun")) { + FreeMutex(); return; } if (argument.Equals("--openfile")) { - // Take filename and send it to running instance or take it while opening - dataTransport = new DataTransport(CommandEnum.OpenFile, args[0]); + string filename = args[++argumentNr]; + filesToOpen.Add(filename); } } - // Fix for Bug 2495900, Multi-user Environment - // check whether there's an local instance running already - - // 1) Create Mutex - applicationMutex = new Mutex(false, @"Local\F48E86D3-E34C-4DB7-8F8F-9A0EA55F0D08"); - // 2) Get the right to it, this returns false if it's already locked - if (!applicationMutex.WaitOne(0, false)) { - if (dataTransport != null) { - SendData(dataTransport); - } else { + // Finished parsing the command line arguments, see if we need to do anything + CopyDataTransport transport = new CopyDataTransport(); + if (filesToOpen.Count > 0) { + foreach(string fileToOpen in filesToOpen) { + transport.AddCommand(CommandEnum.OpenFile, fileToOpen); + } + } + if (isAlreadyRunning) { + if (filesToOpen.Count > 0) { + SendData(transport); + } else { conf = AppConfig.GetInstance(); ILanguage lang = Language.GetInstance(); MessageBox.Show(lang.GetString(LangKey.error_multipleinstances), lang.GetString(LangKey.error)); - } + } + FreeMutex(); Application.Exit(); return; } + + // From here on we continue starting Greenshot Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); @@ -216,20 +234,11 @@ namespace Greenshot { // Check if it's the first time launch? bool firstLaunch = (bool)conf.General_IsFirstLaunch; if(firstLaunch) { - // todo: display basic instructions - try { - } catch (Exception ex) { - LOG.Error("Exception in MainForm.", ex); - } finally { - conf.General_IsFirstLaunch = false; - conf.Store(); - } + conf.General_IsFirstLaunch = false; + conf.Store(); + transport.AddCommand(CommandEnum.FirstLaunch); } - // Pass firstlaunch if it is the firstlaunch and no filename is given - if (dataTransport == null && firstLaunch) { - dataTransport = new DataTransport(CommandEnum.FirstLaunch, null); - } - MainForm mainForm = new MainForm(dataTransport); + MainForm mainForm = new MainForm(transport); Application.Run(mainForm); } catch(Exception ex) { LOG.Error("Exception in startup.", ex); @@ -241,21 +250,34 @@ namespace Greenshot { /// Send DataTransport Object via Window-messages /// /// DataTransport with data for a running instance - private static void SendData(DataTransport dataTransport) { + private static void SendData(CopyDataTransport copyDataTransport) { string appName = Application.ProductName; CopyData copyData = new CopyData(); copyData.Channels.Add(appName); - copyData.Channels[appName].Send(dataTransport); + copyData.Channels[appName].Send(copyDataTransport); } - + + private static void FreeMutex() { + // Remove the application mutex + if (applicationMutex != null) { + try { + applicationMutex.ReleaseMutex(); + applicationMutex = null; + } catch (Exception ex) { + LOG.Error("Error releasing Mutex!", ex); + } + } + } + public static MainForm instance = null; private ILanguage lang; private ToolTip tooltip; private CaptureForm captureForm = null; private string lastImagePath = null; - - public MainForm(DataTransport dataTransport) { + private CopyData copyData = null; + + public MainForm(CopyDataTransport dataTransport) { instance = this; // // The InitializeComponent() call is required for Windows Forms designer support. @@ -280,21 +302,60 @@ namespace Greenshot { // Enable the Greenshot icon to be visible, this prevents Problems with the context menu notifyIcon.Visible = true; - + + // Create a new instance of the class: copyData = new CopyData(); + copyData = new CopyData(); + + // Assign the handle: + copyData.AssignHandle(this.Handle); + // Create the channel to send on: + copyData.Channels.Add("Greenshot"); + // Hook up received event: + copyData.CopyDataReceived += new CopyDataReceivedEventHandler(CopyDataDataReceived); + if (dataTransport != null) { - // See what the dataTransport from the static main brought us! - switch (dataTransport.Command) { - case CommandEnum.FirstLaunch: - // Show user where Greenshot is, only at first start - notifyIcon.ShowBalloonTip(3000, "Greenshot", lang.GetString(LangKey.tooltip_firststart), ToolTipIcon.Info); + HandleDataTransport(dataTransport); + } + } + + /// + /// DataReceivedEventHandler + /// + /// + /// + private void CopyDataDataReceived(object sender, CopyDataReceivedEventArgs copyDataReceivedEventArgs) { + // Cast the data to the type of object we sent: + CopyDataTransport dataTransport = (CopyDataTransport)copyDataReceivedEventArgs.Data; + HandleDataTransport(dataTransport); + } + + private void HandleDataTransport(CopyDataTransport dataTransport) { + foreach(KeyValuePair command in dataTransport.Commands) { + LOG.Debug("Data received, Command = " + command.Key + ", Data: " + command.Value); + switch(command.Key) { + case CommandEnum.Exit: + exit(); + break; + case CommandEnum.ReloadConfig: + AppConfig.Reload(); + // Even update language when needed + UpdateUI(); break; case CommandEnum.OpenFile: - captureForm.HandleDataTransport(dataTransport); + string filename = command.Value; + if (File.Exists(filename)) { + captureForm.MakeCapture(filename); + } else { + LOG.Warn("No such file: " + filename); + } + break; + default: + LOG.Error("Unknown command!"); break; } } } - + public ContextMenuStrip MainMenu { get {return contextMenu;} } @@ -577,14 +638,7 @@ namespace Greenshot { LOG.Error("Error storing configuration!", e); } // Remove the application mutex - if (applicationMutex != null) { - try { - applicationMutex.ReleaseMutex(); - applicationMutex = null; - } catch (Exception ex) { - LOG.Error("Error releasing Mutex!", ex); - } - } + FreeMutex(); // make the icon invisible otherwise it stays even after exit!! if (notifyIcon != null) { diff --git a/Greenshot/releases/additional_files/installer.txt b/Greenshot/releases/additional_files/installer.txt index a1df72e0e..c1890d2ae 100644 --- a/Greenshot/releases/additional_files/installer.txt +++ b/Greenshot/releases/additional_files/installer.txt @@ -3,7 +3,7 @@ Here are some details about Greenshot that might be handy for silent/mass instal The Greenshot installer is made with Inno Setup, see http://www.jrsoftware.org/isinfo.php For command line options of the installer see: http://www.jrsoftware.org/ishelp/index.php?topic=setupcmdline -Since Greenshot build > 0.8.0.700 it is possible to configure Greenshot settings from the command-line. +Since Greenshot 0.8.1 it is possible to configure Greenshot settings from the command-line. This will even work when Greenshot is already running! Greenshot commandline options: @@ -14,19 +14,21 @@ Greenshot commandline options: --help configure A list of the options that can be set - --uninstall - Unstall is called from the unstaller and tries to close all running instances. - + --exit + Try to close all running instances, could be used for installers --configure [property=value] ... Change the configuration of Greenshot via the commandline, multiple properties can be specified after each other. Example to change the language to English: greenshot.exe --configure Ui_Language=en-US Example to change the destination: greenshot.exe --configure Output_File_Path="C:\Documents and Settings\" - --openfile [filename] Open the bitmap file in the running Greenshot instance or start a new instance + --norun + Use as last option if you don't want the started executable to spawn a Greenshot instance. + e.g. when you only want to change settings but don't want to have a running Greenshot afterwards. + With the --configure option many settings can be change, the --help configure will give a list of all available settings! Here are some described in detail: diff --git a/Greenshot/releases/innosetup/setup.iss b/Greenshot/releases/innosetup/setup.iss index abe135fa2..eea689fa3 100644 --- a/Greenshot/releases/innosetup/setup.iss +++ b/Greenshot/releases/innosetup/setup.iss @@ -29,6 +29,8 @@ AppSupportURL=http://getgreenshot.org AppUpdatesURL=http://getgreenshot.org AppVerName={#ExeName} {#Version} AppVersion={#Version} +; changes associations is used when the installer installs new extensions, it clears the explorer icon cache +;ChangesAssociations=yes Compression=lzma/ultra64 InternalCompressLevel=ultra64 LanguageDetectionMethod=uilanguage @@ -38,7 +40,7 @@ VersionInfoCompany={#ExeName} VersionInfoTextVersion={#Version} VersionInfoVersion={#Version} VersionInfoProductName={#ExeName} -PrivilegesRequired=admin +PrivilegesRequired=poweruser ; Reference a bitmap, max size 164x314 WizardImageFile=installer-large.bmp ; Reference a bitmap, max size 55x58 @@ -46,6 +48,11 @@ WizardSmallImageFile=installer-small.bmp MinVersion=,5.01.2600 [Registry] Root: HKCU; Subkey: Software\Microsoft\Windows\CurrentVersion\Run; ValueType: string; ValueName: {#ExeName}; ValueData: {app}\{#ExeName}.exe; Permissions: users-modify; Flags: uninsdeletevalue; Tasks: startup +; Register our own filetype +;Root: HKCR; Subkey: ".gsb"; ValueType: string; ValueName: ""; ValueData: "GreenshotFile"; Flags: uninsdeletevalue +;Root: HKCR; Subkey: "GreenshotFile"; ValueType: string; ValueName: ""; ValueData: "Greenshot File"; Flags: uninsdeletekey +;Root: HKCR; Subkey: "GreenshotFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\Greenshot.EXE,0" +;Root: HKCR; Subkey: "GreenshotFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\Greenshot.EXE"" --openfile ""%1""" [Icons] Name: {group}\{#ExeName}; Filename: {app}\{#ExeName}.exe; WorkingDir: {app} Name: {group}\Uninstall {#ExeName}; Filename: {app}\unins000.exe; WorkingDir: {app} @@ -80,47 +87,49 @@ Name: "plugins\ocr"; Description: {cm:ocr}; Types: Full Name: "plugins\titlefix"; Description: {cm:titlefix}; Types: Full ;Name: "plugins\flickr"; Description: "Flickr Plugin"; Types: Full [Code] -function InitializeSetup(): Boolean; +function KillGreenshot() : Boolean; var - ErrorCode : Integer; - NetFrameWorkInstalled : Boolean; - MsgBoxResult : Boolean; bMutex : Boolean; resultCode: Integer; begin - - NetFrameWorkInstalled := RegKeyExists(HKLM, 'SOFTWARE\Microsoft\.NETFramework\policy\v2.0'); - if NetFrameWorkInstalled = true then + bMutex:= True + while bMutex do begin bMutex:= CheckForMutexes ('Local\{#Mutex}'); if bMutex = True then begin Exec('taskkill.exe', '/F /IM Greenshot.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Sleep(1200); end; - Result := true; end; + Result := True; +end; - if NetFrameWorkInstalled = false then - begin +function InitializeSetup(): Boolean; +var + ErrorCode : Integer; + NetFrameWorkInstalled : Boolean; + MsgBoxResult : Boolean; +begin + + NetFrameWorkInstalled := RegKeyExists(HKLM, 'SOFTWARE\Microsoft\.NETFramework\policy\v2.0'); + if NetFrameWorkInstalled = true then begin + KillGreenshot(); + Result := true; + end + else begin MsgBoxResult := MsgBox(ExpandConstant('{cm:dotnetmissing}'), mbConfirmation, MB_YESNO) = idYes; Result := false; if MsgBoxResult = true then begin - ShellExec('open', 'http://download.microsoft.com/download/5/6/7/567758a3-759e-473e-bf8f-52154438565a/dotnetfx.exe', '','',SW_SHOWNORMAL,ewNoWait,ErrorCode); + ShellExec('open', 'http://download.microsoft.com/download/5/6/7/567758a3-759e-473e-bf8f-52154438565a/dotnetfx.exe', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode); end; end; end; function InitializeUninstall():Boolean; -var - bMutex : Boolean; - resultCode: Integer; begin - bMutex:= CheckForMutexes ('Local\{#Mutex}'); - if bMutex = True then - begin - Exec(ExpandConstant('{app}\{#ExeName}.exe'), '--uninstall', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); - end; + KillGreenshot(); Result := True; end; [Run] diff --git a/GreenshotCore/Helpers/CopyData.cs b/GreenshotCore/Helpers/CopyData.cs index 78dd0a660..8dfd874de 100644 --- a/GreenshotCore/Helpers/CopyData.cs +++ b/GreenshotCore/Helpers/CopyData.cs @@ -20,6 +20,7 @@ */ using System; using System.Collections; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Serialization.Formatters.Binary; @@ -34,25 +35,32 @@ namespace Greenshot.Helpers { public enum CommandEnum { OpenFile, Exit, FirstLaunch, ReloadConfig }; [Serializable()] - public class DataTransport { - private CommandEnum command; - private string commandData; - public CommandEnum Command { - get {return command;} + public class CopyDataTransport { + List> commands; + public List> Commands { + get {return commands;} } - public string CommandData { - get {return commandData;} + public CopyDataTransport() { + this.commands = new List>(); } - public DataTransport(CommandEnum command) { - this.command = command; + + public CopyDataTransport(CommandEnum command) : this() { + AddCommand(command, null); } - public DataTransport(CommandEnum command, string commandData) : this(command){ - this.commandData = commandData; + public CopyDataTransport(CommandEnum command, string commandData) : this() { + AddCommand(command, commandData); } + public void AddCommand(CommandEnum command) { + this.commands.Add(new KeyValuePair(command, null)); + } + public void AddCommand(CommandEnum command, string commandData) { + this.commands.Add(new KeyValuePair(command, commandData)); + } + } - public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e); + public delegate void CopyDataReceivedEventHandler(object sender, CopyDataReceivedEventArgs e); /// /// A class which wraps using Windows native WM_COPYDATA @@ -68,7 +76,7 @@ namespace Greenshot.Helpers { /// Event raised when data is received on any of the channels /// this class is subscribed to. /// - public event DataReceivedEventHandler DataReceived; + public event CopyDataReceivedEventHandler CopyDataReceived; [StructLayout(LayoutKind.Sequential)] private struct COPYDATASTRUCT { @@ -102,8 +110,8 @@ namespace Greenshot.Helpers { CopyDataObjectData cdo = (CopyDataObjectData) b.Deserialize(stream); if (channels.Contains(cdo.Channel)) { - DataReceivedEventArgs d = new DataReceivedEventArgs(cdo.Channel, cdo.Data, cdo.Sent); - OnDataReceived(d); + CopyDataReceivedEventArgs d = new CopyDataReceivedEventArgs(cdo.Channel, cdo.Data, cdo.Sent); + OnCopyDataReceived(d); m.Result = (IntPtr) 1; } } @@ -121,8 +129,8 @@ namespace Greenshot.Helpers { /// Raises the DataReceived event from this class. /// /// The data which has been received. - protected void OnDataReceived(DataReceivedEventArgs e) { - DataReceived(this, e); + protected void OnCopyDataReceived(CopyDataReceivedEventArgs e) { + CopyDataReceived(this, e); } /// @@ -180,7 +188,7 @@ namespace Greenshot.Helpers { /// Contains data and other information associated with data /// which has been sent from another application. /// - public class DataReceivedEventArgs { + public class CopyDataReceivedEventArgs { private string channelName = ""; private object data = null; private DateTime sent; @@ -226,7 +234,7 @@ namespace Greenshot.Helpers { /// The channel that the data was received from /// The data which was sent /// The date and time the data was sent - internal DataReceivedEventArgs(string channelName, object data, DateTime sent) { + internal CopyDataReceivedEventArgs(string channelName, object data, DateTime sent) { this.channelName = channelName; this.data = data; this.sent = sent;