Logo Search packages:      
Sourcecode: qps version File versions

qps.C

// qps.C
//
// qps -- Qt-based visual process status monitor
//
// This program is free software. See the file COPYING for details.
// Author: Mattias Engdegård, 1997-1999

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/utsname.h>
#include <signal.h>
#include <errno.h>
#include <sched.h>

#include "qps.h"
#include "dialogs.h"
#include "scheddlg.h"
#include "lookup.h"
#include "icon.xpm"
#include "svec.C"
#include <qpopupmenu.h>
#include <qmenubar.h>
#include <qkeycode.h>
#include <qapplication.h>
#include <qfont.h>
#include <qpainter.h>
#include <qaccel.h>
#include <qtooltip.h>
#include <qmessagebox.h>
#include <qbitmap.h>
#include <qclipboard.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>


#define QPS_VERSION "1.9.7"

ControlBar::ControlBar(QWidget *parent)
    : QFrame(parent)
{
    setFrameStyle(Panel | Raised);
    const int h = 20;
    QFont f(font());
    f.setBold(FALSE);
    b_linear = new QRadioButton("Linear", this);
    b_linear->setFont(f);
    b_linear->setFixedSize(b_linear->sizeHint().width(), h);
    b_linear->setFocusPolicy(QWidget::NoFocus);
    connect(b_linear, SIGNAL(clicked()), SLOT(linear_clicked()));

    b_tree = new QRadioButton("Tree", this);
    b_tree->setFont(f);
    b_tree->setFixedSize(b_tree->sizeHint().width(), h);
    b_tree->setFocusPolicy(QWidget::NoFocus);
    connect(b_tree, SIGNAL(clicked()), SLOT(tree_clicked()));
    
    b_update = new QPushButton("Update", this);
    b_update->setFixedSize(50, h);
    b_update->setFocusPolicy(QWidget::NoFocus);

    setFixedHeight(h + 2);
}

void ControlBar::setMode(bool treemode)
{
    b_linear->setChecked(!treemode);
    b_tree->setChecked(treemode);
}

void ControlBar::linear_clicked()
{
    setMode(FALSE);
    emit modeChange(FALSE);
}

void ControlBar::tree_clicked()
{
    setMode(TRUE);
    emit modeChange(TRUE);
}

void ControlBar::resizeEvent(QResizeEvent *)
{
    b_linear->move(1, 1);
    b_tree->move(b_linear->x() + b_linear->width() + 8, 1);
    b_update->move(width() - b_update->width(), 1);
}

// default values of settings, overridden by $HOME/.qps-settings if present
bool Qps::show_cmd_path = TRUE;
bool Qps::show_infobar = TRUE;
bool Qps::show_ctrlbar = TRUE;
bool Qps::show_mem_bar = TRUE;
bool Qps::show_swap_bar = TRUE;
bool Qps::show_cpu_bar = TRUE;
bool Qps::show_load_graph = TRUE;
bool Qps::load_in_icon = TRUE;
bool Qps::auto_save_options = TRUE;
#ifdef LINUX
bool Qps::hostname_lookup = TRUE;
bool Qps::service_lookup = TRUE;
#endif
bool Qps::pids_to_selection = TRUE;
bool Qps::cumulative = FALSE;
bool Qps::vertical_cpu_bar = FALSE;
#ifdef SOLARIS
bool Qps::normalize_nice = TRUE;
#endif
bool Qps::tree_gadgets = TRUE;
bool Qps::tree_lines = TRUE;
bool Qps::comm_is_magic = FALSE;

int Qps::swaplimit = 10;      // default: warn when less than 10% swap left
bool Qps::swaplim_percent = TRUE;

Svec<Command *> *Qps::commands;

QColor Qps::color_set[NUM_COLORS] = {
    black, darkGray, lightGray, white, // cpu: user, nice, sys, idle
    black, darkGray, lightGray, white, // mem: used, buff, cache, free
    black, white, red,        // swap: used, free, warn
    black, green, darkGreen,  // load: bg, fg, lines
    yellow, black       // selected process: bg, fg
};

const char *Qps::color_name[NUM_COLORS] = {
    "cpu-user",
#ifdef LINUX
    "cpu-nice",
#endif
    "cpu-sys",
#ifdef SOLARIS
    "cpu-wait",
#endif
    "cpu-idle",
    "mem-used", "mem-buff", "mem-cache", "mem-free",
    "swap-used", "swap-free", "swap-warn",
    "load-bg", "load-fg", "load-lines",
    "selection-bg", "selection-fg"
};

