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