aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/desktop-utils/lumina-terminal/TtyProcess.cpp
blob: 8d72e6675fa3fe81adff3ed904b011c7cf366622 (plain)
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
#include "TtyProcess.h"

#include <QDir>
#include <QProcessEnvironment>

TTYProcess::TTYProcess(QObject *parent) : QObject(parent){
  childProc = 0;
  sn = 0;
  ttyfd = 0;
}

TTYProcess::~TTYProcess(){
  closeTTY(); //make sure everything is closed properly
}
		
// === PUBLIC ===
bool TTYProcess::startTTY(QString prog, QStringList args, QString workdir){
  if(workdir=="~"){ workdir = QDir::homePath(); }
  QDir::setCurrent(workdir);
  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  setenv("TERM","vt100",1); //vt100: VT100 emulation support
  unsetenv("TERMCAP");
  /*setenv("TERMCAP","mvterm|vv100|mvterm emulator with ANSI colors:\
	:pa#64:Co#8:AF=\E[3%dm:AB=\E[4%dm:op=\E[100m:tc=vt102:",1); //see /etc/termcap as well*/
  QStringList filter = env.keys().filter("XTERM");
  for(int i=0; i<filter.length(); i++){ unsetenv(filter[i].toLocal8Bit().data()); }
  //if(env.contains("TERM")){ unsetenv("TERM"); }
  //else if(env.contains
  //Turn the program/arguments into C-compatible arrays
  char cprog[prog.length()]; strcpy(cprog, prog.toLocal8Bit().data());
  char *cargs[args.length()+2];
  QByteArray nullarray;
  for(int i=0; i<args.length()+2; i++){
    // First arg needs to be the program
    if ( i == 0 ) {
      cargs[i] = new char[ prog.toLocal8Bit().size()+1];
      strcpy( cargs[i], prog.toLocal8Bit().data() );
    } else if(i<args.length()){
      cargs[i] = new char[ args[i].toLocal8Bit().size()+1];
      strcpy( cargs[i], args[i].toLocal8Bit().data() );
    }else{
      cargs[i] = NULL;
    }
  }
  qDebug() << "PTY Start:" << prog;
  //Launch the process attached to a new PTY
  int FD = 0;
  pid_t tmp = LaunchProcess(FD, cprog, cargs);
  qDebug() << " - PID:" << tmp;
  qDebug() << " - FD:" << FD;
  if(tmp<0){ return false; } //error
  else{
    childProc = tmp;
    //Load the file for close notifications
      //TO-DO
    //Watch the socket for activity
    sn= new QSocketNotifier(FD, QSocketNotifier::Read);
	sn->setEnabled(true);
	connect(sn, SIGNAL(activated(int)), this, SLOT(checkStatus(int)) );
    ttyfd = FD;
    qDebug() << " - PTY:" << ptsname(FD);
    return true;
  }
}

void TTYProcess::closeTTY(){
  int junk;
  if(0==waitpid(childProc, &junk, WNOHANG)){
    kill(childProc, SIGKILL);	
  }
  if(ttyfd!=0 && sn!=0){
    sn->setEnabled(false);
    ::close(ttyfd);
    ttyfd = 0;
    emit processClosed();
  }
}

void TTYProcess::writeTTY(QByteArray output){
  //qDebug() << "Write:" << output;
  ::write(ttyfd, output.data(), output.size());
}

QByteArray TTYProcess::readTTY(){
  QByteArray BA;
  //qDebug() << "Read TTY";
  if(sn==0){ return BA; } //not setup yet
  char buffer[64];
  ssize_t rtot = read(sn->socket(),&buffer,64);
  //buffer[rtot]='\0';
  BA = QByteArray(buffer, rtot);
  //qDebug() << " - Got Data:" << BA;
  if(!fragBA.isEmpty()){
    //Have a leftover fragment, include this too
    BA = BA.prepend(fragBA);
    fragBA.clear();
  }
  bool bad = true;
  BA = CleanANSI(BA, bad);
  if(bad){ 
    //incomplete fragent - read some more first
    fragBA = BA; 
    return readTTY();
  }else{
    //qDebug() << "Read Data:" << BA;
    return BA;
  }
}

