1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
|
unit cli;
{$mode objfpc}{$H+}
{ Copyright (C) 2017-2024 David Bannon
License:
This code is licensed under MIT License, see the file License.txt
or https://spdx.org/licenses/MIT.html SPDX short identifier: MIT
------------------
This unit is active before the GUI section and may decide GUI is not needed.
Handles command line switches, imports and comms with an existing instance.
History
2020/06/18 Remove unnecessary debug line.
2021/12/28 Tidy LongOpts model.
2022/01/13 When importing file, don't check for its existance, importer will do that
2022/01/13 When importing note, check if FFileName starts with '~'
2022/04/07 Tidy up options.
2022/05/03 Add unix username to IPC pipe.
2022/10/18 Short switch for import MD is -m
2022/10/20 Do Import from this instance with direct call to IndexNewNote().
2024/06/01 Changed cli switch --kde-leftclick to allow-leftclick as useful in gnome too.
}
{ Note that for Qt app, the command line can also include a option meant for Qt.
See https://doc.qt.io/qt-5/qguiapplication.html#supported-command-line-options
It appears that Qt sees such an option before this unit does AND it removes it
and also removes anything after it ! But does act on it. Seems both - and -- acceptable -
--platformtheme qt5ct
-platformtheme qt5ct
And a typo in the option value is ignored.
Use a space as seperator, not '=' !
}
interface
uses
Classes, SysUtils, Dialogs;
function ContinueToGUI() : boolean ;
{ Will take offered file name, report some error else checks its a .note,
saves it to normal repo and requests a running instance (if there is one) to
reindex as necessary. Note that in CLI mode, we do not check for a Title Clash
(because NoteLister may not be running. In GUI mode, existing Title is checked.}
function Import_Note(FFName : string = '') : string;
{ Will take offered file name, report error else converts that file to .note,
saves it to normal repo and requests a running instance (if there is one) to
reindex as necessary. Note that in CLI mode, we do not check for a Title Clash
(because NoteLister may not be running. In GUI mode, existing Title is checked.}
function Import_Text_MD_File(MD : boolean; FFName : string = '') : string;
var
SingleNoteName : string = ''; // other unit will want to know.....
OpenNewNotePlease : boolean = false;
const Version_string = {$I %TOMBOY_NG_VER};
implementation
uses Forms, LCLProc, LazFileUtils, FileUtil, ResourceStr, simpleipc, IniFiles,
import_notes, tb_utils;
type
ENoNotesRepoException = class(Exception);
var
LongOpts : TStringArray; // See initialization section
const
ShortOpts = 'hgo:l:vt:m:n:c'; // help gnome3 open lang ver import-[txt md note]
{ If something on commandline means don't proceed, ret True }
function CommandLineError(inCommingError : string = '') : boolean;
var
ErrorMsg : string;
begin
ErrorMsg := InCommingError;
Result := false;
if ErrorMsg = '' then begin
ErrorMsg := Application.CheckOptions(ShortOpts, LongOpts);
if Application.HasOption('h', 'help') then
ErrorMsg := 'Usage -';
end;
if ErrorMsg <> '' then begin
DebugLn(ErrorMsg);
{$ifdef DARWIN}
debugln(rsMachelp1);
debugln(rsMacHelp2);
{$endif}
debugln(' --dark-theme ' + 'Does not work for GTK2');
debugln(' -l --lang=CCode ' + rsHelpLang); // syntax depends on bugfix https://bugs.freepascal.org/view.php?id=35432
debugln(' -h --help ' + rsHelpHelp);
debugln(' -c ' + rsHelpCreateNew);
debugln(' --version ' + rsHelpVersion);
debugln(' --no-splash ' + rsHelpNoSplash);
debugln(' --debug-sync ' + rsHelpDebugSync);
debugln(' --debug-index ' + rsHelpDebugIndex);
debugln(' --debug-spell ' + rsHelpDebugSpell);
debugln(' --config-dir=PATH_to_DIR ' + rsHelpConfig);
debugln(' --open-note=PATH_to_NOTE ' + rsHelpSingleNote);
debugln(' --debug-log=SOME.LOG ' + rsHelpDebug);
// debugln(' --save-exit ' + rsHelpSaveExit); // legacy but still allowed.
debugln(' --import-txt=PATH_to_FILE ' + rsHelpImportFile + ' also -t');
debugln(' --import-md=PATH_to_FILE ' + rsHelpImportFile + ' also -m');
debugln(' --import-note=PATH_to_NOTE ' + rsHelpImportFile + ' also -n');
debugln(' --title-fname ' + rsHelpTitleISFName);
{$ifdef LCLgtk2}
debugln(' --useappind=yes|no ' + rsParticularSysTray);
{$endif}
{$ifdef LINUX}
debugln(' --allow-leftclick ' + rsAllowLeftClick);
{$endif}
{$if defined(LCLQT5) or defined(LCLQt6)}
debugln(' --strict-theme ' + rsStrictThemeColors);
debugln(' -platform xcb ' + rsBypassWayland);
debugln(' -platformtheme gnome|gtk2|qt5ct|qt6ct ' + rsSelectColors);
{$endif}
result := true;
end;
end;
{ Ret T if we have ONE or more command line Paramaters, not to be confused with
a Option, a parameter has no '-'. Because the only parameter we expect is SingleNoteFileName,
we also honour -o --open-note. More than one such parameter is an error, report to console,
ret true but set SingleFileName to ''. }
function HaveCMDParam() : boolean;
var
Params : TStringArray;
begin
Result := False;
if Application.HasOption('o', 'open-note') then begin
SingleNoteName := Application.GetOptionValue('o', 'open-note');
exit(True);
end;
Params := Application.GetNonOptions(ShortOpts, LongOpts);
if length(Params) = 1 then begin
if Params[0] <> '%f' then begin // MX Linux passes the %f from desktop file during autostart
SingleNoteName := Params[0];
exit(True);
end;
end;
if length(Params) > 1 then begin
CommandLineError('Unrecognised parameters on command line');
SingleNoteName := '';
exit(True);
end;
end;
// Looks for server, if present, sends indicated message and returns true, else false.
function CanSendMessage(Msg : string) : boolean;
var
CommsClient : TSimpleIPCClient;
begin
Result := False;
// if TheReindexProc = nil then begin
try
CommsClient := TSimpleIPCClient.Create(Nil);
CommsClient.ServerID:='tomboy-ng' {$ifdef UNIX} + '-' + GetEnvironmentVariable('USER'){$endif}; // on multiuser system, unique
if CommsClient.ServerRunning then begin
CommsClient.Active := true;
CommsClient.SendStringMessage(Msg);
CommsClient.Active := false;
Result := True;
end;
finally
freeandnil(CommsClient);
end;
// end else TheReindexProc();
end;
{ Returns the absolute notes dir, raises ENoNotesRepoException if it cannot find it }
function GetNotesDir() : string;
var
ConfigFile : TIniFile;
begin
// TiniFile does not care it it does not find the config file, just returns default values.
ConfigFile := TINIFile.Create(TB_GetDefaultConfigDir + 'tomboy-ng.cfg');
try
result := ConfigFile.readstring('BasicSettings', 'NotesPath', ''); // MUST be mentioned in config file
finally
ConfigFile.Free;
end;
if Result = '' then
Raise ENoNotesRepoException.create('tomboy-ng does not appear to be configured');
end;
function Import_Note(FFName : string = '') : string;
var
FFileName, Fname : string;
GUID : TGUID;
begin
Result := '';
if FFName <> '' then
FFileName := FFName
else FFileName := Application.GetOptionValue('n', 'import-note');
{$ifdef UNIX}
if FFileName[1] = '~' then
FFileName := GetEnvironmentVariable('HOME') + FFileName.Remove(0,1);
{$endif}
if not FileExists(FFileName) then begin
Result := 'Error, request to import nonexisting file : ' + FFileName;
debugln(Result);
exit;
end;
if GetTitleFromFFN(FFileName, false) = '' then begin
Result := 'Error, request to import invalid Note file : ' + FFileName;
debugln(Result);
exit;
end;
if IDLooksOK(ExtractFileNameOnly(FFileName)) then
FName := ExtractFileName(FFileName)
else begin
CreateGUID(GUID);
FName := copy(GUIDToString(GUID), 2, 36) + '.note';
end;
// debugln('About to copy ' + FFileName + ' to ' + FName);
if CopyFile(FFileName, GetNotesDir() + FName) then begin
if TheReindexProc = nil then // Defined in tb_utils, set in SearchUnit.
CanSendMessage('REINDEX:' + FName)
else TheReindexProc(FName, True);
end
else begin
Result := 'ERROR - failed to copy ' + FFileName + ' to ' + GetNotesDir() + FName;
debugln(Result);
end;
end;
function Import_Text_MD_File(MD : boolean; FFName : string = '') : string;
// May generate an error but reports it to commad line so, OK ?
// but is also called from SearchUnit, so who sees that error then ?
//
var
FFileName : string;
Importer : TImportNotes;
begin
result := ''; // Anything here is an error message
if FFName <> '' then
FFileName := FFName
else
if MD then
FFileName := Application.GetOptionValue('m', 'import-md')
else FFileName := Application.GetOptionValue('t', 'import-txt');
Importer := TImportNotes.Create;
try
try
Importer.DestinationDir := GetNotesDir(); // might raise ENoNotesRepoException
Importer.Mode := 'plaintext';
if MD then Importer.Mode := 'markdown';
Importer.FirstLineIsTitle := true;
if Application.HasOption('f', 'title-fname') then
Importer.FirstLineIsTitle := false;
Importer.ImportName := FFileName;
if Importer.Execute() = 0 then
debugln(Importer.ErrorMsg)
else // At this stage, we have a valid note in repo but not Indexed.
if TheReindexProc = nil then begin // Defined in tb_utils, set in SearchUnit.
CanSendMessage('REINDEX:'+ Importer.NewFileName) // eg REINDEX:48480CC5-EC3E-4AA0-8C83-62886DB291FD.note
end
else TheReindexProc(Importer.NewFileName, True); // If TheReindexProc then we have a GUI, show message there.
except on
//E: ENoNotesRepoException do begin
E: Exception do begin
Result := E.Message; // SearchUnit will look at that.
debugln(E.Message);
exit;
end;
end;
finally
Importer.Free;
end;
end;
// Looks to see if user just wants some command line activity, returns
// false if GUI is not needed.
function ContinueToGUI() : boolean ;
begin
if CommandLineError() then exit(False);
if Application.HasOption('v', 'version') then begin
debugln('tomboy-ng version ' + Version_String);
exit(False);
end;
if Application.HasOption('t', 'import-txt') then begin
Import_Text_MD_File(false);
exit(False);
end;
if Application.HasOption('m', 'import-md') then begin
Import_Text_MD_File(true);
exit(False);
end;
if Application.HasOption('n', 'import-note') then begin
Import_Note();
exit(False);
end;
if Application.HasOption('c', 'create-note') then begin
if CanSendMessage('CREATENOTE') then
exit(false) // no GUI needed, just tell existing one to open a new note
else
OpenNewNotePlease := True; // mainunit FormShow() will look there to see if it got a new note request at startup
end; // applies only when user uses -c and -ng is not already running
// Note that the useappind option is processed in the LPR file.
if HaveCMDParam() then // Only command line parameter is filename of a Single Note
if SingleNoteName = '' then
exit(False) // thats an error, more than one parameter
else exit(True); // proceed in SNM, we set its File Name further up.
// Looks like a normal startup
if CanSendMessage('SHOWSEARCH') then exit(False); // will fail if -ng is not already running.
Result := true;
end;
initialization
LongOpts := TStringArray.create( // a type, not an object, don't free.
'dark-theme', 'lang:', 'debug-log:',
'help', 'version', 'no-splash',
'debug-sync', 'debug-index', 'debug-spell',
'config-dir:', 'open-note:', 'save-exit', // -o for open also legal. save-exit is legecy
'import-txt:', 'import-md:', 'import-note:', // -t, -m -n respectivly
'title-fname', 'gnome3', 'useappind:', // -g and gnome3 is legal but legacy, ignored.
'strict-theme', // Strict-theme applies to only Qt versions
'allow-leftclick', // overrule wayland decision to use only right click
'create-note', // create a new note (-ng open or not), added post 0.41
'disableaccurateframe'); // possible read by LCL to stop an annoying little window in RasPiOS
end.
|