Installer, start and exit enhancements

git-svn-id: http://svn.code.sf.net/p/greenshot/code/trunk@762 7dccd23d-a4a3-4e1f-8c07-b4c1b4018ab4
This commit is contained in:
RKrom 2010-08-01 15:33:03 +00:00
commit 49ac48a6e8
5 changed files with 175 additions and 148 deletions

View file

@ -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();
}
/// <summary>
/// DataReceivedEventHandler
/// </summary>
/// <param name="sender"></param>
/// <param name="dataReceivedEventArgs"></param>
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();

View file

@ -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<string> filesToOpen = new List<string>();
// 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
/// </summary>
/// <param name="dataTransport">DataTransport with data for a running instance</param>
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);
}
}
/// <summary>
/// DataReceivedEventHandler
/// </summary>
/// <param name="sender"></param>
/// <param name="dataReceivedEventArgs"></param>
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<CommandEnum, string> 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) {

View file

@ -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:

View file

@ -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]

View file

@ -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<KeyValuePair<CommandEnum, string>> commands;
public List<KeyValuePair<CommandEnum, string>> Commands {
get {return commands;}
}
public string CommandData {
get {return commandData;}
public CopyDataTransport() {
this.commands = new List<KeyValuePair<CommandEnum, string>>();
}
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<CommandEnum, string>(command, null));
}
public void AddCommand(CommandEnum command, string commandData) {
this.commands.Add(new KeyValuePair<CommandEnum, string>(command, commandData));
}
}
public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);
public delegate void CopyDataReceivedEventHandler(object sender, CopyDataReceivedEventArgs e);
/// <summary>
/// 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.
/// </summary>
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.
/// </summary>
/// <param name="e">The data which has been received.</param>
protected void OnDataReceived(DataReceivedEventArgs e) {
DataReceived(this, e);
protected void OnCopyDataReceived(CopyDataReceivedEventArgs e) {
CopyDataReceived(this, e);
}
/// <summary>
@ -180,7 +188,7 @@ namespace Greenshot.Helpers {
/// Contains data and other information associated with data
/// which has been sent from another application.
/// </summary>
public class DataReceivedEventArgs {
public class CopyDataReceivedEventArgs {
private string channelName = "";
private object data = null;
private DateTime sent;
@ -226,7 +234,7 @@ namespace Greenshot.Helpers {
/// <param name="channelName">The channel that the data was received from</param>
/// <param name="data">The data which was sent</param>
/// <param name="sent">The date and time the data was sent</param>
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;