1 /*
2  * Copyright Johannes Sixt
3  * This file is licensed under the GNU General Public License Version 2.
4  * See the file COPYING in the toplevel directory of the source directory.
5  */
6 
7 #include "procattach.h"
8 #include <QHeaderView>
9 #include <QProcess>
10 #include <QTreeWidget>
11 #include <ctype.h>
12 #include <klocalizedstring.h>		/* i18n */
13 #include "config.h"
14 
15 
ProcAttachPS(QWidget * parent)16 ProcAttachPS::ProcAttachPS(QWidget* parent) :
17 	QDialog(parent),
18 	m_pidCol(-1),
19 	m_ppidCol(-1)
20 {
21     setupUi(this);
22     on_processList_currentItemChanged();	// update OK button state
23 
24     m_ps = new QProcess;
25     connect(m_ps, SIGNAL(readyReadStandardOutput()),
26 	    this, SLOT(slotTextReceived()));
27     connect(m_ps, SIGNAL(finished(int,QProcess::ExitStatus)),
28 	    this, SLOT(slotPSDone()));
29 
30     processList->setColumnWidth(0, 300);
31     processList->header()->setSectionResizeMode(0, QHeaderView::Interactive);
32     processList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
33     processList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
34     processList->headerItem()->setTextAlignment(1, Qt::AlignRight);
35     processList->headerItem()->setTextAlignment(2, Qt::AlignRight);
36     processList->header()->setStretchLastSection(false);
37 
38     runPS();
39 }
40 
~ProcAttachPS()41 ProcAttachPS::~ProcAttachPS()
42 {
43     delete m_ps;	// kills a running ps
44 }
45 
runPS()46 void ProcAttachPS::runPS()
47 {
48     // clear the parse state from previous runs
49     m_token.clear();
50     m_line.clear();
51     m_pidCol = -1;
52     m_ppidCol = -1;
53 
54     // set the command line
55     const char* const psCommand[] = {
56 #ifdef PS_COMMAND
57 	PS_COMMAND,
58 #else
59 	"/bin/false",
60 #endif
61 	0
62     };
63     QStringList args;
64     for (int i = 1; psCommand[i] != 0; i++) {
65 	args.push_back(psCommand[i]);
66     }
67 
68     m_ps->start(psCommand[0], args);
69 }
70 
slotTextReceived()71 void ProcAttachPS::slotTextReceived()
72 {
73     QByteArray data = m_ps->readAllStandardOutput();
74     char* buffer = data.data();
75     char* end = buffer + data.size();
76 
77     // avoid expensive updates while items are inserted
78     processList->setUpdatesEnabled(false);
79 
80     while (buffer < end)
81     {
82 	// check new line
83 	if (*buffer == '\n')
84 	{
85 	    // push a tokens onto the line
86 	    if (!m_token.isEmpty()) {
87 		m_line.push_back(QString::fromLatin1(m_token));
88 		m_token.clear();
89 	    }
90 	    // and insert the line in the list
91 	    pushLine();
92 	    m_line.clear();
93 	    ++buffer;
94 	}
95 	// blanks: the last column gets the rest of the line, including blanks
96 	else if ((m_pidCol < 0 || int(m_line.size()) < processList->columnCount()-1) &&
97 		 isspace(*buffer))
98 	{
99 	    // push a token onto the line
100 	    if (!m_token.isEmpty()) {
101 		m_line.push_back(QString::fromLatin1(m_token));
102 		m_token.clear();
103 	    }
104 	    do {
105 		++buffer;
106 	    } while (buffer < end && isspace(*buffer));
107 	}
108 	// tokens
109 	else
110 	{
111 	    const char* start = buffer;
112 	    do {
113 		++buffer;
114 	    } while (buffer < end && !isspace(*buffer));
115 	    // append to the current token
116 	    m_token += QByteArray(start, buffer-start);
117 	}
118     }
119     processList->setUpdatesEnabled(true);
120 }
121 
pushLine()122 void ProcAttachPS::pushLine()
123 {
124     if (m_line.size() < 3)	// we need the PID, PPID, and COMMAND columns
125 	return;
126 
127     if (m_pidCol < 0)
128     {
129 	// create columns if we don't have them yet
130 	bool allocate =	processList->columnCount() == 3;
131 
132 	// we assume that the last column is the command
133 	m_line.pop_back();
134 
135 	if (allocate)
136 	    processList->setColumnCount(1 + m_line.size());
137 
138 	for (size_t i = 0; i < m_line.size(); i++)
139 	{
140 	    // we don't allocate the PID and PPID columns,
141 	    // but we need to know where in the ps output they are
142 	    if (m_line[i] == "PID") {
143 		m_pidCol = i;
144 	    } else if (m_line[i] == "PPID") {
145 		m_ppidCol = i;
146 	    } else if (allocate) {
147 		processList->headerItem()->setText(i+1, m_line[i]);
148 		// these columns are normally numbers
149 		processList->headerItem()->setTextAlignment(i+1,
150 					Qt::AlignRight);
151 		processList->header()->setSectionResizeMode(i+1,
152 					QHeaderView::ResizeToContents);
153 	    }
154 	}
155     }
156     else
157     {
158 	// insert a line
159 	// find the parent process
160 	QTreeWidgetItem* parent = 0;
161 	if (m_ppidCol >= 0 && m_ppidCol < int(m_line.size())) {
162 	    QList<QTreeWidgetItem*> items =
163 	    	processList->findItems(m_line[m_ppidCol], Qt::MatchFixedString|Qt::MatchRecursive, 1);
164 	    if (!items.isEmpty())
165 		parent = items.front();
166 	}
167 
168 	// we assume that the last column is the command
169 	QTreeWidgetItem* item;
170 	if (parent == 0) {
171 	    item = new QTreeWidgetItem(processList, QStringList(m_line.back()));
172 	} else {
173 	    item = new QTreeWidgetItem(parent, QStringList(m_line.back()));
174 	}
175 	item->setExpanded(true);
176 	m_line.pop_back();
177 	int k = 3;
178 	for (size_t i = 0; i < m_line.size(); i++)
179 	{
180 	    // display the pid and ppid columns' contents in columns 1 and 2
181 	    int col;
182 	    if (int(i) == m_pidCol)
183 		col = 1;
184 	    else if (int(i) == m_ppidCol)
185 		col = 2;
186 	    else
187 		col = k++;
188 	    item->setText(col, m_line[i]);
189 	    item->setTextAlignment(col, Qt::AlignRight);
190 	}
191 
192 	if (m_ppidCol >= 0 && m_pidCol >= 0) {	// need PID & PPID for this
193 	    /*
194 	     * It could have happened that a process was earlier inserted,
195 	     * whose parent process is the current process. Such processes
196 	     * were placed at the root. Here we go through all root items
197 	     * and check whether we must reparent them.
198 	     */
199 	    int i = 0;
200 	    while (i < processList->topLevelItemCount())
201 	    {
202 		QTreeWidgetItem* it = processList->topLevelItem(i);
203 		if (it->text(2) == m_line[m_pidCol]) {
204 		    processList->takeTopLevelItem(i);
205 		    item->addChild(it);
206 		} else
207 		    ++i;
208 	    }
209 	}
210     }
211 }
212 
slotPSDone()213 void ProcAttachPS::slotPSDone()
214 {
215     on_filterEdit_textChanged(filterEdit->text());
216 }
217 
text() const218 QString ProcAttachPS::text() const
219 {
220     QTreeWidgetItem* item = processList->currentItem();
221 
222     if (item == 0)
223 	return QString();
224 
225     return item->text(1);
226 }
227 
on_buttonRefresh_clicked()228 void ProcAttachPS::on_buttonRefresh_clicked()
229 {
230     if (m_ps->state() == QProcess::NotRunning)
231     {
232 	processList->clear();
233 	dialogButtons->button(QDialogButtonBox::Ok)->setEnabled(false);	// selection was cleared
234 	runPS();
235     }
236 }
237 
on_filterEdit_textChanged(const QString & text)238 void ProcAttachPS::on_filterEdit_textChanged(const QString& text)
239 {
240     for (int i = 0; i < processList->topLevelItemCount(); i++)
241 	setVisibility(processList->topLevelItem(i), text);
242 }
243 
244 /**
245  * Sets the visibility of \a i and
246  * returns whether it was made visible.
247  */
setVisibility(QTreeWidgetItem * i,const QString & text)248 bool ProcAttachPS::setVisibility(QTreeWidgetItem* i, const QString& text)
249 {
250     i->setHidden(false);
251     bool visible = false;
252     for (int j = 0; j < i->childCount(); j++)
253     {
254 	if (setVisibility(i->child(j), text))
255 	    visible = true;
256     }
257     // look for text in the process name and in the PID
258     visible = visible || text.isEmpty() ||
259 	i->text(0).indexOf(text, 0, Qt::CaseInsensitive) >= 0 ||
260 	i->text(1).indexOf(text) >= 0;
261 
262     if (!visible)
263 	i->setHidden(true);
264 
265     // disable the OK button if the selected item becomes invisible
266     if (i->isSelected())
267 	dialogButtons->button(QDialogButtonBox::Ok)->setEnabled(visible);
268 
269     return visible;
270 }
271 
on_processList_currentItemChanged()272 void ProcAttachPS::on_processList_currentItemChanged()
273 {
274     dialogButtons->button(QDialogButtonBox::Ok)->setEnabled(processList->currentItem() != 0);
275 }
276 
277 
ProcAttach(QWidget * parent)278 ProcAttach::ProcAttach(QWidget* parent) :
279 	QDialog(parent),
280 	m_label(this),
281 	m_processId(this),
282 	m_buttonOK(this),
283 	m_buttonCancel(this),
284 	m_layout(this),
285 	m_buttons()
286 {
287     m_label.setMinimumSize(330, 24);
288     m_label.setText(i18n("Specify the process number to attach to:"));
289 
290     m_processId.setMinimumSize(330, 24);
291     m_processId.setMaxLength(100);
292     m_processId.setFrame(true);
293 
294     m_buttonOK.setMinimumSize(100, 30);
295     connect(&m_buttonOK, SIGNAL(clicked()), SLOT(accept()));
296     m_buttonOK.setText(i18n("OK"));
297     m_buttonOK.setDefault(true);
298 
299     m_buttonCancel.setMinimumSize(100, 30);
300     connect(&m_buttonCancel, SIGNAL(clicked()), SLOT(reject()));
301     m_buttonCancel.setText(i18n("Cancel"));
302 
303     m_layout.addWidget(&m_label);
304     m_layout.addWidget(&m_processId);
305     m_layout.addLayout(&m_buttons);
306     m_layout.addStretch(10);
307     m_buttons.addStretch(10);
308     m_buttons.addWidget(&m_buttonOK);
309     m_buttons.addSpacing(40);
310     m_buttons.addWidget(&m_buttonCancel);
311     m_buttons.addStretch(10);
312 
313     m_layout.activate();
314 
315     m_processId.setFocus();
316     resize(350, 120);
317 }
318 
~ProcAttach()319 ProcAttach::~ProcAttach()
320 {
321 }
322 
323 
324 #include "procattach.moc"
325