diff options
-rw-r--r-- | tdecore/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tdecore/hwlibdata/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tdecore/hwlibdata/pnpdev/CMakeLists.txt | 17 | ||||
-rw-r--r-- | tdecore/hwlibdata/pnpdev/pnp.ids | 156 | ||||
-rw-r--r-- | tdecore/ksimpledirwatch.cpp | 1755 | ||||
-rw-r--r-- | tdecore/ksimpledirwatch.h | 293 | ||||
-rw-r--r-- | tdecore/ksimpledirwatch_p.h | 161 | ||||
-rw-r--r-- | tdecore/tdehardwaredevices.cpp | 683 | ||||
-rw-r--r-- | tdecore/tdehardwaredevices.h | 189 |
9 files changed, 3225 insertions, 32 deletions
diff --git a/tdecore/CMakeLists.txt b/tdecore/CMakeLists.txt index 422a1f431..f96c212d6 100644 --- a/tdecore/CMakeLists.txt +++ b/tdecore/CMakeLists.txt @@ -123,7 +123,7 @@ set( ${target}_SRCS ktempdir.cpp kshell.cpp kmountpoint.cpp kcalendarsystemjalali.cpp kprotocolinfo_tdecore.cpp kprotocolinfofactory.cpp kxerrorhandler.cpp kuser.cpp kconfigskeleton.cpp kconfigdialogmanager.cpp klockfile.cpp - kqiodevicegzip_p.cpp ktimezones.cpp tdehardwaredevices.cpp + kqiodevicegzip_p.cpp ktimezones.cpp tdehardwaredevices.cpp ksimpledirwatch.cpp ) tde_add_library( ${target} SHARED AUTOMOC diff --git a/tdecore/hwlibdata/CMakeLists.txt b/tdecore/hwlibdata/CMakeLists.txt index d18877c5f..fe7cee67d 100644 --- a/tdecore/hwlibdata/CMakeLists.txt +++ b/tdecore/hwlibdata/CMakeLists.txt @@ -11,3 +11,4 @@ add_subdirectory( classrules ) +add_subdirectory( pnpdev ) diff --git a/tdecore/hwlibdata/pnpdev/CMakeLists.txt b/tdecore/hwlibdata/pnpdev/CMakeLists.txt new file mode 100644 index 000000000..61b1df38e --- /dev/null +++ b/tdecore/hwlibdata/pnpdev/CMakeLists.txt @@ -0,0 +1,17 @@ +################################################# +# +# (C) 2012 Timothy Pearson +# kb9vqf (AT) pearsoncomputing.net +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + + +##### other data ################################ + +install( FILES + pnp.ids + DESTINATION ${DATA_INSTALL_DIR}/tdehwlib/pnpdev ) diff --git a/tdecore/hwlibdata/pnpdev/pnp.ids b/tdecore/hwlibdata/pnpdev/pnp.ids new file mode 100644 index 000000000..bf4a3ce01 --- /dev/null +++ b/tdecore/hwlibdata/pnpdev/pnp.ids @@ -0,0 +1,156 @@ +# The information provided in this PNP ID to friendly name mapping table was obtained from +# multiple public sources, as well as this authoritative publicly accessible file: +# ftp://ftpmicrosoftcom/developr/drg/plug-and-play/devidstxt +# +# This file is provided soley in the interest of cross platform compatibility +# It only contains information on certain generic core system devices that cannot be looked up via another method +# All marks are the property of their owners +# +# A typical PNP ID takes the form of PNPxxxx:yy +# +# Some additional information that may be useful for applications parsing PNP device ID strings: +# PNP0xxx System devices +# PNP8xxx Network adapters +# PNPAxxx SCSI, proprietary CD adapters +# PNPBxxx Sound, video capture, multimedia +# PNPCxxx - PNPDxxx Modems +# +# The two digit code yy appears to be a PCI device type code followed by a subtype code and should be parsed as such + +PNP0802 Microsoft® Sound System-compatible device + +PNP0000 AT Interrupt Controller +PNP0001 EISA Interrupt Controller +PNP0002 MCA Interrupt Controller +PNP0003 APIC +PNP0004 Cyrix SLiC MP Interrupt Controller + +PNP0100 AT Timer +PNP0101 EISA Timer +PNP0102 MCA Timer +PNP0103 High Precision Event Timer + +PNP0200 AT DMA Controller +PNP0201 EISA DMA Controller +PNP0202 MCA DMA Controller + +PNP0300 IBM PC/XT keyboard controller (83-key) +PNP0301 IBM PC/AT keyboard controller (86-key) +PNP0302 IBM PC/XT keyboard controller (84-key) +PNP0303 IBM Enhanced (101/102-key, PS/2 mouse support) +PNP0304 Olivetti Keyboard (83-key) +PNP0305 Olivetti Keyboard (102-key) +PNP0306 Olivetti Keyboard (86-key) +PNP0307 Microsoft® Windows(R) Keyboard +PNP0308 General Input Device Emulation Interface (GIDEI) legacy +PNP0309 Olivetti Keyboard (A101/102 key) +PNP030A AT&T 302 keyboard +PNP030B Reserved by Microsoft® +PNP0320 Japanese 106-key keyboard A01 +PNP0321 Japanese 101-key keyboard +PNP0322 Japanese AX keyboard +PNP0323 Japanese 106-key keyboard 002/003 +PNP0324 Japanese 106-key keyboard 001 +PNP0325 Japanese Toshiba Desktop keyboard +PNP0326 Japanese Toshiba Laptop keyboard +PNP0327 Japanese Toshiba Notebook keyboard +PNP0340 Korean 84-key keyboard +PNP0341 Korean 86-key keyboard +PNP0342 Korean Enhanced keyboard +PNP0343 Korean Enhanced keyboard 101b +PNP0343 Korean Enhanced keyboard 101c +PNP0344 Korean Enhanced keyboard 103 + +PNP0400 Standard LPT Printer Port +PNP0401 ECP Printer Port + +PNP0500 Standard PC Serial Port +PNP0501 16550A-compatible Serial Port +PNP0502 Multiport Serial Device (non-intelligent 16550) +PNP0510 Generic IRDA-compatible Device +PNP0511 Generic IRDA-compatible Device + +PNP0600 Generic ESDI/IDE/ATA Compatible Hard Disk Controller +PNP0603 Generic IDE supporting Microsoft® Device Bay Specification +PNP0700 PC Standard Floppy Disk Controller +PNP0701 Microsoft® Device Bay Compatible Floppy Controller + +PNP0802 Microsoft® Sound System compatible device + +PNP0900 VGA Compatible Display Controller +PNP09FF Plug and Play Monitor (VESA DDC) + +PNP0A00 ISA Root Bus +PNP0A01 EISA Root Bus +PNP0A02 MCA Root Bus +PNP0A03 PCI Root Bus +PNP0A04 VESA/VL Bus +PNP0A05 Generic ACPI Root Bus +PNP0A06 Generic ACPI Extended-IO Root Bus (EIO Root Bus) +PNP0A08 PCI Express Root Bus + +PNP0800 AT-style Speaker +PNP0B00 AT Real-Time Clock +PNP0C00 Plug and Play BIOS +PNP0C01 System Board +PNP0C02 Plug and Play Motherboard Register Resource +PNP0C03 Plug and Play BIOS Event Notification Interrupt +PNP0C04 Math Coprocessor +PNP0C05 APM BIOS +PNP0C06 Early Plug and Play BIOS +PNP0C07 Early Plug and Play BIOS +PNP0C08 ACPI System Board +PNP0C09 ACPI Embedded Controller +PNP0C0A ACPI Control Method Battery +PNP0C0B ACPI Fan +PNP0C0C ACPI Power Button +PNP0C0D ACPI Lid Switch +PNP0C0E ACPI Sleep Button +PNP0C0F PCI Interrupt Link +PNP0C10 ACPI System Indicator +PNP0C11 ACPI Thermal Zone +PNP0C12 Device Bay Controller +PNP0C13 Plug and Play BIOS + +PNP0E00 Intel 82365-Compatible PCMCIA Controller +PNP0E01 Cirrus Logic CL-PD6720 PCMCIA Controller +PNP0E02 VLSI VL82C146 PCMCIA Controller +PNP0E03 Intel 82365-compatible CardBus controller + +PNP0F00 Microsoft® Bus Mouse +PNP0F01 Microsoft® Serial Mouse +PNP0F02 Microsoft® InPort Mouse +PNP0F03 Microsoft® PS/2-style Mouse +PNP0F04 Mouse Systems Mouse +PNP0F05 Mouse Systems 3-Button Mouse +PNP0F06 Genius Mouse +PNP0F07 Genius Mouse +PNP0F08 Logitech Serial Mouse +PNP0F09 Microsoft® BallPoint Serial Mouse +PNP0F0A Microsoft® Plug and Play Mouse +PNP0F0B Microsoft® Plug and Play BallPoint Mouse +PNP0F0C Microsoft®-compatible Serial Mouse +PNP0F0D Microsoft®-compatible InPort-compatible Mouse +PNP0F0E Microsoft®-compatible PS/2-style Mouse +PNP0F0F Microsoft®-compatible Serial BallPoint-compatible Mouse +PNP0F10 Texas Instruments QuickPort Mouse +PNP0F11 Microsoft®-compatible Bus Mouse +PNP0F12 Logitech PS/2-style Mouse +PNP0F13 PS/2 Port for PS/2-style Mice +PNP0F14 Microsoft® Kids Mouse +PNP0F15 Logitech bus mouse +PNP0F16 Logitech SWIFT device +PNP0F17 Logitech-compatible serial mouse +PNP0F18 Logitech-compatible bus mouse +PNP0F19 Logitech-compatible PS/2-style Mouse +PNP0F1A Logitech-compatible SWIFT Device +PNP0F1B HP Omnibook Mouse +PNP0F1C Compaq LTE Trackball PS/2-style Mouse +PNP0F1D Compaq LTE Trackball Serial Mouse +PNP0F1E Microsoft® Kids Trackball Mouse +PNP0F1F Reserved by Microsoft® Input Device Group +PNP0F20 Reserved by Microsoft® Input Device Group +PNP0F21 Reserved by Microsoft® Input Device Group +PNP0F22 Reserved by Microsoft® Input Device Group +PNP0F23 Reserved by Microsoft® Input Device Group +PNP0FFF Reserved by Microsoft® Systems
\ No newline at end of file diff --git a/tdecore/ksimpledirwatch.cpp b/tdecore/ksimpledirwatch.cpp new file mode 100644 index 000000000..cad0345ab --- /dev/null +++ b/tdecore/ksimpledirwatch.cpp @@ -0,0 +1,1755 @@ +// -*- c-basic-offset: 2 -*- +/* This file is part of the KDE libraries + Copyright (C) 1998 Sven Radej <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +// KSimpleDirWatch is a basic copy of KDirWatch +// but with the KIO linking requirement removed + +#include <config.h> +#include <errno.h> + +#ifdef HAVE_DNOTIFY +#include <unistd.h> +#include <time.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#endif + + +#include <sys/stat.h> +#include <assert.h> +#include <tqdir.h> +#include <tqfile.h> +#include <tqintdict.h> +#include <tqptrlist.h> +#include <tqsocketnotifier.h> +#include <tqstringlist.h> +#include <tqtimer.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kconfig.h> +#include <kglobal.h> +#include <kstaticdeleter.h> +#include <kde_file.h> + +// debug +#include <sys/ioctl.h> + +#ifdef HAVE_INOTIFY +#include <unistd.h> +#include <fcntl.h> +#include <sys/syscall.h> +#include <linux/types.h> +// Linux kernel headers are documented to not compile +#define _S390_BITOPS_H +#include <sys/inotify.h> + +#ifndef __NR_inotify_init +#if defined(__i386__) +#define __NR_inotify_init 291 +#define __NR_inotify_add_watch 292 +#define __NR_inotify_rm_watch 293 +#endif +#if defined(__PPC__) +#define __NR_inotify_init 275 +#define __NR_inotify_add_watch 276 +#define __NR_inotify_rm_watch 277 +#endif +#if defined(__x86_64__) +#define __NR_inotify_init 253 +#define __NR_inotify_add_watch 254 +#define __NR_inotify_rm_watch 255 +#endif +#endif + +#ifndef IN_ONLYDIR +#define IN_ONLYDIR 0x01000000 +#endif + +#ifndef IN_DONT_FOLLOW +#define IN_DONT_FOLLOW 0x02000000 +#endif + +#ifndef IN_MOVE_SELF +#define IN_MOVE_SELF 0x00000800 +#endif + +#endif + +#include <sys/utsname.h> + +#include "ksimpledirwatch.h" +#include "ksimpledirwatch_p.h" + +#define NO_NOTIFY (time_t) 0 + +static KSimpleDirWatchPrivate* dwp_self = 0; + +#ifdef HAVE_DNOTIFY + +static int dnotify_signal = 0; + +/* DNOTIFY signal handler + * + * As this is called asynchronously, only a flag is set and + * a rescan is requested. + * This is done by writing into a pipe to trigger a TQSocketNotifier + * watching on this pipe: a timer is started and after a timeout, + * the rescan is done. + */ +void KSimpleDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *) +{ + if (!dwp_self) return; + + // write might change errno, we have to save it and restore it + // (Richard Stevens, Advanced programming in the Unix Environment) + int saved_errno = errno; + + Entry* e = dwp_self->fd_Entry.find(si->si_fd); + +// kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path " +// << TQString(e ? e->path:"unknown") << endl; + + if(e && e->dn_fd == si->si_fd) + e->dirty = true; + + char c = 0; + write(dwp_self->mPipe[1], &c, 1); + errno = saved_errno; +} + +static struct sigaction old_sigio_act; +/* DNOTIFY SIGIO signal handler + * + * When the kernel queue for the dnotify_signal overflows, a SIGIO is send. + */ +void KSimpleDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p) +{ + if (dwp_self) + { + // write might change errno, we have to save it and restore it + // (Richard Stevens, Advanced programming in the Unix Environment) + int saved_errno = errno; + + dwp_self->rescan_all = true; + char c = 0; + write(dwp_self->mPipe[1], &c, 1); + + errno = saved_errno; + } + + // Call previous signal handler + if (old_sigio_act.sa_flags & SA_SIGINFO) + { + if (old_sigio_act.sa_sigaction) + (*old_sigio_act.sa_sigaction)(sig, si, p); + } + else + { + if ((old_sigio_act.sa_handler != SIG_DFL) && + (old_sigio_act.sa_handler != SIG_IGN)) + (*old_sigio_act.sa_handler)(sig); + } +} +#endif + + +// +// Class KSimpleDirWatchPrivate (singleton) +// + +/* All entries (files/directories) to be watched in the + * application (coming from multiple KSimpleDirWatch instances) + * are registered in a single KSimpleDirWatchPrivate instance. + * + * At the moment, the following methods for file watching + * are supported: + * - Polling: All files to be watched are polled regularly + * using stat (more precise: TQFileInfo.lastModified()). + * The polling frequency is determined from global kconfig + * settings, defaulting to 500 ms for local directories + * and 5000 ms for remote mounts + * - FAM (File Alternation Monitor): first used on IRIX, SGI + * has ported this method to LINUX. It uses a kernel part + * (IMON, sending change events to /dev/imon) and a user + * level damon (fam), to which applications connect for + * notification of file changes. For NFS, the fam damon + * on the NFS server machine is used; if IMON is not built + * into the kernel, fam uses polling for local files. + * - DNOTIFY: In late LINUX 2.3.x, directory notification was + * introduced. By opening a directory, you can request for + * UNIX signals to be sent to the process when a directory + * is changed. + * - INOTIFY: In LINUX 2.6.13, inode change notification was + * introduced. You're now able to watch arbitrary inode's + * for changes, and even get notification when they're + * unmounted. + */ + +KSimpleDirWatchPrivate::KSimpleDirWatchPrivate() + : rescan_timer(0, "KSimpleDirWatchPrivate::rescan_timer") +{ + timer = new TQTimer(this, "KSimpleDirWatchPrivate::timer"); + connect (timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotRescan())); + freq = 3600000; // 1 hour as upper bound + statEntries = 0; + delayRemove = false; + m_ref = 0; + + KConfigGroup config(KGlobal::config(), TQCString("DirWatch")); + m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000); + m_PollInterval = config.readNumEntry("PollInterval", 500); + + TQString available("Stat"); + + // used for FAM and DNOTIFY + rescan_all = false; + connect(&rescan_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotRescan())); + +#ifdef HAVE_FAM + // It's possible that FAM server can't be started + if (FAMOpen(&fc) ==0) { + available += ", FAM"; + use_fam=true; + sn = new TQSocketNotifier( FAMCONNECTION_GETFD(&fc), + TQSocketNotifier::Read, this); + connect( sn, TQT_SIGNAL(activated(int)), + this, TQT_SLOT(famEventReceived()) ); + } + else { + kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl; + use_fam=false; + } +#endif + +#ifdef HAVE_INOTIFY + supports_inotify = true; + + m_inotify_fd = inotify_init(); + + if ( m_inotify_fd <= 0 ) { + kdDebug(7001) << "Can't use Inotify, kernel doesn't support it" << endl; + supports_inotify = false; + } + + { + struct utsname uts; + int major, minor, patch; + if (uname(&uts) < 0) + supports_inotify = false; // *shrug* + else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) + supports_inotify = false; // *shrug* + else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14 + kdDebug(7001) << "Can't use INotify, Linux kernel too old" << endl; + supports_inotify = false; + } + } + + if ( supports_inotify ) { + available += ", Inotify"; + fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC); + + mSn = new TQSocketNotifier( m_inotify_fd, TQSocketNotifier::Read, this ); + connect( mSn, TQT_SIGNAL(activated( int )), this, TQT_SLOT( slotActivated() ) ); + } +#endif + +#ifdef HAVE_DNOTIFY + + // if we have inotify, disable dnotify. +#ifdef HAVE_INOTIFY + supports_dnotify = !supports_inotify; +#else + // otherwise, not guilty until proven guilty. + supports_dnotify = true; +#endif + + struct utsname uts; + int major, minor, patch; + if (uname(&uts) < 0) + supports_dnotify = false; // *shrug* + else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) + supports_dnotify = false; // *shrug* + else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19 + kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl; + supports_dnotify = false; + } + + if( supports_dnotify ) { + available += ", DNotify"; + + pipe(mPipe); + fcntl(mPipe[0], F_SETFD, FD_CLOEXEC); + fcntl(mPipe[1], F_SETFD, FD_CLOEXEC); + fcntl(mPipe[0], F_SETFL, O_NONBLOCK | fcntl(mPipe[0], F_GETFL)); + fcntl(mPipe[1], F_SETFL, O_NONBLOCK | fcntl(mPipe[1], F_GETFL)); + mSn = new TQSocketNotifier( mPipe[0], TQSocketNotifier::Read, this); + connect(mSn, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotActivated())); + // Install the signal handler only once + if ( dnotify_signal == 0 ) + { + dnotify_signal = SIGRTMIN + 8; + + struct sigaction act; + act.sa_sigaction = KSimpleDirWatchPrivate::dnotify_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; +#ifdef SA_RESTART + act.sa_flags |= SA_RESTART; +#endif + sigaction(dnotify_signal, &act, NULL); + + act.sa_sigaction = KSimpleDirWatchPrivate::dnotify_sigio_handler; + sigaction(SIGIO, &act, &old_sigio_act); + } + } + else + { + mPipe[0] = -1; + mPipe[1] = -1; + } +#endif + + kdDebug(7001) << "Available methods: " << available << endl; +} + +/* This is called on app exit (KStaticDeleter) */ +KSimpleDirWatchPrivate::~KSimpleDirWatchPrivate() +{ + timer->stop(); + + /* remove all entries being watched */ + removeEntries(0); + +#ifdef HAVE_FAM + if (use_fam) { + FAMClose(&fc); + kdDebug(7001) << "KSimpleDirWatch deleted (FAM closed)" << endl; + } +#endif +#ifdef HAVE_INOTIFY + if ( supports_inotify ) + ::close( m_inotify_fd ); +#endif +#ifdef HAVE_DNOTIFY + close(mPipe[0]); + close(mPipe[1]); +#endif +} + +#include <stdlib.h> + +void KSimpleDirWatchPrivate::slotActivated() +{ +#ifdef HAVE_DNOTIFY + if ( supports_dnotify ) + { + char dummy_buf[4096]; + read(mPipe[0], &dummy_buf, 4096); + + if (!rescan_timer.isActive()) + rescan_timer.start(m_PollInterval, true /* singleshot */); + + return; + } +#endif + +#ifdef HAVE_INOTIFY + if ( !supports_inotify ) + return; + + int pending = -1; + int offset = 0; + char buf[4096]; + assert( m_inotify_fd > -1 ); + ioctl( m_inotify_fd, FIONREAD, &pending ); + + while ( pending > 0 ) { + + if ( pending > (int)sizeof( buf ) ) + pending = sizeof( buf ); + + pending = read( m_inotify_fd, buf, pending); + + while ( pending > 0 ) { + struct inotify_event *event = (struct inotify_event *) &buf[offset]; + pending -= sizeof( struct inotify_event ) + event->len; + offset += sizeof( struct inotify_event ) + event->len; + + TQString path; + if ( event->len ) + path = TQFile::decodeName( TQCString( event->name, event->len ) ); + + if ( path.length() && isNoisyFile( path.latin1() ) ) + continue; + + kdDebug(7001) << "ev wd: " << event->wd << " mask " << event->mask << " path: " << path << endl; + + // now we're in deep trouble of finding the + // associated entries + // for now, we suck and iterate + for ( EntryMap::Iterator it = m_mapEntries.begin(); + it != m_mapEntries.end(); ++it ) { + Entry* e = &( *it ); + if ( e->wd == event->wd ) { + e->dirty = true; + + if ( 1 || e->isDir) { + if( event->mask & IN_DELETE_SELF) { + kdDebug(7001) << "-->got deleteself signal for " << e->path << endl; + e->m_status = NonExistent; + if (e->isDir) + addEntry(0, TQDir::cleanDirPath(e->path+"/.."), e, true); + else + addEntry(0, TQFileInfo(e->path).dirPath(true), e, true); + } + if ( event->mask & IN_IGNORED ) { + e->wd = 0; + } + if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) { + Entry *sub_entry = e->m_entries.first(); + for(;sub_entry; sub_entry = e->m_entries.next()) + if (sub_entry->path == e->path + "/" + path) break; + + if (sub_entry /*&& sub_entry->isDir*/) { + removeEntry(0,e->path, sub_entry); + KDE_struct_stat stat_buf; + TQCString tpath = TQFile::encodeName(path); + KDE_stat(tpath, &stat_buf); + + //sub_entry->isDir = S_ISDIR(stat_buf.st_mode); + //sub_entry->m_ctime = stat_buf.st_ctime; + //sub_entry->m_status = Normal; + //sub_entry->m_nlink = stat_buf.st_nlink; + + if(!useINotify(sub_entry)) + useStat(sub_entry); + sub_entry->dirty = true; + } + } + } + + if (!rescan_timer.isActive()) + rescan_timer.start(m_PollInterval, true /* singleshot */); + + break; // there really should be only one matching wd + } + } + + } + } +#endif +} + +/* In DNOTIFY/FAM mode, only entries which are marked dirty are scanned. + * We first need to mark all yet nonexistent, but possible created + * entries as dirty... + */ +void KSimpleDirWatchPrivate::Entry::propagate_dirty() +{ + for (TQPtrListIterator<Entry> sub_entry (m_entries); + sub_entry.current(); ++sub_entry) + { + if (!sub_entry.current()->dirty) + { + sub_entry.current()->dirty = true; + sub_entry.current()->propagate_dirty(); + } + } +} + + +/* A KSimpleDirWatch instance is interested in getting events for + * this file/Dir entry. + */ +void KSimpleDirWatchPrivate::Entry::addClient(KSimpleDirWatch* instance) +{ + Client* client = m_clients.first(); + for(;client; client = m_clients.next()) + if (client->instance == instance) break; + + if (client) { + client->count++; + return; + } + + client = new Client; + client->instance = instance; + client->count = 1; + client->watchingStopped = instance->isStopped(); + client->pending = NoChange; + + m_clients.append(client); +} + +void KSimpleDirWatchPrivate::Entry::removeClient(KSimpleDirWatch* instance) +{ + Client* client = m_clients.first(); + for(;client; client = m_clients.next()) + if (client->instance == instance) break; + + if (client) { + client->count--; + if (client->count == 0) { + m_clients.removeRef(client); + delete client; + } + } +} + +/* get number of clients */ +int KSimpleDirWatchPrivate::Entry::clients() +{ + int clients = 0; + Client* client = m_clients.first(); + for(;client; client = m_clients.next()) + clients += client->count; + + return clients; +} + + +KSimpleDirWatchPrivate::Entry* KSimpleDirWatchPrivate::entry(const TQString& _path) +{ +// we only support absolute paths + if (TQDir::isRelativePath(_path)) { + return 0; + } + + TQString path = _path; + + if ( path.length() > 1 && path.right(1) == "/" ) + path.truncate( path.length() - 1 ); + + EntryMap::Iterator it = m_mapEntries.find( path ); + if ( it == m_mapEntries.end() ) + return 0; + else + return &(*it); +} + +// set polling frequency for a entry and adjust global freq if needed +void KSimpleDirWatchPrivate::useFreq(Entry* e, int newFreq) +{ + e->freq = newFreq; + + // a reasonable frequency for the global polling timer + if (e->freq < freq) { + freq = e->freq; + if (timer->isActive()) timer->changeInterval(freq); + kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl; + } +} + + +#ifdef HAVE_FAM +// setup FAM notification, returns false if not possible +bool KSimpleDirWatchPrivate::useFAM(Entry* e) +{ + if (!use_fam) return false; + + // handle FAM events to avoid deadlock + // (FAM sends back all files in a directory when monitoring) + famEventReceived(); + + e->m_mode = FAMMode; + e->dirty = false; + + if (e->isDir) { + if (e->m_status == NonExistent) { + // If the directory does not exist we watch the parent directory + addEntry(0, TQDir::cleanDirPath(e->path+"/.."), e, true); + } + else { + int res =FAMMonitorDirectory(&fc, TQFile::encodeName(e->path), + &(e->fr), e); + if (res<0) { + e->m_mode = UnknownMode; + use_fam=false; + return false; + } + kdDebug(7001) << " Setup FAM (Req " + << FAMREQUEST_GETREQNUM(&(e->fr)) + << ") for " << e->path << endl; + } + } + else { + if (e->m_status == NonExistent) { + // If the file does not exist we watch the directory + addEntry(0, TQFileInfo(e->path).dirPath(true), e, true); + } + else { + int res = FAMMonitorFile(&fc, TQFile::encodeName(e->path), + &(e->fr), e); + if (res<0) { + e->m_mode = UnknownMode; + use_fam=false; + return false; + } + + kdDebug(7001) << " Setup FAM (Req " + << FAMREQUEST_GETREQNUM(&(e->fr)) + << ") for " << e->path << endl; + } + } + + // handle FAM events to avoid deadlock + // (FAM sends back all files in a directory when monitoring) + famEventReceived(); + + return true; +} +#endif + + +#ifdef HAVE_DNOTIFY +// setup DNotify notification, returns false if not possible +bool KSimpleDirWatchPrivate::useDNotify(Entry* e) +{ + e->dn_fd = 0; + e->dirty = false; + if (!supports_dnotify) return false; + + e->m_mode = DNotifyMode; + + if (e->isDir) { + if (e->m_status == Normal) { + int fd = KDE_open(TQFile::encodeName(e->path).data(), O_RDONLY); + // Migrate fd to somewhere above 128. Some libraries have + // constructs like: + // fd = socket(...) + // if (fd > ARBITRARY_LIMIT) + // return error; + // + // Since programs might end up using a lot of KSimpleDirWatch objects + // for a rather long time the above braindamage could get + // triggered. + // + // By moving the ksimpledirwatch fd's to > 128, calls like socket() will keep + // returning fd's < ARBITRARY_LIMIT for a bit longer. + int fd2 = fcntl(fd, F_DUPFD, 128); + if (fd2 >= 0) + { + close(fd); + fd = fd2; + } + if (fd<0) { + e->m_mode = UnknownMode; + return false; + } + + int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT; + // if dependant is a file watch, we check for MODIFY & ATTRIB too + for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) + if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; } + + if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 || + fcntl(fd, F_NOTIFY, mask) < 0) { + + kdDebug(7001) << "Not using Linux Directory Notifications." + << endl; + supports_dnotify = false; + ::close(fd); + e->m_mode = UnknownMode; + return false; + } + + fd_Entry.replace(fd, e); + e->dn_fd = fd; + + kdDebug(7001) << " Setup DNotify (fd " << fd + << ") for " << e->path << endl; + } + else { // NotExisting + addEntry(0, TQDir::cleanDirPath(e->path+"/.."), e, true); + } + } + else { // File + // we always watch the directory (DNOTIFY can't watch files alone) + // this notifies us about changes of files therein + addEntry(0, TQFileInfo(e->path).dirPath(true), e, true); + } + + return true; +} +#endif + +#ifdef HAVE_INOTIFY +// setup INotify notification, returns false if not possible +bool KSimpleDirWatchPrivate::useINotify( Entry* e ) +{ + e->wd = 0; + e->dirty = false; + if (!supports_inotify) return false; + + e->m_mode = INotifyMode; + + int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW; + if(!e->isDir) + mask |= IN_MODIFY|IN_ATTRIB; + else + mask |= IN_ONLYDIR; + + // if dependant is a file watch, we check for MODIFY & ATTRIB too + for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) { + if (!dep->isDir) { mask |= IN_MODIFY|IN_ATTRIB; break; } + } + + if ( ( e->wd = inotify_add_watch( m_inotify_fd, + TQFile::encodeName( e->path ), mask) ) > 0 ) + return true; + + if ( e->m_status == NonExistent ) { + if (e->isDir) + addEntry(0, TQDir::cleanDirPath(e->path+"/.."), e, true); + else + addEntry(0, TQFileInfo(e->path).dirPath(true), e, true); + return true; + } + + return false; +} +#endif + +bool KSimpleDirWatchPrivate::useStat(Entry* e) +{ + useFreq(e, m_PollInterval); + + if (e->m_mode != StatMode) { + e->m_mode = StatMode; + statEntries++; + + if ( statEntries == 1 ) { + // if this was first STAT entry (=timer was stopped) + timer->start(freq); // then start the timer + kdDebug(7001) << " Started Polling Timer, freq " << freq << endl; + } + } + + kdDebug(7001) << " Setup Stat (freq " << e->freq + << ") for " << e->path << endl; + + return true; +} + + +/* If <instance> !=0, this KSimpleDirWatch instance wants to watch at <_path>, + * providing in <isDir> the type of the entry to be watched. + * Sometimes, entries are dependant on each other: if <sub_entry> !=0, + * this entry needs another entry to watch himself (when notExistent). + */ +void KSimpleDirWatchPrivate::addEntry(KSimpleDirWatch* instance, const TQString& _path, + Entry* sub_entry, bool isDir) +{ + TQString path = _path; + if (path.startsWith("/dev/") || (path == "/dev")) + return; // Don't even go there. + + if ( path.length() > 1 && path.right(1) == "/" ) + path.truncate( path.length() - 1 ); + + EntryMap::Iterator it = m_mapEntries.find( path ); + if ( it != m_mapEntries.end() ) + { + if (sub_entry) { + (*it).m_entries.append(sub_entry); + kdDebug(7001) << "Added already watched Entry " << path + << " (for " << sub_entry->path << ")" << endl; + +#ifdef HAVE_DNOTIFY + { + Entry* e = &(*it); + if( (e->m_mode == DNotifyMode) && (e->dn_fd > 0) ) { + int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT; + // if dependant is a file watch, we check for MODIFY & ATTRIB too + for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) + if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; } + if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen + ::close(e->dn_fd); + e->m_mode = UnknownMode; + fd_Entry.remove(e->dn_fd); + e->dn_fd = 0; + useStat( e ); + } + } + } +#endif + +#ifdef HAVE_INOTIFY + { + Entry* e = &(*it); + if( (e->m_mode == INotifyMode) && (e->wd > 0) ) { + int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW; + if(!e->isDir) + mask |= IN_MODIFY|IN_ATTRIB; + else + mask |= IN_ONLYDIR; + + inotify_rm_watch (m_inotify_fd, e->wd); + e->wd = inotify_add_watch( m_inotify_fd, TQFile::encodeName( e->path ), mask); + } + } +#endif + + } + else { + (*it).addClient(instance); + kdDebug(7001) << "Added already watched Entry " << path + << " (now " << (*it).clients() << " clients)" + << TQString(TQString(" [%1]").arg(instance->name())) << endl; + } + return; + } + + // we have a new path to watch + + KDE_struct_stat stat_buf; + TQCString tpath = TQFile::encodeName(path); + bool exists = (KDE_stat(tpath, &stat_buf) == 0); + + Entry newEntry; + m_mapEntries.insert( path, newEntry ); + // the insert does a copy, so we have to use <e> now + Entry* e = &(m_mapEntries[path]); + + if (exists) { + e->isDir = S_ISDIR(stat_buf.st_mode); + + if (e->isDir && !isDir) + kdWarning() << "KSimpleDirWatch: " << path << " is a directory. Use addDir!" << endl; + else if (!e->isDir && isDir) + kdWarning() << "KSimpleDirWatch: " << path << " is a file. Use addFile!" << endl; + + e->m_ctime = stat_buf.st_ctime; + e->m_status = Normal; + e->m_nlink = stat_buf.st_nlink; + } + else { + e->isDir = isDir; + e->m_ctime = invalid_ctime; + e->m_status = NonExistent; + e->m_nlink = 0; + } + + e->path = path; + if (sub_entry) + e->m_entries.append(sub_entry); + else + e->addClient(instance); + + kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path + << (e->m_status == NonExistent ? " NotExisting" : "") + << (sub_entry ? TQString(TQString(" for %1").arg(sub_entry->path)) : TQString("")) + << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString("")) + << endl; + + + // now setup the notification method + e->m_mode = UnknownMode; + e->msecLeft = 0; + + if ( isNoisyFile( tpath ) ) + return; + +#ifdef HAVE_FAM + if (useFAM(e)) return; +#endif + +#ifdef HAVE_INOTIFY + if (useINotify(e)) return; +#endif + +#ifdef HAVE_DNOTIFY + if (useDNotify(e)) return; +#endif + + useStat(e); +} + + +void KSimpleDirWatchPrivate::removeEntry( KSimpleDirWatch* instance, + const TQString& _path, Entry* sub_entry ) +{ + kdDebug(7001) << "KSimpleDirWatchPrivate::removeEntry for '" << _path << "' sub_entry: " << sub_entry << endl; + Entry* e = entry(_path); + if (!e) { + kdDebug(7001) << "KSimpleDirWatchPrivate::removeEntry can't handle '" << _path << "'" << endl; + return; + } + + if (sub_entry) + e->m_entries.removeRef(sub_entry); + else + e->removeClient(instance); + + if (e->m_clients.count() || e->m_entries.count()) { + kdDebug(7001) << "removeEntry: unwatched " << e->path << " " << _path << endl; + return; + } + + if (delayRemove) { + // removeList is allowed to contain any entry at most once + if (removeList.findRef(e)==-1) + removeList.append(e); + // now e->isValid() is false + return; + } + +#ifdef HAVE_FAM + if (e->m_mode == FAMMode) { + if ( e->m_status == Normal) { + FAMCancelMonitor(&fc, &(e->fr) ); + kdDebug(7001) << "Cancelled FAM (Req " + << FAMREQUEST_GETREQNUM(&(e->fr)) + << ") for " << e->path << endl; + } + else { + if (e->isDir) + removeEntry(0, TQDir::cleanDirPath(e->path+"/.."), e); + else + removeEntry(0, TQFileInfo(e->path).dirPath(true), e); + } + } +#endif + +#ifdef HAVE_INOTIFY + kdDebug(7001) << "inotify remove " << ( e->m_mode == INotifyMode ) << " " << ( e->m_status == Normal ) << endl; + if (e->m_mode == INotifyMode) { + if ( e->m_status == Normal ) { + (void) inotify_rm_watch( m_inotify_fd, e->wd ); + kdDebug(7001) << "Cancelled INotify (fd " << + m_inotify_fd << ", " << e->wd << + ") for " << e->path << endl; + } + else { + if (e->isDir) + removeEntry(0, TQDir::cleanDirPath(e->path+"/.."), e); + else + removeEntry(0, TQFileInfo(e->path).dirPath(true), e); + } + } +#endif + +#ifdef HAVE_DNOTIFY + if (e->m_mode == DNotifyMode) { + if (!e->isDir) { + removeEntry(0, TQFileInfo(e->path).dirPath(true), e); + } + else { // isDir + // must close the FD. + if ( e->m_status == Normal) { + if (e->dn_fd) { + ::close(e->dn_fd); + fd_Entry.remove(e->dn_fd); + + kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd + << ") for " << e->path << endl; + e->dn_fd = 0; + + } + } + else { + removeEntry(0, TQDir::cleanDirPath(e->path+"/.."), e); + } + } + } +#endif + + if (e->m_mode == StatMode) { + statEntries--; + if ( statEntries == 0 ) { + timer->stop(); // stop timer if lists are empty + kdDebug(7001) << " Stopped Polling Timer" << endl; + } + } + + kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path + << (sub_entry ? TQString(TQString(" for %1").arg(sub_entry->path)) : TQString("")) + << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString("")) + << endl; + m_mapEntries.remove( e->path ); // <e> not valid any more +} + + +/* Called from KSimpleDirWatch destructor: + * remove <instance> as client from all entries + */ +void KSimpleDirWatchPrivate::removeEntries( KSimpleDirWatch* instance ) +{ + TQPtrList<Entry> list; + int minfreq = 3600000; + + // put all entries where instance is a client in list + EntryMap::Iterator it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) { + Client* c = (*it).m_clients.first(); + for(;c;c=(*it).m_clients.next()) + if (c->instance == instance) break; + if (c) { + c->count = 1; // forces deletion of instance as client + list.append(&(*it)); + } + else if ( (*it).m_mode == StatMode && (*it).freq < minfreq ) + minfreq = (*it).freq; + } + + for(Entry* e=list.first();e;e=list.next()) + removeEntry(instance, e->path, 0); + + if (minfreq > freq) { + // we can decrease the global polling frequency + freq = minfreq; + if (timer->isActive()) timer->changeInterval(freq); + kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl; + } +} + +// instance ==0: stop scanning for all instances +bool KSimpleDirWatchPrivate::stopEntryScan( KSimpleDirWatch* instance, Entry* e) +{ + int stillWatching = 0; + Client* c = e->m_clients.first(); + for(;c;c=e->m_clients.next()) { + if (!instance || instance == c->instance) + c->watchingStopped = true; + else if (!c->watchingStopped) + stillWatching += c->count; + } + + kdDebug(7001) << instance->name() << " stopped scanning " << e->path + << " (now " << stillWatching << " watchers)" << endl; + + if (stillWatching == 0) { + // if nobody is interested, we don't watch + e->m_ctime = invalid_ctime; // invalid + e->m_status = NonExistent; + // e->m_status = Normal; + } + return true; +} + +// instance ==0: start scanning for all instances +bool KSimpleDirWatchPrivate::restartEntryScan( KSimpleDirWatch* instance, Entry* e, + bool notify) +{ + int wasWatching = 0, newWatching = 0; + Client* c = e->m_clients.first(); + for(;c;c=e->m_clients.next()) { + if (!c->watchingStopped) + wasWatching += c->count; + else if (!instance || instance == c->instance) { + c->watchingStopped = false; + newWatching += c->count; + } + } + if (newWatching == 0) + return false; + + kdDebug(7001) << (instance ? instance->name() : "all") << " restarted scanning " << e->path + << " (now " << wasWatching+newWatching << " watchers)" << endl; + + // restart watching and emit pending events + + int ev = NoChange; + if (wasWatching == 0) { + if (!notify) { + KDE_struct_stat stat_buf; + bool exists = (KDE_stat(TQFile::encodeName(e->path), &stat_buf) == 0); + if (exists) { + e->m_ctime = stat_buf.st_ctime; + e->m_status = Normal; + e->m_nlink = stat_buf.st_nlink; + } + else { + e->m_ctime = invalid_ctime; + e->m_status = NonExistent; + e->m_nlink = 0; + } + } + e->msecLeft = 0; + ev = scanEntry(e); + } + emitEvent(e,ev); + + return true; +} + +// instance ==0: stop scanning for all instances +void KSimpleDirWatchPrivate::stopScan(KSimpleDirWatch* instance) +{ + EntryMap::Iterator it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) + stopEntryScan(instance, &(*it)); +} + + +void KSimpleDirWatchPrivate::startScan(KSimpleDirWatch* instance, + bool notify, bool skippedToo ) +{ + if (!notify) + resetList(instance,skippedToo); + + EntryMap::Iterator it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) + restartEntryScan(instance, &(*it), notify); + + // timer should still be running when in polling mode +} + + +// clear all pending events, also from stopped +void KSimpleDirWatchPrivate::resetList( KSimpleDirWatch* /*instance*/, + bool skippedToo ) +{ + EntryMap::Iterator it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) { + + Client* c = (*it).m_clients.first(); + for(;c;c=(*it).m_clients.next()) + if (!c->watchingStopped || skippedToo) + c->pending = NoChange; + } +} + +// Return event happened on <e> +// +int KSimpleDirWatchPrivate::scanEntry(Entry* e) +{ +#ifdef HAVE_FAM + if (e->m_mode == FAMMode) { + // we know nothing has changed, no need to stat + if(!e->dirty) return NoChange; + e->dirty = false; + } +#endif + + // Shouldn't happen: Ignore "unknown" notification method + if (e->m_mode == UnknownMode) return NoChange; + +#if defined ( HAVE_DNOTIFY ) || defined( HAVE_INOTIFY ) + if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) { + // we know nothing has changed, no need to stat + if(!e->dirty) return NoChange; + kdDebug(7001) << "scanning " << e->path << " " << e->m_status << " " << e->m_ctime << endl; + e->dirty = false; + } +#endif + + if (e->m_mode == StatMode) { + // only scan if timeout on entry timer happens; + // e.g. when using 500msec global timer, a entry + // with freq=5000 is only watched every 10th time + + e->msecLeft -= freq; + if (e->msecLeft>0) return NoChange; + e->msecLeft += e->freq; + } + + KDE_struct_stat stat_buf; + bool exists = (KDE_stat(TQFile::encodeName(e->path), &stat_buf) == 0); + if (exists) { + + if (e->m_status == NonExistent) { + e->m_ctime = stat_buf.st_ctime; + e->m_status = Normal; + e->m_nlink = stat_buf.st_nlink; + return Created; + } + + if ( (e->m_ctime != invalid_ctime) && + ((stat_buf.st_ctime != e->m_ctime) || + (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) { + e->m_ctime = stat_buf.st_ctime; + e->m_nlink = stat_buf.st_nlink; + return Changed; + } + + return NoChange; + } + + // dir/file doesn't exist + + if (e->m_ctime == invalid_ctime && e->m_status == NonExistent) { + e->m_nlink = 0; + e->m_status = NonExistent; + return NoChange; + } + + e->m_ctime = invalid_ctime; + e->m_nlink = 0; + e->m_status = NonExistent; + + return Deleted; +} + +/* Notify all interested KSimpleDirWatch instances about a given event on an entry + * and stored pending events. When watching is stopped, the event is + * added to the pending events. + */ +void KSimpleDirWatchPrivate::emitEvent(Entry* e, int event, const TQString &fileName) +{ + TQString path = e->path; + if (!fileName.isEmpty()) { + if (!TQDir::isRelativePath(fileName)) + path = fileName; + else +#ifdef Q_OS_UNIX + path += "/" + fileName; +#elif defined(Q_WS_WIN) + //current drive is passed instead of / + path += TQDir::currentDirPath().left(2) + "/" + fileName; +#endif + } + + TQPtrListIterator<Client> cit( e->m_clients ); + for ( ; cit.current(); ++cit ) + { + Client* c = cit.current(); + + if (c->instance==0 || c->count==0) continue; + + if (c->watchingStopped) { + // add event to pending... + if (event == Changed) + c->pending |= event; + else if (event == Created || event == Deleted) + c->pending = event; + continue; + } + // not stopped + if (event == NoChange || event == Changed) + event |= c->pending; + c->pending = NoChange; + if (event == NoChange) continue; + + if (event & Deleted) { + c->instance->setDeleted(path); + // emit only Deleted event... + continue; + } + + if (event & Created) { + c->instance->setCreated(path); + // possible emit Change event after creation + } + + if (event & Changed) + c->instance->setDirty(path); + } +} + +// Remove entries which were marked to be removed +void KSimpleDirWatchPrivate::slotRemoveDelayed() +{ + Entry* e; + delayRemove = false; + for(e=removeList.first();e;e=removeList.next()) + removeEntry(0, e->path, 0); + removeList.clear(); +} + +/* Scan all entries to be watched for changes. This is done regularly + * when polling and once after a DNOTIFY signal. This is NOT used by FAM. + */ +void KSimpleDirWatchPrivate::slotRescan() +{ + EntryMap::Iterator it; + + // People can do very long things in the slot connected to dirty(), + // like showing a message box. We don't want to keep polling during + // that time, otherwise the value of 'delayRemove' will be reset. + bool timerRunning = timer->isActive(); + if ( timerRunning ) + timer->stop(); + + // We delay deletions of entries this way. + // removeDir(), when called in slotDirty(), can cause a crash otherwise + delayRemove = true; + +#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY) + TQPtrList<Entry> dList, cList; +#endif + + if (rescan_all) + { + // mark all as dirty + it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) + (*it).dirty = true; + rescan_all = false; + } + else + { + // progate dirty flag to dependant entries (e.g. file watches) + it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) + if (((*it).m_mode == INotifyMode || (*it).m_mode == DNotifyMode) && (*it).dirty ) + (*it).propagate_dirty(); + } + + it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) { + // we don't check invalid entries (i.e. remove delayed) + if (!(*it).isValid()) continue; + + int ev = scanEntry( &(*it) ); + + +#ifdef HAVE_INOTIFY + if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) { + cList.append( &(*it) ); + if (! useINotify( &(*it) )) { + useStat( &(*it) ); + } + } +#endif + +#ifdef HAVE_DNOTIFY + if ((*it).m_mode == DNotifyMode) { + if ((*it).isDir && (ev == Deleted)) { + dList.append( &(*it) ); + + // must close the FD. + if ((*it).dn_fd) { + ::close((*it).dn_fd); + fd_Entry.remove((*it).dn_fd); + (*it).dn_fd = 0; + } + } + + else if ((*it).isDir && (ev == Created)) { + // For created, but yet without DNOTIFYing ... + if ( (*it).dn_fd == 0) { + cList.append( &(*it) ); + if (! useDNotify( &(*it) )) { + // if DNotify setup fails... + useStat( &(*it) ); + } + } + } + } +#endif + + if ( ev != NoChange ) + emitEvent( &(*it), ev); + } + + +#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY) + // Scan parent of deleted directories for new creation + Entry* e; + for(e=dList.first();e;e=dList.next()) + addEntry(0, TQDir::cleanDirPath( e->path+"/.."), e, true); + + // Remove watch of parent of new created directories + for(e=cList.first();e;e=cList.next()) + removeEntry(0, TQDir::cleanDirPath( e->path+"/.."), e); +#endif + + if ( timerRunning ) + timer->start(freq); + + TQTimer::singleShot(0, this, TQT_SLOT(slotRemoveDelayed())); +} + +bool KSimpleDirWatchPrivate::isNoisyFile( const char * filename ) +{ + // $HOME/.X.err grows with debug output, so don't notify change + if ( *filename == '.') { + if (strncmp(filename, ".X.err", 6) == 0) return true; + if (strncmp(filename, ".xsession-errors", 16) == 0) return true; + // fontconfig updates the cache on every KDE app start + // (inclusive kio_thumbnail slaves) + if (strncmp(filename, ".fonts.cache", 12) == 0) return true; + } + + return false; +} + +#ifdef HAVE_FAM +void KSimpleDirWatchPrivate::famEventReceived() +{ + static FAMEvent fe; + + delayRemove = true; + + while(use_fam && FAMPending(&fc)) { + if (FAMNextEvent(&fc, &fe) == -1) { + kdWarning(7001) << "FAM connection problem, switching to polling." + << endl; + use_fam = false; + delete sn; sn = 0; + + // Replace all FAMMode entries with DNotify/Stat + EntryMap::Iterator it; + it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) + if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) { +#ifdef HAVE_INOTIFY + if (useINotify( &(*it) )) continue; +#endif +#ifdef HAVE_DNOTIFY + if (useDNotify( &(*it) )) continue; +#endif + useStat( &(*it) ); + } + } + else + checkFAMEvent(&fe); + } + + TQTimer::singleShot(0, this, TQT_SLOT(slotRemoveDelayed())); +} + +void KSimpleDirWatchPrivate::checkFAMEvent(FAMEvent* fe) +{ + // Don't be too verbose ;-) + if ((fe->code == FAMExists) || + (fe->code == FAMEndExist) || + (fe->code == FAMAcknowledge)) return; + + if ( isNoisyFile( fe->filename ) ) + return; + + Entry* e = 0; + EntryMap::Iterator it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) + if (FAMREQUEST_GETREQNUM(&( (*it).fr )) == + FAMREQUEST_GETREQNUM(&(fe->fr)) ) { + e = &(*it); + break; + } + + // Entry* e = static_cast<Entry*>(fe->userdata); + +#if 0 // #88538 + kdDebug(7001) << "Processing FAM event (" + << ((fe->code == FAMChanged) ? "FAMChanged" : + (fe->code == FAMDeleted) ? "FAMDeleted" : + (fe->code == FAMStartExecuting) ? "FAMStartExecuting" : + (fe->code == FAMStopExecuting) ? "FAMStopExecuting" : + (fe->code == FAMCreated) ? "FAMCreated" : + (fe->code == FAMMoved) ? "FAMMoved" : + (fe->code == FAMAcknowledge) ? "FAMAcknowledge" : + (fe->code == FAMExists) ? "FAMExists" : + (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code") + << ", " << fe->filename + << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) + << ")" << endl; +#endif + + if (!e) { + // this happens e.g. for FAMAcknowledge after deleting a dir... + // kdDebug(7001) << "No entry for FAM event ?!" << endl; + return; + } + + if (e->m_status == NonExistent) { + kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl; + return; + } + + // Delayed handling. This rechecks changes with own stat calls. + e->dirty = true; + if (!rescan_timer.isActive()) + rescan_timer.start(m_PollInterval, true); + + // needed FAM control actions on FAM events + if (e->isDir) + switch (fe->code) + { + case FAMDeleted: + // file absolute: watched dir + if (!TQDir::isRelativePath(fe->filename)) + { + // a watched directory was deleted + + e->m_status = NonExistent; + FAMCancelMonitor(&fc, &(e->fr) ); // needed ? + kdDebug(7001) << "Cancelled FAMReq " + << FAMREQUEST_GETREQNUM(&(e->fr)) + << " for " << e->path << endl; + // Scan parent for a new creation + addEntry(0, TQDir::cleanDirPath( e->path+"/.."), e, true); + } + break; + + case FAMCreated: { + // check for creation of a directory we have to watch + Entry *sub_entry = e->m_entries.first(); + for(;sub_entry; sub_entry = e->m_entries.next()) + if (sub_entry->path == e->path + "/" + fe->filename) break; + if (sub_entry && sub_entry->isDir) { + TQString path = e->path; + removeEntry(0,e->path,sub_entry); // <e> can be invalid here!! + sub_entry->m_status = Normal; + if (!useFAM(sub_entry)) +#ifdef HAVE_INOTIFY + if (!useINotify(sub_entry )) +#endif + useStat(sub_entry); + } + break; + } + + default: + break; + } +} +#else +void KSimpleDirWatchPrivate::famEventReceived() {} +#endif + + +void KSimpleDirWatchPrivate::statistics() +{ + EntryMap::Iterator it; + + kdDebug(7001) << "Entries watched:" << endl; + if (m_mapEntries.count()==0) { + kdDebug(7001) << " None." << endl; + } + else { + it = m_mapEntries.begin(); + for( ; it != m_mapEntries.end(); ++it ) { + Entry* e = &(*it); + kdDebug(7001) << " " << e->path << " (" + << ((e->m_status==Normal)?"":"Nonexistent ") + << (e->isDir ? "Dir":"File") << ", using " + << ((e->m_mode == FAMMode) ? "FAM" : + (e->m_mode == INotifyMode) ? "INotify" : + (e->m_mode == DNotifyMode) ? "DNotify" : + (e->m_mode == StatMode) ? "Stat" : "Unknown Method") + << ")" << endl; + + Client* c = e->m_clients.first(); + for(;c; c = e->m_clients.next()) { + TQString pending; + if (c->watchingStopped) { + if (c->pending & Deleted) pending += "deleted "; + if (c->pending & Created) pending += "created "; + if (c->pending & Changed) pending += "changed "; + if (!pending.isEmpty()) pending = " (pending: " + pending + ")"; + pending = ", stopped" + pending; + } + kdDebug(7001) << " by " << c->instance->name() + << " (" << c->count << " times)" + << pending << endl; + } + if (e->m_entries.count()>0) { + kdDebug(7001) << " dependent entries:" << endl; + Entry* d = e->m_entries.first(); + for(;d; d = e->m_entries.next()) { + kdDebug(7001) << " " << d << endl; + kdDebug(7001) << " " << d->path << " (" << d << ") " << endl; + } + } + } + } +} + + +// +// Class KSimpleDirWatch +// + +static KStaticDeleter<KSimpleDirWatch> sd_dw; +KSimpleDirWatch* KSimpleDirWatch::s_pSelf = 0L; + +KSimpleDirWatch* KSimpleDirWatch::self() +{ + if ( !s_pSelf ) { + sd_dw.setObject( s_pSelf, new KSimpleDirWatch ); + } + + return s_pSelf; +} + +bool KSimpleDirWatch::exists() +{ + return s_pSelf != 0; +} + +KSimpleDirWatch::KSimpleDirWatch (TQObject* parent, const char* name) + : TQObject(parent,name) +{ + if (!name) { + static int nameCounter = 0; + + nameCounter++; + setName(TQString(TQString("KSimpleDirWatch-%1").arg(nameCounter)).ascii()); + } + + if (!dwp_self) + dwp_self = new KSimpleDirWatchPrivate; + d = dwp_self; + d->ref(); + + _isStopped = false; +} + +KSimpleDirWatch::~KSimpleDirWatch() +{ + d->removeEntries(this); + if ( d->deref() ) + { + // delete it if it's the last one + delete d; + dwp_self = 0L; + } +} + + +// TODO: add watchFiles/recursive support +void KSimpleDirWatch::addDir( const TQString& _path, + bool watchFiles, bool recursive) +{ + if (watchFiles || recursive) { + kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in KDE 3.x" << endl; + } + if (d) d->addEntry(this, _path, 0, true); +} + +void KSimpleDirWatch::addFile( const TQString& _path ) +{ + if (d) d->addEntry(this, _path, 0, false); +} + +TQDateTime KSimpleDirWatch::ctime( const TQString &_path ) +{ + KSimpleDirWatchPrivate::Entry* e = d->entry(_path); + + if (!e) + return TQDateTime(); + + TQDateTime result; + result.setTime_t(e->m_ctime); + return result; +} + +void KSimpleDirWatch::removeDir( const TQString& _path ) +{ + if (d) d->removeEntry(this, _path, 0); +} + +void KSimpleDirWatch::removeFile( const TQString& _path ) +{ + if (d) d->removeEntry(this, _path, 0); +} + +bool KSimpleDirWatch::stopDirScan( const TQString& _path ) +{ + if (d) { + KSimpleDirWatchPrivate::Entry *e = d->entry(_path); + if (e && e->isDir) return d->stopEntryScan(this, e); + } + return false; +} + +bool KSimpleDirWatch::restartDirScan( const TQString& _path ) +{ + if (d) { + KSimpleDirWatchPrivate::Entry *e = d->entry(_path); + if (e && e->isDir) + // restart without notifying pending events + return d->restartEntryScan(this, e, false); + } + return false; +} + +void KSimpleDirWatch::stopScan() +{ + if (d) d->stopScan(this); + _isStopped = true; +} + +void KSimpleDirWatch::startScan( bool notify, bool skippedToo ) +{ + _isStopped = false; + if (d) d->startScan(this, notify, skippedToo); +} + + +bool KSimpleDirWatch::contains( const TQString& _path ) const +{ + KSimpleDirWatchPrivate::Entry* e = d->entry(_path); + if (!e) + return false; + + KSimpleDirWatchPrivate::Client* c = e->m_clients.first(); + for(;c;c=e->m_clients.next()) + if (c->instance == this) return true; + + return false; +} + +void KSimpleDirWatch::statistics() +{ + if (!dwp_self) { + kdDebug(7001) << "KSimpleDirWatch not used" << endl; + return; + } + dwp_self->statistics(); +} + + +void KSimpleDirWatch::setCreated( const TQString & _file ) +{ + kdDebug(7001) << name() << " emitting created " << _file << endl; + emit created( _file ); +} + +void KSimpleDirWatch::setDirty( const TQString & _file ) +{ + kdDebug(7001) << name() << " emitting dirty " << _file << endl; + emit dirty( _file ); +} + +void KSimpleDirWatch::setDeleted( const TQString & _file ) +{ + kdDebug(7001) << name() << " emitting deleted " << _file << endl; + emit deleted( _file ); +} + +KSimpleDirWatch::Method KSimpleDirWatch::internalMethod() +{ +#ifdef HAVE_FAM + if (d->use_fam) + return KSimpleDirWatch::FAM; +#endif +#ifdef HAVE_INOTIFY + if (d->supports_inotify) + return KSimpleDirWatch::INotify; +#endif +#ifdef HAVE_DNOTIFY + if (d->supports_dnotify) + return KSimpleDirWatch::DNotify; +#endif + return KSimpleDirWatch::Stat; +} + + +#include "ksimpledirwatch.moc" +#include "ksimpledirwatch_p.moc" + +//sven + +// vim: sw=2 ts=8 et diff --git a/tdecore/ksimpledirwatch.h b/tdecore/ksimpledirwatch.h new file mode 100644 index 000000000..36ceafba6 --- /dev/null +++ b/tdecore/ksimpledirwatch.h @@ -0,0 +1,293 @@ +/* This file is part of the KDE libraries + Copyright (C) 1998 Sven Radej <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef _KSIMPLEDIRWATCH_H +#define _KSIMPLEDIRWATCH_H + +#include <tqtimer.h> +#include <tqdatetime.h> +#include <tqmap.h> + +#include <tdelibs_export.h> + +#define kdirwatch KSimpleDirWatch::self() + +class KSimpleDirWatchPrivate; + + /** + * KSimpleDirWatch is a basic copy of KDirWatch + * but with the KIO linking requirement removed + * + * Watch directories and files for changes. + * The watched directories or files don't have to exist yet. + * + * When a watched directory is changed, i.e. when files therein are + * created or deleted, KSimpleDirWatch will emit the signal dirty(). + * + * When a watched, but previously not existing directory gets created, + * KSimpleDirWatch will emit the signal created(). + * + * When a watched directory gets deleted, KSimpleDirWatch will emit the + * signal deleted(). The directory is still watched for new + * creation. + * + * When a watched file is changed, i.e. attributes changed or written + * to, KSimpleDirWatch will emit the signal dirty(). + * + * Scanning of particular directories or files can be stopped temporarily + * and restarted. The whole class can be stopped and restarted. + * Directories and files can be added/removed from the list in any state. + * + * The implementation uses the FAM service when available; + * if FAM is not available, the DNOTIFY functionality is used on LINUX. + * As a last resort, a regular polling for change of modification times + * is done; the polling interval is a global config option: + * DirWatch/PollInterval and DirWatch/NFSPollInterval for NFS mounted + * directories. + * + * @see self() + * @short Class for watching directory and file changes. + * @author Sven Radej <[email protected]> + */ +class KIO_EXPORT KSimpleDirWatch : public TQObject +{ + Q_OBJECT + + public: + /** + * Constructor. + * + * Scanning begins immediately when a dir/file watch + * is added. + * @param parent the parent of the TQObject (or 0 for parent-less KDataTools) + * @param name the name of the TQObject, can be 0 + */ + KSimpleDirWatch (TQObject* parent = 0, const char* name = 0); + + /** + * Destructor. + * + * Stops scanning and cleans up. + */ + ~KSimpleDirWatch(); + + /** + * Adds a directory to be watched. + * + * The directory does not have to exist. When @p watchFiles is + * false (the default), the signals dirty(), created(), deleted() + * can be emitted, all for the watched directory. + * When @p watchFiles is true, all files in the watched directory + * are watched for changes, too. Thus, the signals dirty(), + * created(), deleted() can be emitted. + * + * @param path the path to watch + * @param watchFiles if true, the KSimpleDirWatch will also watch files - NOT IMPLEMENTED YET + * @param recursive if true, all sub directories are also watched - NOT IMPLEMENTED YET + */ + void addDir(const TQString& path, + bool watchFiles = false, bool recursive = false); + + /** + * Adds a file to be watched. + * @param file the file to watch + */ + void addFile(const TQString& file); + + /** + * Returns the time the directory/file was last changed. + * @param path the file to check + * @return the date of the last modification + */ + TQDateTime ctime(const TQString& path); + + /** + * Removes a directory from the list of scanned directories. + * + * If specified path is not in the list this does nothing. + * @param path the path of the dir to be removed from the list + */ + void removeDir(const TQString& path); + + /** + * Removes a file from the list of watched files. + * + * If specified path is not in the list this does nothing. + * @param file the file to be removed from the list + */ + void removeFile(const TQString& file); + + /** + * Stops scanning the specified path. + * + * The @p path is not deleted from the interal just, it is just skipped. + * Call this function when you perform an huge operation + * on this directory (copy/move big files or many files). When finished, + * call restartDirScan(path). + * + * @param path the path to skip + * @return true if the @p path is being watched, otherwise false + * @see restartDirScanning() + */ + bool stopDirScan(const TQString& path); + + /** + * Restarts scanning for specified path. + * + * Resets ctime. It doesn't notify + * the change (by emitted a signal), since the ctime value is reset. + * + * Call it when you are finished with big operations on that path, + * @em and when @em you have refreshed that path. + * + * @param path the path to restart scanning + * @return true if the @p path is being watched, otherwise false + * @see stopDirScanning() + */ + bool restartDirScan(const TQString& path); + + /** + * Starts scanning of all dirs in list. + * + * @param notify If true, all changed directories (since + * stopScan() call) will be notified for refresh. If notify is + * false, all ctimes will be reset (except those who are stopped, + * but only if @p skippedToo is false) and changed dirs won't be + * notified. You can start scanning even if the list is + * empty. First call should be called with @p false or else all + * directories + * in list will be notified. + * @param skippedToo if true, the skipped directoris (scanning of which was + * stopped with stopDirScan() ) will be reset and notified + * for change. Otherwise, stopped directories will continue to be + * unnotified. + */ + void startScan( bool notify=false, bool skippedToo=false ); + + /** + * Stops scanning of all directories in internal list. + * + * The timer is stopped, but the list is not cleared. + */ + void stopScan(); + + /** + * Is scanning stopped? + * After creation of a KSimpleDirWatch instance, this is false. + * @return true when scanning stopped + */ + bool isStopped() { return _isStopped; } + + /** + * Check if a directory is being watched by this KSimpleDirWatch instance + * @param path the directory to check + * @return true if the directory is being watched + */ + bool contains( const TQString& path ) const; + + /** + * Dump statistic information about all KSimpleDirWatch instances. + * This checks for consistency, too. + */ + static void statistics(); + + /** + * Emits created(). + * @param path the path of the file or directory + */ + void setCreated( const TQString &path ); + /** + * Emits dirty(). + * @param path the path of the file or directory + */ + void setDirty( const TQString &path ); + /** + * Emits deleted(). + * @param path the path of the file or directory + */ + void setDeleted( const TQString &path ); + + enum Method { FAM, DNotify, Stat, INotify }; + /** + * Returns the preferred internal method to + * watch for changes. + * @since 3.2 + */ + Method internalMethod(); + + /** + * The KSimpleDirWatch instance usually globally used in an application. + * It is automatically deleted when the application exits. + * + * However, you can create an arbitrary number of KSimpleDirWatch instances + * aside from this one - for those you have to take care of memory management. + * + * This function returns an instance of KSimpleDirWatch. If there is none, it + * will be created. + * + * @return a KSimpleDirWatch instance + */ + static KSimpleDirWatch* self(); + /** + * Returns true if there is an instance of KSimpleDirWatch. + * @return true if there is an instance of KSimpleDirWatch. + * @see KSimpleDirWatch::self() + * @since 3.1 + */ + static bool exists(); + + signals: + + /** + * Emitted when a watched object is changed. + * For a directory this signal is emitted when files + * therein are created or deleted. + * For a file this signal is emitted when its size or attributes change. + * + * When you watch a directory, changes in the size or attributes of + * contained files may or may not trigger this signal to be emitted + * depending on which backend is used by KSimpleDirWatch. + * + * The new ctime is set before the signal is emitted. + * @param path the path of the file or directory + */ + void dirty (const TQString &path); + + /** + * Emitted when a file or directory is created. + * @param path the path of the file or directory + */ + void created (const TQString &path ); + + /** + * Emitted when a file or directory is deleted. + * + * The object is still watched for new creation. + * @param path the path of the file or directory + */ + void deleted (const TQString &path ); + + private: + bool _isStopped; + + KSimpleDirWatchPrivate *d; + static KSimpleDirWatch* s_pSelf; +}; + +#endif + +// vim: sw=3 et diff --git a/tdecore/ksimpledirwatch_p.h b/tdecore/ksimpledirwatch_p.h new file mode 100644 index 000000000..a0022f3ae --- /dev/null +++ b/tdecore/ksimpledirwatch_p.h @@ -0,0 +1,161 @@ +/* Private Header for class of KSimpleDirWatchPrivate + * + * this separate header file is needed for TQMOC processing + * because KSimpleDirWatchPrivate has signals and slots + * + * KSimpleDirWatch is a basic copy of KDirWatch + * but with the KIO linking requirement removed + */ + +#ifndef _KSIMPLEDIRWATCH_P_H +#define _KSIMPLEDIRWATCH_P_H + +#ifdef HAVE_FAM +#include <fam.h> +#endif + +#include <ctime> + +#define invalid_ctime ((time_t)-1) + +/* KSimpleDirWatchPrivate is a singleton and does the watching + * for every KSimpleDirWatch instance in the application. + */ +class KSimpleDirWatchPrivate : public TQObject +{ + Q_OBJECT +public: + + enum entryStatus { Normal = 0, NonExistent }; + enum entryMode { UnknownMode = 0, StatMode, DNotifyMode, INotifyMode, FAMMode }; + enum { NoChange=0, Changed=1, Created=2, Deleted=4 }; + + struct Client { + KSimpleDirWatch* instance; + int count; + // did the instance stop watching + bool watchingStopped; + // events blocked when stopped + int pending; + }; + + class Entry + { + public: + // the last observed modification time + time_t m_ctime; + // the last observed link count + int m_nlink; + entryStatus m_status; + entryMode m_mode; + bool isDir; + // instances interested in events + TQPtrList<Client> m_clients; + // nonexistent entries of this directory + TQPtrList<Entry> m_entries; + TQString path; + + int msecLeft, freq; + + void addClient(KSimpleDirWatch*); + void removeClient(KSimpleDirWatch*); + int clients(); + bool isValid() { return m_clients.count() || m_entries.count(); } + + bool dirty; + void propagate_dirty(); + +#ifdef HAVE_FAM + FAMRequest fr; +#endif + +#ifdef HAVE_DNOTIFY + int dn_fd; +#endif +#ifdef HAVE_INOTIFY + int wd; +#endif + }; + + typedef TQMap<TQString,Entry> EntryMap; + + KSimpleDirWatchPrivate(); + ~KSimpleDirWatchPrivate(); + + void resetList (KSimpleDirWatch*,bool); + void useFreq(Entry* e, int newFreq); + void addEntry(KSimpleDirWatch*,const TQString&, Entry*, bool); + void removeEntry(KSimpleDirWatch*,const TQString&, Entry*); + bool stopEntryScan(KSimpleDirWatch*, Entry*); + bool restartEntryScan(KSimpleDirWatch*, Entry*, bool ); + void stopScan(KSimpleDirWatch*); + void startScan(KSimpleDirWatch*, bool, bool); + + void removeEntries(KSimpleDirWatch*); + void statistics(); + + Entry* entry(const TQString&); + int scanEntry(Entry* e); + void emitEvent(Entry* e, int event, const TQString &fileName = TQString::null); + + // Memory management - delete when last KSimpleDirWatch gets deleted + void ref() { m_ref++; } + bool deref() { return ( --m_ref == 0 ); } + + static bool isNoisyFile( const char *filename ); + +public slots: + void slotRescan(); + void famEventReceived(); // for FAM + void slotActivated(); // for DNOTIFY + void slotRemoveDelayed(); + +public: + TQTimer *timer; + EntryMap m_mapEntries; + + int freq; + int statEntries; + int m_nfsPollInterval, m_PollInterval; + int m_ref; + bool useStat(Entry*); + + bool delayRemove; + TQPtrList<Entry> removeList; + + bool rescan_all; + TQTimer rescan_timer; + +#ifdef HAVE_FAM + TQSocketNotifier *sn; + FAMConnection fc; + bool use_fam; + + void checkFAMEvent(FAMEvent*); + bool useFAM(Entry*); +#endif + +#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY) + TQSocketNotifier *mSn; +#endif + +#ifdef HAVE_DNOTIFY + bool supports_dnotify; + int mPipe[2]; + TQIntDict<Entry> fd_Entry; + + static void dnotify_handler(int, siginfo_t *si, void *); + static void dnotify_sigio_handler(int, siginfo_t *si, void *); + bool useDNotify(Entry*); +#endif + +#ifdef HAVE_INOTIFY + bool supports_inotify; + int m_inotify_fd; + + bool useINotify(Entry*); +#endif +}; + +#endif // KSIMPLEDIRWATCH_P_H + diff --git a/tdecore/tdehardwaredevices.cpp b/tdecore/tdehardwaredevices.cpp index ba2fc5bfb..e62abeb37 100644 --- a/tdecore/tdehardwaredevices.cpp +++ b/tdecore/tdehardwaredevices.cpp @@ -27,6 +27,7 @@ #include <klocale.h> #include <kconfig.h> #include <ktempfile.h> +#include <ksimpledirwatch.h> #include <kstandarddirs.h> #include <libudev.h> @@ -37,10 +38,31 @@ #include <sys/ioctl.h> #include <linux/fs.h> +// BEGIN BLOCK +// Copied from include/linux/genhd.h +#define GENHD_FL_REMOVABLE 1 +#define GENHD_FL_MEDIA_CHANGE_NOTIFY 4 +#define GENHD_FL_CD 8 +#define GENHD_FL_UP 16 +#define GENHD_FL_SUPPRESS_PARTITION_INFO 32 +#define GENHD_FL_EXT_DEVT 64 +#define GENHD_FL_NATIVE_CAPACITY 128 +#define GENHD_FL_BLOCK_EVENTS_ON_EXCL_WRITE 256 +// END BLOCK + // NOTE TO DEVELOPERS // This command will greatly help when attempting to find properties to distinguish one device from another // udevadm info --query=all --path=/sys/.... +TDESensorCluster::TDESensorCluster() { + label = TQString::null; + current = -1; + minimum = -1; + maximum = -1; + warning = -1; + critical = -1; +} + TDEGenericDevice::TDEGenericDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn) { m_deviceType = dt; m_deviceName = dn; @@ -128,6 +150,22 @@ void TDEGenericDevice::setModelID(TQString id) { m_modelID.replace("0x", ""); } +TQString &TDEGenericDevice::vendorEncoded() { + return m_vendorenc; +} + +void TDEGenericDevice::setVendorEncoded(TQString id) { + m_vendorenc = id; +} + +TQString &TDEGenericDevice::modelEncoded() { + return m_modelenc; +} + +void TDEGenericDevice::setModelEncoded(TQString id) { + m_modelenc = id; +} + TQString &TDEGenericDevice::subVendorID() { return m_subvendorID; } @@ -223,6 +261,20 @@ TQString TDEGenericDevice::friendlyName() { else if (m_modAlias.lower().startsWith("usb")) { m_friendlyName = KGlobal::hardwareDevices()->findUSBDeviceName(m_vendorID, m_modelID, m_subvendorID, m_submodelID); } + else { + TQString acpigentype = systemPath(); + acpigentype.remove(0, acpigentype.findRev("/")+1); + TQString pnpgentype = acpigentype; + pnpgentype.truncate(pnpgentype.find(":")); + if (pnpgentype.startsWith("PNP")) { + m_friendlyName = KGlobal::hardwareDevices()->findPNPDeviceName(pnpgentype); + } + else if (acpigentype.startsWith("device:")) { + acpigentype.remove(0, acpigentype.findRev(":")+1); + acpigentype.prepend("0x"); + m_friendlyName = i18n("ACPI Node %1").arg(acpigentype.toUInt(0,0)); + } + } } if (m_friendlyName.isNull()) { @@ -231,6 +283,24 @@ TQString TDEGenericDevice::friendlyName() { if (type() == TDEGenericDeviceType::CPU) { m_friendlyName = name(); } + else if (type() == TDEGenericDeviceType::Event) { + // Use parent node name + if (m_parentDevice) { + return m_parentDevice->friendlyName(); + } + else { + m_friendlyName = i18n("Generic Event Device"); + } + } + else if (type() == TDEGenericDeviceType::Input) { + // Use parent node name + if (m_parentDevice) { + return m_parentDevice->friendlyName(); + } + else { + m_friendlyName = i18n("Generic Input Device"); + } + } // Guess by driver else if (!m_deviceDriver.isNull()) { TQString friendlyDriverName = m_deviceDriver.lower(); @@ -345,6 +415,27 @@ void TDEStorageDevice::setSlaveDevices(TQStringList sd) { } TQString TDEStorageDevice::friendlyName() { + // Return the actual storage device name + TQString devicevendorid = vendorEncoded(); + TQString devicemodelid = modelEncoded(); + + devicevendorid.replace("\\x20", " "); + devicemodelid.replace("\\x20", " "); + + devicevendorid = devicevendorid.stripWhiteSpace(); + devicemodelid = devicemodelid.stripWhiteSpace(); + devicevendorid = devicevendorid.simplifyWhiteSpace(); + devicemodelid = devicemodelid.simplifyWhiteSpace(); + + TQString devicename = devicevendorid + " " + devicemodelid; + + devicename = devicename.stripWhiteSpace(); + devicename = devicename.simplifyWhiteSpace(); + + if (devicename != "") { + return devicename; + } + if (isDiskOfType(TDEDiskDeviceType::Floppy)) { return friendlyDeviceType(); } @@ -352,7 +443,7 @@ TQString TDEStorageDevice::friendlyName() { TQString label = diskLabel(); if (label.isNull()) { if (deviceSize() > 0) { - if (checkDiskStatus(TDEDiskDeviceStatus::Removable)) { + if (checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable)) { label = i18n("%1 Removable Device").arg(deviceFriendlySize()); } else { @@ -399,7 +490,7 @@ TQString TDEStorageDevice::friendlyDeviceType() { if (isDiskOfType(TDEDiskDeviceType::HDD)) { ret = i18n("Hard Disk Drive"); - if (checkDiskStatus(TDEDiskDeviceStatus::Removable)) { + if (checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable)) { ret = i18n("Removable Storage"); } if (isDiskOfType(TDEDiskDeviceType::CompactFlash)) { @@ -447,7 +538,7 @@ TQPixmap TDEStorageDevice::icon(KIcon::StdSizes size) { if (isDiskOfType(TDEDiskDeviceType::HDD)) { ret = DesktopIcon("hdd_unmount", size); - if (checkDiskStatus(TDEDiskDeviceStatus::Removable)) { + if (checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable)) { ret = DesktopIcon("usbpendrive_unmount", size); } if (isDiskOfType(TDEDiskDeviceType::CompactFlash)) { @@ -710,10 +801,95 @@ bool TDEStorageDevice::unmountDevice(TQString* errRet, int* retcode) { return false; } +TDECPUDevice::TDECPUDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn) : TDEGenericDevice(dt, dn) { +} + +TDECPUDevice::~TDECPUDevice() { +} + +double &TDECPUDevice::frequency() { + return m_frequency; +} + +void TDECPUDevice::setFrequency(double fr) { + m_frequency = fr; +} + +double &TDECPUDevice::minFrequency() { + return m_minfrequency; +} + +void TDECPUDevice::setMinFrequency(double fr) { + m_minfrequency = fr; +} + +double &TDECPUDevice::maxFrequency() { + return m_maxfrequency; +} + +void TDECPUDevice::setMaxFrequency(double fr) { + m_maxfrequency = fr; +} + +double &TDECPUDevice::transitionLatency() { + return m_transitionlatency; +} + +void TDECPUDevice::setTransitionLatency(double tl) { + m_transitionlatency = tl; +} + +TQString &TDECPUDevice::governor() { + return m_governor; +} + +void TDECPUDevice::setGovernor(TQString gr) { + m_governor = gr; +} + +TQString &TDECPUDevice::scalingDriver() { + return m_scalingdriver; +} + +void TDECPUDevice::setScalingDriver(TQString dr) { + m_scalingdriver = dr; +} + +TQStringList &TDECPUDevice::dependentProcessors() { + return m_tiedprocs; +} + +void TDECPUDevice::setDependentProcessors(TQStringList dp) { + m_tiedprocs = dp; +} + +TQStringList &TDECPUDevice::availableFrequencies() { + return m_frequencies; +} + +void TDECPUDevice::setAvailableFrequencies(TQStringList af) { + m_frequencies = af; +} + +TDESensorDevice::TDESensorDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn) : TDEGenericDevice(dt, dn) { +} + +TDESensorDevice::~TDESensorDevice() { +} + +TDESensorClusterMap TDESensorDevice::values() { + return m_sensorValues; +} + +void TDESensorDevice::setValues(TDESensorClusterMap cl) { + m_sensorValues = cl; +} + TDEHardwareDevices::TDEHardwareDevices() { // Initialize members pci_id_map = 0; usb_id_map = 0; + pnp_id_map = 0; // Set up device list m_deviceList.setAutoDelete( TRUE ); // the list owns the objects @@ -750,12 +926,49 @@ TDEHardwareDevices::TDEHardwareDevices() { m_mountScanNotifier = new TQSocketNotifier(m_procMountsFd, TQSocketNotifier::Exception, this); connect( m_mountScanNotifier, TQT_SIGNAL(activated(int)), this, TQT_SLOT(processModifiedMounts()) ); + // Read in the current cpu information + // Yes, a race condition exists between this and the cpu monitor start below, but it shouldn't be a problem 99.99% of the time + m_cpuInfo.clear(); + TQFile cpufile( "/proc/cpuinfo" ); + if ( cpufile.open( IO_ReadOnly ) ) { + TQTextStream stream( &cpufile ); + while ( !stream.atEnd() ) { + m_cpuInfo.append(stream.readLine()); + } + cpufile.close(); + } + +// [FIXME 0.01] +// Apparently the Linux kernel just does not notify userspace applications of CPU frequency changes +// This is STUPID, as it means I have to poll the CPU information structures with a 0.5 second or so timer just to keep the information up to date +#if 0 + // Monitor for changed cpu information + // Watched directories are set up during the initial CPU scan + m_cpuWatch = new KSimpleDirWatch(this); + connect( m_cpuWatch, TQT_SIGNAL(dirty(const TQString &)), this, TQT_SLOT(processModifiedCPUs()) ); +#else + m_cpuWatchTimer = new TQTimer(this); + connect( m_cpuWatchTimer, SIGNAL(timeout()), this, SLOT(processModifiedCPUs()) ); + TQDir nodezerocpufreq("/sys/devices/system/cpu/cpu0/cpufreq"); + if (nodezerocpufreq.exists()) { + m_cpuWatchTimer->start( 500, FALSE ); // 0.5 second repeating timer + } +#endif + // Update internal device information queryHardwareInformation(); } } TDEHardwareDevices::~TDEHardwareDevices() { +// [FIXME 0.01] +#if 0 + // Stop CPU scanning + m_cpuWatch->stopScan(); +#else + m_cpuWatchTimer->stop(); +#endif + // Stop mount scanning close(m_procMountsFd); @@ -769,6 +982,9 @@ TDEHardwareDevices::~TDEHardwareDevices() { if (usb_id_map) { delete usb_id_map; } + if (pnp_id_map) { + delete pnp_id_map; + } } void TDEHardwareDevices::rescanDeviceInformation(TDEGenericDevice* hwdevice) { @@ -885,6 +1101,158 @@ void TDEHardwareDevices::processHotPluggedHardware() { } } +void TDEHardwareDevices::processModifiedCPUs() { + // Detect what changed between the old cpu information and the new information, + // and emit appropriate events + + // Read new CPU information table + m_cpuInfo.clear(); + TQFile cpufile( "/proc/cpuinfo" ); + if ( cpufile.open( IO_ReadOnly ) ) { + TQTextStream stream( &cpufile ); + while ( !stream.atEnd() ) { + m_cpuInfo.append(stream.readLine()); + } + cpufile.close(); + } + + // Parse CPU information table + TDECPUDevice *cdevice; + cdevice = 0; + bool modified = false; + + TQString curline; + int processorNumber = 0; + int processorCount = 0; + for (TQStringList::Iterator cpuit = m_cpuInfo.begin(); cpuit != m_cpuInfo.end(); ++cpuit) { + // WARNING This routine assumes that "processor" is always the first entry in /proc/cpuinfo! + curline = *cpuit; + if (curline.startsWith("processor")) { + curline.remove(0, curline.find(":")+1); + curline = curline.stripWhiteSpace(); + processorNumber = curline.toInt(); + cdevice = dynamic_cast<TDECPUDevice*>(findBySystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber))); + } + if (curline.startsWith("model name")) { + curline.remove(0, curline.find(":")+1); + curline = curline.stripWhiteSpace(); + if (cdevice->name() != curline) modified = true; + cdevice->setName(curline); + } + if (curline.startsWith("cpu MHz")) { + curline.remove(0, curline.find(":")+1); + curline = curline.stripWhiteSpace(); + if (cdevice->frequency() != curline.toDouble()) modified = true; + cdevice->setFrequency(curline.toDouble()); + } + if (curline.startsWith("vendor_id")) { + curline.remove(0, curline.find(":")+1); + curline = curline.stripWhiteSpace(); + if (cdevice->vendorName() != curline) modified = true; + cdevice->setVendorName(curline); + if (cdevice->vendorEncoded() != curline) modified = true; + cdevice->setVendorEncoded(curline); + } + } + + processorCount = processorNumber+1; + + // Read in other information from cpufreq, if available + for (processorNumber=0; processorNumber<processorCount; processorNumber++) { + cdevice = dynamic_cast<TDECPUDevice*>(findBySystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber))); + TQDir cpufreq_dir(TQString("/sys/devices/system/cpu/cpu%1/cpufreq").arg(processorNumber)); + TQString scalinggovernor; + TQString scalingdriver; + double minfrequency = -1; + double maxfrequency = -1; + double trlatency = -1; + TQStringList affectedcpulist; + TQStringList frequencylist; + if (cpufreq_dir.exists()) { + TQString nodename = cpufreq_dir.path(); + nodename.append("/scaling_governor"); + TQFile scalinggovernorfile(nodename); + if (scalinggovernorfile.open(IO_ReadOnly)) { + TQTextStream stream( &scalinggovernorfile ); + scalinggovernor = stream.readLine(); + scalinggovernorfile.close(); + } + nodename = cpufreq_dir.path(); + nodename.append("/scaling_driver"); + TQFile scalingdriverfile(nodename); + if (scalingdriverfile.open(IO_ReadOnly)) { + TQTextStream stream( &scalingdriverfile ); + scalingdriver = stream.readLine(); + scalingdriverfile.close(); + } + nodename = cpufreq_dir.path(); + nodename.append("/scaling_min_freq"); + TQFile minfrequencyfile(nodename); + if (minfrequencyfile.open(IO_ReadOnly)) { + TQTextStream stream( &minfrequencyfile ); + minfrequency = stream.readLine().toDouble()/1000.0; + minfrequencyfile.close(); + } + nodename = cpufreq_dir.path(); + nodename.append("/scaling_max_freq"); + TQFile maxfrequencyfile(nodename); + if (maxfrequencyfile.open(IO_ReadOnly)) { + TQTextStream stream( &maxfrequencyfile ); + maxfrequency = stream.readLine().toDouble()/1000.0; + maxfrequencyfile.close(); + } + nodename = cpufreq_dir.path(); + nodename.append("/cpuinfo_transition_latency"); + TQFile trlatencyfile(nodename); + if (trlatencyfile.open(IO_ReadOnly)) { + TQTextStream stream( &trlatencyfile ); + trlatency = stream.readLine().toDouble()/1000.0; + trlatencyfile.close(); + } + nodename = cpufreq_dir.path(); + nodename.append("/affected_cpus"); + TQFile tiedcpusfile(nodename); + if (tiedcpusfile.open(IO_ReadOnly)) { + TQTextStream stream( &tiedcpusfile ); + affectedcpulist = TQStringList::split(" ", stream.readLine()); + tiedcpusfile.close(); + } + nodename = cpufreq_dir.path(); + nodename.append("/scaling_available_frequencies"); + TQFile availfreqsfile(nodename); + if (availfreqsfile.open(IO_ReadOnly)) { + TQTextStream stream( &availfreqsfile ); + frequencylist = TQStringList::split(" ", stream.readLine()); + availfreqsfile.close(); + } + } + + // Update CPU information structure + if (cdevice->governor() != scalinggovernor) modified = true; + cdevice->setGovernor(scalinggovernor); + if (cdevice->scalingDriver() != scalingdriver) modified = true; + cdevice->setScalingDriver(scalingdriver); + if (cdevice->minFrequency() != minfrequency) modified = true; + cdevice->setMinFrequency(minfrequency); + if (cdevice->maxFrequency() != maxfrequency) modified = true; + cdevice->setMaxFrequency(maxfrequency); + if (cdevice->transitionLatency() != trlatency) modified = true; + cdevice->setTransitionLatency(trlatency); + if (cdevice->dependentProcessors().join(" ") != affectedcpulist.join(" ")) modified = true; + cdevice->setDependentProcessors(affectedcpulist); + if (cdevice->availableFrequencies().join(" ") != frequencylist.join(" ")) modified = true; + cdevice->setAvailableFrequencies(frequencylist); + } + + if (modified) { + for (processorNumber=0; processorNumber<processorCount; processorNumber++) { + TDEGenericDevice* hwdevice = findBySystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber)); + // Signal new information available + emit hardwareUpdated(hwdevice); + } + } +} + void TDEHardwareDevices::processModifiedMounts() { // Detect what changed between the old mount table and the new one, // and emit appropriate events @@ -1205,6 +1573,12 @@ TDEGenericDeviceType::TDEGenericDeviceType readGenericDeviceTypeFromString(TQStr else if (query == "Platform") { ret = TDEGenericDeviceType::Platform; } + else if (query == "Event") { + ret = TDEGenericDeviceType::Event; + } + else if (query == "Input") { + ret = TDEGenericDeviceType::Input; + } else if (query == "PNP") { ret = TDEGenericDeviceType::PNP; } @@ -1479,12 +1853,13 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD TQString systempath(udev_device_get_syspath(dev)); TQString devicevendorid(udev_device_get_property_value(dev, "ID_VENDOR_ID")); TQString devicemodelid(udev_device_get_property_value(dev, "ID_MODEL_ID")); + TQString devicevendoridenc(udev_device_get_property_value(dev, "ID_VENDOR_ENC")); + TQString devicemodelidenc(udev_device_get_property_value(dev, "ID_MODEL_ENC")); TQString devicesubvendorid(udev_device_get_property_value(dev, "ID_SUBVENDOR_ID")); TQString devicesubmodelid(udev_device_get_property_value(dev, "ID_SUBMODEL_ID")); TQString devicetypestring(udev_device_get_property_value(dev, "ID_TYPE")); TQString devicetypestring_alt(udev_device_get_property_value(dev, "DEVTYPE")); TQString devicepciclass(udev_device_get_property_value(dev, "PCI_CLASS")); - bool removable = false; TDEGenericDevice* device = existingdevice; // FIXME @@ -1522,12 +1897,13 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD // Go after it manually... if (devicevendorid.isNull() || devicemodelid.isNull()) { if (devicemodalias != TQString::null) { - int vloc = devicemodalias.find("v"); - int dloc = devicemodalias.find("d", vloc); - int svloc = devicemodalias.find("sv"); - int sdloc = devicemodalias.find("sd", vloc); // For added fun the device string lengths differ between pci and usb if (devicemodalias.startsWith("pci")) { + int vloc = devicemodalias.find("v"); + int dloc = devicemodalias.find("d", vloc); + int svloc = devicemodalias.find("sv"); + int sdloc = devicemodalias.find("sd", vloc); + devicevendorid = devicemodalias.mid(vloc+1, 8).lower(); devicemodelid = devicemodalias.mid(dloc+1, 8).lower(); if (svloc != -1) { @@ -1540,6 +1916,11 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD devicesubmodelid.remove(0,4); } if (devicemodalias.startsWith("usb")) { + int vloc = devicemodalias.find("v"); + int dloc = devicemodalias.find("p", vloc); + int svloc = devicemodalias.find("sv"); + int sdloc = devicemodalias.find("sp", vloc); + devicevendorid = devicemodalias.mid(vloc+1, 4).lower(); devicemodelid = devicemodalias.mid(dloc+1, 4).lower(); if (svloc != -1) { @@ -1579,11 +1960,43 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD // Classify generic device type and create appropriate object - // Pull out all event special devices and stuff them under Platform + // Pull out all event special devices and stuff them under Event TQString syspath_tail = systempath.lower(); syspath_tail.remove(0, syspath_tail.findRev("/")+1); if (syspath_tail.startsWith("event")) { - if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Platform); + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Event); + } + // Pull out all input special devices and stuff them under Input + if (syspath_tail.startsWith("input")) { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Input); + } + + // Check for keyboard + // Linux doesn't actually ID the keyboard device itself as such, it instead IDs the input device that is underneath the actual keyboard itseld + // Therefore we need to scan <syspath>/input/input* for the ID_INPUT_KEYBOARD attribute + bool is_keyboard = false; + TQString inputtopdirname = udev_device_get_syspath(dev); + inputtopdirname.append("/input/"); + TQDir inputdir(inputtopdirname); + inputdir.setFilter(TQDir::All); + const TQFileInfoList *dirlist = inputdir.entryInfoList(); + if (dirlist) { + TQFileInfoListIterator inputdirsit(*dirlist); + TQFileInfo *dirfi; + while ( (dirfi = inputdirsit.current()) != 0 ) { + if ((dirfi->fileName() != ".") && (dirfi->fileName() != "..")) { + struct udev_device *slavedev; + slavedev = udev_device_new_from_syspath(m_udevStruct, (inputtopdirname + dirfi->fileName()).ascii()); + if (udev_device_get_property_value(slavedev, "ID_INPUT_KEYBOARD") != 0) { + is_keyboard = true; + } + udev_device_unref(slavedev); + } + ++inputdirsit; + } + } + if (is_keyboard) { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Keyboard); } // Classify specific known devices @@ -1597,7 +2010,38 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD } else if (devicetype.isNull()) { if (devicesubsystem == "acpi") { - if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::OtherACPI); + // If the ACPI device exposes a system path ending in /PNPxxxx:yy, the device type can be precisely determined + // See ftp://ftp.microsoft.com/developr/drg/plug-and-play/devids.txt for more information + TQString pnpgentype = systempath; + pnpgentype.remove(0, pnpgentype.findRev("/")+1); + pnpgentype.truncate(pnpgentype.find(":")); + if (pnpgentype.startsWith("PNP")) { + // We support a limited number of specific PNP IDs here + if (pnpgentype == "PNP0C0A") { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Battery); + } + else if (pnpgentype == "PNP0C0B") { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::ThermalControl); + } + else if (pnpgentype == "PNP0C0C") { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Power); + } + else if (pnpgentype == "PNP0C0D") { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Power); + } + else if (pnpgentype == "PNP0C0E") { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Power); + } + else if (pnpgentype == "PNP0C11") { + if (!device) device = new TDESensorDevice(TDEGenericDeviceType::ThermalSensor); + } + else { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::OtherACPI); + } + } + else { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::OtherACPI); + } } else if (devicesubsystem == "input") { // Figure out if this device is a mouse, keyboard, or something else @@ -1640,7 +2084,7 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD else if (devicesubsystem == "hwmon") { // FIXME // This might pick up thermal sensors - if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::OtherSensor); + if (!device) device = new TDESensorDevice(TDEGenericDeviceType::OtherSensor); } } @@ -1709,9 +2153,22 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::PNP); } if ((devicesubsystem == "hid") - || (devicesubsystem == "hidraw")) { + || (devicesubsystem == "hidraw") + || (devicesubsystem == "usbhid")) { if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::HID); } + if (devicesubsystem == "power_supply") { + TQString powersupplyname(udev_device_get_property_value(dev, "POWER_SUPPLY_NAME")); + if (powersupplyname.upper().startsWith("AC")) { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Power); + } + else { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Battery); + } + } + if (devicesubsystem == "backlight") { + if (!device) device = new TDEGenericDevice(TDEGenericDeviceType::Power); + } // Moderate accuracy classification, if PCI device class is available // See http://www.acm.uiuc.edu/sigops/roll_your_own/7.c.1.html for codes and meanings @@ -1773,6 +2230,8 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD device->setSystemPath(systempath); device->setVendorID(devicevendorid); device->setModelID(devicemodelid); + device->setVendorEncoded(devicevendoridenc); + device->setModelEncoded(devicemodelidenc); device->setSubVendorID(devicesubvendorid); device->setSubModelID(devicesubmodelid); device->setModuleAlias(devicemodalias); @@ -1788,18 +2247,37 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD } if (device->type() == TDEGenericDeviceType::Disk) { - // Determine if disk is removable - TQString removablenodename = udev_device_get_syspath(dev); - removablenodename.append("/removable"); - FILE *fp = fopen(removablenodename.ascii(),"r"); - if (fp) { - if (fgetc(fp) == '1') { - removable = true; - } - fclose(fp); + bool removable = false; + bool hotpluggable = false; + + // We can get the removable flag, but we have no idea if the device has the ability to notify on media insertion/removal + // If there is no such notification possible, then we should not set the removable flag + // udev can be such an amazing pain at times + // It exports a /capabilities node with no info on what the bits actually mean + // This information is very poorly documented as a set of #defines in include/linux/genhd.h + // We are specifically interested in GENHD_FL_REMOVABLE and GENHD_FL_MEDIA_CHANGE_NOTIFY + // The "removable" flag should also really be renamed to "hotpluggable", as that is far more precise... + TQString capabilitynodename = systempath; + capabilitynodename.append("/capability"); + TQFile capabilityfile( capabilitynodename ); + unsigned int capabilities = 0; + if ( capabilityfile.open( IO_ReadOnly ) ) { + TQTextStream stream( &capabilityfile ); + TQString capabilitystring; + capabilitystring = stream.readLine(); + capabilities = capabilitystring.toUInt(); + capabilityfile.close(); + } + if (capabilities & GENHD_FL_REMOVABLE) { + // FIXME + // For added fun this is not always true; i.e. GENHD_FL_REMOVABLE can be set when the device cannot be hotplugged (floppy drives). + hotpluggable = true; + } + if (capabilities & GENHD_FL_MEDIA_CHANGE_NOTIFY) { + removable = true; } - // See if any other devices are exclusively using this device, such as the Device Mapper| + // See if any other devices are exclusively using this device, such as the Device Mapper TQStringList holdingDeviceNodes; TQString holdersnodename = udev_device_get_syspath(dev); holdersnodename.append("/holders/"); @@ -1955,6 +2433,9 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD if (removable) { diskstatus = diskstatus | TDEDiskDeviceStatus::Removable; } + if (hotpluggable) { + diskstatus = diskstatus | TDEDiskDeviceStatus::Hotpluggable; + } if ((filesystemtype.upper() != "CRYPTO_LUKS") && (filesystemtype.upper() != "CRYPTO") && (!filesystemtype.isNull())) { diskstatus = diskstatus | TDEDiskDeviceStatus::ContainsFilesystem; @@ -1962,7 +2443,7 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD // Set mountable flag if device is likely to be mountable diskstatus = diskstatus | TDEDiskDeviceStatus::Mountable; - if ((!devicetypestring.upper().isNull()) && (disktype & TDEDiskDeviceType::HDD)) { + if ((devicetypestring.upper().isNull()) && (disktype & TDEDiskDeviceType::HDD)) { diskstatus = diskstatus & ~TDEDiskDeviceStatus::Mountable; } if (removable) { @@ -2035,12 +2516,65 @@ TDEGenericDevice* TDEHardwareDevices::classifyUnknownDevice(udev_device* dev, TD devicenode.remove(0, devicenode.findRev("/")+1); } + if ((device->type() == TDEGenericDeviceType::OtherSensor) || (device->type() == TDEGenericDeviceType::ThermalSensor)) { + // Populate all sensor values + TDESensorClusterMap sensors; + TQString valuesnodename = systempath + "/"; + TQDir valuesdir(valuesnodename); + valuesdir.setFilter(TQDir::All); + TQString nodename; + const TQFileInfoList *dirlist = valuesdir.entryInfoList(); + if (dirlist) { + TQFileInfoListIterator valuesdirit(*dirlist); + TQFileInfo *dirfi; + while ( (dirfi = valuesdirit.current()) != 0 ) { + nodename = dirfi->fileName(); + if (nodename.contains("_")) { + TQFile file( valuesnodename + nodename ); + if ( file.open( IO_ReadOnly ) ) { + TQTextStream stream( &file ); + TQString line; + line = stream.readLine(); + TQStringList sensornodelist = TQStringList::split("_", nodename); + TQString sensornodename = *(sensornodelist.at(0)); + TQString sensornodetype = *(sensornodelist.at(1)); + if (sensornodetype == "label") { + sensors[sensornodename].label = line; + } + if (sensornodetype == "input") { + sensors[sensornodename].current = line.toDouble(); + } + if (sensornodetype == "min") { + sensors[sensornodename].minimum = line.toDouble(); + } + if (sensornodetype == "max") { + sensors[sensornodename].maximum = line.toDouble(); + } + if (sensornodetype == "warn") { + sensors[sensornodename].warning = line.toDouble(); + } + if (sensornodetype == "crit") { + sensors[sensornodename].critical = line.toDouble(); + } + file.close(); + } + } + ++valuesdirit; + } + } + + TDESensorDevice* sdevice = dynamic_cast<TDESensorDevice*>(device); + sdevice->setValues(sensors); + } + // Set basic device information again, as some information may have changed device->setName(devicename); device->setDeviceNode(devicenode); device->setSystemPath(systempath); device->setVendorID(devicevendorid); device->setModelID(devicemodelid); + device->setVendorEncoded(devicevendoridenc); + device->setModelEncoded(devicemodelidenc); device->setSubVendorID(devicesubvendorid); device->setSubModelID(devicesubmodelid); device->setDeviceDriver(devicedriver); @@ -2189,23 +2723,28 @@ void TDEHardwareDevices::addCoreSystemDevices() { while ( !stream.atEnd() ) { line = stream.readLine(); // WARNING This routine assumes that "processor" is always the first entry in /proc/cpuinfo! + // FIXME Parse all available information, such as frequency, etc. if (line.startsWith("processor")) { line.remove(0, line.find(":")+1); line = line.stripWhiteSpace(); processorNumber = line.toInt(); - hwdevice = new TDEGenericDevice(TDEGenericDeviceType::CPU); - hwdevice->setSystemPath(TQString("/sys/devices/cpu/cpu%1").arg(processorNumber)); // FIXME: A system path is required, but I can't give a real, extant, unique path to the hardware manager due to kernel limitations + hwdevice = new TDECPUDevice(TDEGenericDeviceType::CPU); + hwdevice->setSystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber)); m_deviceList.append(hwdevice); - } - if (line.startsWith("model name")) { - line.remove(0, line.find(":")+1); - line = line.stripWhiteSpace(); - hwdevice->setName(line); +#if 0 + // Set up CPU information monitor + // The only way CPU information can be changed is if something changes in the cpufreq node + // This may change in the future, but for now it is a fairly good assumption + m_cpuWatch->addDir(TQString("/sys/devices/system/cpu/cpu%1/cpufreq").arg(processorNumber)); +#endif } lines += line; } file.close(); } + + // Populate CPU information + processModifiedCPUs(); } TQString TDEHardwareDevices::findPCIDeviceName(TQString vendorid, TQString modelid, TQString subvendorid, TQString submodelid) { @@ -2428,6 +2967,76 @@ TQString TDEHardwareDevices::findUSBDeviceName(TQString vendorid, TQString model } } +TQString TDEHardwareDevices::findPNPDeviceName(TQString pnpid) { + TQString friendlyName = TQString::null; + + if (!pnp_id_map) { + pnp_id_map = new TDEDeviceIDMap; + + TQStringList hardware_info_directories(KGlobal::dirs()->resourceDirs("data")); + TQString hardware_info_directory_suffix("tdehwlib/pnpdev/"); + TQString hardware_info_directory; + TQString database_filename; + + for ( TQStringList::Iterator it = hardware_info_directories.begin(); it != hardware_info_directories.end(); ++it ) { + hardware_info_directory = (*it); + hardware_info_directory += hardware_info_directory_suffix; + + if (KGlobal::dirs()->exists(hardware_info_directory)) { + database_filename = hardware_info_directory + "pnp.ids"; + if (TQFile::exists(database_filename)) { + break; + } + } + } + + if (!TQFile::exists(database_filename)) { + printf("[tdehardwaredevices] Unable to locate PNP information database pnp.ids\n\r"); fflush(stdout); + return i18n("Unknown PNP Device"); + } + + TQFile database(database_filename); + if (database.open(IO_ReadOnly)) { + TQTextStream stream(&database); + TQString line; + TQString pnpID; + TQString vendorName; + TQString deviceMapKey; + TQStringList devinfo; + while (!stream.atEnd()) { + line = stream.readLine(); + if ((!line.upper().startsWith("\t")) && (!line.upper().startsWith("#"))) { + devinfo = TQStringList::split('\t', line, false); + if (devinfo.count() > 1) { + pnpID = *(devinfo.at(0)); + vendorName = *(devinfo.at(1));; + vendorName = vendorName.stripWhiteSpace(); + deviceMapKey = pnpID.upper().stripWhiteSpace(); + if (!deviceMapKey.isNull()) { + pnp_id_map->insert(deviceMapKey, vendorName, true); + } + } + } + } + database.close(); + } + else { + printf("[tdehardwaredevices] Unable to open PNP information database %s\n\r", database_filename.ascii()); fflush(stdout); + } + } + + if (pnp_id_map) { + TQString deviceName; + + deviceName = (*pnp_id_map)[pnpid]; + + return deviceName; + } + else { + return i18n("Unknown PNP Device"); + } +} + TQString TDEHardwareDevices::getFriendlyDeviceTypeStringFromType(TDEGenericDeviceType::TDEGenericDeviceType query) { TQString ret = "Unknown Device"; @@ -2528,6 +3137,12 @@ TQString TDEHardwareDevices::getFriendlyDeviceTypeStringFromType(TDEGenericDevic else if (query == TDEGenericDeviceType::Platform) { ret = i18n("Platform"); } + else if (query == TDEGenericDeviceType::Event) { + ret = i18n("Platform Event"); + } + else if (query == TDEGenericDeviceType::Input) { + ret = i18n("Platform Input"); + } else if (query == TDEGenericDeviceType::PNP) { ret = i18n("Plug and Play"); } @@ -2656,6 +3271,12 @@ TQPixmap TDEHardwareDevices::getDeviceTypeIconFromType(TDEGenericDeviceType::TDE else if (query == TDEGenericDeviceType::Platform) { ret = DesktopIcon("kcmsystem", size); } + else if (query == TDEGenericDeviceType::Event) { + ret = DesktopIcon("kcmsystem", size); + } + else if (query == TDEGenericDeviceType::Input) { + ret = DesktopIcon("kcmsystem", size); + } else if (query == TDEGenericDeviceType::PNP) { ret = DesktopIcon("kcmsystem", size); } diff --git a/tdecore/tdehardwaredevices.h b/tdecore/tdehardwaredevices.h index f284a7d0c..0073cb454 100644 --- a/tdecore/tdehardwaredevices.h +++ b/tdecore/tdehardwaredevices.h @@ -74,6 +74,8 @@ enum TDEGenericDeviceType { ThermalControl, Bridge, Platform, + Event, + Input, PNP, OtherACPI, OtherUSB, @@ -149,6 +151,7 @@ enum TDEDiskDeviceStatus { UsedByDevice = 0x00000010, UsesDevice = 0x00000020, ContainsFilesystem = 0x00000040, + Hotpluggable = 0x00000080, Other = 0x80000000 }; @@ -168,6 +171,22 @@ inline TDEDiskDeviceStatus operator~(TDEDiskDeviceStatus a) } }; +class TDECORE_EXPORT TDESensorCluster +{ + public: + /** + * Constructor. + */ + TDESensorCluster(); + + TQString label; + double current; + double minimum; + double maximum; + double warning; + double critical; +}; + class TDECORE_EXPORT TDEGenericDevice { public: @@ -299,6 +318,26 @@ class TDECORE_EXPORT TDEGenericDevice void setModelID(TQString id); /** + * @return a TQString with the encoded vendor, if any + */ + TQString &vendorEncoded(); + + /** + * @param a TQString with the encoded vendor, if any + */ + void setVendorEncoded(TQString id); + + /** + * @return a TQString with the encoded model, if any + */ + TQString &modelEncoded(); + + /** + * @param a TQString with the encoded model, if any + */ + void setModelEncoded(TQString id); + + /** * @return a TQString with the subvendor ID, if any */ TQString &subVendorID(); @@ -396,6 +435,8 @@ class TDECORE_EXPORT TDEGenericDevice TQString m_uniqueID; TQString m_vendorID; TQString m_modelID; + TQString m_vendorenc; + TQString m_modelenc; TQString m_subvendorID; TQString m_submodelID; TQString m_pciClass; @@ -613,10 +654,146 @@ class TDECORE_EXPORT TDEStorageDevice : public TDEGenericDevice TQStringList m_slaveDevices; }; +class TDECORE_EXPORT TDECPUDevice : public TDEGenericDevice +{ + public: + /** + * Constructor. + * @param Device type + */ + TDECPUDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn=TQString::null); + + /** + * Destructor. + */ + ~TDECPUDevice(); + + /** + * @return a double with the current CPU frequency in MHz, if available + */ + double &frequency(); + + /** + * @param a double with the current CPU frequency in MHz, if available + */ + void setFrequency(double fr); + + /** + * @return a double with the minimum CPU frequency in MHz, if available + */ + double &minFrequency(); + + /** + * @param a double with the minimum CPU frequency in MHz, if available + */ + void setMinFrequency(double fr); + + /** + * @return a double with the maximum CPU frequency in MHz, if available + */ + double &maxFrequency(); + + /** + * @param a double with the maximum CPU frequency in MHz, if available + */ + void setMaxFrequency(double fr); + + /** + * @return a double with the transition latency in ns, if available + */ + double &transitionLatency(); + + /** + * @param a double with the transition latency in ns, if available + */ + void setTransitionLatency(double tl); + + /** + * @return a TQString with the current CPU governor policy, if available + */ + TQString &governor(); + + /** + * @param a TQString with the current CPU governor policy, if available + */ + void setGovernor(TQString gr); + + /** + * @return a TQString with the current CPU scaling driver, if available + */ + TQString &scalingDriver(); + + /** + * @param a TQString with the current CPU scaling driver, if available + */ + void setScalingDriver(TQString dr); + + /** + * @return a TQStringList with the IDs of all processors that are dependent on the frequency/power settings of this one, if available + */ + TQStringList &dependentProcessors(); + + /** + * @param a TQStringList with the IDs of all processors that are dependent on the frequency/power settings of this one, if available + */ + void setDependentProcessors(TQStringList dp); + + /** + * @return a TQStringList with all valid scaling frequencies in Hz, if available + */ + TQStringList &availableFrequencies(); + + /** + * @param a TQStringList with all valid scaling frequencies in Hz, if available + */ + void setAvailableFrequencies(TQStringList af); + + private: + double m_frequency; + double m_minfrequency; + double m_maxfrequency; + double m_transitionlatency; + TQString m_governor; + TQString m_scalingdriver; + TQStringList m_tiedprocs; + TQStringList m_frequencies; +}; + +typedef TQMap<TQString, TDESensorCluster> TDESensorClusterMap; + +class TDECORE_EXPORT TDESensorDevice : public TDEGenericDevice +{ + public: + /** + * Constructor. + * @param Device type + */ + TDESensorDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn=TQString::null); + + /** + * Destructor. + */ + ~TDESensorDevice(); + + /** + * @return a TDESensorClusterMap with the current sensor values + */ + TDESensorClusterMap values(); + + /** + * @param a TDESensorClusterMap with the current sensor values + */ + void setValues(TDESensorClusterMap cl); + + private: + TDESensorClusterMap m_sensorValues; +}; + typedef TQPtrList<TDEGenericDevice> TDEGenericHardwareList; typedef TQMap<TQString, TQString> TDEDeviceIDMap; class TQSocketNotifier; +class KSimpleDirWatch; class TDECORE_EXPORT TDEHardwareDevices : public TQObject { @@ -697,6 +874,13 @@ class TDECORE_EXPORT TDEHardwareDevices : public TQObject TQString findUSBDeviceName(TQString vendorid, TQString modelid, TQString subvendorid, TQString submodelid); /** + * Look up the device in the system PNP database + * @param pnpid a TQString containing the PNP ID + * @return a TQString containing the device name, if found + */ + TQString findPNPDeviceName(TQString pnpid); + + /** * Get a friendly string describing a device type * @param query a TDEGenericDeviceType::TDEGenericDeviceType specifying a device type * @return a TQString containing the friendly type name @@ -720,6 +904,7 @@ class TDECORE_EXPORT TDEHardwareDevices : public TQObject private slots: void processHotPluggedHardware(); void processModifiedMounts(); + void processModifiedCPUs(); private: void rescanDeviceInformation(TDEGenericDevice* hwdevice); @@ -738,14 +923,18 @@ class TDECORE_EXPORT TDEHardwareDevices : public TQObject struct udev_monitor *m_udevMonitorStruct; TDEGenericHardwareList m_deviceList; int m_procMountsFd; + KSimpleDirWatch* m_cpuWatch; + TQTimer* m_cpuWatchTimer; TQSocketNotifier* m_devScanNotifier; TQSocketNotifier* m_mountScanNotifier; TQStringList m_mountTable; + TQStringList m_cpuInfo; TDEDeviceIDMap* pci_id_map; TDEDeviceIDMap* usb_id_map; + TDEDeviceIDMap* pnp_id_map; friend class TDEGenericDevice; friend class TDEStorageDevice; |