diff options
Diffstat (limited to 'ksystraycmd')
-rw-r--r-- | ksystraycmd/Makefile.am | 18 | ||||
-rw-r--r-- | ksystraycmd/README | 29 | ||||
-rw-r--r-- | ksystraycmd/ksystraycmd.cpp | 341 | ||||
-rw-r--r-- | ksystraycmd/ksystraycmd.h | 89 | ||||
-rw-r--r-- | ksystraycmd/main.cpp | 143 |
5 files changed, 620 insertions, 0 deletions
diff --git a/ksystraycmd/Makefile.am b/ksystraycmd/Makefile.am new file mode 100644 index 000000000..0fd20878b --- /dev/null +++ b/ksystraycmd/Makefile.am @@ -0,0 +1,18 @@ +####### Fiddle here + +INCLUDES = $(all_includes) +LDADD = $(LIB_KDEUI) + +####### Files + +bin_PROGRAMS = ksystraycmd +METASOURCES = ksystraycmd.moc +noinst_HEADERS = ksystraycmd.h + +ksystraycmd_SOURCES = ksystraycmd.cpp main.cpp +ksystraycmd_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +messages: + $(XGETTEXT) $(ksystraycmd_SOURCES) -o $(podir)/ksystraycmd.pot + + diff --git a/ksystraycmd/README b/ksystraycmd/README new file mode 100644 index 000000000..8e41777a0 --- /dev/null +++ b/ksystraycmd/README @@ -0,0 +1,29 @@ + README for KSysTrayCmd + ====================== + +Introduction +============ + +KSysTrayCmd is a utility that allows you to run any application you +like in the system tray, not just those designed to use it. + +Examples +======== + +ksystraycmd --window 'kmail' kmail + +ksystraycmd --startonshow --icon logfile --tooltip 'X Log' --hidden --window 'X Log' \ + konsole --icon logfile --caption 'X Log' -e 'less -M ~/.xsession-errors' + +More Information +================ + +I'm currently writing an article for the dot which will describe the +usage of both kstart and ksystraycmd in more detail, the text of the +article will be used to improve this document. + +Richard Moore +rich@kde.org + + + diff --git a/ksystraycmd/ksystraycmd.cpp b/ksystraycmd/ksystraycmd.cpp new file mode 100644 index 000000000..77edf73ea --- /dev/null +++ b/ksystraycmd/ksystraycmd.cpp @@ -0,0 +1,341 @@ +#include <qtooltip.h> +#include <qtextstream.h> +#include <qimage.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kapplication.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kprocess.h> +#include <kwinmodule.h> +#include <kconfig.h> +#include <ksystemtray.h> + +#include <netwm.h> + +#include "ksystraycmd.h" +#include "ksystraycmd.moc" + + +KSysTrayCmd::KSysTrayCmd() + : QLabel( 0, "systray_cmd" ), + isVisible(true), lazyStart( false ), noquit( false ), quitOnHide( false ), onTop(false), ownIcon(false), + win(0), client(0), kwinmodule(0), top(0), left(0) +{ + setAlignment( AlignCenter ); + kwinmodule = new KWinModule( this ); + refresh(); +} + +KSysTrayCmd::~KSysTrayCmd() +{ + delete client; +} + +// +// Main entry point to the class +// + +bool KSysTrayCmd::start() +{ + // If we have no command we must catching an existing window + if ( !command ) { + if ( win ) { + setTargetWindow( win ); + return true; + } + + checkExistingWindows(); + if ( win ) { + // Window always on top + if (onTop) { + KWin::setState(win, NET::StaysOnTop); + } + return true; + } + + errStr = i18n( "No window matching pattern '%1' and no command specified.\n" ) + .arg( window ); + return false; + } + + // Run the command and watch for its window + if ( !startClient() ) { + errStr = i18n( "KSysTrayCmd: KShellProcess cannot find a shell." ); + clientExited(); + return false; + } + + return true; +} + +// +// Window related functions. +// + +void KSysTrayCmd::showWindow() +{ + isVisible = true; + if ( !win ) + return; + XMapWindow( qt_xdisplay(), win ); + // We move the window to the memorized position + XMoveWindow( qt_xdisplay(), win, left, top); + + // Window always on top + if (onTop) + { + KWin::setState(win, NET::StaysOnTop); + } + + KWin::activateWindow( win ); + +} + +void KSysTrayCmd::hideWindow() +{ + isVisible = false; + if ( !win ) + return; + //We memorize the position of the window + left = KWin::windowInfo(win).frameGeometry().left(); + top=KWin::windowInfo(win).frameGeometry().top(); + + XUnmapWindow( qt_xdisplay(), win ); +} + +void KSysTrayCmd::setTargetWindow( WId w ) +{ + setTargetWindow( KWin::windowInfo( w ) ); +} + +void KSysTrayCmd::setTargetWindow( const KWin::WindowInfo &info ) +{ + disconnect( kwinmodule, SIGNAL(windowAdded(WId)), this, SLOT(windowAdded(WId)) ); + connect( kwinmodule, SIGNAL(windowChanged(WId)), SLOT(windowChanged(WId)) ); + win = info.win(); + KWin::setSystemTrayWindowFor( winId(), win ); + refresh(); + show(); + + if ( isVisible ) + KWin::activateWindow( win ); + else + hideWindow(); + + // Always on top ? + if (onTop) + { + KWin::setState(win, NET::StaysOnTop); + } +} + +// +// Refresh the tray icon +// + +void KSysTrayCmd::refresh() +{ + KWin::setSystemTrayWindowFor( winId(), win ? win : winId() ); + + QToolTip::remove( this ); + if ( win ) { + KConfig *appCfg = kapp->config(); + KConfigGroupSaver configSaver(appCfg, "System Tray"); + int iconWidth = appCfg->readNumEntry("systrayIconWidth", 22); + + // ksystraycmd's icon or app's icon + if (ownIcon) + { + setPixmap( KSystemTray::loadIcon( kapp->iconName() ) ); + } + else + { + setPixmap( KWin::icon( win, iconWidth, iconWidth, true ) ); + } + + QToolTip::add( this, KWin::windowInfo( win ).name() ); + } + else { + if ( !tooltip.isEmpty() ) + QToolTip::add( this, tooltip ); + else if ( !command.isEmpty() ) + QToolTip::add( this, command ); + else + QToolTip::add( this, window ); + + setPixmap( KSystemTray::loadIcon( kapp->iconName() ) ); + } +} + +// +// Client related functions. +// + +bool KSysTrayCmd::startClient() +{ + client = new KShellProcess(); + *client << command; + connect( kwinmodule, SIGNAL(windowAdded(WId)), SLOT(windowAdded(WId)) ); + connect( client, SIGNAL( processExited(KProcess *) ), + this, SLOT( clientExited() ) ); + + return client->start(); +} + +void KSysTrayCmd::clientExited() +{ + delete client; + client = 0; + win = 0; + + if ( lazyStart && noquit ) + refresh(); + else + qApp->quit(); +} + +void KSysTrayCmd::quitClient() +{ + if ( win ) { + // Before sending the close request we have to show the window + XMapWindow( qt_xdisplay(), win ); + NETRootInfo ri( qt_xdisplay(), NET::CloseWindow ); + ri.closeWindowRequest( win ); + win=0; + noquit = false; + + // We didn't give command, so we didn't open an application. + // That's why when the application is closed we aren't informed. + // So we quit now. + if ( !command ) { + qApp->quit(); + } + } + else { + qApp->quit(); + } +} + +void KSysTrayCmd::quit() +{ + if ( !isVisible ) { + showWindow(); + } + qApp->quit(); +} + +void KSysTrayCmd::execContextMenu( const QPoint &pos ) +{ + KPopupMenu *menu = new KPopupMenu(); + menu->insertTitle( *pixmap(), i18n( "KSysTrayCmd" ) ); + int hideShowId = menu->insertItem( isVisible ? i18n( "&Hide" ) : i18n( "&Restore" ) ); + int undockId = menu->insertItem( SmallIcon("close"), i18n( "&Undock" ) ); + int quitId = menu->insertItem( SmallIcon("exit"), i18n( "&Quit" ) ); + + int cmd = menu->exec( pos ); + + if ( cmd == quitId ) + quitClient(); + else if ( cmd == undockId ) + quit(); + else if ( cmd == hideShowId ) + { + if ( lazyStart && ( !hasRunningClient() ) ) + { + start(); + isVisible=true; + } + else if ( quitOnHide && ( hasRunningClient() ) && isVisible ) + { + NETRootInfo ri( qt_xdisplay(), NET::CloseWindow ); + ri.closeWindowRequest( win ); + isVisible=false; + } + else + toggleWindow(); + } + + delete menu; +} + +void KSysTrayCmd::checkExistingWindows() +{ + QValueList<WId>::ConstIterator it; + for ( it = kwinmodule->windows().begin(); it != kwinmodule->windows().end(); ++it ) { + windowAdded( *it ); + if ( win ) + break; + } +} + +void KSysTrayCmd::windowAdded(WId w) +{ + if ( !window.isEmpty() && ( QRegExp( window ).search( KWin::windowInfo(w).name() ) == -1 ) ) + return; // no match + setTargetWindow( w ); +} + +void KSysTrayCmd::windowChanged( WId w ) +{ + if ( w != win ) + return; + refresh(); +} + +// +// Tray icon event handlers +// + +void KSysTrayCmd::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == RightButton ) + execContextMenu( e->globalPos() ); + else if ( lazyStart && ( !hasRunningClient() ) ) + { + start(); + isVisible=true; + } + else if ( quitOnHide && ( hasRunningClient() ) && isVisible ) + { + NETRootInfo ri( qt_xdisplay(), NET::CloseWindow ); + ri.closeWindowRequest( win ); + isVisible=false; + } + else + toggleWindow(); +} + +WId KSysTrayCmd::findRealWindow( WId w, int depth ) +{ + if( depth > 5 ) + return None; + static Atom wm_state = XInternAtom( qt_xdisplay(), "WM_STATE", False ); + Atom type; + int format; + unsigned long nitems, after; + unsigned char* prop; + if( XGetWindowProperty( qt_xdisplay(), w, wm_state, 0, 0, False, AnyPropertyType, + &type, &format, &nitems, &after, &prop ) == Success ) { + if( prop != NULL ) + XFree( prop ); + if( type != None ) + return w; + } + Window root, parent; + Window* children; + unsigned int nchildren; + Window ret = None; + if( XQueryTree( qt_xdisplay(), w, &root, &parent, &children, &nchildren ) != 0 ) { + for( unsigned int i = 0; + i < nchildren && ret == None; + ++i ) + ret = findRealWindow( children[ i ], depth + 1 ); + if( children != NULL ) + XFree( children ); + } + return ret; +} diff --git a/ksystraycmd/ksystraycmd.h b/ksystraycmd/ksystraycmd.h new file mode 100644 index 000000000..ecb670fd5 --- /dev/null +++ b/ksystraycmd/ksystraycmd.h @@ -0,0 +1,89 @@ +// -*- c++ -*- + +#ifndef KSYSTRAYCMD_H +#define KSYSTRAYCMD_H + +#include <qlabel.h> +#include <kwin.h> + +class KShellProcess; +class KWinModule; + +/** + * Provides a system tray icon for a normal window. + * + * @author Richard Moore, rich@kde.org + */ +class KSysTrayCmd : public QLabel +{ + Q_OBJECT +public: + KSysTrayCmd(); + ~KSysTrayCmd(); + + void setWindow( WId w ) { win = w; } + void setCommand( const QString &cmd ) { command = cmd; } + void setPattern( const QString ®exp ) { window = regexp; } + void setStartOnShow( bool enable ) { lazyStart = enable; isVisible = !enable; } + void setNoQuit( bool enable ) { noquit = enable; } + void setQuitOnHide( bool enable ) { quitOnHide = enable; } + void setOnTop( bool enable ) { onTop = enable; } + void setOwnIcon( bool enable ) { ownIcon = enable; } + void setDefaultTip( const QString &tip ) { tooltip = tip; } + bool hasTargetWindow() const { return (win != 0); } + bool hasRunningClient() const { return (client != 0); } + const QString &errorMsg() const { return errStr; } + + bool start(); + + static WId findRealWindow( WId w, int depth = 0 ); + +public slots: + void refresh(); + + void showWindow(); + void hideWindow(); + void toggleWindow() { if ( isVisible ) hideWindow(); else showWindow(); } + + void setTargetWindow( WId w ); + void execContextMenu( const QPoint &pos ); + + void quit(); + void quitClient(); + +protected slots: + void clientExited(); + + void windowAdded(WId w); + void windowChanged(WId w); + +protected: + bool startClient(); + void checkExistingWindows(); + void setTargetWindow( const KWin::WindowInfo &info ); + + void mousePressEvent( QMouseEvent *e ); + +private: + QString command; + QString window; + QString tooltip; + bool isVisible; + bool lazyStart; + bool noquit; + bool quitOnHide; + bool onTop; ///< tells if window must stay on top or not + bool ownIcon; ///< tells if the ksystraycmd icon must be used in systray + + WId win; + KShellProcess *client; + KWinModule *kwinmodule; + QString errStr; + + /** Memorized 'top' position of the window*/ + int top; + /** Memorized 'left' position of the window*/ + int left; +}; + +#endif // KSYSTRAYCMD_H diff --git a/ksystraycmd/main.cpp b/ksystraycmd/main.cpp new file mode 100644 index 000000000..b10728fd9 --- /dev/null +++ b/ksystraycmd/main.cpp @@ -0,0 +1,143 @@ +#include <fcntl.h> + +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <klocale.h> +#include <kprocess.h> + +#include "ksystraycmd.h" + +#include <X11/Xlib.h> +#ifndef KDE_USE_FINAL +const int XFocusOut = FocusOut; +const int XFocusIn = FocusIn; +#endif +#undef FocusOut +#undef FocusIn +#undef KeyPress +#undef KeyRelease + + +static KCmdLineOptions options[] = +{ + { "!+command", I18N_NOOP("Command to execute"), 0 }, + // "!" means: all options after command are treated as arguments to the command + { "window <regexp>", I18N_NOOP("A regular expression matching the window title\n" + "If you do not specify one, then the very first window\n" + "to appear will be taken - not recommended."), 0 }, + { "wid <int>", I18N_NOOP("The window id of the target window\n" + "Specifies the id of the window to use. If the id starts with 0x\n" + "it is assumed to be in hex."), 0 }, + { "hidden", I18N_NOOP( "Hide the window to the tray on startup" ), 0 }, + { "startonshow", I18N_NOOP( "Wait until we are told to show the window before\n" + "executing the command" ), 0 }, + { "tooltip <text>", I18N_NOOP( "Sets the initial tooltip for the tray icon" ), 0 }, + { "keeprunning", I18N_NOOP( "Keep the tray icon even if the client exits. This option\n" + "has no effect unless startonshow is specified." ), 0 }, + { "ownicon", I18N_NOOP( "Use ksystraycmd's icon instead of window's icon in systray\n" + "(should be used with --icon to specify ksystraycmd icon)" ), 0 }, + { "ontop", I18N_NOOP( "Try to keep the window above other windows"), 0 }, + { "quitonhide", I18N_NOOP( "Quit the client when we are told to hide the window.\n" + "This has no effect unless startonshow is specified and implies keeprunning." ), 0 }, + /* { "menuitem <item>", I18N_NOOP( "Adds a custom entry to the tray icon menu\n" + "The item should have the form text:command." ), 0 },*/ + KCmdLineLastOption +}; + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "ksystraycmd", I18N_NOOP( "KSysTrayCmd" ), + "KSysTrayCmd 0.1", + I18N_NOOP( "Allows any application to be kept in the system tray" ), + KAboutData::License_GPL, + "(C) 2001-2002 Richard Moore (rich@kde.org)" ); + aboutData.addAuthor( "Richard Moore", 0, "rich@kde.org" ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + + KApplication app; + + // + // Setup the tray icon from the arguments. + // + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + KSysTrayCmd cmd; + + // Read the window id + QString wid = args->getOption( "wid" ); + if ( !wid.isEmpty() ) { + int base = 10; + if ( wid.startsWith( "0x" ) ) { + base = 16; + wid = wid.right( wid.length() - 2 ); + } + + bool ok=true; + ulong w = wid.toULong( &ok, base ); + if ( ok ) + cmd.setTargetWindow( w ); + else { + kdWarning() << "KSysTrayCmd: Got bad win id" << endl; + } + } + + // Read window title regexp + QString title = args->getOption( "window" ); + if ( !title.isEmpty() ) + cmd.setPattern( title ); + + if ( !title && !wid && (args->count() == 0) ) + KCmdLineArgs::usage(i18n("No command or window specified")); + + // Read the command + QString command; + for ( int i = 0; i < args->count(); i++ ) + command += KProcess::quote(QString::fromLocal8Bit( args->arg(i) )) + " "; + if ( !command.isEmpty() ) + cmd.setCommand( command ); + + // Tooltip + QString tip = args->getOption( "tooltip" ); + if ( !tip.isEmpty() ) + cmd.setDefaultTip( tip ); + + // Keep running flag + if ( args->isSet( "keeprunning" ) ) + cmd.setNoQuit( true ); + + if ( args->isSet( "quitonhide" ) ) { + cmd.setNoQuit( true ); + cmd.setQuitOnHide( true ); + } + + // Start hidden + if ( args->isSet( "hidden" ) ) + cmd.hideWindow(); + + // On top + if ( args->isSet( "ontop" ) ) + cmd.setOnTop(true); + + // Use ksystraycmd icon + if ( args->isSet( "ownicon" ) ) + cmd.setOwnIcon(true); + + // Lazy invocation flag + if ( args->isSet( "startonshow" ) ) { + cmd.setStartOnShow( true ); + cmd.show(); + } + else { + if ( !cmd.start() ) + return 1; + } + + fcntl(ConnectionNumber(qt_xdisplay()), F_SETFD, 1); + args->clear(); + + return app.exec(); +} + |