Qps::Qps(QWidget *parent)
   : QWidget(parent)
{
    setWindowGroup(this);

    set_color_set();

    m_process = new QPopupMenu;
    m_process->insertItem("Renice...", MENU_RENICE);
    m_process->connectItem(MENU_RENICE, this, SLOT(menu_renice()));
    m_process->setAccel(ALT + Key_R, MENU_RENICE);
    m_process->insertItem("Change Scheduling...", MENU_SCHED);
    m_process->connectItem(MENU_SCHED, this, SLOT(menu_sched()));
    m_process->setAccel(ALT + Key_S, MENU_SCHED);
    m_process->insertSeparator();
    m_process->insertItem("View Details", MENU_DETAILS);
    m_process->connectItem(MENU_DETAILS, this, SLOT(menu_info()));
    m_process->setAccel(ALT + Key_I, MENU_DETAILS);
    m_process->insertSeparator();
    m_process->insertItem("Find Parent", MENU_PARENT);
    m_process->connectItem(MENU_PARENT, this, SLOT(menu_parent()));
    m_process->setAccel(ALT + Key_P, MENU_PARENT);
    m_process->insertItem("Find Children", MENU_CHILD);
    m_process->connectItem(MENU_CHILD, this, SLOT(menu_children()));
    m_process->setAccel(ALT + Key_C, MENU_CHILD);
    m_process->insertItem("Find Descendants", MENU_DYNASTY);
    m_process->connectItem(MENU_DYNASTY, this, SLOT(menu_dynasty()));
    m_process->setAccel(SHIFT + ALT + Key_C, MENU_DYNASTY);
    m_process->insertSeparator();
    m_process->insertItem("Quit", this, SLOT(save_quit()), ALT + Key_Q);

    QPopupMenu *menu_signals = make_signal_menu();

    m_signal = new QPopupMenu;
    m_signal->insertItem("Terminate", MENU_SIGTERM);
    m_signal->connectItem(MENU_SIGTERM, this, SLOT(sig_term()));
    m_signal->setAccel(ALT + Key_T, MENU_SIGTERM);
    m_signal->insertItem("Hangup", MENU_SIGHUP);
    m_signal->connectItem(MENU_SIGHUP, this, SLOT(sig_hup()));
    m_signal->setAccel(ALT + Key_H, MENU_SIGHUP);
    m_signal->insertItem("Stop", MENU_SIGSTOP);
    m_signal->connectItem(MENU_SIGSTOP, this, SLOT(sig_stop()));
    m_signal->insertSeparator();
    m_signal->insertItem("Kill", MENU_SIGKILL);
    m_signal->connectItem(MENU_SIGKILL, this, SLOT(sig_kill()));
    m_signal->setAccel(ALT + Key_K, MENU_SIGKILL);
    m_signal->insertItem("Other", menu_signals, MENU_OTHERS);
    connect(menu_signals, SIGNAL(activated(int)), SLOT(signal_menu(int)));

    QPopupMenu *popup_signals = make_signal_menu();
    
    m_popup = new QPopupMenu;
    m_popup->insertItem("Renice...", this, SLOT(menu_renice()));
    m_popup->insertItem("Scheduling...", this, SLOT(menu_sched()));
    m_popup->insertSeparator();
    m_popup->insertItem("View Details", this, SLOT(menu_info()));
    m_popup->insertSeparator();
    m_popup->insertItem("Find Parent", this, SLOT(menu_parent()));
    m_popup->insertItem("Find Children", this, SLOT(menu_children()));
    m_popup->insertItem("Find Descendants", this, SLOT(menu_dynasty()));

#ifdef MOSIX
    Procinfo::check_for_mosix();
    if(Procinfo::mosix_running) {
      QPopupMenu *popup_migrate = make_migrate_menu();
      m_popup->insertSeparator();
      m_popup->insertItem("Migrate", popup_migrate, POPUP_MIGRATE);
      connect(popup_migrate, SIGNAL(activated(int)), SLOT(mig_menu(int)));
    }
#endif
    m_popup->insertSeparator();
    m_popup->insertItem("Terminate", this, SLOT(sig_term()));
    m_popup->insertItem("Hangup", this, SLOT(sig_hup()));
    m_popup->insertItem("Stop", MENU_SIGSTOP);
    m_popup->connectItem(MENU_SIGSTOP, this, SLOT(sig_stop()));
    m_popup->insertSeparator();
    m_popup->insertItem("Kill", this, SLOT(sig_kill()));
    m_popup->insertItem("Other Signals", popup_signals);
    connect(popup_signals, SIGNAL(activated(int)), SLOT(signal_menu(int)));

    m_fields = new QPopupMenu;
    // m_fields will be filled with all non-displayed fields when used
    connect(m_fields, SIGNAL(activated(int)), SLOT(add_fields_menu(int)));

    m_headpopup = new QPopupMenu;
    m_headpopup->insertItem("Remove Field", this, SLOT(menu_remove_field()));
    m_headpopup->insertItem("Add Field", m_fields);

    m_view = new CheckMenu;
    m_view->setCheckable(TRUE);
    m_view->insertItem("All Processes", Procview::ALL);
    m_view->insertItem("Your Processes", Procview::OWNED);
    m_view->insertItem("Non-Root Processes", Procview::NROOT );
    m_view->insertItem("Running Processes", Procview::RUNNING);
    m_view->insertSeparator();
    m_view->insertItem("User Fields", Procview::USER);
    m_view->insertItem("Jobs Fields", Procview::JOBS);
    m_view->insertItem("Memory Fields", Procview::MEM);
    m_view->insertSeparator();
    m_view->insertItem("Select Fields...", MENU_CUSTOM);
    m_view->connectItem(MENU_CUSTOM, this, SLOT(menu_custom()));
    connect(m_view, SIGNAL(activated(int)), SLOT(view_menu(int)));

    commands = new Svec<Command *>;
    m_command = new QPopupMenu;     // filled in later
    connect(m_command, SIGNAL(activated(int)), SLOT(run_command(int)));

    m_options = new QPopupMenu;
    m_options->insertItem("Update Period...", this, SLOT(menu_update()));
    m_options->insertSeparator();
    m_options->insertItem("", MENU_PATH); // text will be set later
    m_options->connectItem(MENU_PATH, this, SLOT(menu_toggle_path()));
    m_options->insertItem("", MENU_INFOBAR);    // text will be set later
    m_options->connectItem(MENU_INFOBAR, this, SLOT(menu_toggle_infobar()));
    m_options->insertItem("", MENU_CTRLBAR);    // text will be set later
    m_options->connectItem(MENU_CTRLBAR, this, SLOT(menu_toggle_ctrlbar()));
    m_options->insertItem("", MENU_CUMUL);      // text will be set later
    m_options->connectItem(MENU_CUMUL, this, SLOT(menu_toggle_cumul()));
    m_options->insertSeparator();
    m_options->insertItem("Preferences...", MENU_PREFS);
    m_options->connectItem(MENU_PREFS, this, SLOT(menu_prefs()));
    m_options->insertSeparator();
    m_options->insertItem("Save Settings Now", this, SLOT(menu_savenow()));

    QPopupMenu *help = new QPopupMenu;
    help->insertItem("License", this, SLOT(license()));
    help->insertSeparator();
    help->insertItem("About qps", this, SLOT(about()));

    menu = new QMenuBar(this);
    menu->insertItem("Process", m_process);
    menu->insertItem("Signal", m_signal);
    menu->insertItem("View", m_view);
    menu->insertItem("Command", m_command);
    menu->insertItem("Options", m_options);
    menu->insertSeparator();
    menu->insertItem("Help", help);
    menu->setSeparator(QMenuBar::InWindowsStyle);

    context_col = -1;

    proc = new Proc();        // creates fields etc
    procview = new Procview(proc);

    Procinfo::read_loadavg();

    set_update_period(5000);  // also default
    vertical_cpu_bar = Procinfo::num_cpus >= 2;

    // add some useful default commands
#ifdef LINUX
    commands->add(new Command("gdb",
                        "xterm -T \"gdb %c (%p)\""
                        " -e gdb /proc/%p/exe %p &"));
    commands->add(new Command("strace",
                        "xterm -T \"strace %c (%p)\""
                        " -e sh -c 'strace -f -p%p; sleep 10000'&"));
#endif
#ifdef SOLARIS
    commands->add(new Command("gdb",
                        "xterm -T \"gdb %c (%p)\""
                        " -e gdb /proc/%p/object/a.out %p &"));
    commands->add(new Command("truss",
                        "xterm -T \"truss %c (%p)\""
                        " -e sh -c 'truss -f -p %p; sleep 10000'&"));
#endif

    pstable = new Pstable(this);
    pstable->setSelectionColors(color_set[COLOR_SELECTION_FG],
                        color_set[COLOR_SELECTION_BG]);
    pstable->setProcview(procview);
    pstable->setNumCols(procview->cats.size());

    if(!read_settings())
      resize(640, 350); // default initial size

    procview->refresh();
    make_command_menu();

    pstable->set_initial_mode(procview->treeview);
    pstable->enableFolding(tree_gadgets);
    pstable->enableLines(tree_lines);

    default_icon = 0;
    default_icon_set = FALSE;

    infobar = new Infobar(this);
    infobar->move(0, menu->height());

    int ly = infobar->y() + (show_infobar ? infobar->height() : 0);

    ctrlbar = new ControlBar(this);
    ctrlbar->setMode(procview->treeview);
    ctrlbar->move(0, ly);
    if(show_ctrlbar)
      ly += ctrlbar->height();

    pstable->setGeometry(0, ly, width(), height() - ly);
    pstable->setRows();
    pstable->resetWidths();

    QAccel *acc = new QAccel(this);
    // misc. accelerators
    acc->connectItem(acc->insertItem(Key_Space),
                 this, SLOT(forced_update()));
    acc->connectItem(acc->insertItem(Key_Return),
                 this, SLOT(forced_update()));
    acc->connectItem(acc->insertItem(Key_Q),
                 this, SLOT(save_quit()));
    acc->connectItem(acc->insertItem(CTRL + Key_Z),
                 this, SLOT(iconify_window()));

    connect(ctrlbar->updateButton(), SIGNAL(clicked()), SLOT(forced_update()));
    connect(ctrlbar, SIGNAL(modeChange(bool)), SLOT(table_mode(bool)));
    connect(pstable, SIGNAL(selection_changed()),
          SLOT(update_selection_status()));
    connect(pstable, SIGNAL(doubleClicked(int)),
          SLOT(open_details(int)));
    connect(pstable, SIGNAL(rightClickedRow(QPoint)),
          this, SLOT(context_row_menu(QPoint)));
    connect(pstable, SIGNAL(rightClickedHeading(QPoint, int)),
          this, SLOT(context_heading_menu(QPoint, int)));
    connect(pstable, SIGNAL(colMoved(int, int)), SLOT(col_reorder(int, int)));

    field_win = 0;
    prefs_win = 0;
    command_win = 0;

    selection_items_enabled = TRUE;

    details.setAutoDelete(TRUE);

    update_load_time = 0;

    infobar->configure();     // make settings take effect in status bar
    infobar->refresh(TRUE);
    update_menu_status();
    bar_visibility();

    startTimer(update_period);
}

