Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
The opposite of ~/Desktop/Autostart - killing apps on logout
View unanswered posts
View posts from last 24 hours
View posts from last 7 days

 
Reply to topic    Gentoo Forums Forum Index Desktop Environments
View previous topic :: View next topic  
Author Message
optilude
Apprentice
Apprentice


Joined: 29 May 2002
Posts: 248
Location: England

PostPosted: Fri Nov 21, 2003 2:51 pm    Post subject: The opposite of ~/Desktop/Autostart - killing apps on logout Reply with quote

Hello,

I've got two dameons starting in ~/Desktop/Autostart (one to periodically check my mail and download headers, another to run asus_acpid, which lets me use the special hotkeys on this laptop). This makes them start in both Gnome and XFCE4 (mostly use the latter).

However, if I log out and back in again, they get started again, too! For the asus_acpid in particular, this is a problem because it means that all the hotkeys are processed twice (so I press the Web button and it starts up two Firebird instances!). How can I kill these programs when I log out? Even being able to run any code when my XFCE4 or Gnome session is closed (e.g. killall asus_acpid) would do; although something more elegent would be preferred.

Best wishes,
Martin
_________________
--
"Life is both a major and a minor key" -- Travis
Back to top
View user's profile Send private message
grant.mcdorman
Apprentice
Apprentice


Joined: 29 Jan 2003
Posts: 295
Location: Toronto, ON, Canada

PostPosted: Fri Nov 21, 2003 8:02 pm    Post subject: Reply with quote

