IDE: remote control: Linux

git-svn-id: trunk@31020 -
This commit is contained in:
mattias 2011-06-03 00:29:11 +00:00
parent a4437fdf73
commit decee4a238
6 changed files with 257 additions and 66 deletions

View File

@ -1,4 +1,3 @@
{ $Id:$ }
{ {
/*************************************************************************** /***************************************************************************
idecmdline.pas idecmdline.pas
@ -27,13 +26,11 @@
*************************************************************************** ***************************************************************************
Author: Ido Kanner Author: Ido Kanner
}
(*
This unit manages the commandline utils that are used across Lazarus. This unit manages the commandline utils that are used across Lazarus.
It was created for avoding duplicates and easier access for commandline utils It was created for avoding duplicates and easier access for commandline utils
that are required by the IDE. that are required by the IDE.
*) }
unit IDECmdLine; unit IDECmdLine;
{$mode objfpc}{$H+} {$mode objfpc}{$H+}
@ -43,6 +40,21 @@ interface
uses uses
Classes, SysUtils, FileUtil, LazConf, LCLProc, LazarusIDEStrConsts; Classes, SysUtils, FileUtil, LazConf, LCLProc, LazarusIDEStrConsts;
const
ShowSetupDialogOptLong='--setup';
PrimaryConfPathOptLong='--primary-config-path=';
PrimaryConfPathOptShort='--pcp=';
SecondaryConfPathOptLong='--secondary-config-path=';
SecondaryConfPathOptShort='--scp=';
NoSplashScreenOptLong='--no-splash-screen';
NoSplashScreenOptShort='--nsc';
StartedByStartLazarusOpt='--started-by-startlazarus';
SkipLastProjectOpt='--skip-last-project';
EnableRemoteControlOpt='--remote-control';
DebugLogOpt='--debug-log=';
LanguageOpt='--language=';
LazarusDirOpt ='--lazarusdir=';
procedure ParseCommandLine(aCmdLineParams : TStrings; out IDEPid : Integer; procedure ParseCommandLine(aCmdLineParams : TStrings; out IDEPid : Integer;
out ShowSplashScreen: boolean); out ShowSplashScreen: boolean);
function GetCommandLineParameters(aCmdLineParams : TStrings; function GetCommandLineParameters(aCmdLineParams : TStrings;
@ -64,6 +76,16 @@ function ExtractCmdLineFilenames : TStrings;
function GetLazarusDirectory : String; function GetLazarusDirectory : String;
// remote control
var
EnableRemoteControl: boolean = false;
function SetupMainIDEInstance: boolean; // false if this is a secondary instance
function GetPidFile: string;
function IsLazarusPIDRunning(aPID: int64): boolean;
function GetRemoteControlFilename: string;
procedure CleanUpPIDFile;
implementation implementation
procedure ParseCommandLine(aCmdLineParams: TStrings; out IDEPid: Integer; out procedure ParseCommandLine(aCmdLineParams: TStrings; out IDEPid: Integer; out
@ -228,5 +250,179 @@ begin
Result := ExtractFileDir(ParamStrUTF8(0)); Result := ExtractFileDir(ParamStrUTF8(0));
end; end;
function GetPidFile: string;
begin
Result:=AppendPathDelim(GetPrimaryConfigPath)+'pid.txt';
end;
function IsLazarusPIDRunning(aPID: int64): boolean;
function IsLinuxIDERunning: boolean;
var
sl: TStringList;
Filename: String;
begin
Result:=false;
Filename:='/proc/'+IntToStr(aPID)+'/cmdline';
if not FileExists(Filename) then exit;
sl:=TStringList.Create;
try
try
sl.LoadFromFile(Filename);
if sl.Count=0 then exit;
if Pos('lazarus',lowercase(sl[0]))<1 then exit;
Result:=true;
except
end;
finally
sl.Free;
end;
end;
begin
Result:=false;
{$IFDEF Linux}
Result:=IsLinuxIDERunning;
{$ENDIF}
end;
function SetupMainIDEInstance: boolean;
procedure WritePIDFile(const Filename: string; aPID: int64);
var
Dir: String;
sl: TStringList;
begin
debugln(['WritePIDFile File="',Filename,'" PID=',aPID]);
sl:=TStringList.Create;
try
sl.Add(IntToStr(aPID));
try
Dir:=ChompPathDelim(ExtractFilePath(Filename));
if not DirectoryExistsUTF8(Dir) then begin
if not CreateDirUTF8(Dir) then
debugln(['WritePIDFile failed to create directory ',Dir]);
exit;
end;
sl.SaveToFile(UTF8ToSys(Filename));
except
on E: Exception do begin
debugln(['WritePIDFile "',Filename,'" failed:']);
debugln(E.Message);
end;
end;
finally
sl.Free;
end;
end;
function ReadPIDFile(const Filename: string; out ConfigPID: int64): boolean;
var
sl: TStringList;
begin
Result:=false;
ConfigPID:=-1;
debugln(['ReadPIDFile ',Filename]);
if not FileExistsUTF8(Filename) then exit;
sl:=TStringList.Create;
try
try
sl.LoadFromFile(UTF8ToSys(Filename));
ConfigPID:=StrToInt64(sl[0]);
Result:=true;
debugln(['ReadPIDFile ConfigPID=',ConfigPID]);
except
on E: Exception do begin
debugln(['ReadPIDFile "',Filename,'" failed:']);
debugln(E.Message);
end;
end;
finally
sl.Free;
end;
end;
procedure SendCmdlineActionsToMainInstance;
var
sl: TStringList;
Param: String;
Filename: String;
i: Integer;
begin
sl:=TStringList.Create;
try
sl.Add('Show');
for i:=1 to Paramcount do begin
Param:=ParamStrUTF8(i);
if (Param='') or (Param[1]='-') then continue;
sl.Add('Open '+Param);
end;
Filename:=GetRemoteControlFilename;
try
debugln(['SendCmdlineActionsToMainInstance Commands="',sl.Text,'"']);
sl.SaveToFile(UTF8ToSys(Filename));
except
on E: Exception do begin
debugln(['SendCmdlineActionsToMainInstance failed to write ',Filename]);
debugln(E.Message);
end;
end;
finally
sl.Free;
end;
end;
var
PIDFilename: String;
MyPID, ConfigPID: int64;
PIDRead: Boolean;
begin
Result:=true;
if not EnableRemoteControl then exit;
// check if another IDE (of this user and same configuration) is already
// running. Request it to handle the show and handle the command line
// parameters (e.g. open files). And if successful return false.
// Otherwise become the main instance.
PIDFilename:=GetPidFile;
MyPID:=GetProcessID;
ConfigPID:=-1;
PIDRead:=ReadPIDFile(PIDFilename,ConfigPID);
if PIDRead and (ConfigPID<>MyPID) then begin
// there is a pid file from another instance
if not IsLazarusPIDRunning(ConfigPID) then begin
// clean up
DeleteFileUTF8(PIDFilename);
PIDRead:=false;
end;
end;
if not FileExistsUTF8(PIDFilename) then begin
// try to become the main instance
WritePIDFile(PIDFilename,MyPID);
PIDRead:=false;
end;
if not PIDRead then
PIDRead:=ReadPIDFile(PIDFilename,ConfigPID);
if ConfigPID=MyPID then begin
// this is the main instance
exit;
end;
// this is a second instance
Result:=false;
SendCmdlineActionsToMainInstance;
end;
function GetRemoteControlFilename: string;
begin
Result:=AppendPathDelim(GetPrimaryConfigPath)+'ideremotecontrol.txt';
end;
procedure CleanUpPIDFile;
begin
if EnableRemoteControl then
DeleteFileUTF8(GetRemoteControlFilename);
end;
end. end.

View File

@ -51,7 +51,7 @@ uses
SysUtils, SysUtils,
Interfaces, Interfaces,
Forms, LCLProc, Forms, LCLProc,
LazConf, LazConf, IDECmdLine,
Splash, Splash,
Main, Main,
AboutFrm, AboutFrm,
@ -95,6 +95,7 @@ begin
OnGetApplicationName:=@GetLazarusApplicationName; OnGetApplicationName:=@GetLazarusApplicationName;
Application.Initialize; Application.Initialize;
TMainIDE.ParseCmdLineOptions; TMainIDE.ParseCmdLineOptions;
if not SetupMainIDEInstance then exit;
if Application.Terminated then exit; if Application.Terminated then exit;
// Show splashform // Show splashform
@ -119,9 +120,11 @@ begin
Application.Run; Application.Run;
except except
debugln('lazarus.pp - unhandled exception'); debugln('lazarus.pp - unhandled exception');
CleanUpPIDFile;
Halt; Halt;
end; end;
end; end;
CleanUpPIDFile;
if (SplashForm<>nil) then begin if (SplashForm<>nil) then begin
SplashForm.Free; SplashForm.Free;
SplashForm:=nil; SplashForm:=nil;

View File

@ -39,21 +39,6 @@ interface
uses uses
Classes, SysUtils; Classes, SysUtils;
const
ShowSetupDialogOptLong='--setup';
PrimaryConfPathOptLong='--primary-config-path=';
PrimaryConfPathOptShort='--pcp=';
SecondaryConfPathOptLong='--secondary-config-path=';
SecondaryConfPathOptShort='--scp=';
NoSplashScreenOptLong='--no-splash-screen';
NoSplashScreenOptShort='--nsc';
StartedByStartLazarusOpt='--started-by-startlazarus';
SkipLastProjectOpt='--skip-last-project';
EnableRemoteControlOpt='--remote-control';
DebugLogOpt='--debug-log=';
LanguageOpt='--language=';
LazarusDirOpt ='--lazarusdir=';
resourcestring resourcestring
lisErrInvalidOption = 'Invalid option at position %d: "%s"'; lisErrInvalidOption = 'Invalid option at position %d: "%s"';
lisErrNoOptionAllowed = 'Option at position %d does not allow an argument: %s'; lisErrNoOptionAllowed = 'Option at position %d does not allow an argument: %s';

View File

@ -34,7 +34,7 @@ uses
// IDE // IDE
IDEProcs, InitialSetupDlgs, OutputFilter, CompilerOptions, ApplicationBundle, IDEProcs, InitialSetupDlgs, OutputFilter, CompilerOptions, ApplicationBundle,
TransferMacros, EnvironmentOpts, IDETranslations, LazarusIDEStrConsts, TransferMacros, EnvironmentOpts, IDETranslations, LazarusIDEStrConsts,
ExtToolDialog, IDECmdLine, ExtToolDialog,
MiscOptions, Project, LazConf, PackageDefs, PackageLinks, PackageSystem, MiscOptions, Project, LazConf, PackageDefs, PackageLinks, PackageSystem,
BuildLazDialog, BuildProfileManager, BuildManager, BaseBuildManager; BuildLazDialog, BuildProfileManager, BuildManager, BaseBuildManager;

View File

@ -631,7 +631,7 @@ type
FCheckingFilesOnDisk: boolean; FCheckingFilesOnDisk: boolean;
FCheckFilesOnDiskNeeded: boolean; FCheckFilesOnDiskNeeded: boolean;
FRemoteControlTimer: TTimer; FRemoteControlTimer: TTimer;
FRemoteControlFileValid: boolean; FRemoteControlFileAge: integer;
FIDECodeToolsDefines: TIDECodetoolsDefines; FIDECodeToolsDefines: TIDECodetoolsDefines;
@ -1105,7 +1105,6 @@ implementation
var var
SkipAutoLoadingLastProject: boolean = false; SkipAutoLoadingLastProject: boolean = false;
StartedByStartLazarus: boolean = false; StartedByStartLazarus: boolean = false;
EnableRemoteControl: boolean = false;
ShowSetupDialog: boolean = false; ShowSetupDialog: boolean = false;
function FindDesignComponent(const aName: string): TComponent; function FindDesignComponent(const aName: string): TComponent;
@ -2273,7 +2272,8 @@ procedure TMainIDE.SetupRemoteControl;
var var
Filename: String; Filename: String;
begin begin
// delete old remote control file debugln(['TMainIDE.SetupRemoteControl ']);
// delete old remote commands
Filename:=GetRemoteControlFilename; Filename:=GetRemoteControlFilename;
if FileExistsUTF8(Filename) then if FileExistsUTF8(Filename) then
DeleteFileUTF8(Filename); DeleteFileUTF8(Filename);
@ -11925,20 +11925,30 @@ procedure TMainIDE.DoExecuteRemoteControl;
ProjectLoaded:=Project1<>nil; ProjectLoaded:=Project1<>nil;
DebugLn(['TMainIDE.DoExecuteRemoteControl.OpenFiles ProjectLoaded=',ProjectLoaded]); DebugLn(['TMainIDE.DoExecuteRemoteControl.OpenFiles ProjectLoaded=',ProjectLoaded]);
// open project // open project (only the last in the list)
if (Files<>nil) and (Files.Count>0) then begin AProjectFilename:='';
for i:=Files.Count-1 downto 0 do begin
AProjectFilename:=Files[0]; AProjectFilename:=Files[0];
if (CompareFileExt(AProjectFilename,'.lpr',false)=0) then if (CompareFileExt(AProjectFilename,'.lpr',false)=0) then
AProjectFilename:=ChangeFileExt(AProjectFilename,'.lpi'); AProjectFilename:=ChangeFileExt(AProjectFilename,'.lpi');
if (CompareFileExt(AProjectFilename,'.lpi',false)=0) then begin if (CompareFileExt(AProjectFilename,'.lpi',false)=0) then begin
// open a project
Files.Delete(i); // remove from the list
AProjectFilename:=CleanAndExpandFilename(AProjectFilename); AProjectFilename:=CleanAndExpandFilename(AProjectFilename);
if FileExistsUTF8(AProjectFilename) then begin if FileExistsUTF8(AProjectFilename) then begin
DebugLn(['TMainIDE.DoExecuteRemoteControl.OpenFiles AProjectFilename="',AProjectFilename,'"']); DebugLn(['TMainIDE.DoExecuteRemoteControl.OpenFiles AProjectFilename="',AProjectFilename,'"']);
Files.Delete(0); if (Project1<>nil)
and (CompareFilenames(AProjectFilename,Project1.ProjectInfoFile)=0)
then begin
// project is already open => do not reopen
ProjectLoaded:=true;
end else begin
// open another project
ProjectLoaded:=(DoOpenProjectFile(AProjectFilename,[])=mrOk); ProjectLoaded:=(DoOpenProjectFile(AProjectFilename,[])=mrOk);
end; end;
end; end;
end; end;
end;
if not ProjectLoaded then begin if not ProjectLoaded then begin
// create new project // create new project
@ -11971,11 +11981,12 @@ var
List: TStringList; List: TStringList;
Files: TStrings; Files: TStrings;
i: Integer; i: Integer;
CmdShow: Boolean;
begin begin
Filename:=GetRemoteControlFilename; Filename:=GetRemoteControlFilename;
if FileExistsUTF8(Filename) then begin if FileExistsUTF8(Filename) and (FRemoteControlFileAge<>FileAgeUTF8(Filename))
// the control file exists then begin
if FRemoteControlFileValid then begin // the control file exists and has changed
List:=TStringList.Create; List:=TStringList.Create;
Files:=nil; Files:=nil;
try try
@ -11986,13 +11997,20 @@ begin
DebugLn(['TMainIDE.DoExecuteRemoteControl reading file failed: ',Filename]); DebugLn(['TMainIDE.DoExecuteRemoteControl reading file failed: ',Filename]);
end; end;
DeleteFileUTF8(Filename); DeleteFileUTF8(Filename);
FRemoteControlFileValid:=not FileExistsUTF8(Filename); FRemoteControlFileAge:=-1;
// execute // execute
Files:=TStringList.Create; Files:=TStringList.Create;
CmdShow:=false;
for i:=0 to List.Count-1 do begin for i:=0 to List.Count-1 do begin
if SysUtils.CompareText(List[i],'show')=0 then
CmdShow:=true;
if SysUtils.CompareText(copy(List[i],1,5),'open ')=0 then if SysUtils.CompareText(copy(List[i],1,5),'open ')=0 then
Files.Add(copy(List[i],6,length(List[i]))); Files.Add(copy(List[i],6,length(List[i])));
end; end;
if CmdShow then begin
// if minimized then restore, bring IDE to front
Application.MainForm.ShowOnTop;
end;
if Files.Count>0 then begin if Files.Count>0 then begin
OpenFiles(Files); OpenFiles(Files);
end; end;
@ -12000,14 +12018,9 @@ begin
List.Free; List.Free;
Files.Free; Files.Free;
end; end;
end else begin
// the last time there was an error (e.g. read/delete failed)
// do not waste time again
end;
end else begin end else begin
// the control file does not exist // the control file does not exist
// => remember the good state FRemoteControlFileAge:=-1;
FRemoteControlFileValid:=true;
end; end;
end; end;

View File

@ -195,7 +195,6 @@ type
class function GetPrimaryConfigPath: String; override; class function GetPrimaryConfigPath: String; override;
class function GetSecondaryConfigPath: String; override; class function GetSecondaryConfigPath: String; override;
procedure CopySecondaryConfigFile(const AFilename: String); override; procedure CopySecondaryConfigFile(const AFilename: String); override;
function GetRemoteControlFilename: string;
function ShowProgress(const SomeText: string; function ShowProgress(const SomeText: string;
Step, MaxStep: integer): boolean; override; Step, MaxStep: integer): boolean; override;
@ -400,11 +399,6 @@ begin
LazConf.CopySecondaryConfigFile(AFilename); LazConf.CopySecondaryConfigFile(AFilename);
end; end;
function TMainIDEInterface.GetRemoteControlFilename: string;
begin
Result:=AppendPathDelim(GetPrimaryConfigPath)+'ideremotecontrol.txt';
end;
function TMainIDEInterface.ShowProgress(const SomeText: string; Step, function TMainIDEInterface.ShowProgress(const SomeText: string; Step,
MaxStep: integer): boolean; MaxStep: integer): boolean;
begin begin