// explicit destructor needed for gcc
Qps::~Qps()
{}

// return true if all selected processes are stopped
bool Qps::all_selected_stopped()
{
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected && p->state != 'T')
          return FALSE;
    }
    return TRUE;
}

// Adjust menu to contain Stop or Continue
void Qps::adjust_popup_menu(QPopupMenu *m, bool cont)
{
    int ix = m->indexOf(MENU_SIGSTOP);
    if((ix >= 0) != cont)
      return;
    if(ix >= 0) {
      m->removeItemAt(ix);
      m->insertItem("Continue", MENU_SIGCONT, ix);
      m->connectItem(MENU_SIGCONT, this, SLOT(sig_cont()));
    } else {
      ix = m->indexOf(MENU_SIGCONT);
      m->removeItemAt(ix);
      m->insertItem("Stop", MENU_SIGSTOP, ix);
      m->connectItem(MENU_SIGSTOP, this, SLOT(sig_stop()));
    }
}

// build signal menu (used in two places)
QPopupMenu *Qps::make_signal_menu()
{
    QPopupMenu *m = new QPopupMenu;
    m->insertItem("SIGINT (interrupt)", MENU_SIGINT);
    m->insertItem("SIGCONT (continue)", MENU_SIGCONT);
    m->insertItem("SIGSTOP (stop)", MENU_SIGSTOP);
    m->insertItem("SIGQUIT (quit)", MENU_SIGQUIT);
    m->insertItem("SIGILL (illegal instruction)", MENU_SIGILL);
    m->insertItem("SIGABRT (abort)", MENU_SIGABRT);
    m->insertItem("SIGFPE (floating point exception)", MENU_SIGFPE);
    m->insertItem("SIGSEGV (segmentation violation)", MENU_SIGSEGV);
    m->insertItem("SIGPIPE (broken pipe)", MENU_SIGPIPE);
    m->insertItem("SIGALRM (timer signal)", MENU_SIGALRM);
    m->insertItem("SIGUSR1 (user-defined 1)", MENU_SIGUSR1);
    m->insertItem("SIGUSR2 (user-defined 2)", MENU_SIGUSR2);
    m->insertItem("SIGCHLD (child death)", MENU_SIGCHLD);
    m->insertItem("SIGTSTP (stop from tty)", MENU_SIGTSTP);
    m->insertItem("SIGTTIN (tty input)", MENU_SIGTTIN);
    m->insertItem("SIGTTOU (tty output)", MENU_SIGTTOU);
    return m;
}


#ifdef MOSIX

// build migrate menu
static int intcmp(const int *a, const int *b)
{
    return *a - *b;
}

QPopupMenu *Qps::make_migrate_menu()
{
    QString buf;
    QPopupMenu *m = new QPopupMenu;
    Svec<int> lst = Procinfo::mosix_nodes();
    lst.sort(intcmp);
    m->insertItem("Home", 1);
    m->insertItem("Find Best", 0);
    for(int i = 0; i < lst.size(); i++) {
      buf.sprintf("to node %d", lst[i]);
      m->insertItem(buf, lst[i] + 1);
    }
    return m;
}
#endif // MOSIX

bool Qps::event(QEvent *e)
{
    switch(e->type()) {
#if QT_VERSION >= 200
    case QEvent::Show:
#else
    case Event_Show:
#endif
      myShowEvent();
      return TRUE;
#if QT_VERSION >= 200
    case QEvent::Hide:
#else
    case Event_Hide:
#endif
      myHideEvent();
      return TRUE;
    default:
      return QWidget::event(e);
    }
}

void Qps::resizeEvent(QResizeEvent *)
{
    infobar->resize(width(), infobar->height());
    int y = infobar->y();
    if(show_infobar)
      y += infobar->height();
    ctrlbar->setGeometry(0, y, width(), ctrlbar->height());
    if(show_ctrlbar)
      y += ctrlbar->height();
    pstable->setGeometry(0, y, width(), height() - y);
}

void Qps::timerEvent(QTimerEvent *)
{
    killTimers();       // avoid accumulation of timer events if slow
    timer_refresh();
    startTimer(update_period);
}

void Qps::closeEvent(QCloseEvent *)
{
    save_quit();
}

void Qps::myShowEvent()
{
    static bool ignore_event = TRUE; // always ignore first show event
    if(!ignore_event) {
      timerEvent(0);          // immediately repaint after de-iconify
    } else
      ignore_event = FALSE;
}

void Qps::myHideEvent()
{
    update_icon();
}

void Qps::save_quit()
{
    if(auto_save_options)
      write_settings();
    qApp->quit();
}

void Qps::timer_refresh()
{
    // don't update load more often than necessary
    bool update_load = FALSE;
    update_load_time -= update_period;
    if(update_load_time <= 0) {
      update_load = TRUE;
      update_load_time = load_update_period;
      Procinfo::read_loadavg();
    }
    if(isVisible() && !pstable->locked()) {
      procview->refresh();
      if(show_infobar)
          infobar->refresh(update_load);
      update_icon();
      pstable->setRows();
      pstable->invalidateCache();
      pstable->recompute_table_widths();
      pstable->transfer_selection();
      pstable->repaint_changed();
      update_menu_selection_status();
    } else {
      if(update_load) {
          infobar->update_load();
          update_icon();
      }
    }
    refresh_details();
}

void Qps::update_icon()
{
    if(load_in_icon)
      set_load_icon();
    else
      set_default_icon();
    QApplication::flushX(); // let it take effect immediately
}

// update table due to a configuration change
// col is column that has changed
void Qps::update_table(int col)
{
    pstable->invalidateCache();     // some cells might have changed
    pstable->resetWidth(col);
    pstable->repaintColumns(pstable->physCol(col));
}

void Qps::set_default_icon()
{
    if(!default_icon_set) {
      if(!default_icon)
          default_icon = new QPixmap((const char**)icon_xpm);
      setIcon(*default_icon);
      default_icon_set = TRUE;
    }
}