void TTYProcess::setTerminalSize(QSize chars, QSize pixels){
  if(ttyfd==0){ return; }
  
  struct winsize c_sz;
    c_sz.ws_row = chars.height();
    c_sz.ws_col = chars.width();
    c_sz.ws_xpixel = pixels.width();
    c_sz.ws_ypixel = pixels.height();
  if( ioctl(ttyfd, TIOCSWINSZ, &ws) ){
    qDebug() << "Error settings terminal size";
  }else{
    //qDebug() <<"Set Terminal Size:" << pixels << chars;
  }
}

bool TTYProcess::isOpen(){
  return (ttyfd!=0);
}

QByteArray TTYProcess::CleanANSI(QByteArray raw, bool &incomplete){
  incomplete = true;
  //qDebug() << "Clean ANSI Data:" << raw;
  //IN_LINE TERMINAL COLOR CODES (ANSI Escape Codes) "\x1B[<colorcode>m"
  //  - Just remove them for now
	
  //Special XTERM encoding (legacy support)
  int index = raw.indexOf("\x1B]");
  while(index>=0){
    //The end character of this sequence is the Bell command ("\x07")
    int end = raw.indexOf("\x07");
    if(end<0){ return raw; } //incomplete raw array
    raw = raw.remove(index, end-index+1);
    index = raw.indexOf("\x1B]");
  }

  // GENERIC ANSI CODES ((Make sure the output is not cut off in the middle of a code)
  index = raw.indexOf("\x1B");
  while(index>=0){
    //CURSOR MOVEMENT
    int end = 0;
    for(int i=1; i<raw.size() && end==0; i++){
      if(raw.size() < index+i){ return raw; }//cut off - go back for more data
      //qDebug() << "Check Char:" << raw.at(index+i);
      if( QChar(raw.at(index+i)).isLetter() ){
        end = i; //found the end of the control code
      }
    }
    index = raw.indexOf("\x1B",index+1); //now find the next one
  }
  
  // SYSTEM BELL
  index = raw.indexOf("\x07");
  while(index>=0){ 
    //qDebug() << "Remove Bell:" << index;
    raw = raw.remove(index,1); 
    index = raw.indexOf("\x07");
  }

  incomplete = false;
  return raw;
}

// === PRIVATE ===
pid_t TTYProcess::LaunchProcess(int& fd, char *prog, char **child_args){
  //Returns: -1 for errors, positive value (file descriptor) for the master side of the TTY to watch	

  //First open/setup a new pseudo-terminal file/device on the system (master side)
  fd = posix_openpt(O_RDWR | O_NOCTTY); //open read/write
  if(fd<0){ return -1; } //could not create pseudo-terminal
  int rc = grantpt(fd); //set permissions
  if(rc!=0){ return -1; }
  rc = unlockpt(fd); //unlock file (ready for use)
  if(rc!=0){ return -1; }	
  //Now fork, return the Master device and setup the child
  pid_t PID = fork();
  if(PID==0){
    //SLAVE/child
    int fds = ::open(ptsname(fd), O_RDWR | O_NOCTTY); //open slave side read/write
    ::close(fd); //close the master side from the slave thread
	  
    //Adjust the slave side mode to RAW
    struct termios TSET;
    rc = tcgetattr(fds, &TSET); //read the current settings
    cfmakesane(&TSET); //set the RAW mode on the settings ( cfmakeraw(&TSET); )
    tcsetattr(fds, TCSANOW, &TSET); //apply the changed settings

    //Change the controlling terminal in child thread to the slave PTY
    ::close(0); //close current terminal standard input
    ::close(1); //close current terminal standard output
    ::close(2); //close current terminal standard error
    dup(fds); // Set slave PTY as standard input (0);
    dup(fds); // Set slave PTY as standard output (1);
    dup(fds); // Set slave PTY as standard error (2);
	  
    setsid();  //Make current process new session leader (so we can set controlling terminal)
    ioctl(0,TIOCSCTTY, 1); //Set the controlling terminal to the slave PTY
	  
    //Execute the designated program
    rc = execvp(prog, child_args);
    ::close(fds); //no need to keep original file descriptor open any more
    exit(rc);
  }
  //MASTER thread (or error)
  return PID;
}

// === PRIVATE SLOTS ===
void TTYProcess::checkStatus(int sock){
  //This is run when the socket gets activated
  if(sock!=ttyfd){
	  
  }
  //Make sure the child PID is still active
  int junk;
  if(0!=waitpid(childProc, &junk, WNOHANG)){
    this->closeTTY(); //clean up everything else
  }else{
    emit readyRead();
  }
}
bgstack15