View previous topic :: View next topic |
Author |
Message |
optilude Apprentice
Joined: 29 May 2002 Posts: 248 Location: England
|
Posted: Fri Nov 21, 2003 2:51 pm Post subject: The opposite of ~/Desktop/Autostart - killing apps on logout |
|
|
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 |
|
|
grant.mcdorman Apprentice
Joined: 29 Jan 2003 Posts: 295 Location: Toronto, ON, Canada
|
Posted: Fri Nov 21, 2003 8:02 pm Post subject: |
|
|
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 |
|
|
optilude Apprentice
Joined: 29 May 2002 Posts: 248 Location: England
|
Posted: Fri Nov 21, 2003 9:27 pm Post subject: |
|
|
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 |
|
|
optilude Apprentice
Joined: 29 May 2002 Posts: 248 Location: England
|
Posted: Fri Nov 21, 2003 9:34 pm Post subject: |
|
|
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 |
|
|
optilude Apprentice
Joined: 29 May 2002 Posts: 248 Location: England
|
Posted: Fri Nov 21, 2003 9:53 pm Post subject: |
|
|
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 |
|
|
grant.mcdorman Apprentice
Joined: 29 Jan 2003 Posts: 295 Location: Toronto, ON, Canada
|
Posted: Fri Nov 21, 2003 10:19 pm Post subject: |
|
|
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 |
|
|
grant.mcdorman Apprentice
Joined: 29 Jan 2003 Posts: 295 Location: Toronto, ON, Canada
|
Posted: Sun Nov 30, 2003 12:43 am Post subject: |
|
|
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 |
|
|
|
|
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
|
|