// avoid a fvwm/Qt 1.30 problem and create a filled mask for the icon
// (without mask, Qt would attempt to use a heuristically created mask)
void Qps::make_filled_mask(QPixmap *pm)
{
    QBitmap bm(pm->size());
    bm.fill(color1);
    pm->setMask(bm);
}

void Qps::set_load_icon()
{
    QPixmap *pm = infobar->load_icon(icon_width, icon_height);
    if(!pm->mask())
      make_filled_mask(pm);
    setIcon(*pm);
    default_icon_set = FALSE;
}

void Qps::refresh_details()
{     
    details.first();
    Details *d = 0;
#ifdef LINUX
    Procinfo::invalidate_sockets();
#endif
    while((d = details.current()) != 0) {
      if(d->isVisible())
          d->refresh();
      details.next();
    }
}

void Qps::forced_update()
{
    ctrlbar->updateButton()->setDown(TRUE);
    QApplication::flushX(); 

    killTimers();
    timer_refresh();

    startTimer(update_period);
    ctrlbar->updateButton()->setDown(FALSE);
}

// update the menu status
void Qps::update_menu_status()
{
    update_menu_selection_status();
    for(int i = Procview::ALL; i <= Procview::RUNNING; i++)
      m_view->setItemChecked(i, i == procview->viewproc);
    for(int i = Procview::USER; i <= Procview::MEM; i++)
      m_view->setItemChecked(i, i == procview->viewfields);

    m_options->changeItem(show_cmd_path
                    ? "Hide Command Path" : "Show Command Path",
                    MENU_PATH);
    m_options->changeItem(show_infobar
                    ? "Hide Status Bar" : "Show Status Bar",
                    MENU_INFOBAR);
    m_options->changeItem(show_ctrlbar
                    ? "Hide Control bar" : "Show Control Bar",
                    MENU_CTRLBAR);
    m_options->changeItem(cumulative
                    ? "Exclude Child Times" : "Include Child Times",
                    MENU_CUMUL);
}

void Qps::update_menu_selection_status()
{
    bool enabled = (pstable->numSelected() > 0);
    if(enabled != selection_items_enabled) {
      m_process->setItemEnabled(MENU_RENICE, enabled);
      m_process->setItemEnabled(MENU_SCHED, enabled);
      m_process->setItemEnabled(MENU_DETAILS, enabled);
      m_process->setItemEnabled(MENU_PARENT, enabled);
      m_process->setItemEnabled(MENU_CHILD, enabled);
      m_process->setItemEnabled(MENU_DYNASTY, enabled);
      m_signal->setItemEnabled(MENU_SIGTERM, enabled);
      m_signal->setItemEnabled(MENU_SIGHUP, enabled);
      m_signal->setItemEnabled(MENU_SIGSTOP, enabled);
      m_signal->setItemEnabled(MENU_SIGCONT, enabled);
      m_signal->setItemEnabled(MENU_SIGKILL, enabled);
      m_signal->setItemEnabled(MENU_OTHERS, enabled);
      for(int i = 0; i < commands->size(); i++)
          m_command->setItemEnabled(MENU_FIRST_COMMAND + i, enabled);
      selection_items_enabled = enabled;

    }
    if(enabled) {
      bool cont = all_selected_stopped();
      adjust_popup_menu(m_signal, cont);
      adjust_popup_menu(m_popup, cont);
    }
}

// update various states according to current selection
// called whenever selection is changed interactively (in the table)
void Qps::update_selection_status()
{
    update_menu_selection_status();
    if(pstable->numSelected() > 0 && pids_to_selection) {
      // set the X11 selection to "PID1 PID2 PID3 ..."
      QString s, num;
      int n = pstable->numRows();
      for(int i = 0; i < n; i++) {
          if(pstable->isSelected(i)) {
            num.setNum(procview->procs[i]->pid);
            s.append(num);
            if(i < n - 1)
                s.append(" ");
          }
      }

      // important: this mustn't be called non-interactively since Qt uses
      // the selection time of the last mouse or keyboard event
      QApplication::clipboard()->setText(s);
    }
}

void Qps::sig_term()
{
    send_to_selected(SIGTERM);
}

void Qps::sig_hup()
{
    send_to_selected(SIGHUP);
}

void Qps::sig_stop()
{
    send_to_selected(SIGSTOP);
}

void Qps::sig_cont()
{
    send_to_selected(SIGCONT);
}

void Qps::sig_kill()
{
    send_to_selected(SIGKILL);
}

struct { int id, sig; } sigtab[] = {
    { Qps::MENU_SIGQUIT, SIGQUIT },
    { Qps::MENU_SIGILL, SIGILL },
    { Qps::MENU_SIGABRT, SIGABRT },
    { Qps::MENU_SIGFPE, SIGFPE },
    { Qps::MENU_SIGSEGV, SIGSEGV },
    { Qps::MENU_SIGPIPE, SIGPIPE },
    { Qps::MENU_SIGALRM, SIGALRM },
    { Qps::MENU_SIGUSR1, SIGUSR1 },
    { Qps::MENU_SIGUSR2, SIGUSR2 },
    { Qps::MENU_SIGCHLD, SIGCHLD },
    { Qps::MENU_SIGCONT, SIGCONT },
    { Qps::MENU_SIGSTOP, SIGSTOP },
    { Qps::MENU_SIGTSTP, SIGTSTP },
    { Qps::MENU_SIGTTIN, SIGTTIN },
    { Qps::MENU_SIGTTOU, SIGTTOU },
    { Qps::MENU_SIGTERM, SIGTERM },
    { Qps::MENU_SIGHUP, SIGHUP },
    { Qps::MENU_SIGINT, SIGINT },
    { Qps::MENU_SIGKILL, SIGKILL }
};

void Qps::signal_menu(int id)
{
    for(unsigned i = 0; i < sizeof(sigtab) / sizeof(sigtab[0]); i++)
      if(id == sigtab[i].id) {
          send_to_selected(sigtab[i].sig);
          return;
      }
}

void Qps::view_menu(int id)
{
    if(id >= Procview::ALL && id <= Procview::RUNNING) {
      Procview::procstates state = (Procview::procstates)id;
      if(procview->viewproc != state) {
          procview->viewproc = state;
          procview->rebuild();
          pstable->invalidateCache();
          pstable->setRows();
          pstable->recompute_table_widths();
          pstable->transfer_selection();
          pstable->topAndRepaint();
          update_menu_status();
      }
    } else if(id >= Procview::USER && id <= Procview::MEM) {
      Procview::fieldstates state = (Procview::fieldstates)id;
      if(procview->viewfields != state) {
          procview->viewfields = state;
          procview->set_fields();
          pstable->invalidateCache();
          pstable->resetWidths();
          pstable->setNumCols(procview->cats.size());
          pstable->set_sortcol();
          pstable->transfer_selection();
          pstable->topAndRepaint();
          update_menu_status();
          if(field_win)
            field_win->update_boxes();
      }
    }
}

void Qps::about()
{
    QPixmap icon((const char**)icon_xpm);
    QString s("qps " QPS_VERSION " - A Visual Process Manager\n\n"
            "using Qt library ");
    s.append(qVersion());
    s.append("\n\n"
            "Author: Mattias Engdegård\n(f91-men@nada.kth.se)\n\n"
            "http://www.nada.kth.se/~f91-men/qps/");
    MessageDialog::message("About qps", s, "OK", &icon);
}

void Qps::license()
{
    QPixmap icon((const char**)icon_xpm);
    MessageDialog::message("qps license",
                     "This program is free software, and you are"
                     " welcome\nto redistribute it under certain"
                     " conditions.\n"
                     "See the GNU General Public License\n"
                     "distributed in the file COPYING for details.",
                     "OK", &icon);
}

void Qps::menu_custom()
{
    if(field_win) {
      field_win->show();
      field_win->raise();
    } else {
      field_win = new FieldSelect(procview, proc);
      setWindowGroup(field_win);
      field_win->show();
      connect(field_win, SIGNAL(added_field(int)),
            this, SLOT(field_added(int)));
      connect(field_win, SIGNAL(removed_field(int)),
            this, SLOT(field_removed(int)));
    }
}

void Qps::field_added(int index)
{
    int where;
    if(context_col >= 0)
      where = pstable->physCol(context_col) + 1;
    else {
      // add field next to the previous displayed field
      int j, c = -1;
      for(j = index - 1; j >= 0 && (c = procview->findCol(j)) == -1; j--)
          ;
      where = (c < 0) ? 1 : pstable->physCol(c) + 1;
    }
    Category *newcat = proc->allcats[index];
    procview->cats.add(newcat);
    pstable->addCol(where);
    procview->viewfields = Procview::CUSTOM;
    update_menu_status();
    if(index == F_COMM)
      comm_is_magic = FALSE;
}

void Qps::field_removed(int index)
{
    // don't remove last field
    if(procview->cats.size() == 1) {
      field_win->update_boxes();
      return;
    }
    for(int i = 0; i < procview->cats.size(); i++) {
      if(procview->cats[i]->index == index) {
          procview->remove_cat(i);
          pstable->deleteCol(i);
          procview->viewfields = Procview::CUSTOM;
          update_menu_status();
          if(index == F_COMM)
            comm_is_magic = FALSE;
          return;
      }
    }
}

void Qps::menu_edit_cmd()
{
    if(command_win) {
      command_win->show();
      command_win->raise();
    } else {
      command_win = new CommandDialog();
      setWindowGroup(command_win);
      command_win->show();
      connect(command_win, SIGNAL(command_change()),
            SLOT(make_command_menu()));
    }
}

void Qps::make_command_menu()
{
    m_command->clear();
    m_command->connectItem(m_command->insertItem("Edit Commands..."),
                     this, SLOT(menu_edit_cmd()));
    if(commands->size())
      m_command->insertSeparator();
    for(int i = 0; i < commands->size(); i++)
      (*commands)[i]->menu = m_command->insertItem((*commands)[i]->name,
                                     MENU_FIRST_COMMAND + i);
    update_menu_selection_status();
}

void Qps::run_command(int id)
{
    int j = id - MENU_FIRST_COMMAND;
    if(j >= 0 && j < commands->size())
      for(int i = 0; i < procview->procs.size(); i++) {
          Procinfo *p = procview->procs[i];
          if(p->selected)
            (*commands)[j]->call(p);
      }
}

void Qps::table_mode(bool treemode)
{
    if(treemode == pstable->treeMode())
      return;
    if(treemode) {
      // If COMM isn't the leftmost column, move it there
      for(int i = 0; i < procview->cats.size(); i++)
          if(procview->cats[i]->index == F_COMM) {
            pstable->moveCol(i, 0);
            goto done;
          }
      procview->cats.add(proc->allcats[F_COMM]);
      pstable->addCol(0, FALSE);
      comm_is_magic = TRUE;
      if(field_win)
          field_win->update_boxes();
    done:;
    } else if(comm_is_magic) {
      int col = pstable->logCol(0);
      if(procview->cats[col]->index == F_COMM
         && procview->cats.size() >= 2) {
          // Remove COMM if it is the leftmost field
          procview->remove_cat(col);
          pstable->deleteCol(col, FALSE);
          comm_is_magic = FALSE;
          if(field_win)
            field_win->update_boxes();
      }
    }
    pstable->set_mode(treemode);
}

// slot: called when user reorders columns
void Qps::col_reorder(int col, int)
{
    if(procview->cats[col]->index == F_COMM)
      comm_is_magic = FALSE;
}

void Qps::menu_info()
{
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected)
          open_details(i);
    }
}

void Qps::open_details(int row)
{
    Procinfo *p = procview->procs[row];
    if(p->details)
      p->details->raise();
    else {
      Details *d = new Details(p, this, proc);
      details.append(d);
      setWindowGroup(d);
      d->show();
      connect(d, SIGNAL(closed(Details *)),
            this, SLOT(details_closed(Details *)));
    }
}

void Qps::details_closed(Details *d)
{
    // This is potentially dangerous, since this is called in response to a
    // signal sent by the widget that is about to be deleted here. Better hope
    // that nobody references the object down the call chain!
    details.removeRef(d);     // deletes window
}

// find parents of selected processes
void Qps::menu_parent()
{
    locate_relatives(&Procinfo::ppid, &Procinfo::pid);
}

void Qps::menu_children()
{
    locate_relatives(&Procinfo::pid, &Procinfo::ppid);
}

// Find processes whose attribute b is equal to the attribute a of
// selected processes. Center around topmost found.
// This is quadratic in worst case (shouldn't be a problem)
void Qps::locate_relatives(int Procinfo::*a, int Procinfo::*b)
{
    Svec<int> relatives;
    const int infinity = 2000000000;
    int topmost = infinity;
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected) {
          pstable->setSelected(i, FALSE);
          for(int j = 0; j < procview->procs.size(); j++) {
            Procinfo *q = procview->procs[j];
            if(p->*a == q->*b) {
                relatives.add(j);
                if(j < topmost)
                  topmost = j;
            }
          }
      }
    }
    for(int i = 0; i < relatives.size(); i++)
      pstable->setSelected(relatives[i], TRUE);
    if(topmost < infinity)
      pstable->centerVertically(topmost);
    pstable->selectionNotify();
}

// select all (direct and indirect) offsprings of currently selected
// processes, without deselecting them
void Qps::menu_dynasty()
{
    Svec<int> family;
    for(int i = 0; i < procview->procs.size(); i++)
      if(pstable->isSelected(i))
          family.add(i);
    for(int i = 0, j = family.size(); i < j;) {
      for(int k = 0; k < procview->procs.size(); k++) {
          Procinfo *p = procview->procs[k];
          for(int m = i; m < j; m++) {
            Procinfo *q = procview->procs[family[m]];
            if(q->pid == p->ppid)
                family.add(k);
          }
      }
      i = j;
      j = family.size();
    }
    const int infinity = 2000000000;
    int topmost = infinity;
    for(int i = 0; i < family.size(); i++) {
      pstable->setSelected(family[i], TRUE);
      if(family[i] < topmost)
          topmost = family[i];
    }
    if(topmost < infinity)
      pstable->centerVertically(topmost);
    pstable->selectionNotify();
}

// change the update period, recomputing the averaging factor
void Qps::set_update_period(int milliseconds)
{
    update_period = milliseconds;
    Procview::avg_factor =
      exp(-(float)update_period / Procview::cpu_avg_time);
}

// called when right button is clicked in table
void Qps::context_row_menu(QPoint p)
{
#ifdef MOSIX
    bool may_migrate = FALSE;
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected && p->cantmove.isEmpty()) {
          may_migrate = TRUE;
          break;
      }
    }
    m_popup->setItemEnabled(POPUP_MIGRATE, may_migrate);
#endif
    m_popup->popup(p);
}

// called when right button is clicked in heading
void Qps::context_heading_menu(QPoint p, int col)
{
    // rebuild the submenu: only include non-displayed fields
    m_fields->clear();
    int ncats = proc->allcats.size();
    QBitArray displayed(ncats);
    displayed.fill(FALSE);
    for(int i = 0; i < procview->cats.size(); i++)
      displayed.setBit(procview->cats[i]->index);
    for(int i = 0; i < ncats; i++)
      if(!displayed.testBit(i))
          m_fields->insertItem(proc->allcats[i]->name, i);
    m_headpopup->setItemEnabled(1,
                        procview->cats.size() < proc->allcats.size());
    context_col = col;
    m_headpopup->popup(p);
}

// called when field is added from heading context menu
void Qps::add_fields_menu(int id)
{
    field_added(id);
    context_col = -1;
    if(field_win)
      field_win->update_boxes();
}

void Qps::menu_remove_field()
{
    if(procview->cats.size() >= 2) {
      field_removed(procview->cats[context_col]->index);
      if(field_win)
          field_win->update_boxes();
    }
}

void Qps::menu_update()
{
    QString txt;
    for(;;) {
      if(update_period % 1000 == 0)
          txt.sprintf("%d s", update_period / 1000);
      else
          txt.sprintf("%d ms", update_period);
      ValueDialog vd("Change Update Period", "New update period:", txt);
      if(vd.exec()) {
          QString s = vd.ed_result;
          int i = 0;
          while(s[i] >= '0' && s[i] <= '9' || s[i] == '.') i++;
          float period = (i > 0) ? s.left(i).toFloat() : -1;
          s = s.mid(i, 3).stripWhiteSpace();
          if(s.length() == 0 || s == "s")
            period *= 1000;
          else if(s == "min")
            period *= 60000;
          else if(s != "ms")
            period = -1;
          if(period < 0) {
            MessageDialog::message("Invalid input",
                               "The time between updates should be a"
                               " number, optionally followed\n"
                               "by a unit (ms, s or min). If no unit"
                               " is given, seconds is assumed.",
                               "OK", MessageDialog::warningIcon());
            continue;
          } else {
            set_update_period((int)period);
            killTimers();
            startTimer(update_period);
          }
      }
      return;
    }
}

void Qps::menu_toggle_path()
{     
    show_cmd_path = !show_cmd_path;
    update_menu_status();
    int col = procview->findCol(F_CMDLINE);
    if(col != -1)
      update_table(col);
}

void Qps::menu_prefs()
{
    if(prefs_win) {
      prefs_win->show();
      prefs_win->raise();
    } else {
      prefs_win = new Preferences();
      setWindowGroup(prefs_win);
      prefs_win->show();
      connect(prefs_win, SIGNAL(prefs_change()),
            this, SLOT(config_change()));
      connect(infobar, SIGNAL(config_change()),
            prefs_win, SLOT(update_boxes()));
    }
}

void Qps::config_change()
{
    infobar->configure();
    resizeEvent(0);           // in case it caused geometry change
    pstable->enableFolding(tree_gadgets);
    pstable->enableLines(tree_lines);
    details.first();
    Details *d = 0;
    while((d = details.current()) != 0) {
      d->config_change();
      details.next();
    }
}

void Qps::menu_savenow()
{
    write_settings();
}

// update the visibility of the {info, control} bar
void Qps::bar_visibility()
{
    update_menu_status();
    int ly;
    if(show_infobar) {
      ly = infobar->y() + infobar->height();
      infobar->show();
    } else {
      infobar->hide();
      ly = menu->height();
    }
    ctrlbar->move(0, ly);
    if(show_ctrlbar) {
      ly += ctrlbar->height();
      ctrlbar->show();
    } else {
      ctrlbar->hide();
    }
    pstable->setGeometry(0, ly, width(), height() - ly);
}

void Qps::menu_toggle_infobar()
{     
    show_infobar = !show_infobar;
    bar_visibility();
}

void Qps::menu_toggle_ctrlbar()
{
    show_ctrlbar = !show_ctrlbar;
    bar_visibility();
}

void Qps::menu_toggle_cumul()
{
    cumulative = !cumulative;
    update_menu_status();
    int col = procview->findCol(F_TIME);
    if(col == pstable->sortedCol()) {
      procview->rebuild();
      pstable->transfer_selection();
      pstable->topAndRepaint();
    } else if(col != -1)
      update_table(col);
}

void Qps::menu_renice()
{
    if(pstable->numSelected() == 0)
      return;
    int defnice = -1000;

    // use nice of first selected process as default, and check permission
    bool possible = TRUE;
    int euid = geteuid();
    Procinfo *p = 0;
    for(int i = 0; i < procview->procs.size(); i++) {
      p = procview->procs[i];
      if(p->selected) {
          if(defnice == -1000)
            defnice = p->nice;
          if(euid != 0 && euid != p->uid && euid != p->euid)
            possible = FALSE;
      }
    }
    if(!possible) {
      QString s;
      s.sprintf("You do not have permission to renice the\n"
              "selected process%s.\n"
              "Only the process owner and the super-user\n"
              "are allowed to do that.",
              (pstable->numSelected() == 1) ? "" : "es");
      MessageDialog::message("Permission denied", s, "OK",
                         MessageDialog::warningIcon());
      return;
    }

    int new_nice;
    for(;;) {
      SliderDialog vd("Renice Process", "New nice value:",
                  defnice, -20, 20, "(faster)", "(slower)");
      if(!vd.exec())
          return;
      bool ok;
      new_nice = vd.ed_result.toInt(&ok);
      if(ok && new_nice >= -20 && new_nice <= 20)
          break;
      else {
          MessageDialog::message("Invalid input",
                           "The nice value should be\n"
                           "in the range -20 to 20.", "OK",
                           MessageDialog::warningIcon());
      }
    }
    int nicecol = procview->findCol(F_NICE);
    int statcol = procview->findCol(F_STAT);

    // do the actual renicing
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected) {
          if(setpriority(PRIO_PROCESS, p->pid, new_nice) < 0) {
            QString s;
            switch(errno) {
            case EPERM:
                // this shouldn't happen, but (e)uid could be changed...
                s.sprintf("You do not have permission to renice"
                        " process %d (", p->pid);
                s.append(p->comm);
                s.append(").\n"
                       "Only the process owner and the super-user are"
                       " allowed to do that.");
                MessageDialog::message("Permission denied", s, "OK",
                                 MessageDialog::warningIcon());
                break;
            case EACCES:
                MessageDialog::message("Permission denied",
                                 "Only the super-user may lower"
                                 " the nice value of a process.",
                                 "OK",
                                 MessageDialog::warningIcon());
                return;
            }
          } else {
            p->nice = new_nice; // don't wait for update
            pstable->invalidateCache();
            if(nicecol != -1)
                pstable->updateCell(i, pstable->physCol(nicecol));
            if(statcol != -1)
                pstable->updateCell(i, pstable->physCol(statcol));
          }
      }
    }
}

void Qps::menu_sched()
{
    if(pstable->numSelected() == 0)
      return;
    if(geteuid() != 0) {
      MessageDialog::message("Permission denied",
                         "Only the super-user may change the\n"
                         "scheduling policy and static priority.",
                         "OK", MessageDialog::warningIcon());
      return;
    }

    // provide reasonable defaults (first selected process)
    Procinfo *p = 0;
    for(int i = 0; i < procview->procs.size(); i++) {
      p = procview->procs[i];
      if(p->selected)
          break;
    }
    int pol = p->get_policy();
    int pri = p->get_rtprio();
    SchedDialog sd(pol, pri);
    if(!sd.exec())
      return;
    if(sd.out_policy == SCHED_OTHER)
      sd.out_prio = 0;  // only allowed value
    int plcycol = procview->findCol(F_PLCY);
    int rpricol = procview->findCol(F_RPRI);
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected) {
          struct sched_param sp;
          sp.sched_priority = sd.out_prio;
          if(sched_setscheduler(p->pid, sd.out_policy, &sp) < 0) {
            QString s;
            if(errno == EPERM) {
                s.sprintf("You do not have permission to change the\n"
                        "scheduling and/or priority of"
                        " process %d (", p->pid);
                s.append(p->comm);
                s.append(").\n"
                       "Only the super-user may do that.");
                MessageDialog::message("Permission denied", s, "OK",
                                 MessageDialog::warningIcon());
                break;
            }
          } else {
            p->policy = sd.out_policy; // don't wait for update
            p->rtprio = sd.out_prio;
            if(plcycol != -1)
                pstable->updateCell(i, pstable->physCol(plcycol));
            if(rpricol != -1)
                pstable->updateCell(i, pstable->physCol(rpricol));
          }
      }
    }
}

void Qps::iconify_window()
{
    iconify();
}

#ifdef MOSIX

void Qps::mig_menu(int id)
{
    migrate_selected(id - 1);
}

void Qps::migrate_selected(int migto)
{
    // User wants to migrate a process somewhere
    // Write destination into /proc/XX/goto
    int warnremote = 0;
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected) {
          if(p->isremote)
            ++warnremote;
          char buf[80];
          sprintf(buf, "/proc/%d/goto", p->pid);
          FILE *f = fopen(buf, "w");
          if(f) {
            sprintf(buf, "%d", migto);
            fprintf(f, buf);
            fclose(f);
          }
      }
    }
    if (warnremote)
      MessageDialog::message("Remote migration attempt",
                         "You can only migrate an immigrated process "
                         "using qps on the home node.",
                         "OK",
                         MessageDialog::warningIcon());
    earlier_refresh();        
}
#else

// Since this is a slot, at least a stub must be defined even when it isn't
// used (moc ignores preprocessor directives)
void Qps::mig_menu(int) {}

#endif // MOSIX

void Qps::send_to_selected(int sig)
{
    for(int i = 0; i < procview->procs.size(); i++) {
      Procinfo *p = procview->procs[i];
      if(p->selected)
          sendsig(p, sig);
    }
    earlier_refresh();        // in case we killed one
}

void Qps::sendsig(Procinfo *p, int sig)
{
    if(kill(p->pid, sig) < 0) {
      // if the process is gone, do nothing - no need to alert the user
      if(errno == EPERM) {
          QString s;
          s.sprintf("You do not have permission to send a signal to"
                  " process %d (", p->pid);
          s.append(p->comm);
          s.append(").\n"
                 "Only the super-user and the owner of the process"
                 " may send signals to it.");
          MessageDialog::message("Permission denied", s, "OK",
                           MessageDialog::warningIcon());
      }
    }
}

// make next timer_refresh happen a little earlier to remove processes that
// might have died after a signal
void Qps::earlier_refresh()
{
    const int delay = 500;    // wait no more than this (ms)
    if(update_period > delay) {
      killTimers();
      startTimer(delay);
    }
}

// If the file format is changed in any way (including adding new
// viewable fields), QPS_FILE_VERSION must be incremented to prevent
// version mismatches and core dumps

#define QPS_FILE_VERSION 24   // version of .qps-settings file format

// return true if settings could be successfully read, false otherwise
bool Qps::read_settings()
{
    int x, y, w, h;
    int procsel, fieldsel;
    int index;
    int ver;
    char name[128];
    strcpy(name, getenv("HOME"));
    strcat(name, "/.qps-settings");
    FILE *f = fopen(name, "r");
    if(!f) return FALSE;
    if(fscanf(f, "%d", &ver) != 1 || ver != QPS_FILE_VERSION) {
      fclose(f);
      return FALSE;
    }
    fscanf(f, "%d %d %d %d %d %d", &x, &y, &w, &h, &procsel, &fieldsel);
    setGeometry(x, y, w, h);
    procview->viewproc = (Procview::procstates)procsel;
    procview->viewfields = (Procview::fieldstates)fieldsel;
    procview->cats.clear();
    while(fscanf(f, "%d", &index), index >= 0)
      procview->add_cat(proc->allcats[index]);
    int sortindex, rev;
    fscanf(f, "%d %d", &sortindex, &rev);
    procview->sortcat = proc->allcats[sortindex];
    procview->reversed = rev;
    int cols = procview->cats.size();
    Svec<int> phys(cols);
    for(int i = 0; i < cols; i++) {
      int place;
      fscanf(f, "%d", &place);
      phys.add(place);
    }
    pstable->setPhysCols(&phys);
    pstable->set_sortcol();
    int showpath, showinfo, showctrl, interval, autosave;
    int membar, swapbar, cpubar, loadgr, icon;
#ifdef LINUX
    int lookup, port_map;
#endif
#ifdef SOLARIS
    int normalize;
#endif
    int selection, vertical;
    int tree, lines, gadgets, magic;
    int cumul, percent;
    fscanf(f, "%d %d %d %d %d %d %d %d %d "
#ifdef LINUX
         "%d %d "
#endif
#ifdef SOLARIS
         "%d "
#endif
         "%d %d %d %d %d %d %d %d %d %d\n",
         &showpath, &showinfo, &showctrl, &autosave,
         &membar, &swapbar, &cpubar, &loadgr, &icon,
#ifdef LINUX
         &lookup, &port_map,
#endif
#ifdef SOLARIS
         &normalize,
#endif
         &selection, &vertical,
         &tree, &lines, &gadgets, &magic,
         &cumul, &percent,
         &swaplimit, &interval);
    set_update_period(interval);
    show_cmd_path = showpath;
    show_infobar = showinfo;
    show_ctrlbar = showctrl;
    auto_save_options = autosave;
    show_mem_bar = membar;
    show_swap_bar = swapbar;
    show_cpu_bar = cpubar;
    show_load_graph = loadgr;
    load_in_icon = icon;
#ifdef LINUX
    hostname_lookup = lookup;
    service_lookup = port_map;
#endif
#ifdef SOLARIS
    normalize_nice = normalize;
#endif
    pids_to_selection = selection;
    vertical_cpu_bar = vertical;
    procview->treeview = tree;
    tree_lines = lines;
    tree_gadgets = gadgets;
    comm_is_magic = magic;
    cumulative = cumul;
    swaplim_percent = percent;

    commands->purge();
    char buf[128], buf2[512];
    while(fgets(buf, sizeof(buf), f) && fgets(buf2, sizeof(buf2), f)) {
      buf[strlen(buf) - 1] = '\0';
      buf2[strlen(buf2) - 1] = '\0';
      commands->add(new Command(buf, buf2));
    }

    fclose(f);
    return TRUE;
}

