Aplikacja z zadokowną systemową konsolą

0

Chcę napisać program, który umożliwi odpalenie systemowej konsoli (cmd.exe) w oknie mojej aplikacji.
Chodzi o to, żeby:

  1. Użytkownik uruchamia program
  2. W programie jest pole do wpisania linii poleceń oraz argumentów oraz przycisk Wykonaj
  3. Program ma zadokować utworzone okno konsoli
  4. Po kliknięciu przycisku, zadokowana konsola ma wykonać program/polecenie i jego parametry (w ramach naszego okna programu)

np:
Użytkownik wpisuje w polu edycji polecenie: ping -a microsoft.com
Zadokowana konsola ma wykonać zadanie, wyświetlić wyniki (czyli wszystko to co normalnie, tylko że okno konsoli CMD ma być w oknie mojej aplikacji.

Czy jest możliwe? Czy ktoś dysponuje jakimś przykładowym kodem?
Za każdą pomoc dziękuję...

-Pawel

2

Kiedyś robiłem podobny myk, tyle że osadzałem okno emulatora FCEUX w oknie mojej aplikacji. Ot jedyne co trzeba było zrobić to zmienić parent okna emulatora na uchwyt mojego okna — resztą zajmował się system (osadzał okno i usuwał jego dekoracje). Pobaw się tym i zobacz czy to działa na obecnych systemach Windows.

W przypadku konsoli systemowej, zawsze możesz skorzystać z czegoś podobnego jak TProcess w Lazarusie — odpalić program konsoli (jako ukryty), przekazać mu dane wejściowe (komendę), poczekać na wykonanie, a na koniec odczytać wyjście i wyświetlić je w swoim programie.

2

Kiedyś na 100% był jakiś komponent do tego. Przyjrzyj się temu ale nie wiem (nie pamiętam nawet nazwy tego komponentu) ale możliwe że było to albo czy się nie nada: https://github.com/TurboPack/DOSCommand

1

Wydaje mi się, że też korzystałem ze sposobu opisanego przez @furious programming robisz normalnie createprocess do utworzenia konsoli pobierasz jego handla i zmieniasz mu parenta na np panel w swojej aplikacji i to powinno Ci na formie->panelu zadokować cmd.exe

1

Terminal windowsa jest open source: https://github.com/microsoft/terminal tylko że nie ten język
Możesz osadzić sobie okno conhost wewnątrz swojej aplikacji ale wtedy nie będziesz miał nad nim dużej kontroli, po prostu aplikacja w aplikacji.
Możesz przekierować strumienie i je obsługiwać ale to nie będzie działało z interaktywnymi programami konsolowymi, kolorowaniem itd. natomiast to najprostsze rozwiązanie - do pinga się nada.

0
furious programming napisał(a):

Kiedyś robiłem podobny myk, tyle że osadzałem okno emulatora FCEUX w oknie mojej aplikacji. Ot jedyne co trzeba było zrobić to zmienić parent okna emulatora na uchwyt mojego okna — resztą zajmował się system (osadzał okno i usuwał jego dekoracje). Pobaw się tym i zobacz czy to działa na obecnych systemach Windows.

W przypadku konsoli systemowej, zawsze możesz skorzystać z czegoś podobnego jak TProcess w Lazarusie — odpalić program konsoli (jako ukryty), przekazać mu dane wejściowe (komendę), poczekać na wykonanie, a na koniec odczytać wyjście i wyświetlić je w swoim programie.

Tak, czytałem Twój post. Ale, tam blokowałeś skróty kkalwiszowe oraz wydaje mi się, że to było raczej autoskie rozwiązanie, pod log gry...
W sumie można skorzystać z okna konsoli, które ukryje i przy pomocy "pipe" (CreatePipe()) przekierować wyjście do mojego programu... choć wolałbym zadokować oryginalne okno cmd, z wszystkimi jego dobrodziejstwami.

kAzek napisał(a):

Kiedyś na 100% był jakiś komponent do tego. Przyjrzyj się temu ale nie wiem (nie pamiętam nawet nazwy tego komponentu) ale możliwe że było to albo czy się nie nada: https://github.com/TurboPack/DOSCommand

Ten komponent chyba działa tak, że ukrywa okienko konsoli i przekierowuje wyjście... chyba...

woolfik napisał(a):

Wydaje mi się, że też korzystałem ze sposobu opisanego przez @furious programming robisz normalnie createprocess do utworzenia konsoli pobierasz jego handla i zmieniasz mu parenta na np panel w swojej aplikacji i to powinno Ci na formie->panelu zadokować cmd.exe

Tak, to chyba najlepszy sposób - muszę jadnak nauczyć się jak to zrobić, ewentualnie znaleźć jakiś przykładowy kod

obscurity napisał(a):

Terminal windowsa jest open source: https://github.com/microsoft/terminal tylko że nie ten język
Możesz osadzić sobie okno conhost wewnątrz swojej aplikacji ale wtedy nie będziesz miał nad nim dużej kontroli, po prostu aplikacja w aplikacji.
Możesz przekierować strumienie i je obsługiwać ale to nie będzie działało z interaktywnymi programami konsolowymi, kolorowaniem itd. natomiast to najprostsze rozwiązanie - do pinga się nada.

Tak, tyle że, ping to był tylko przykład - ja chcę dać użytkownikowi możliwość odpalenia wszystkiego, nawet programu ze skomplikowanym ciągiem parametrów (stąd, dobrze, żeby użytkownik wpisał polecenie w polu edycji i dopiero po kliknięciu przycisku 'Wykonaj" przekazać to do okna konsoli...

Dziękuję wszystkim za wskazówki... szukam wciąż jakiegoś rozwiązania (przy okazji próbuję sam ze zmianą okna rodzica, może coś z tego będzie).
-Pawel

1

Czy wystarczy uruchomić program, podać dane wejściowe, poczekać, aż program się zakończy i odczytać dane wyjściowe? Czy potrzeba pełnej interakcji?

Jeżeli to drugie, czyli pełna interakcja, to najprościej pójść zupełnie innym sposobem. Na komputerze uruchomić serwer telnet, a Twój program niech będzie klientem telnet. W tym przypadku nie szkodzi, że połączenie nie jest szyfrowane, bo będzie na localhost, czyli dane nie będą wychodzić poza komputer. Po zestawieniu połączenia trzeba się zalogować swoim użytkownikiem (nie wiem, jak będzie w przypadku Windows, w przypadku Linux na pewno tak jest) i ma się już konsolę.

O ile sam protokół telnet jest bardzo prosty, o tyle poprawność wyświetlania uruchamianych programów zależy od implementacji standardu ANSI i VT100. W najprostszym przypadku nie ma kodów ucieczki. Tak naprawdę, to musiałbyś zaimplementować dwie rzeczy, czyli protokół telnet i emulator terminala ANSI i VT100 (w większości przypadków wystarczy częściowa kompatybilność).

W przypadku przekierowania strumienia we/wy, jak już proponowano, to też da się obsłużyć programy interaktywne i ze skomplikowanym interfejsem w taki sposób, że odczyt strumienia wyjścia następuje w pętli uruchamianej w osobnym wątku, Jeżeli cokolwiek zostanie odczytane, to nastąpi przetworzenie i wyświetlanie. W ten sposób na Windows można uruchomić cmd.exe i będzie mniej więcej działać. Mniej więcej, bo jak ja to przerabiałem, to generalnie dało sie wpisywać polecenia, ale nie do końca dobrze dział backspace. Jednak uruchomienie programu ping lub ewentualnie przygotowanie pliku *.bat, który uruchamia ping i uruchomienie polecenia cmd.exe /C somefile.bat nie powinno być problemem, uruchomi się konsola, od razu wykona się polecenie, Twój program odbierze wynik i będziesz mieć.

Mam taki jeden projekt, w którym zastosowałem obydwa sposoby interakcji z konsolą: https://github.com/andrzejlisek/TextPaint

Ten projekt jest w C#, przedstawiam klasy związane z połączeniami, żeby mieć pojęcie, jak to zrobić:
https://github.com/andrzejlisek/TextPaint/blob/master/TextPaintFramework/TextPaint/UniConnTelnet.cs
https://github.com/andrzejlisek/TextPaint/blob/master/TextPaintFramework/TextPaint/UniConnApp.cs

Tutaj nowszy projekt zastępujący wyżej wymieniony: https://github.com/andrzejlisek/TextPaintWeb

Tutaj protokół telnet od strony klienta:
https://github.com/andrzejlisek/TextPaintWeb/blob/main/prog/terminalconnworkertelnet.h
https://github.com/andrzejlisek/TextPaintWeb/blob/main/prog/terminalconnworkertelnet.cpp
W tej aplikacji jest zastosowany taki myk, że uruchamiam własny serwer WebSocket, który przyjmuje i wysyła bajty w sieć tak, jak leci, serwer "nie zna" żadnego protokołu.

Dobry opis kodów ucieczki jest tutaj: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Jeżeli w ogóle coś uruchomisz, to na początek wystarczą kody stąd: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797

Generalnie, można powiedzieć, że ja ten temat przerabiałem, samo podłączenie się do aplikacji nie jest trudne, ale pełna zgodność z VT100 nie jest łatwa. Jednak ja testowałem na programie VTTEST i animacjach z http://artscene.textfiles.com/vt100/ więc wymagania odnośnie kompatybilności były wyższe.

Żeby było jeszcze śmieszniej, to w Windows 11 są dwie aplikacje konsoli, przetestowałem je na wylot programem VTTEST uruchomionym przez cygwin i generalnie działają mniej więcej dobrze, ale okazało się mój program bije microsoftowe programy kompatybilnością. W VTTEST są testy, które mój program wyświetla poprawnie, a ten od M$ z błędami.

1

@andrzejlisek Dziękuję za twój post. Widzę, że masz temat opracowany i jesteś z niego dumny. Tak trzymać.
Twoje rozwiązanie jednak wydaje się być, w moim przypadku, nadmiarowe (i chyba skomplikowane). Mój program ma być niejako hubem dla istniejących aplikacji (te z GUI chcę tylko uruchomić, a te kosolowe, które przyjmuja parametry chcę odpalić w konsoli, ale tak, żeby to wszystko było zintegrowane). Wydaje mi się, że najlepszym rozwiązaniem będzie utworzenie procesu konoli systemowej, zmianę jej okna rodzica, a tym samym "zadokowanie" w moim oknie. Teraz tylko muszę to napisać 😛

1

https://github.com/z505/TProcess-Delphi
uruchamiasz proces i jego wyjcie rysujesz w taki sposob aby wygladalo to jak zadokowana konsola

Koncepcja zadokowania okna konsoli pewnie da sie zrobic ale mi wystrarczył ten link co podalem

0

https://github.com/z505/TProcess-Delphi

Jak wyżej napisałem, tak naprawdę to są dwa osobne elementy całego zadania i na każdy lepiej spojrzeć osobno. Jedynie przy próbie osadzania okna windowsowej aplikacji konsoli lub jego wysterowania, o ile to się w ogóle da zrobić, to ma się oba elementy z głowy.

uruchamiasz proces

To jest proste do zrobienia, a tym bardziej, że jest gotowiec to umożliwia, jednak należy mieć świadomość, że nie każdy program daje się w ten sposób obsługiwać. W większości przypadków to jest możliwe i warto od tego zacząć. Jednak trzeba mieć świadomość, że zdarzają się przypadki, w których ta metoda zawodzi. Nie wiem, jak w Delphi czy Lazarus, ale przerabiałem to w .NET i Java, w obu są te same problemy.

  1. Program VTTEST. To jest prosty program napisany w C, który z stdin odbiera dane tekstowe (wpisanie numeru opcji i klawisz ENTER), a na stdout wypisuje tekst z kodami generujący obraz testowy. Przy uruchamianiu z przekierowaniem strumieni, wyświetla podstawowe informacje i numer wersji i na tym program zawiesza się. Pewnie da się to ominąć, trzeba by zajrzeć do źródeł i popatrzeć, na czym się zawiesza.
  2. Dowolny program konsolowy w C# zawierający instrukcje Console.ReadKey(true). W przypadku przekierowania stdout, ta instrukcja rzuca wyjątek zamiast czekać na znak.

Stąd moja sugestia, żeby użyć telnet, w nim to takich problemów nie ma, a dodatkowo da się wysłać do serwera informację o rozdzielczości konsoli.

i jego wyjcie rysujesz w taki sposob aby wygladalo to jak zadokowana konsola

No i dopiero tu zaczyna się zabawa, szczególnie, jak chcesz uruchamiać bardziej zaawansowane programy, jak np. mc, htop, aptitude (wiem, że w Windows nie ma tych programów, ale chodzi o podobny stopień złożoności i wykorzystania konsoli tekstowej). Z jednej strony, to już jest poza tematem samego uruchomienia, wysterowania i odbierania informacji z programu zewnętrznego, o czym napisałem wyżej i można zrobić to na dwa sposoby (uruchamianie procesu z przekierowaniem strumieni lub łączność telnet).

W przypadku takich prostych rzeczy, jak ping, tracert, ipconfig i większości "szkolnych" programów, w których użytkownik coś wpisuje, naciska Enter i program wypisuje jakieś informacje tekstowe (większość zadań na SPOJ polega na napisaniu takiego właśnie programu) nie ma potrzeby w zaden sposób obsługiwać sekwencji ucieczki.

Tutaj potrzebne są dwa elementy:

  1. Wejście, co jest prostsze do zrobienia. Każdy klawisz wysyła jakąś sekwencję bajtów. Klawisze ze znakami pisarskimi wysyłają po prostu znak, natomiast inne klawisze, jak strzałki, funkcje, enter, wysyłają różne sekwencje. Można w ramach testów podpatrzeć i próbować. Np. Enter wysyła 13 lub 10 w zależności od implementacji i zapotrzebowania programu docelowego. Backspace to raz potrzeba wysłać 8, a raz 127. Dlatego w swoim programie umożliwiłem wybór różnych wariantów.
  2. Wyjście, wcale nie jest takie trudne, tylko faktem jest, że sekwencji sterujących jest bardzo dużo, ale oczywiście nie trzeba wszystkich obsługiwać. Wystarczy napotkać znak 27, który znaczy, że to jest kod ucieczki, który wyświetlany, tylko wysterowuje terminal, np. czyści ekran, przemieszcza kursor, ustawia kolory itp. Im więcej kodów ucieczki obsłużysz, tym więcej programów będziesz mógł uruchomić, przy czym, jak czegoś nie obsłużysz, to program i tak będzie działać prawidłowo, tylko, ze będzie źle wyświetlać informacje, co może utrudniać, a nawet uniemożliwiać korzystanie z programu. Jak wspomniałem wyżej, w niektórych przypadkach wystarczy, że nie będziesz obsługiwać żadnej sekwencji, w wielu przypadkach wystarczy ok. 20 najpopularniejszych, ale bardziej wyrafinowane programy używają większej liczby sekwencji.
0
Pepe napisał(a):

@andrzejlisek Dziękuję za twój post. Widzę, że masz temat opracowany i jesteś z niego dumny. Tak trzymać.
Twoje rozwiązanie jednak wydaje się być, w moim przypadku, nadmiarowe (i chyba skomplikowane). Mój program ma być niejako hubem dla istniejących aplikacji (te z GUI chcę tylko uruchomić, a te kosolowe, które przyjmuja parametry chcę odpalić w konsoli, ale tak, żeby to wszystko było zintegrowane). Wydaje mi się, że najlepszym rozwiązaniem będzie utworzenie procesu konoli systemowej, zmianę jej okna rodzica, a tym samym "zadokowanie" w moim oknie. Teraz tylko muszę to napisać 😛

Ja sam się wiele nauczyłem przy tworzeniu własnej konsoli/terminala. Niby rzecz bardzo prosta, ale jest tyle niuansów, że żeby to dobrze obsłużyć to trzeba się napocić.
Jednym z założeń było prawidłowe odtwarzanie "grafik" z tych stron:
https://16colo.rs/ (chodzi o pliki *.txt, *.asc i *.ans, bo na tej stronie są też inne formaty)
http://artscene.textfiles.com/vt100/
http://artscene.textfiles.com/ansi/
To są tak naprawdę pliki tekstowe i generowanie grafiki polega na wypisaniu pliku do stdout jak leci, a jak robi się to powoli, to uzyskuje się animację.
Te grafiki i program vttest (który w końcu służy do testowania kompatybilności konsoli z VT100) wysoko stawiają poprzeczkę.

1

możesz napisac własną konsole w oparciu o cmd.
Wywołuje polecenia cmd wymagające administratora bez zgody administratora.

wywołujesz i zwraca Ci wynik:

memo1.Text:= GetDosOutput('dir c:\');
//uses
//  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
//  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,  ShellApi, Vcl.ExtCtrls;

function GetDosOutput(CommandLine: string; Work: string = 'C:\'): string;
var
  SA: TSecurityAttributes;
  SI: TStartupInfo;
  PI: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  WasOK: Boolean;
  Buffer: array[0..255] of AnsiChar;
  BytesRead: Cardinal;
  WorkDir: string;
  Handle: Boolean;
begin
  Result := '';
  with SA do begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
  try
    with SI do
    begin
      FillChar(SI, SizeOf(SI), 0);
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
      hStdOutput := StdOutPipeWrite;
      hStdError := StdOutPipeWrite;
    end;
    WorkDir := Work;
    Handle := CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),
                            nil, nil, True, 0, nil,
                            PChar(WorkDir), SI, PI);
    CloseHandle(StdOutPipeWrite);
    if Handle then
      try
        repeat
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
          if BytesRead > 0 then
          begin
            Buffer[BytesRead] := #0;
            Result := Result + Buffer;
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        CloseHandle(PI.hThread);
        CloseHandle(PI.hProcess);
      end;
  finally
    CloseHandle(StdOutPipeRead);
  end;
end;

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.