1 /*
2 * Part of WCM Commander
3 * https://github.com/corporateshark/WCMCommander
4 * wcm@linderdaum.com
5 */
6
7 #include "file-exec.h"
8 #include "file-util.h"
9 #include "ncwin.h"
10 #include "ltext.h"
11 #include "string-util.h"
12 #include "panel.h"
13 #include "strmasks.h"
14 #include "ext-app.h"
15
16 #ifndef _WIN32
17 # include <signal.h>
18 # include <sys/wait.h>
19 # include "ux_util.h"
20 #else
21 # include "w32util.h"
22 #endif
23
24
25 #define TERMINAL_THREAD_ID 1
26
27
ReturnToDefaultSysDir()28 void ReturnToDefaultSysDir()
29 {
30 #ifdef _WIN32
31 wchar_t buf[4096] = L"";
32
33 if ( GetSystemDirectoryW( buf, 4096 ) > 0 )
34 {
35 SetCurrentDirectoryW( buf );
36 }
37
38 #else
39 chdir( "/" );
40 #endif
41 }
42
43
44 enum
45 {
46 CMD_RC_RUN = 999,
47 CMD_RC_OPEN_0 = 1000
48 };
49
50 static const int CMD_OPEN_FILE = 1000;
51 static const int CMD_EXEC_FILE = 1001;
52
53
54 struct AppMenuData
55 {
56 struct Node
57 {
58 unicode_t* cmd;
59 bool terminal;
NodeAppMenuData::Node60 Node() : cmd( 0 ), terminal( 0 ) {}
NodeAppMenuData::Node61 Node( unicode_t* c, bool t ) : cmd( c ), terminal( t ) {}
62 };
63
64 ccollect<clPtr<MenuData>> mData;
65 ccollect<Node> nodeList;
66 MenuData* AppendAppList( AppList* list );
67 };
68
AppendAppList(AppList * list)69 MenuData* AppMenuData::AppendAppList( AppList* list )
70 {
71 if ( !list )
72 {
73 return 0;
74 }
75
76 clPtr<MenuData> p = new MenuData();
77
78 for ( int i = 0; i < list->Count(); i++ )
79 {
80 if ( list->list[i].sub.ptr() )
81 {
82 MenuData* sub = AppendAppList( list->list[i].sub.ptr() );
83 p->AddSub( list->list[i].name.data(), sub );
84 }
85 else
86 {
87 p->AddCmd( nodeList.count() + CMD_RC_OPEN_0, list->list[i].name.data() );
88 nodeList.append( Node( list->list[i].cmd.data(), list->list[i].terminal ) );
89 }
90 }
91
92 MenuData* ret = p.ptr();
93 mData.append( p );
94 return ret;
95 }
96
97
FileExecutor(NCWin * NCWin,StringWin & editPref,NCHistory & history,TerminalWin_t & terminal)98 FileExecutor::FileExecutor( NCWin* NCWin, StringWin& editPref, NCHistory& history, TerminalWin_t& terminal )
99 : m_NCWin( NCWin )
100 , m_EditPref( editPref )
101 , m_History( history )
102 , m_Terminal( terminal )
103 , m_ExecId( -1 )
104 {
105 m_ExecSN[0] = 0;
106 }
107
ShowFileContextMenu(cpoint point,PanelWin * Panel)108 void FileExecutor::ShowFileContextMenu( cpoint point, PanelWin* Panel )
109 {
110 FSNode* p = Panel->GetCurrent();
111
112 if ( !p || p->IsDir() )
113 {
114 return;
115 }
116
117 clPtr<AppList> appList = GetAppList( Panel->UriOfCurrent().GetUnicode() );
118
119 //if (!appList.data()) return;
120
121 AppMenuData data;
122 MenuData mdRes, *md = data.AppendAppList( appList.ptr() );
123
124 if ( !md )
125 {
126 md = &mdRes;
127 }
128
129 if ( p->IsExe() )
130 {
131 md->AddCmd( CMD_RC_RUN, _LT( "Execute" ) );
132 }
133
134 if ( !md->Count() )
135 {
136 return;
137 }
138
139 int ret = DoPopupMenu( 0, m_NCWin, md, point.x, point.y );
140
141 m_NCWin->SetCommandLineFocus();
142
143 if ( ret == CMD_RC_RUN )
144 {
145 ExecuteFile( Panel );
146 return;
147 }
148
149 ret -= CMD_RC_OPEN_0;
150
151 if ( ret < 0 || ret >= data.nodeList.count() )
152 {
153 return;
154 }
155
156 StartExecute( data.nodeList[ret].cmd, Panel->GetFS(), Panel->GetPath(), !data.nodeList[ret].terminal );
157 }
158
IsCommand_CD(const unicode_t * p)159 bool IsCommand_CD( const unicode_t* p )
160 {
161 #ifdef _WIN32
162 return (p[0] == 'c' || p[0] == 'C') && (p[1] == 'd' || p[1] == 'D') && (!p[2] || p[2] == ' ');
163 #else
164 return (p[0] == 'c' && p[1] == 'd' && (!p[2] || p[2] == ' '));
165 #endif
166 }
167
ApplyEnvVariable(const char * EnvVarName,std::vector<unicode_t> * Out)168 bool ApplyEnvVariable( const char* EnvVarName, std::vector<unicode_t>* Out )
169 {
170 if ( !Out )
171 {
172 return false;
173 }
174
175 std::string Value = GetEnvVariable( EnvVarName );
176
177 if ( Value.empty() )
178 {
179 return false;
180 }
181
182 *Out = utf8_to_unicode( Value.c_str() );
183 return true;
184 }
185
186 // handle the "cd" command, convert its argument to a valid path, expand ~ and env variables
ConvertCDArgToPath(const unicode_t * p)187 std::vector<unicode_t> ConvertCDArgToPath( const unicode_t* p )
188 {
189 std::vector<unicode_t> Out;
190 std::vector<unicode_t> Temp;
191
192 while ( p && *p )
193 {
194 unicode_t Ch = 0;
195
196 if ( *p == '~' )
197 {
198 if ( LookAhead( p, &Ch ) )
199 {
200 if ( (IsPathSeparator( Ch ) || Ch == 0) && ApplyEnvVariable( "HOME", &Temp ) )
201 {
202 // replace ~ with the HOME path
203 Out.insert( Out.end(), Temp.begin(), Temp.end() );
204 PopLastNull( &Out );
205 }
206 }
207 }
208 else if ( *p == '$' )
209 {
210 // skip `$`
211 std::string EnvVarName = unicode_to_utf8( p + 1 );
212
213 for ( auto i = EnvVarName.begin(); i != EnvVarName.end(); i++ )
214 {
215 if ( IsPathSeparator( *i ) )
216 {
217 *i = 0;
218 break;
219 }
220 }
221
222 if ( ApplyEnvVariable( EnvVarName.data(), &Temp ) )
223 {
224 // replace the var name with its value
225 Out.insert( Out.end(), Temp.begin(), Temp.end() );
226 PopLastNull( &Out );
227 // skip var name
228 p += strlen( EnvVarName.data() );
229 }
230 }
231 else if ( IsPathSeparator( *p ) )
232 {
233 if ( !LastCharEquals( Out, '/' ) && !LastCharEquals( Out, '\\' ) )
234 {
235 Out.push_back( DIR_SPLITTER );
236 }
237 }
238 else
239 {
240 Out.push_back( *p );
241 }
242
243 p++;
244 }
245
246 Out.push_back( 0 );
247
248 // debug
249 // std::vector<char> U = unicode_to_utf8( Out.data() );
250 // const char* UTF = U.data();
251
252 return Out;
253 }
254
ProcessCommand_CD(const unicode_t * cmd,PanelWin * Panel)255 bool FileExecutor::ProcessCommand_CD( const unicode_t* cmd, PanelWin* Panel )
256 {
257 // make a mutable copy
258 std::vector<unicode_t> copy = new_unicode_str( cmd );
259
260 unicode_t* p = copy.data();
261
262 //change dir
263 m_History.Put( p );
264 p += 2;
265
266 SkipSpaces( p );
267
268 std::vector<unicode_t> Path = ConvertCDArgToPath( p );
269
270 if ( Path.empty() || !Path[0] )
271 {
272 #if defined(_WIN32)
273 StartExecute( cmd, Panel->GetFS(), Panel->GetPath() );
274 #else
275 OpenHomeDir( Panel );
276 #endif
277 return true;
278 }
279
280 p = Path.data();
281
282 unicode_t* lastNoSpace = nullptr;
283
284 for ( unicode_t* s = p; *s; s++ )
285 {
286 if ( *s != ' ' )
287 {
288 lastNoSpace = s;
289 }
290 }
291
292 if ( lastNoSpace )
293 {
294 lastNoSpace[1] = 0;
295 } //erase last spaces
296
297 FSPath path = Panel->GetPath();
298
299 ccollect<unicode_t, 0x100> pre;
300 int sc = 0;
301
302 while ( *p )
303 {
304 if ( sc )
305 {
306 if ( *p == sc )
307 {
308 sc = 0; p++; continue;
309 }
310 }
311 else if ( *p == '\'' || *p == '"' )
312 {
313 sc = *p;
314 p++;
315 continue;
316 }
317
318 #ifndef _WIN32
319
320 if ( *p == '\\' && !sc )
321 {
322 p++;
323 }
324
325 #endif
326
327 if ( !p )
328 {
329 break;
330 }
331
332 pre.append( *p );
333 p++;
334 }
335
336 pre.append( 0 );
337 p = pre.ptr();
338
339 const std::vector<clPtr<FS>> checkFS =
340 {
341 Panel->GetFSPtr(),
342 m_NCWin->GetOtherPanel( Panel )->GetFSPtr()
343 };
344
345 clPtr<FS> fs = ParzeURI( p, path, checkFS );
346
347 if ( fs.IsNull() )
348 {
349 char buf[4096];
350 FSString name = p;
351 Lsnprintf( buf, sizeof( buf ), _LT( "can`t change directory to:%s\n" ), name.GetUtf8() );
352 NCMessageBox( m_NCWin, "CD", buf, true );
353 }
354 else
355 {
356 Panel->LoadPath( fs, path, 0, 0, PanelWin::SET );
357 }
358
359 return true;
360 }
361
ProcessCommand_CLS(const unicode_t * cmd)362 bool FileExecutor::ProcessCommand_CLS( const unicode_t* cmd )
363 {
364 m_Terminal.TerminalReset( true );
365 return true;
366 }
367
ProcessBuiltInCommands(const unicode_t * cmd,PanelWin * Panel)368 bool FileExecutor::ProcessBuiltInCommands( const unicode_t* cmd, PanelWin* Panel )
369 {
370 #if defined( _WIN32 ) || defined( __APPLE__ )
371 bool CaseSensitive = false;
372 #else
373 bool CaseSensitive = true;
374 #endif
375
376 if ( IsEqual_Unicode_CStr( cmd, "cls", CaseSensitive ) )
377 {
378 ProcessCommand_CLS( cmd );
379 return true;
380 }
381
382 if ( IsCommand_CD( cmd ) )
383 {
384 ProcessCommand_CD( cmd, Panel );
385 return true;
386 }
387
388 return false;
389 }
390
StartCommand(const std::vector<unicode_t> & CommandString,PanelWin * Panel,bool ForceNoTerminal,bool ReplaceSpecialChars)391 bool FileExecutor::StartCommand( const std::vector<unicode_t>& CommandString, PanelWin* Panel, bool ForceNoTerminal, bool ReplaceSpecialChars )
392 {
393 std::vector<unicode_t> Command = ReplaceSpecialChars ? MakeCommand( CommandString, Panel->GetCurrentFileName() ) : CommandString;
394
395 const unicode_t* p = Command.data();
396
397 SkipSpaces( p );
398
399 // printf( "StartCommand %s, %i\n", (const char*)p, (int)ForceNoTerminal );
400
401 if ( !*p )
402 {
403 return false;
404 }
405
406 if ( *p )
407 {
408 m_History.ResetToLast();
409
410 if ( !ProcessBuiltInCommands( p, Panel ) )
411 {
412 bool NoTerminal = (p[0] == '&' || ForceNoTerminal);
413
414 if ( NoTerminal )
415 {
416 m_History.Put( p );
417
418 if ( p[0] == '&' )
419 {
420 p++;
421 }
422 }
423
424 FS* fs = Panel->GetFS();
425
426 if ( fs && fs->Type() == FS::SYSTEM )
427 {
428 StartExecute( Command.data(), fs, Panel->GetPath(), NoTerminal );
429 }
430 else
431 {
432 NCMessageBox( m_NCWin, _LT( "Execute" ), _LT( "Can`t execute command in non system fs" ), true );
433 }
434 }
435 }
436
437 return true;
438 }
439
ApplyCommand(const std::vector<unicode_t> & cmd,PanelWin * Panel)440 void FileExecutor::ApplyCommand( const std::vector<unicode_t>& cmd, PanelWin* Panel )
441 {
442 clPtr<FSList> list = Panel->GetSelectedList();
443
444 if ( !cmd.data() || !list.ptr() || list->Count() <= 0 )
445 {
446 return;
447 }
448
449 std::vector<FSNode*> nodes = list->GetArray();
450
451 m_NCWin->SetMode( NCWin::TERMINAL );
452
453 for ( auto i = nodes.begin(); i != nodes.end(); i++ )
454 {
455 FSNode* Node = *i;
456
457 const unicode_t* Name = Node->GetUnicodeName();
458
459 std::vector<unicode_t> Command = MakeCommand( cmd, Name );
460
461 StartExecute( Command.data(), Panel->GetFS(), Panel->GetPath() );
462 }
463 }
464
FindFileAssociation(const unicode_t * FileName) const465 const clNCFileAssociation* FileExecutor::FindFileAssociation( const unicode_t* FileName ) const
466 {
467 const auto& Assoc = g_Env.GetFileAssociations();
468
469 for ( const auto& i : Assoc )
470 {
471 std::vector<unicode_t> Mask = i.GetMask();
472
473 clMultimaskSplitter Splitter( Mask );
474
475 if ( Splitter.CheckAndFetchAllMasks( FileName ) )
476 {
477 return &i;
478 }
479 }
480
481 return nullptr;
482 }
483
StartFileAssociation(PanelWin * panel,eFileAssociation Mode)484 bool FileExecutor::StartFileAssociation( PanelWin* panel, eFileAssociation Mode )
485 {
486 const unicode_t* FileName = panel->GetCurrentFileName();
487
488 const clNCFileAssociation* Assoc = FindFileAssociation( FileName );
489
490 if ( !Assoc )
491 {
492 return false;
493 }
494
495 std::vector<unicode_t> Cmd = MakeCommand( Assoc->Get( Mode ), FileName );
496
497 if ( Cmd.data() && *Cmd.data() )
498 {
499 StartExecute( Cmd.data(), panel->GetFS(), panel->GetPath(), !Assoc->GetHasTerminal() );
500 return true;
501 }
502
503 return false;
504 }
505
ExecuteFileByEnter(PanelWin * Panel,bool Shift)506 void FileExecutor::ExecuteFileByEnter( PanelWin* Panel, bool Shift )
507 {
508 FSNode* p = Panel->GetCurrent();
509
510 bool cmdChecked = false;
511 std::vector<unicode_t> cmd;
512 bool terminal = true;
513 const unicode_t* pAppName = 0;
514 clPtr<FS> LocalFs = Panel->GetFSPtr();
515 FSPath LocalPath = Panel->GetPath();
516
517 FSString Uri;
518
519 if ( !(LocalFs->Flags() & FS::HAVE_SEEK) )
520 {
521 // append file name to the path
522 LocalPath.Push( CS_UTF8, p->name.GetUtf8() );
523
524 // try to load virtual system file to local temp file
525 if ( !LoadToTempFile( m_NCWin, &LocalFs, &LocalPath ) )
526 {
527 return;
528 }
529
530 // get full URI to the loaded temp local file
531 Uri = LocalFs->Uri( LocalPath );
532
533 // remove file name from the dir path
534 LocalPath.Pop();
535 }
536 else
537 {
538 Uri = Panel->UriOfCurrent();
539 }
540
541 if ( Shift )
542 {
543 ExecuteDefaultApplication( Uri.GetUnicode() );
544 return;
545 }
546
547 if ( StartFileAssociation( Panel, eFileAssociation_Execute ) )
548 {
549 return;
550 }
551
552 if ( g_WcmConfig.systemAskOpenExec )
553 {
554 cmd = GetOpenCommand( Uri.GetUnicode(), &terminal, &pAppName );
555 cmdChecked = true;
556 }
557
558 if ( p->IsExe() )
559 {
560 #ifndef _WIN32
561
562 if ( g_WcmConfig.systemAskOpenExec && cmd.data() )
563 {
564 ButtonDataNode bListOpenExec[] = { { "&Open", CMD_OPEN_FILE }, { "&Execute", CMD_EXEC_FILE }, { "&Cancel", CMD_CANCEL }, { 0, 0 } };
565
566 static unicode_t emptyStr[] = { 0 };
567
568 if ( !pAppName )
569 {
570 pAppName = emptyStr;
571 }
572
573 int ret = NCMessageBox( m_NCWin, "Open",
574 carray_cat<char>( "Executable file: ", p->name.GetUtf8(), "\ncan be opened by: ", unicode_to_utf8( pAppName ).data(), "\nExecute or Open?" ).data(),
575 false, bListOpenExec );
576
577 if ( ret == CMD_CANCEL )
578 {
579 return;
580 }
581
582 if ( ret == CMD_OPEN_FILE )
583 {
584 StartExecute( cmd.data(), LocalFs.ptr(), LocalPath, !terminal );
585 return;
586 }
587 }
588
589 #endif
590 ExecuteFile( Panel );
591 return;
592 }
593
594 if ( !cmdChecked )
595 {
596 cmd = GetOpenCommand( Uri.GetUnicode(), &terminal, 0 );
597 }
598
599 if ( cmd.data() )
600 {
601 StartExecute( cmd.data(), LocalFs.ptr(), LocalPath, !terminal );
602 }
603 }
604
ExecuteFile(PanelWin * panel)605 void FileExecutor::ExecuteFile( PanelWin* panel )
606 {
607 FSNode* p = panel->GetCurrent();
608
609 if ( !p || p->IsDir() || !p->IsExe() )
610 {
611 return;
612 }
613
614 FS* fs = panel->GetFS();
615
616 if ( !fs || fs->Type() != FS::SYSTEM )
617 {
618 NCMessageBox( m_NCWin, _LT( "Run" ), _LT( "Can`t execute file in not system fs" ), true );
619 return;
620 }
621
622 #ifdef _WIN32
623
624 static unicode_t w[2] = { '"', 0 };
625 StartExecute( carray_cat<unicode_t>( w, panel->UriOfCurrent().GetUnicode(), w ).data(), fs, panel->GetPath() );
626
627 #else
628
629 const unicode_t* fName = p->GetUnicodeName();
630 int len = unicode_strlen( fName );
631 std::vector<unicode_t> cmd( 2 + len + 1 );
632 cmd[0] = '.';
633 cmd[1] = '/';
634 memcpy( cmd.data() + 2, fName, len * sizeof( unicode_t ) );
635 cmd[2 + len] = 0;
636 StartExecute( cmd.data(), fs, panel->GetPath() );
637
638 #endif
639 }
640
StartExecute(const unicode_t * cmd,FS * fs,FSPath & path,bool NoTerminal)641 void FileExecutor::StartExecute( const unicode_t* cmd, FS* fs, FSPath& path, bool NoTerminal )
642 {
643 SkipSpaces( cmd );
644
645 if ( DoStartExecute( m_EditPref.Get(), cmd, fs, path ) )
646 {
647 m_History.Put( cmd );
648 m_NCWin->SetMode( NCWin::TERMINAL );
649 }
650
651 ReturnToDefaultSysDir();
652 }
653
DoStartExecute(const unicode_t * pref,const unicode_t * cmd,FS * fs,FSPath & path,bool NoTerminal)654 bool FileExecutor::DoStartExecute( const unicode_t* pref, const unicode_t* cmd, FS* fs, FSPath& path, bool NoTerminal )
655 {
656 #ifdef _WIN32
657
658 if ( !m_Terminal.Execute( m_NCWin, TERMINAL_THREAD_ID, cmd, 0, fs->Uri( path ).GetUnicode() ) )
659 {
660 return false;
661 }
662
663 #else
664
665 static unicode_t empty[] = {0};
666 static unicode_t newLine[] = { '\n', 0 };
667
668 if ( !pref )
669 {
670 pref = empty;
671 }
672
673 if ( !*cmd )
674 {
675 return false;
676 }
677
678 m_Terminal.TerminalReset();
679
680 if ( NoTerminal )
681 {
682 unsigned fg = 0xB;
683 unsigned bg = 0;
684
685 m_Terminal.TerminalPrint( newLine, fg, bg );
686 m_Terminal.TerminalPrint( pref, fg, bg );
687 m_Terminal.TerminalPrint( cmd, fg, bg );
688 m_Terminal.TerminalPrint( newLine, fg, bg );
689
690 char* dir = 0;
691
692 if ( fs && fs->Type() == FS::SYSTEM )
693 {
694 dir = (char*) path.GetString( sys_charset_id );
695 }
696
697 FSString s = cmd;
698 sys_char_t* SysCmd = (sys_char_t*) s.Get( sys_charset_id );
699
700 pid_t pid = fork();
701 if ( pid < 0 )
702 {
703 return false;
704 }
705
706 if ( pid )
707 {
708 waitpid( pid, 0, 0 );
709 }
710 else
711 {
712 if ( !fork() )
713 {
714 //printf("exec: %s\n", SysCmd);
715 signal( SIGINT, SIG_DFL );
716 static char shell[] = "/bin/sh";
717 const char* params[] = { shell, "-c", SysCmd, NULL };
718
719 if ( dir )
720 {
721 chdir( dir );
722 }
723
724 execv( shell, (char**) params );
725 exit( 1 );
726 }
727
728 exit( 0 );
729 }
730 }
731 else
732 {
733 unsigned fg_pref = 0xB;
734 unsigned fg_cmd = 0xF;
735 unsigned bg = 0;
736
737 m_Terminal.TerminalPrint( newLine, fg_pref, bg );
738 m_Terminal.TerminalPrint( pref, fg_pref, bg );
739 m_Terminal.TerminalPrint( cmd, fg_cmd, bg );
740 m_Terminal.TerminalPrint( newLine, fg_cmd, bg );
741
742 int l = unicode_strlen( cmd );
743 int i;
744
745 if ( l >= 64 )
746 {
747 for ( i = 0; i < 64 - 1; i++ )
748 {
749 m_ExecSN[i] = cmd[i];
750 }
751
752 m_ExecSN[60] = '.';
753 m_ExecSN[61] = '.';
754 m_ExecSN[62] = '.';
755 m_ExecSN[63] = 0;
756 }
757 else
758 {
759 for ( i = 0; i < l; i++ )
760 {
761 m_ExecSN[i] = cmd[i];
762 }
763
764 m_ExecSN[l] = 0;
765 }
766
767 m_Terminal.Execute( m_NCWin, TERMINAL_THREAD_ID, cmd, (sys_char_t*) path.GetString( sys_charset_id ) );
768 }
769
770 #endif
771
772 return true;
773 }
774
StopExecute()775 void FileExecutor::StopExecute()
776 {
777 #ifdef _WIN32
778
779 if ( NCMessageBox( m_NCWin, _LT( "Stop" ), _LT( "Drop current console?" ), false, bListOkCancel ) == CMD_OK )
780 {
781 m_Terminal.DropConsole();
782 }
783
784 #else
785
786 if ( m_ExecId > 0 )
787 {
788 int ret = KillCmdDialog( m_NCWin, m_ExecSN );
789
790 if ( m_ExecId > 0 )
791 {
792 if ( ret == CMD_KILL_9 )
793 {
794 kill( m_ExecId, SIGKILL );
795 }
796 else if ( ret == CMD_KILL )
797 {
798 kill( m_ExecId, SIGTERM );
799 }
800 }
801 }
802
803 #endif
804 }
805
ThreadSignal(int id,int data)806 void FileExecutor::ThreadSignal( int id, int data )
807 {
808 if ( id == TERMINAL_THREAD_ID )
809 {
810 m_ExecId = data;
811 }
812 }
813
ThreadStopped(int id,void * data)814 void FileExecutor::ThreadStopped( int id, void* data )
815 {
816 if ( id == TERMINAL_THREAD_ID )
817 {
818 m_ExecId = -1;
819 m_ExecSN[0] = 0;
820 }
821 }
822