// write geometry, visible fields and other settings to $HOME/.qps-settings

void Qps::write_settings()
{
    char name[128];
    strcpy(name, getenv("HOME"));
    strcat(name, "/.qps-settings");
    FILE *f = fopen(name, "w");
    if(!f) return;
    fprintf(f, "%d\n%d %d %d %d\n%d %d\n",
          QPS_FILE_VERSION,
          pos().x(), pos().y(), width(), height(),
          procview->viewproc, procview->viewfields);
    for(int i = 0; i < procview->cats.size(); i++)
      fprintf(f, "%d ", procview->cats[i]->index);
    fprintf(f, "-1 %d %d\n",
          procview->sortcat->index, (int)procview->reversed);
    for(int i = 0; i < procview->cats.size(); i++)
      fprintf(f, "%d ", pstable->physCol(i));
    fprintf(f, "\n%d %d %d %d %d %d %d %d %d "
#ifdef LINUX
          "%d %d "
#endif
#ifdef SOLARIS
          "%d "
#endif
          "%d %d %d %d %d %d %d %d\n%d\n%d\n",
          (int)show_cmd_path,
          (int)show_infobar,
          (int)show_ctrlbar,
          (int)auto_save_options,
          (int)show_mem_bar,
          (int)show_swap_bar,
          (int)show_cpu_bar,
          (int)show_load_graph,
          (int)load_in_icon,
#ifdef LINUX
          (int)hostname_lookup,
          (int)service_lookup,
#endif
#ifdef SOLARIS
          (int)normalize_nice,
#endif
          (int)pids_to_selection,
          (int)vertical_cpu_bar,
          (int)procview->treeview,
          (int)tree_lines,
          (int)tree_gadgets,
          (int)comm_is_magic,
          (int)cumulative,
          (int)swaplim_percent,
          swaplimit,
          update_period);

    for(int i = 0; i < commands->size(); i++)
      fprintf(f, "%s\n%s\n",
            (const char *)(*commands)[i]->name,
            (const char *)(*commands)[i]->cmdline);

    fclose(f);
}

// read user-supplied color set from environment
void Qps::set_color_set()
{
    // Allowed environment variable syntax:
    // colorname=value pairs  (or colorname:value), separated by commas
    const char *env = getenv("QPS_COLORS");
    if(!env)
      return;
    char *p = strdup(env);
    for(char *q = strtok(p, ","); q; q = strtok(NULL, ",")) {
      char *v = strpbrk(q, "=:");
      if(v) {
          *v++ = '\0';
          int i;
          for(i = 0; i < NUM_COLORS; i++)
            if(strcasecmp(color_name[i], q) == 0)
                break;
          if(i == NUM_COLORS)
            fprintf(stderr, "qps: unknown color category '%s'\n", q);
          else
            color_set[i].setNamedColor(v);
      } else
          fprintf(stderr, "qps: bad environment variable syntax\n");
    }
    free(p);
}

// set the window_group hint to that of the main (qps) window
void Qps::setWindowGroup(QWidget *w)
{
    XWMHints wmh;
    wmh.flags = WindowGroupHint;
    wmh.window_group = handle();
    XSetWMHints(w->x11Display(), w->handle(), &wmh);
}

void Qps::setCommand(int argc, char **argv)
{
    // bug: argv[0] should really be frobbed into an absolute path name here
    XSetCommand(x11Display(), handle(), argv, argc);
}

// things to do when started iconified
void Qps::iconicStartFix()
{
    // convince the widget that it is hidden because we started iconified
    clearWFlags(WState_Visible);
    update_icon();
}

// return host name with domain stripped
QString short_hostname()
{
    struct utsname u;
    uname(&u);
    char *p = strchr(u.nodename, '.');
    if(p) *p = '\0';
    QString s(u.nodename);
    return s;
}

bool opt_eq(const char *arg, const char *opt)
{
    if(arg[0] == '-') {
      arg++;
      if(arg[0] == '-')
          arg++;
      return strcmp(arg, opt) == 0;
    } else
      return FALSE;
}

// print some help to stdout and exit
void print_help(char *cmdname)
{
    fprintf(stderr, "Usage: %s [options]\nOptions:\n"
          "  -display <disp>\tX11 display to use\n"
          "  -geometry <geom>\tgeometry of main window\n"
          "  -title <title>\ttitle of main window\n"
          "  -style <style>\tGUI style; <style> is "
#if QT_VERSION >= 200
          "motif, cde, windows, or platinum.\n"
#else
          "motif or windows.\n"
#endif
          "  -iconic\t\tstart iconified\n"
          "  -font <font>\t\tset application font\n"
          "  -version\t\tdisplay version and exit\n",
          cmdname);
}

int main(int argc, char **argv, char **envp)
{
    Lookup::initproctitle(argv, envp);
    // Qt might modify argc, argv so save them first
    int saved_argc = argc;
    char **saved_argv = (char **)malloc(sizeof(char *) * (argc + 1));
    memcpy(saved_argv, argv, sizeof(char *) * (argc + 1));

    bool start_iconic = FALSE;
    for(int i = 1; i < argc; i++) {
      if(opt_eq(argv[i], "version")) {
          fprintf(stderr, "qps version " QPS_VERSION
                ", using Qt library %s\n",
                qVersion());
          exit(1);
      } else if(opt_eq(argv[i], "help") || opt_eq(argv[i], "h")) {
          print_help(argv[0]);
          exit(1);
      } else if(opt_eq(argv[i], "iconic"))
          start_iconic = TRUE;
    }
    // Qt 1.4x: If LANG isn't set, the default font is fixed, not Helvetica
    if(!getenv("LANG"))
      putenv("LANG=C");

    QApplication app(argc, argv);

    // gross hack: if the font is too wide, then it's probably a 100dpi font
    // so we use a 9pt one instead (9pt at 100dpi == 12pt at 75dpi)
    // the right way to handle this would be proper geometry management instead
#if QT_VERSION >= 200
    QFont f(app.font());
#else
    QFont f(*app.font());     // assume it is Helvetica 12
#endif
    f.setBold(TRUE);
    QFontMetrics fm(f);
    if(fm.width('0') > 7)
      f.setPointSize(9);
    app.setFont(f, TRUE);
    f.setBold(FALSE);         // same font for tooltips, but not bold
    QToolTip::setFont(f);

    Qps q;
    q.setCommand(saved_argc, saved_argv);
    free(saved_argv);
    QString cap = "qps@";
    cap.append(short_hostname());
    if(geteuid() == 0)
      cap.append(" (root)");
    q.setCaption(cap);
    app.setMainWidget(&q);

    if(start_iconic) {
      // Qt has no explicit support for this, so we do it manually
      XWMHints wmh;
      wmh.initial_state = IconicState;
      wmh.flags = StateHint;
      XSetWMHints(q.x11Display(), q.winId(), &wmh);
    }
    q.show();                 // won't map the window if we start iconified
    if(start_iconic) {
      // show() will send Event_Show even if iconified. We need to convince
      // the widget that it is hidden:
      q.iconicStartFix();
    }

    return app.exec();
}


Generated by  Doxygen 1.6.0   Back to index