Just for fun (and since it'll be useful), I wrote a C++ program to handle this for you. Compile it with:
Code:
g++ -o task task.cpp -L/usr/X11R6/lib -lX11
To run it, make a file with a line each command you want to run; for example
Code:
# check mail
/usr/bin/mailcheck
Note that these commands are not run using a shell, so the shell constructs - ~ for home, $ variables, etc. - don't work.

Then, put in ~/Desktop/Autostart:
Code:
#!/bin/sh
task task_list
with, of course, the correct paths to the 'task' program and your task_list file.

This program compiles and works on Solaris, and compiles on Gentoo.

Here is the full program:
Code:
//
// Task manager for X
// Copyright (c) 2003 by Grant McDorman
//  BSD-style license:
//       Redistribution and use in source and binary forms, with or
//       without modification, are permitted provided that the following
//       conditions are met:
//
//            Redistributions of source code must retain the above
//            copyright notice, this list of conditions and the
//            following disclaimer.
//
//            Redistributions in binary form must reproduce the above
//            copyright notice, this list of conditions and the
//            following disclaimer in the documentation and/or other
//            materials provided with the distribution.
//
//       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
//       CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
//       INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
//       MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//       DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR THE
//       CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
//       NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//       LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
//       HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//       CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
//       OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
//       EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <signal.h>

#include <X11/Xlib.h>

#include <string>
#include <vector>
#include <iostream>
#include <fstream>

using namespace std;

// This is for Sun's Forte compiler, which warns unless we have a C-style function pointer.
namespace TaskDefinitions {
    extern "C" {
        typedef void (*signal_function)(int);
    }
};

class Task {
    public:
      Task(const string command_line);
            ~Task();

            // return True if the task is running
            bool isRunning(void) const;
            // return exit status of task. 0 if the task never ran.
            int exitStatus(void) const { return exit_status; };
            // start the task; return false if an error occured
            bool start(void);
            // send the task the specified signal to kill it
            void kill(int signal = SIGINT);

            string command(void) const { return command_line; }
        private:
            string command_line;
            pid_t  pid;
            int       exit_status;
            bool   finished;
            std::vector<string> argv_vector;
};

Task::Task(const string given_command_line):
    command_line(given_command_line),
    pid(0),
    exit_status(-1),
    finished(false)
{
    // parse command line
    const string::size_type len = command_line.length();
    string::size_type i = 0;

    while ( i < len )
    {
        // eat leading whitespace
        i = command_line.find_first_not_of (" \t", i);
        if (i == string::npos)
            return;

        // find the end of the token
        // handle quotes (", ') and escapes (\)
        string delimiters = " \t";
        if (command_line[i] == '"' ||
             command_line[i] == '\'') {
                 delimiters = command_line[i];
                 i++;
        }
        string arg;
        bool done = false;
        char ch;
        while (!done && i < len) {
            ch = command_line[i];
            if (ch == '\\') {
                // escaped, pop over
                i++;
                if (i < len) {
                    // we do not handle special escapes (\000, \0x00, \t, \r, \n, etc.)
                    arg += command_line[i];
                    i++;
                }
            } else {
                // not escaped. delimiter?
                if (delimiters.find_first_of(ch) != string::npos) {
                    done = true;
                } else {
                    arg += ch;
                    i++;
                }
            }
        }
        argv_vector.push_back(arg);
    }
}

Task::~Task()
{
    if(isRunning()) {
        kill();
    }
}

bool Task::isRunning(void) const
{
    if (pid > 0) {
        int pid_status;
        int status;
        // poll child
        status = waitpid(pid, &status, WNOHANG);
        if (status == pid) {
            // process exitied
            const_cast<Task *>(this)->pid = 0;
            const_cast<Task *>(this)->exit_status = pid_status;
        }
    }
    return pid > 0;
}
   
bool Task::start(void)
{
    static const int PIPE_READ_INDEX = 0;
    static const int PIPE_WRITE_INDEX = 1;
    int     pipe_fd[2]; /* read=0 write=1 */
    int     status;
    int     exec_status;
    TaskDefinitions::signal_function old_signal;
   
    /* get a pipe for determining success of operation */
    status = ::pipe(pipe_fd);
    if (status != 0) {
        return false;
    }
   
    /* mark pipe write end as close-on-exec */
    status = ::fcntl(pipe_fd[PIPE_WRITE_INDEX], F_SETFD, FD_CLOEXEC);
    if (status != 0) {
        (void) ::close(pipe_fd[PIPE_READ_INDEX]);
        (void) ::close(pipe_fd[PIPE_WRITE_INDEX]);
        return false;
    }

    // create argv from argument list
    std::vector<string>::iterator iter;
    const char **argv = new const char *[argv_vector.size() + 1];
    const char * program;
    int i = 0;
    for (iter = argv_vector.begin(); iter != argv_vector.end(); iter++) {
        argv[i] = argv_vector[i].c_str();
        i++;
    }
    argv[argv_vector.size()] = NULL;
    program = argv[0];

    pid = fork();
    if (pid < 0) {
        /* fork failed */
        (void) ::close(pipe_fd[PIPE_READ_INDEX]);
        (void) ::close(pipe_fd[PIPE_WRITE_INDEX]);
        return false;
    }
   
    if (pid == 0) {
        /* child process */
        (void) ::close(pipe_fd[PIPE_READ_INDEX]);
        status = ::execvp(program, const_cast<char*const*>(argv));
        /* failed - pass status through pipe */
        (void) ::write(pipe_fd[PIPE_WRITE_INDEX], &status, sizeof (status));
        (void) ::close(pipe_fd[PIPE_WRITE_INDEX]);
        ::exit(255);
    }
    /* parent - close pipe write end */
    (void) ::close(pipe_fd[PIPE_WRITE_INDEX]);
    old_signal = ::signal(SIGPIPE, SIG_IGN);  /* prevent SIGPIPE from causing problems */
    exec_status = -1;
    status = ::read(pipe_fd[PIPE_READ_INDEX], &exec_status, sizeof (exec_status));
    /* restore sigipe handler */
    (void) ::signal(SIGPIPE, old_signal);
    /* don't care about failure of close here */
    (void) ::close(pipe_fd[PIPE_READ_INDEX]);
    // delete allocated memory
    delete [] argv;
    if (status > 0) {
        /* error */
        /* reap child */
        int stat_loc;
        (void) ::waitpid(pid, &stat_loc, 0);
        return false;
    }
    return true;
}

void Task::kill(int signal)
{
    if (isRunning()) {
        ::kill(pid, signal);
    }
}
// END OF CLASS TASK

static std::vector<Task> tasks;

extern "C" {
    static int IO_error_handler(Display *)
    {
        //
        // kill tasks
        cerr << "Detected server exit, shutting down" <<endl;
        bool allexited = true;
        std::vector<Task>::iterator iter;
        for (iter = tasks.begin(); iter != tasks.end(); iter++) {
            if (iter->isRunning()) {
                cerr << "Sending " << iter->command() << " SIGHUP" << endl;
                iter->kill(SIGHUP);
                allexited &= !iter->isRunning();
            } else {
                cerr << iter->command() << " already exited" << endl;
            }
        }
        // wait a bit for normal termination
        if (!allexited) {
            cerr << "Waiting for task termination" << endl;
            sleep(5);
            for (iter = tasks.begin(); iter != tasks.end(); iter++) {
                if (iter->isRunning()) {
                    cerr << iter->command() << " did not terminate, sending SIGTERM" << endl;
                    iter->kill(SIGTERM);
                }
            }
        }
        exit(0);
        return 0;   // keep compiler happy
    }
};

int main(int argc, const char *argv[])
{
    if (argc != 2) {
        cerr << "Usage: " <<
            argv[0] << " config_file" << endl;
        exit(1);
    }

    // open and read configuration file
    std::ifstream input(argv[1]);
    std::string command;
    if (!input) {
        cerr << "Cannot open " <<argv[1] << endl;
        exit(2);
    }
    while(getline(input, command)) {
        // eat leading whitespace
        string::size_type i;
        i = command.find_first_not_of (" \t", i);
        if (i != string::npos && command[i] != '#') {
            tasks.push_back(Task(command));
        }
    }
   
    input.close();

    if (tasks.size() == 0) {
        cerr << "Found no command lines in " << argv[1] << endl;
        exit(3);
    }

    // open X display
    // do this before starting tasks so we won't fail if there is none
    char * display_name = getenv("DISPLAY");
    if (display_name == NULL) display_name = ":0";
    Display *disp = XOpenDisplay(display_name);
    if (disp == NULL) {
        cerr << "Could not open display " << display_name << endl;
        exit(4);
    }

    // start tasks
    std::vector<Task>::iterator iter;
    for (iter = tasks.begin(); iter != tasks.end(); iter++) {
        cerr << "Starting " << iter->command() << endl;
        if (!iter->start()) {
            cerr << "Could not start " << iter->command() << endl;
        }
    }

    // set I/O error handler, which will handle orderly shutdown
    XSetIOErrorHandler(IO_error_handler);
    // wait for disconnection
    XEvent event;
    while (XNextEvent(disp, &event)) {
        // idle
    }
    // NEVER REACHED
}
Back to top
View user's profile Send private message
optilude
Apprentice
Apprentice


Joined: 29 May 2002
Posts: 248
Location: England

PostPosted: Fri Nov 21, 2003 9:27 pm    Post subject: Reply with quote

This looks very cool; I assume it works on the principle that the task process is not detatched from the shell and hence killed when the spawning process dies.

However, no matter what I do, I seem to get:

Code:

Found no command lines in task_list.


Any ideas?

Martin
_________________
--
"Life is both a major and a minor key" -- Travis
Back to top
View user's profile Send private message
optilude
Apprentice
Apprentice


Joined: 29 May 2002
Posts: 248
Location: England

PostPosted: Fri Nov 21, 2003 9:34 pm    Post subject: Reply with quote

Actually found the bug: On line 285, you must initialise the variable i to 0 before the find_first_not_of() call may start at some unspecified location in the string (probably beyond the end of it).
_________________
--
"Life is both a major and a minor key" -- Travis
Back to top
View user's profile Send private message
optilude
Apprentice
Apprentice


Joined: 29 May 2002
Posts: 248
Location: England

PostPosted: Fri Nov 21, 2003 9:53 pm    Post subject: Reply with quote

Wow - works like a charm now!

It's quite strange, actually; from within your program, I run "asus_acpid -d" (the -d flag means debug, which makes it not detach from the terminal - without it, taskman appears to have no effect since it detaches from the spawning process) and "popcheck" (which does not detach) from scripts in ~/Desktop/Autostart/, there do not appear to get killed when I log out, but takman *does* get killed - why is that? I assume it has something to do with the part of your code that talks to X?

Anyway, this is fantastic. Could you maybe host it somewhere and make an ebuild to share it with the rest of the world? I think it is very useful, and not only from within Freedesktop.org's ~/Desktop/Autostart directory.

Best wishes,
Martin
_________________
--
"Life is both a major and a minor key" -- Travis
Back to top
View user's profile Send private message
grant.mcdorman
Apprentice
Apprentice


Joined: 29 Jan 2003
Posts: 295
Location: Toronto, ON, Canada

PostPosted: Fri Nov 21, 2003 10:19 pm    Post subject: Reply with quote

optilude wrote:
Actually found the bug: On line 285, you must initialise the variable i to 0 before the find_first_not_of() call may start at some unspecified location in the string (probably beyond the end of it).
Thanks. Actually, line 286 should read:
Code:
        i = command.find_first_not_of (" \t");
as it should start searching from the beginning always. I guess I just got lucky on Solaris at this point.

optilude wrote:
[asus_acpid and popcheck] do not appear to get killed when I log out, but [taskman] *does* get killed - why is that? I assume it has something to do with the part of your code that talks to X?
Yes. The XOpenDisplay/XNextEvent essentially keeps a connection to the display open - until there's an error, at which time the I/O error handler is called, which shuts down. Your other apps (asus_acpid, popcheck) are not X apps, so they never notice the X server has gone away. This is a very efficient loop, by the way; most of the time it'll be blocked waiting for an event from the X server.

As for putting up a website, I'll have to create another SourceForge project, I guess. I may get around to it on the weekend.
Back to top
View user's profile Send private message
grant.mcdorman
Apprentice
Apprentice


Joined: 29 Jan 2003
Posts: 295
Location: Toronto, ON, Canada

PostPosted: Sun Nov 30, 2003 12:43 am    Post subject: Reply with quote

The application is now hosted on SourceForge: http://sourceforge.net/projects/xtaskmanager/

The version here includes a minor fix (programs that exit early weren't cleaned up), and a major enhancement: the ability to run apps at logout as well as killing the managed apps. This allows you to do something like ssh-agent -k on logout.

Ebuild to follow.
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Desktop Environments All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum