diff --git a/ksgrd/SensorAgent.cpp b/ksgrd/SensorAgent.cpp index 12a8b33..adc4856 100644 --- a/ksgrd/SensorAgent.cpp +++ b/ksgrd/SensorAgent.cpp @@ -1,310 +1,310 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999 - 2001 Chris Schlaeger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ //#include #include "processcore/processcore_debug.h" #include #include #include "SensorClient.h" #include "SensorManager.h" #include "SensorAgent.h" /** This can be used to debug communication problems with the daemon. Should be set to 0 in any production version. */ #define SA_TRACE 0 using namespace KSGRD; SensorAgent::SensorAgent( SensorManager *sm ) : QObject(sm) { mSensorManager = sm; mDaemonOnLine = false; } SensorAgent::~SensorAgent() { for(int i = mInputFIFO.size()-1; i >= 0; --i) delete mInputFIFO.takeAt(i); for(int i = mProcessingFIFO.size()-1; i >= 0; --i) delete mProcessingFIFO.takeAt(i); } void SensorAgent::sendRequest( const QString &req, SensorClient *client, int id ) { - SensorRequest *sensorreq = 0; + SensorRequest *sensorreq = nullptr; for(int i =0; i < mInputFIFO.size(); ++i) { sensorreq = mInputFIFO.at(i); if(id == sensorreq->id() && client == sensorreq->client() && req == sensorreq->request()) { executeCommand(); return; //don't bother to resend the same request if we already have it in our queue to send } } for(int i =0; i < mProcessingFIFO.size(); ++i) { sensorreq = mProcessingFIFO.at(i); if(id == sensorreq->id() && client == sensorreq->client() && req == sensorreq->request()) return; //don't bother to resend the same request if we have already sent the request to client and just waiting for an answer } /* The request is registered with the FIFO so that the answer can be * routed back to the requesting client. */ mInputFIFO.enqueue( new SensorRequest( req, client, id ) ); #if SA_TRACE qCDebug(LIBKSYSGUARD) << "-> " << req << "(" << mInputFIFO.count() << "/" << mProcessingFIFO.count() << ")" << endl; #endif executeCommand(); } void SensorAgent::processAnswer( const char *buf, int buflen ) { //It is possible for an answer/error message to be split across multiple processAnswer calls. This makes our life more difficult //We have to keep track of the state we are in. Any characters that we have not parsed yet we put in //mLeftOverBuffer QByteArray buffer = QByteArray::fromRawData(buf, buflen); if(!mLeftOverBuffer.isEmpty()) { buffer = mLeftOverBuffer + buffer; //If we have data left over from a previous processAnswer, then we have to prepend this on mLeftOverBuffer.clear(); } #if SA_TRACE qCDebug(LIBKSYSGUARD) << "<- " << QString::fromUtf8(buffer, buffer.size()); #endif int startOfAnswer = 0; //This can become >= buffer.size(), so check before using! for ( int i = 0; i < buffer.size(); ++i ) { if ( buffer.at(i) == '\033' ) { // 033 in octal is the escape character. The signifies the start of an error int startOfError = i; bool found = false; while(++i < buffer.size()) { if(buffer.at(i) == '\033') { QString error = QString::fromUtf8(buffer.constData() + startOfError+1, i-startOfError-1); if ( error.startsWith(QLatin1String("RECONFIGURE")) ) { emit reconfigure( this ); } else { /* We just received the end of an error message, so we * can display it. */ SensorMgr->notify( i18nc( "%1 is a host name", "Message from %1:\n%2", mHostName , error ) ); } found = true; break; } } if(found) { buffer.remove(startOfError, i-startOfError+1); i = startOfAnswer - 1; continue; } else { //We have not found the end of the escape string. Try checking in the next packet mLeftOverBuffer = QByteArray(buffer.constData()+startOfAnswer, buffer.size()-startOfAnswer); return; } } //The spec was supposed to be that it returned "\nksysguardd> " but some seem to forget the space, so we have to compensate. Sigh if( (i==startOfAnswer && buffer.size() -i >= (signed)(sizeof("ksysguardd>" ))-1 && qstrncmp(buffer.constData()+i, "ksysguardd>", sizeof("ksysguardd>" )-1) == 0) || (buffer.size() -i >= (signed)(sizeof("\nksysguardd>"))-1 && qstrncmp(buffer.constData()+i, "\nksysguardd>", sizeof("\nksysguardd>")-1) == 0)) { QByteArray answer(buffer.constData()+startOfAnswer, i-startOfAnswer); if(!answer.isEmpty()) mAnswerBuffer << answer; #if SA_TRACE qCDebug(LIBKSYSGUARD) << "<= " << mAnswerBuffer << "(" << mInputFIFO.count() << "/" << mProcessingFIFO.count() << ")" << endl; #endif if(buffer.at(i) == '\n') i++; i += sizeof("ksysguardd>") -2; //Move i on to the next answer (if any). -2 because sizeof adds one for \0 and the for loop will increment by 1 also if(i+1 < buffer.size() && buffer.at(i+1) == ' ') i++; startOfAnswer = i+1; //We have found the end of one reply if ( !mDaemonOnLine ) { /* First '\nksysguardd> ' signals that the daemon is * ready to serve requests now. */ mDaemonOnLine = true; #if SA_TRACE qCDebug(LIBKSYSGUARD) << "Daemon now online!"; #endif mAnswerBuffer.clear(); continue; } //Deal with the answer we have now read in // remove pending request from FIFO if ( mProcessingFIFO.isEmpty() ) { qCDebug(LIBKSYSGUARD) << "ERROR: Received answer but have no pending " << "request!" << endl; mAnswerBuffer.clear(); continue; } SensorRequest *req = mProcessingFIFO.dequeue(); // we are now responsible for the memory of req - we must delete it! if ( !req->client() ) { /* The client has disappeared before receiving the answer * to his request. */ delete req; mAnswerBuffer.clear(); continue; } if(!mAnswerBuffer.isEmpty() && mAnswerBuffer[0] == "UNKNOWN COMMAND") { /* Notify client that the sensor seems to be no longer available. */ qCDebug(LIBKSYSGUARD) << "Received UNKNOWN COMMAND for: " << req->request(); req->client()->sensorLost( req->id() ); } else { // Notify client of newly arrived answer. req->client()->answerReceived( req->id(), mAnswerBuffer ); } delete req; mAnswerBuffer.clear(); } else if(buffer.at(i) == '\n'){ mAnswerBuffer << QByteArray(buffer.constData()+startOfAnswer, i-startOfAnswer); startOfAnswer = i+1; } } mLeftOverBuffer += QByteArray(buffer.constData()+startOfAnswer, buffer.size()-startOfAnswer); executeCommand(); } void SensorAgent::executeCommand() { /* This function is called whenever there is a chance that we have a * command to pass to the daemon. But the command may only be sent * if the daemon is online and there is no other command currently * being sent. */ if ( mDaemonOnLine && !mInputFIFO.isEmpty() ) { SensorRequest *req = mInputFIFO.dequeue(); #if SA_TRACE qCDebug(LIBKSYSGUARD) << ">> " << req->request().toAscii() << "(" << mInputFIFO.count() << "/" << mProcessingFIFO.count() << ")" << endl; #endif // send request to daemon QString cmdWithNL = req->request() + '\n'; if ( !writeMsg( cmdWithNL.toLatin1().constData(), cmdWithNL.length() ) ) qCDebug(LIBKSYSGUARD) << "SensorAgent::writeMsg() failed"; // add request to processing FIFO. // Note that this means that mProcessingFIFO is now responsible for managing the memory for it. mProcessingFIFO.enqueue( req ); } } void SensorAgent::disconnectClient( SensorClient *client ) { for (int i = 0; i < mInputFIFO.size(); ++i) if ( mInputFIFO[i]->client() == client ) - mInputFIFO[i]->setClient(0); + mInputFIFO[i]->setClient(nullptr); for (int i = 0; i < mProcessingFIFO.size(); ++i) if ( mProcessingFIFO[i]->client() == client ) - mProcessingFIFO[i]->setClient( 0 ); + mProcessingFIFO[i]->setClient( nullptr ); } SensorManager *SensorAgent::sensorManager() { return mSensorManager; } void SensorAgent::setDaemonOnLine( bool value ) { mDaemonOnLine = value; } bool SensorAgent::daemonOnLine() const { return mDaemonOnLine; } void SensorAgent::setHostName( const QString &hostName ) { mHostName = hostName; } QString SensorAgent::hostName() const { return mHostName; } QString SensorAgent::reasonForOffline() const { return mReasonForOffline; } void SensorAgent::setReasonForOffline(const QString &reasonForOffline) { mReasonForOffline = reasonForOffline; } SensorRequest::SensorRequest( const QString &request, SensorClient *client, int id ) : mRequest( request ), mClient( client ), mId( id ) { } SensorRequest::~SensorRequest() { } void SensorRequest::setRequest( const QString &request ) { mRequest = request; } QString SensorRequest::request() const { return mRequest; } void SensorRequest::setClient( SensorClient *client ) { mClient = client; } SensorClient *SensorRequest::client() { return mClient; } void SensorRequest::setId( int id ) { mId = id; } int SensorRequest::id() { return mId; } diff --git a/ksgrd/SensorManager.cpp b/ksgrd/SensorManager.cpp index f7ba058..2a824f1 100644 --- a/ksgrd/SensorManager.cpp +++ b/ksgrd/SensorManager.cpp @@ -1,452 +1,452 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999 - 2001 Chris Schlaeger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "processcore/processcore_debug.h" #include "SensorShellAgent.h" #include "SensorSocketAgent.h" #include "SensorManager.h" using namespace KSGRD; SensorManager::MessageEvent::MessageEvent( const QString &message ) : QEvent( QEvent::User ), mMessage( message ) { } QString SensorManager::MessageEvent::message() const { return mMessage; } SensorManager* KSGRD::SensorMgr; SensorManager::SensorManager(QObject * parent) : QObject(parent) { retranslate(); } int SensorManager::count() const { return mAgents.count(); } void SensorManager::retranslate() { // Fill the sensor description dictionary. mDict.clear(); mDict.insert( QStringLiteral( "Delta" ), i18n( "Change" ) ); mDict.insert( QStringLiteral( "Rate" ), i18n( "Rate" ) ); mDict.insert( QStringLiteral( "cpu" ), i18n( "CPU Load" ) ); mDict.insert( QStringLiteral( "idle" ), i18n( "Idling" ) ); mDict.insert( QStringLiteral( "nice" ), i18n( "Nice Load" ) ); mDict.insert( QStringLiteral( "user" ), i18n( "User Load" ) ); mDict.insert( QStringLiteral( "sys" ), i18nc( "@item sensor description", "System Load" ) ); mDict.insert( QStringLiteral( "wait" ), i18n( "Waiting" ) ); mDict.insert( QStringLiteral( "intr" ), i18n( "Interrupt Load" ) ); mDict.insert( QStringLiteral( "TotalLoad" ), i18n( "Total Load" ) ); mDict.insert( QStringLiteral( "mem" ), i18n( "Memory" ) ); mDict.insert( QStringLiteral( "physical" ), i18n( "Physical Memory" ) ); mDict.insert( QStringLiteral( "swap" ), i18n( "Swap Memory" ) ); mDict.insert( QStringLiteral( "cached" ), i18n( "Cached Memory" ) ); mDict.insert( QStringLiteral( "buf" ), i18n( "Buffered Memory" ) ); mDict.insert( QStringLiteral( "used" ), i18n( "Used Memory" ) ); mDict.insert( QStringLiteral( "application" ), i18n( "Application Memory" ) ); mDict.insert( QStringLiteral( "free" ), i18n( "Free Memory" ) ); mDict.insert( QStringLiteral( "active" ), i18n( "Active Memory" ) ); mDict.insert( QStringLiteral( "inactive" ), i18n( "Inactive Memory" ) ); mDict.insert( QStringLiteral( "wired" ), i18n( "Wired Memory" ) ); mDict.insert( QStringLiteral( "execpages" ), i18n( "Exec Pages" ) ); mDict.insert( QStringLiteral( "filepages" ), i18n( "File Pages" ) ); /* Processes */ mDict.insert( QStringLiteral( "processes" ), i18n( "Processes" ) ); mDict.insert( QStringLiteral( "ps" ), i18n( "Process Controller" ) ); mDict.insert( QStringLiteral( "lastpid" ), i18n( "Last Process ID" ) ); mDict.insert( QStringLiteral( "procspawn" ), i18n( "Process Spawn Count" ) ); mDict.insert( QStringLiteral( "pscount" ), i18n( "Process Count" ) ); mDict.insert( QStringLiteral( "psidle" ), i18n( "Idle Processes Count" ) ); mDict.insert( QStringLiteral( "psrun" ), i18n( "Running Processes Count" ) ); mDict.insert( QStringLiteral( "pssleep" ), i18n( "Sleeping Processes Count" ) ); mDict.insert( QStringLiteral( "psstop" ), i18n( "Stopped Processes Count" ) ); mDict.insert( QStringLiteral( "pszombie" ), i18n( "Zombie Processes Count" ) ); mDict.insert( QStringLiteral( "pswait" ), i18n( "Waiting Processes Count" ) ); mDict.insert( QStringLiteral( "pslock" ), i18n( "Locked Processes Count" ) ); mDict.insert( QStringLiteral( "disk" ), i18n( "Disk Throughput" ) ); mDict.insert( QStringLiteral( "load" ), i18nc( "CPU Load", "Load" ) ); mDict.insert( QStringLiteral( "totalio" ), i18n( "Total Accesses" ) ); mDict.insert( QStringLiteral( "rio" ), i18n( "Read Accesses" ) ); mDict.insert( QStringLiteral( "wio" ), i18n( "Write Accesses" ) ); mDict.insert( QStringLiteral( "rblk" ), i18n( "Read Data" ) ); mDict.insert( QStringLiteral( "wblk" ), i18n( "Written Data" ) ); mDict.insert( QStringLiteral( "rtim" ), i18n( "Milliseconds spent reading" ) ); mDict.insert( QStringLiteral( "wtim" ), i18n( "Milliseconds spent writing" ) ); mDict.insert( QStringLiteral( "ioqueue" ), i18n( "I/Os currently in progress" ) ); mDict.insert( QStringLiteral( "pageIn" ), i18n( "Pages In" ) ); mDict.insert( QStringLiteral( "pageOut" ), i18n( "Pages Out" ) ); mDict.insert( QStringLiteral( "context" ), i18n( "Context Switches" ) ); mDict.insert( QStringLiteral( "trap" ), i18n( "Traps" ) ); mDict.insert( QStringLiteral( "syscall" ), i18n( "System Calls" ) ); mDict.insert( QStringLiteral( "network" ), i18n( "Network" ) ); mDict.insert( QStringLiteral( "interfaces" ), i18n( "Interfaces" ) ); mDict.insert( QStringLiteral( "receiver" ), i18n( "Receiver" ) ); mDict.insert( QStringLiteral( "transmitter" ), i18n( "Transmitter" ) ); mDict.insert( QStringLiteral( "data" ), i18n( "Data Rate" ) ); mDict.insert( QStringLiteral( "compressed" ), i18n( "Compressed Packets Rate" ) ); mDict.insert( QStringLiteral( "drops" ), i18n( "Dropped Packets Rate" ) ); mDict.insert( QStringLiteral( "errors" ), i18n( "Error Rate" ) ); mDict.insert( QStringLiteral( "fifo" ), i18n( "FIFO Overruns Rate" ) ); mDict.insert( QStringLiteral( "frame" ), i18n( "Frame Error Rate" ) ); mDict.insert( QStringLiteral( "multicast" ), i18n( "Multicast Packet Rate" ) ); mDict.insert( QStringLiteral( "packets" ), i18n( "Packet Rate" ) ); mDict.insert( QStringLiteral( "carrier" ), i18nc( "@item sensor description ('carrier' is a type of network signal)", "Carrier Loss Rate" ) ); mDict.insert( QStringLiteral( "collisions" ), i18n( "Collisions" ) ); mDict.insert( QStringLiteral( "dataTotal" ), i18n( "Data" ) ); mDict.insert( QStringLiteral( "compressedTotal" ), i18n( "Compressed Packets" ) ); mDict.insert( QStringLiteral( "dropsTotal" ), i18n( "Dropped Packets" ) ); mDict.insert( QStringLiteral( "errorsTotal" ), i18n( "Errors" ) ); mDict.insert( QStringLiteral( "fifoTotal" ), i18n( "FIFO Overruns" ) ); mDict.insert( QStringLiteral( "frameTotal" ), i18n( "Frame Errors" ) ); mDict.insert( QStringLiteral( "multicastTotal" ), i18n( "Multicast Packets" ) ); mDict.insert( QStringLiteral( "packetsTotal" ), i18n( "Packets" ) ); mDict.insert( QStringLiteral( "carrierTotal" ), i18nc( "@item sensor description ('carrier' is a type of network signal)", "Carrier Losses" ) ); mDict.insert( QStringLiteral( "collisionsTotal" ), i18n( "Collisions" ) ); mDict.insert( QStringLiteral( "sockets" ), i18n( "Sockets" ) ); mDict.insert( QStringLiteral( "count" ), i18n( "Total Number" ) ); mDict.insert( QStringLiteral( "list" ), i18n( "Table" ) ); mDict.insert( QStringLiteral( "apm" ), i18n( "Advanced Power Management" ) ); mDict.insert( QStringLiteral( "acpi" ), i18n( "ACPI" ) ); mDict.insert( QStringLiteral( "Cooling_Device" ), i18n( "Cooling Device" ) ); mDict.insert( QStringLiteral( "Current_State" ), i18n( "Current State" ) ); mDict.insert( QStringLiteral( "thermal_zone" ), i18n( "Thermal Zone" ) ); mDict.insert( QStringLiteral( "Thermal_Zone" ), i18n( "Thermal Zone" ) ); mDict.insert( QStringLiteral( "temperature" ), i18n( "Temperature" ) ); mDict.insert( QStringLiteral( "Temperature" ), i18n( "Temperature" ) ); mDict.insert( QStringLiteral( "AverageTemperature" ), i18n( "Average CPU Temperature" ) ); mDict.insert( QStringLiteral( "fan" ), i18n( "Fan" ) ); mDict.insert( QStringLiteral( "state" ), i18n( "State" ) ); mDict.insert( QStringLiteral( "battery" ), i18n( "Battery" ) ); mDict.insert( QStringLiteral( "batterycapacity" ), i18n( "Battery Capacity" ) ); mDict.insert( QStringLiteral( "batterycharge" ), i18n( "Battery Charge" ) ); mDict.insert( QStringLiteral( "batteryusage" ), i18n( "Battery Usage" ) ); mDict.insert( QStringLiteral( "batteryvoltage" ), i18n( "Battery Voltage" ) ); mDict.insert( QStringLiteral( "batteryrate" ), i18n( "Battery Discharge Rate" ) ); mDict.insert( QStringLiteral( "remainingtime" ), i18n( "Remaining Time" ) ); mDict.insert( QStringLiteral( "interrupts" ), i18n( "Interrupts" ) ); mDict.insert( QStringLiteral( "loadavg1" ), i18n( "Load Average (1 min)" ) ); mDict.insert( QStringLiteral( "loadavg5" ), i18n( "Load Average (5 min)" ) ); mDict.insert( QStringLiteral( "loadavg15" ), i18n( "Load Average (15 min)" ) ); mDict.insert( QStringLiteral( "clock" ), i18n( "Clock Frequency" ) ); mDict.insert( QStringLiteral( "AverageClock" ), i18n( "Average Clock Frequency" ) ); mDict.insert( QStringLiteral( "lmsensors" ), i18n( "Hardware Sensors" ) ); mDict.insert( QStringLiteral( "partitions" ), i18n( "Partition Usage" ) ); mDict.insert( QStringLiteral( "usedspace" ), i18n( "Used Space" ) ); mDict.insert( QStringLiteral( "freespace" ), i18n( "Free Space" ) ); mDict.insert( QStringLiteral( "filllevel" ), i18n( "Fill Level" ) ); mDict.insert( QStringLiteral( "usedinode" ), i18n( "Used Inodes" ) ); mDict.insert( QStringLiteral( "freeinode" ), i18n( "Free Inodes" ) ); mDict.insert( QStringLiteral( "inodelevel" ), i18n( "Inode Level" ) ); mDict.insert( QStringLiteral( "system" ), i18n( "System" ) ); mDict.insert( QStringLiteral( "uptime" ), i18n( "Uptime" ) ); mDict.insert( QStringLiteral( "SoftRaid" ), i18n( "Linux Soft Raid (md)" ) ); mDict.insert( QStringLiteral( "processors" ), i18n( "Processors" ) ); mDict.insert( QStringLiteral( "cores" ), i18n( "Cores" ) ); mDict.insert( QStringLiteral( "NumBlocks" ), i18n( "Number of Blocks" ) ); mDict.insert( QStringLiteral( "TotalDevices" ), i18n( "Total Number of Devices" ) ); mDict.insert( QStringLiteral( "FailedDevices" ), i18n( "Failed Devices" ) ); mDict.insert( QStringLiteral( "SpareDevices" ), i18n( "Spare Devices" ) ); mDict.insert( QStringLiteral( "NumRaidDevices" ), i18n( "Number of Raid Devices" ) ); mDict.insert( QStringLiteral( "WorkingDevices" ), i18n( "Working Devices" ) ); mDict.insert( QStringLiteral( "ActiveDevices" ), i18n( "Active Devices" ) ); mDict.insert( QStringLiteral( "DeviceNumber" ), i18n( "Number of Devices" ) ); mDict.insert( QStringLiteral( "ResyncingPercent" ), i18n( "Resyncing Percent" ) ); mDict.insert( QStringLiteral( "DiskInfo" ), i18n( "Disk Information" ) ); for ( int i = 0; i < 32; i++ ) { mDict.insert( QLatin1String( "cpu" ) + QString::number( i ), i18n( "CPU %1", i+1 ) ); mDict.insert( QLatin1String( "disk" ) + QString::number( i ), i18n( "Disk %1", i+1 ) ); } for ( int i = 1; i < 6; i++) { mDict.insert( QLatin1String( "batt" ) + QString::number( i ), i18n( "Battery %1", i ) ); mDict.insert( QLatin1String( "fan" ) + QString::number( i ), i18n( "Fan %1", i ) ); mDict.insert( QLatin1String( "temp" ) + QString::number( i ), i18n( "Temperature %1", i ) ); } mDict.insert( QStringLiteral( "int00" ), i18n( "Total" ) ); mDict.insert( QStringLiteral( "softint" ), i18n( "Software Interrupts" ) ); mDict.insert( QStringLiteral( "hardint" ), i18n( "Hardware Interrupts" ) ); QString num; for ( int i = 1; i < 25; i++ ) { num.sprintf( "%.2d", i ); mDict.insert( QLatin1String( "int" ) + num, ki18n( "Int %1" ).subs( i - 1, 3 ).toString() ); num.sprintf( "%.3d", i + 255); mDict.insert( QLatin1String( "int" ) + num, ki18n( "Int %1" ).subs( i + 255, 4 ).toString() ); } mDict.insert( QStringLiteral( "quality" ), i18n( "Link Quality" ) ); mDict.insert( QStringLiteral( "signal" ), i18n( "Signal Level" ) ); mDict.insert( QStringLiteral( "noise" ), i18n( "Noise Level" ) ); mDict.insert( QStringLiteral( "nwid" ), i18n( "Rx Invalid Nwid Packets") ); mDict.insert( QStringLiteral( "nwidTotal" ), i18n( "Total Rx Invalid Nwid Packets") ); mDict.insert( QStringLiteral( "crypt" ), i18n( "Rx Invalid Crypt Packets") ); mDict.insert( QStringLiteral( "cryptTotal" ), i18n( "Total Rx Invalid Crypt Packets") ); mDict.insert( QStringLiteral( "frag" ), i18n( "Rx Invalid Frag Packets") ); mDict.insert( QStringLiteral( "fragTotal" ), i18n( "Total Rx Invalid Frag Packets") ); mDict.insert( QStringLiteral( "retry" ), i18n( "Tx Excessive Retries Packets") ); mDict.insert( QStringLiteral( "retryTotal" ), i18n( "Total Tx Excessive Retries Packets") ); mDict.insert( QStringLiteral( "misc" ), i18n( "Invalid Misc Packets") ); mDict.insert( QStringLiteral( "miscTotal" ), i18n( "Total Invalid Misc Packets") ); mDict.insert( QStringLiteral( "beacon" ), i18n( "Missed Beacons") ); mDict.insert( QStringLiteral( "beaconTotal" ), i18n( "Total Missed Beacons") ); mDict.insert( QStringLiteral( "logfiles" ), i18n( "Log Files") ); // TODO: translated descriptions not yet implemented. mUnits.clear(); mUnits.insert( QStringLiteral( "1/s" ), i18nc( "the unit 1 per second", "1/s" ) ); mUnits.insert( QStringLiteral( "kBytes" ), i18n( "kBytes" ) ); mUnits.insert( QStringLiteral( "min" ), i18nc( "the unit minutes", "min" ) ); mUnits.insert( QStringLiteral( "MHz" ), i18nc( "the frequency unit", "MHz" ) ); mUnits.insert( QStringLiteral( "%" ), i18nc( "a percentage", "%" ) ); mUnits.insert( QStringLiteral( "mA" ), i18nc( "the unit milliamperes", "mA" ) ); mUnits.insert( QStringLiteral( "mAh" ), i18nc( "the unit milliampere hours", "mAh" ) ); mUnits.insert( QStringLiteral( "mW" ), i18nc( "the unit milliwatts", "mW" ) ); mUnits.insert( QStringLiteral( "mWh" ), i18nc( "the unit milliwatt hours", "mWh" ) ); mUnits.insert( QStringLiteral( "mV" ), i18nc( "the unit millivolts", "mV" ) ); mTypes.clear(); mTypes.insert( QStringLiteral( "integer" ), i18n( "Integer Value" ) ); mTypes.insert( QStringLiteral( "float" ), i18n( "Floating Point Value" ) ); mTypes.insert( QStringLiteral( "table" ), i18n( "Process Controller" ) ); mTypes.insert( QStringLiteral( "listview" ), i18n( "Table" ) ); mTypes.insert( QStringLiteral( "logfile" ), i18n( "Log File") ); - mBroadcaster = NULL; + mBroadcaster = nullptr; } SensorManager::~SensorManager() { } bool SensorManager::engage( const QString &hostName, const QString &shell, const QString &command, int port ) { if ( !mAgents.contains( hostName ) ) { - SensorAgent *agent = 0; + SensorAgent *agent = nullptr; if ( port == -1 ) agent = new SensorShellAgent( this ); else agent = new SensorSocketAgent( this ); if ( !agent->start( hostName.toLatin1(), shell, command, port ) ) { delete agent; return false; } mAgents.insert( hostName, agent ); connect( agent, &SensorAgent::reconfigure, this, &SensorManager::reconfigure ); emit hostAdded(agent,hostName); return true; } return false; } bool SensorManager::disengage( SensorAgent *agent ) { if(!agent) return false; const QString key = mAgents.key( const_cast( agent ) ); return disengage(key); } bool SensorManager::isConnected( const QString &hostName ) { return mAgents.contains( hostName ); } bool SensorManager::disengage( const QString &hostName ) { if ( mAgents.contains( hostName ) ) { mAgents.take( hostName )->deleteLater(); emit hostConnectionLost( hostName ); return true; } return false; } bool SensorManager::resynchronize( const QString &hostName ) { const SensorAgent *agent = mAgents.value( hostName ); if ( !agent ) return false; QString shell, command; int port; hostInfo( hostName, shell, command, port ); mAgents.remove( hostName ); qCDebug(LIBKSYSGUARD) << "Re-synchronizing connection to " << hostName; return engage( hostName, shell, command ); } void SensorManager::notify( const QString &msg ) const { /* This function relays text messages to the toplevel widget that * displays the message in a pop-up box. It must be used for objects * that might have been deleted before the pop-up box is closed. */ if ( mBroadcaster ) { MessageEvent *event = new MessageEvent( msg ); qApp->postEvent( mBroadcaster, event ); } } void SensorManager::setBroadcaster( QWidget *wdg ) { mBroadcaster = wdg; } void SensorManager::reconfigure( const SensorAgent* ) { emit update(); } bool SensorManager::sendRequest( const QString &hostName, const QString &req, SensorClient *client, int id ) { SensorAgent *agent = mAgents.value( hostName ); if ( !agent && hostName == QLatin1String("localhost")) { //we should always be able to reconnect to localhost engage(QStringLiteral("localhost"), QLatin1String(""), QStringLiteral("ksysguardd"), -1); agent = mAgents.value( hostName ); } if ( agent ) { agent->sendRequest( req, client, id ); return true; } return false; } const QString SensorManager::hostName( const SensorAgent *agent ) const { return mAgents.key( const_cast( agent ) ); } bool SensorManager::hostInfo( const QString &hostName, QString &shell, QString &command, int &port ) { const SensorAgent *agent = mAgents.value( hostName ); if ( agent ) { agent->hostInfo( shell, command, port ); return true; } return false; } QString SensorManager::translateUnit( const QString &unit ) const { if ( !unit.isEmpty() && mUnits.contains( unit ) ) return mUnits[ unit ]; else return unit; } QString SensorManager::translateSensorPath( const QString &path ) const { if ( !path.isEmpty() && mDict.contains( path ) ) return mDict[ path ]; else return path; } QString SensorManager::translateSensorType( const QString &type ) const { if ( !type.isEmpty() && mTypes.contains( type ) ) return mTypes[ type ]; else return type; } QString SensorManager::translateSensor( const QString &sensor ) const { QString token, out; int start = 0, end = 0; for ( ; ; ) { end = sensor.indexOf( '/', start ); if ( end > 0 ) out += translateSensorPath( sensor.mid( start, end - start ) ) + '/'; else { out += translateSensorPath( sensor.right( sensor.length() - start ) ); break; } start = end + 1; } return out; } void SensorManager::readProperties( const KConfigGroup& cfg ) { mHostList = cfg.readEntry( "HostList" ,QStringList()); mCommandList = cfg.readEntry( "CommandList",QStringList() ); } void SensorManager::saveProperties( KConfigGroup &cfg ) { cfg.writeEntry( "HostList", mHostList ); cfg.writeEntry( "CommandList", mCommandList ); } void SensorManager::disconnectClient( SensorClient *client ) { QHashIterator it( mAgents ); while ( it.hasNext() ) it.next().value()->disconnectClient( client ); } diff --git a/ksgrd/SensorManager.h b/ksgrd/SensorManager.h index fb944df..e6c252a 100644 --- a/ksgrd/SensorManager.h +++ b/ksgrd/SensorManager.h @@ -1,143 +1,143 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSG_SENSORMANAGER_H #define KSG_SENSORMANAGER_H #include #include #include #include #include #include #include "SensorAgent.h" namespace KSGRD { class SensorManagerIterator; /** The SensorManager handles all interaction with the connected hosts. Connections to a specific hosts are handled by SensorAgents. Use engage() to establish a connection and disengage() to terminate the connection. */ class Q_DECL_EXPORT SensorManager : public QObject { Q_OBJECT friend class SensorManagerIterator; public: class Q_DECL_EXPORT MessageEvent : public QEvent { public: MessageEvent( const QString &message ); QString message() const; private: QString mMessage; }; - explicit SensorManager(QObject * parent = 0); + explicit SensorManager(QObject * parent = nullptr); ~SensorManager() override; /*! Number of hosts connected to */ int count() const; bool engage( const QString &hostName, const QString &shell = QStringLiteral("ssh"), const QString &command = QLatin1String(""), int port = -1 ); /* Returns true if we are connected or trying to connect to the host given */ bool isConnected( const QString &hostName ); bool disengage( SensorAgent *agent ); bool disengage( const QString &hostName ); bool resynchronize( const QString &hostName ); void notify( const QString &msg ) const; void setBroadcaster( QWidget *wdg ); bool sendRequest( const QString &hostName, const QString &request, SensorClient *client, int id = 0 ); const QString hostName( const SensorAgent *sensor ) const; bool hostInfo( const QString &host, QString &shell, QString &command, int &port ); QString translateUnit( const QString &unit ) const; QString translateSensorPath( const QString &path ) const; QString translateSensorType( const QString &type ) const; QString translateSensor(const QString& u) const; void readProperties( const KConfigGroup& cfg ); void saveProperties( KConfigGroup& cfg ); void disconnectClient( SensorClient *client ); /** Call to retranslate all the strings - for example if the language has changed */ void retranslate(); public Q_SLOTS: void reconfigure( const SensorAgent *agent ); Q_SIGNALS: void update(); void hostAdded(KSGRD::SensorAgent *sensorAgent, const QString &hostName); void hostConnectionLost( const QString &hostName ); protected: QHash mAgents; private: /** These dictionary stores the localized versions of the sensor descriptions and units. */ QHash mDescriptions; QHash mUnits; QHash mDict; QHash mTypes; /** Store the data from the config file to pass to the MostConnector dialog box*/ QStringList mHostList; QStringList mCommandList; QPointer mBroadcaster; }; Q_DECL_EXPORT extern SensorManager* SensorMgr; class Q_DECL_EXPORT SensorManagerIterator : public QHashIterator { public: explicit SensorManagerIterator( const SensorManager *sm ) : QHashIterator( sm->mAgents ) { } ~SensorManagerIterator() { } }; } #endif diff --git a/ksgrd/SensorShellAgent.cpp b/ksgrd/SensorShellAgent.cpp index 5c66b33..fe3e384 100644 --- a/ksgrd/SensorShellAgent.cpp +++ b/ksgrd/SensorShellAgent.cpp @@ -1,150 +1,150 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999 - 2001 Chris Schlaeger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "processcore/processcore_debug.h" //#include "SensorClient.h" #include "SensorManager.h" #include "SensorShellAgent.h" using namespace KSGRD; SensorShellAgent::SensorShellAgent( SensorManager *sm ) - : SensorAgent( sm ), mDaemon( 0 ) + : SensorAgent( sm ), mDaemon( nullptr ) { } SensorShellAgent::~SensorShellAgent() { if ( mDaemon ) { mDaemon->write( "quit\n", sizeof( "quit\n" )-1 ); mDaemon->disconnect(); mDaemon->waitForFinished(); delete mDaemon; - mDaemon = 0; + mDaemon = nullptr; } } bool SensorShellAgent::start( const QString &host, const QString &shell, const QString &command, int ) { mDaemon = new KProcess(); mDaemon->setOutputChannelMode( KProcess::SeparateChannels ); mRetryCount=3; setHostName( host ); mShell = shell; mCommand = command; connect( mDaemon, SIGNAL(error(QProcess::ProcessError)), SLOT(daemonError(QProcess::ProcessError)) ); connect( mDaemon, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(daemonExited(int,QProcess::ExitStatus)) ); connect( mDaemon.data(), &QProcess::readyReadStandardOutput, this, &SensorShellAgent::msgRcvd ); connect( mDaemon.data(), &QProcess::readyReadStandardError, this, &SensorShellAgent::errMsgRcvd ); if ( !command.isEmpty() ) { *mDaemon << KShell::splitArgs(command); } else *mDaemon << mShell << hostName() << QStringLiteral("ksysguardd"); mDaemon->start(); return true; } void SensorShellAgent::hostInfo( QString &shell, QString &command, int &port) const { shell = mShell; command = mCommand; port = -1; } void SensorShellAgent::msgRcvd( ) { QByteArray buffer = mDaemon->readAllStandardOutput(); mRetryCount = 3; //we received an answer, so reset our retry count back to 3 processAnswer( buffer.constData(), buffer.size()); } void SensorShellAgent::errMsgRcvd( ) { QByteArray buffer = mDaemon->readAllStandardOutput(); // Because we read the error buffer in chunks, we may not have a proper utf8 string. // We should never get input over stderr anyway, so no need to worry too much about it. // But if this is extended, we will need to handle this better QString buf = QString::fromUtf8( buffer ); qCDebug(LIBKSYSGUARD) << "SensorShellAgent: Warning, received text over stderr!" << endl << buf << endl; } void SensorShellAgent::daemonExited( int exitCode, QProcess::ExitStatus exitStatus ) { Q_UNUSED(exitCode); qCDebug(LIBKSYSGUARD) << "daemon exited, exit status " << exitStatus; if ( mRetryCount-- <= 0 || (mDaemon->start(), !mDaemon->waitForStarted()) ) { setDaemonOnLine( false ); if(sensorManager()) { sensorManager()->disengage( this ); //delete ourselves } } } void SensorShellAgent::daemonError( QProcess::ProcessError errorStatus ) { QString error; switch(errorStatus) { case QProcess::FailedToStart: qCDebug(LIBKSYSGUARD) << "failed to run" << mDaemon->program().join(QStringLiteral(" ")); error = i18n("Could not run daemon program '%1'.", mDaemon->program().join(" ")); break; case QProcess::Crashed: case QProcess::Timedout: case QProcess::WriteError: case QProcess::ReadError: default: error = i18n("The daemon program '%1' failed.", mDaemon->program().join(" ")); } setReasonForOffline(error); qCDebug(LIBKSYSGUARD) << "Error received " << error << "(" << errorStatus << ")"; setDaemonOnLine( false ); if(sensorManager()) sensorManager()->disengage( this ); //delete ourselves } bool SensorShellAgent::writeMsg( const char *msg, int len ) { //write returns -1 on error, in which case we should return false. true otherwise. return mDaemon->write( msg, len ) != -1; } diff --git a/lsofui/lsof.cpp b/lsofui/lsof.cpp index 2bfedc5..2cfc6d7 100644 --- a/lsofui/lsof.cpp +++ b/lsofui/lsof.cpp @@ -1,89 +1,89 @@ #include #include #include #include "lsof.h" struct KLsofWidgetPrivate { qlonglong pid; QProcess *process; }; KLsofWidget::KLsofWidget(QWidget *parent) : QTreeWidget(parent), d(new KLsofWidgetPrivate) { d->pid = -1; setColumnCount(3); setUniformRowHeights(true); setRootIsDecorated(false); setItemsExpandable(false); setSortingEnabled(true); setAllColumnsShowFocus(true); setHeaderLabels(QStringList() << i18nc("Short for File Descriptor", "FD") << i18n("Type") << i18n("Object")); d->process = new QProcess(this); connect(d->process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished(int,QProcess::ExitStatus))); } KLsofWidget::~KLsofWidget() { delete d; } qlonglong KLsofWidget::pid() const { return d->pid; } void KLsofWidget::setPid(qlonglong pid) { d->pid = pid; update(); } bool KLsofWidget::update() { clear(); QStringList args; d->process->waitForFinished(); args << QStringLiteral("-Fftn"); if(d->pid > 0) args << ("-p" + QString::number(d->pid)); d->process->start(QStringLiteral("lsof"), args); return true; } void KLsofWidget::finished ( int exitCode, QProcess::ExitStatus exitStatus ) { Q_UNUSED(exitCode); Q_UNUSED(exitStatus); char buf[1024]; - QTreeWidgetItem *process = NULL; + QTreeWidgetItem *process = nullptr; while(true) { qint64 lineLength = d->process->readLine(buf, sizeof(buf)); if(lineLength <= 0) break; if(buf[lineLength-1] == '\n') lineLength--; switch(buf[0]) { /* Process related stuff */ case 'f': process = new QTreeWidgetItem(this); process->setText(0,QString::fromUtf8(buf+1, lineLength - 1)); break; case 't': if(process) process->setText(1,QString::fromUtf8(buf+1, lineLength - 1)); break; case 'n': if(process) process->setText(2,QString::fromUtf8(buf+1, lineLength - 1)); break; default: break; } } } diff --git a/lsofui/lsof.h b/lsofui/lsof.h index 2d0fd06..81339de 100644 --- a/lsofui/lsof.h +++ b/lsofui/lsof.h @@ -1,82 +1,82 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 LSOFWIDGET_H_ #define LSOFWIDGET_H_ #include #include #include struct KLsofWidgetPrivate; class Q_DECL_EXPORT KLsofWidget : public QTreeWidget { Q_OBJECT Q_PROPERTY( qlonglong pid READ pid WRITE setPid ) public: - KLsofWidget(QWidget *parent = NULL); + KLsofWidget(QWidget *parent = nullptr); ~KLsofWidget() override; bool update(); private Q_SLOTS: /* For QProcess *process */ //void error ( QProcess::ProcessError error ); void finished ( int exitCode, QProcess::ExitStatus exitStatus ); //void readyReadStandardError (); //void readyReadStandardOutput (); //void started (); qlonglong pid() const; void setPid(qlonglong pid); private: KLsofWidgetPrivate* const d; }; /* class LsofProcessInfo { public: pid_t tpid; int pidst; pid_t pid; pid_t ppid; pid_t pgrp; int uid; QString cmd; QString login; }; class LsofFileInfo { QString file_descriptor; char access; int file_struct_share_count; char device_character_code; long major_minor; long file_struct_address; long file_flags; long inode; long link_count; char lock; long file_struct_node_id; long file_offset; QString protocol_name; QString stream_module; QString file_type; QString tcp_info; }; */ #endif diff --git a/processcore/processes.cpp b/processcore/processes.cpp index 1c5d52d..90074d5 100644 --- a/processcore/processes.cpp +++ b/processcore/processes.cpp @@ -1,544 +1,544 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "processes.h" #include "processes_base_p.h" #include "processes_local_p.h" #include "processes_remote_p.h" #include "processes_atop_p.h" #include "process.h" #include #include #include #include //for sysconf #include /* if porting to an OS without signal.h please #define SIGTERM to something */ #include namespace KSysGuard { class Processes::Private { public: Private(Processes *q_ptr) { mFakeProcess.setParent(&mFakeProcess); - mAbstractProcesses = 0; - mHistoricProcesses = 0; + mAbstractProcesses = nullptr; + mHistoricProcesses = nullptr; mIsLocalHost = true; mProcesses.insert(-1, &mFakeProcess); mElapsedTimeMilliSeconds = 0; mHavePreviousIoValues = false; - mUpdateFlags = 0; + mUpdateFlags = nullptr; mUsingHistoricalData = false; q = q_ptr; } ~Private(); void markProcessesAsEnded(long pid); QSet mToBeProcessed; QSet mProcessedLastTime; QSet mEndedProcesses; ///< Processes that have finished QHash mProcesses; ///< This must include mFakeProcess at pid -1 QList mListProcesses; ///< A list of the processes. Does not include mFakeProcesses Process mFakeProcess; ///< A fake process with pid -1 just so that even init points to a parent AbstractProcesses *mAbstractProcesses; ///< The OS specific code to get the process information ProcessesATop *mHistoricProcesses; ///< A way to get historic information about processes bool mIsLocalHost; ///< Whether this is localhost or not QTime mLastUpdated; ///< This is the time we last updated. Used to calculate cpu usage. long mElapsedTimeMilliSeconds; ///< The number of milliseconds (1000ths of a second) that passed since the last update Processes::UpdateFlags mUpdateFlags; bool mHavePreviousIoValues; ///< This is whether we updated the IO value on the last update bool mUsingHistoricalData; ///< Whether to return historical data for updateProcess() etc Processes *q; }; Processes::Private::~Private() { Q_FOREACH(Process *process, mProcesses) { if(process != &mFakeProcess) delete process; } mProcesses.clear(); mListProcesses.clear(); delete mAbstractProcesses; - mAbstractProcesses = NULL; + mAbstractProcesses = nullptr; } Processes::Processes(const QString &host, QObject *parent) : QObject(parent), d(new Private(this)) { if(host.isEmpty()) { d->mAbstractProcesses = new ProcessesLocal(); } else { ProcessesRemote *remote = new ProcessesRemote(host); d->mAbstractProcesses = remote; connect(remote, &ProcessesRemote::runCommand, this, &Processes::runCommand); } d->mIsLocalHost = host.isEmpty(); connect( d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); } Processes::~Processes() { delete d; } Processes::Error Processes::lastError() const { return d->mAbstractProcesses->errorCode; } Process *Processes::getProcess(long pid) const { return d->mProcesses.value(pid); } const QList &Processes::getAllProcesses() const { return d->mListProcesses; } int Processes::processCount() const { return d->mListProcesses.count(); } bool Processes::updateProcess( Process *ps, long ppid) { Process *parent = d->mProcesses.value(ppid, &d->mFakeProcess); Q_ASSERT(parent); //even init has a non-null parent - the mFakeProcess if(ps->parent() != parent) { emit beginMoveProcess(ps, parent/*new parent*/); //Processes has been reparented Process *p = ps; do { p = p->parent(); p->numChildren()--; } while (p->pid()!= -1); Q_ASSERT(ps != parent); ps->parent()->children().removeAll(ps); ps->setParent(parent); //the parent has changed parent->children().append(ps); p = ps; do { p = p->parent(); p->numChildren()++; } while (p->pid()!= -1); emit endMoveProcess(); Q_ASSERT(ps != parent); ps->setParent(parent); } ps->setParentPid(ppid); bool success = updateProcessInfo(ps); emit processChanged(ps, false); return success; } bool Processes::updateProcessInfo(Process *ps) { //Now we can actually get the process info qlonglong oldUserTime = ps->userTime(); qlonglong oldSysTime = ps->sysTime(); qlonglong oldIoCharactersRead = 0; qlonglong oldIoCharactersWritten = 0; qlonglong oldIoReadSyscalls = 0; qlonglong oldIoWriteSyscalls = 0; qlonglong oldIoCharactersActuallyRead = 0; qlonglong oldIoCharactersActuallyWritten = 0; if(d->mUpdateFlags.testFlag(Processes::IOStatistics)) { oldIoCharactersRead = ps->ioCharactersRead(); oldIoCharactersWritten = ps->ioCharactersWritten(); oldIoReadSyscalls = ps->ioReadSyscalls(); oldIoWriteSyscalls = ps->ioWriteSyscalls(); oldIoCharactersActuallyRead = ps->ioCharactersActuallyRead(); oldIoCharactersActuallyWritten = ps->ioCharactersActuallyWritten(); } ps->setChanges(Process::Nothing); bool success; if(d->mUsingHistoricalData) success = d->mHistoricProcesses->updateProcessInfo(ps->pid(), ps); else success = d->mAbstractProcesses->updateProcessInfo(ps->pid(), ps); //Now we have the process info. Calculate the cpu usage and total cpu usage for itself and all its parents if(!d->mUsingHistoricalData && d->mElapsedTimeMilliSeconds != 0) { //Update the user usage and sys usage #ifndef Q_OS_NETBSD /* The elapsed time is the d->mElapsedTimeMilliSeconds * (which is of the order 2 seconds or so) plus a small * correction where we get the amount of time elapsed since * we start processing. This is because the processing itself * can take a non-trivial amount of time. */ int elapsedTime = ps->elapsedTimeMilliSeconds(); ps->setElapsedTimeMilliSeconds(d->mLastUpdated.elapsed()); elapsedTime = ps->elapsedTimeMilliSeconds() - elapsedTime + d->mElapsedTimeMilliSeconds; if(elapsedTime) { ps->setUserUsage((int)(((ps->userTime() - oldUserTime)*1000.0) / elapsedTime)); ps->setSysUsage((int)(((ps->sysTime() - oldSysTime)*1000.0) / elapsedTime)); } #endif if(d->mUpdateFlags.testFlag(Processes::IOStatistics)) { if( d->mHavePreviousIoValues ) { ps->setIoCharactersReadRate((ps->ioCharactersRead() - oldIoCharactersRead) * 1000.0 / elapsedTime); ps->setIoCharactersWrittenRate((ps->ioCharactersWritten() - oldIoCharactersWritten) * 1000.0 / elapsedTime); ps->setIoReadSyscallsRate((ps->ioReadSyscalls() - oldIoReadSyscalls) * 1000.0 / elapsedTime); ps->setIoWriteSyscallsRate((ps->ioWriteSyscalls() - oldIoWriteSyscalls) * 1000.0 / elapsedTime); ps->setIoCharactersActuallyReadRate((ps->ioCharactersActuallyRead() - oldIoCharactersActuallyRead) * 1000.0 / elapsedTime); ps->setIoCharactersActuallyWrittenRate((ps->ioCharactersActuallyWritten() - oldIoCharactersActuallyWritten) * 1000.0 / elapsedTime); } else d->mHavePreviousIoValues = true; } else if(d->mHavePreviousIoValues) { d->mHavePreviousIoValues = false; ps->setIoCharactersReadRate(0); ps->setIoCharactersWrittenRate(0); ps->setIoReadSyscallsRate(0); ps->setIoWriteSyscallsRate(0); ps->setIoCharactersActuallyReadRate(0); ps->setIoCharactersActuallyWrittenRate(0); } } if(d->mUsingHistoricalData || d->mElapsedTimeMilliSeconds != 0) { ps->setTotalUserUsage(ps->userUsage()); ps->setTotalSysUsage(ps->sysUsage()); if(ps->userUsage() != 0 || ps->sysUsage() != 0) { Process *p = ps->parent(); while(p->pid() != -1) { p->totalUserUsage() += ps->userUsage(); p->totalSysUsage() += ps->sysUsage(); emit processChanged(p, true); p = p->parent(); } } } return success; } bool Processes::addProcess(long pid, long ppid) { Process *parent = d->mProcesses.value(ppid); if(!parent) { //Under race conditions, the parent could have already quit //In this case, attach to top leaf parent = &d->mFakeProcess; Q_ASSERT(parent); //even init has a non-null parent - the mFakeProcess } //it's a new process - we need to set it up Process *ps = new Process(pid, ppid, parent); emit beginAddProcess(ps); d->mProcesses.insert(pid, ps); ps->setIndex(d->mListProcesses.count()); d->mListProcesses.append(ps); ps->parent()->children().append(ps); Process *p = ps; do { Q_ASSERT(p); p = p->parent(); p->numChildren()++; } while (p->pid() != -1); ps->setParentPid(ppid); //Now we can actually get the process info bool success = updateProcessInfo(ps); emit endAddProcess(); return success; } bool Processes::updateOrAddProcess( long pid) { long ppid; if(d->mUsingHistoricalData) ppid = d->mHistoricProcesses->getParentPid(pid); else ppid = d->mAbstractProcesses->getParentPid(pid); if (ppid == pid) //Shouldn't ever happen ppid = -1; if(d->mToBeProcessed.contains(ppid)) { //Make sure that we update the parent before we update this one. Just makes things a bit easier. d->mToBeProcessed.remove(ppid); d->mProcessedLastTime.remove(ppid); //It may or may not be here - remove it if it is there updateOrAddProcess(ppid); } Process *ps = d->mProcesses.value(pid); if(!ps) return addProcess(pid, ppid); else return updateProcess(ps, ppid); } void Processes::updateAllProcesses(long updateDurationMS, Processes::UpdateFlags updateFlags) { d->mUpdateFlags = updateFlags; if(d->mUsingHistoricalData || d->mLastUpdated.elapsed() >= updateDurationMS || !d->mLastUpdated.isValid()) { d->mElapsedTimeMilliSeconds = d->mLastUpdated.restart(); if(d->mUsingHistoricalData) d->mHistoricProcesses->updateAllProcesses(d->mUpdateFlags); else d->mAbstractProcesses->updateAllProcesses(d->mUpdateFlags); //For a local machine, this will directly call Processes::processesUpdated() } } void Processes::processesUpdated() { //First really delete any processes that ended last time long pid; { QSetIterator i(d->mEndedProcesses); while( i.hasNext() ) { pid = i.next(); deleteProcess(pid); } } if(d->mUsingHistoricalData) d->mToBeProcessed = d->mHistoricProcesses->getAllPids(); else d->mToBeProcessed = d->mAbstractProcesses->getAllPids(); QSet beingProcessed(d->mToBeProcessed); //keep a copy so that we can replace mProcessedLastTime with this at the end of this function { QMutableSetIterator i(d->mToBeProcessed); while( i.hasNext()) { pid = i.next(); i.remove(); d->mProcessedLastTime.remove(pid); //It may or may not be here - remove it if it is there updateOrAddProcess(pid); //This adds the process or changes an existing one i.toFront(); //we can remove entries from this set elsewhere, so our iterator might be invalid. Reset it back to the start of the set } } { QSetIterator i(d->mProcessedLastTime); while( i.hasNext()) { //We saw these pids last time, but not this time. That means we have to mark them for deletion now pid = i.next(); d->markProcessesAsEnded(pid); } d->mEndedProcesses = d->mProcessedLastTime; } d->mProcessedLastTime = beingProcessed; //update the set for next time this function is called return; } void Processes::Private::markProcessesAsEnded(long pid) { Q_ASSERT(pid >= 0); Process *process = mProcesses.value(pid); if(!process) return; process->setStatus(Process::Ended); emit q->processChanged(process, false); } void Processes::deleteProcess(long pid) { Q_ASSERT(pid >= 0); Process *process = d->mProcesses.value(pid); if(!process) return; Q_FOREACH( Process *it, process->children()) { d->mProcessedLastTime.remove(it->pid()); deleteProcess(it->pid()); } emit beginRemoveProcess(process); d->mProcesses.remove(pid); d->mListProcesses.removeAll(process); process->parent()->children().removeAll(process); Process *p = process; do { Q_ASSERT(p); p = p->parent(); p->numChildren()--; } while (p->pid() != -1); #ifndef QT_NO_DEBUG int i = 0; #endif Q_FOREACH( Process *it, d->mListProcesses ) { if(it->index() > process->index()) it->setIndex(it->index() - 1); #ifndef QT_NO_DEBUG Q_ASSERT(it->index() == i++); #endif } delete process; emit endRemoveProcess(); } bool Processes::killProcess(long pid) { return sendSignal(pid, SIGTERM); } bool Processes::sendSignal(long pid, int sig) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->sendSignal(pid, sig); } bool Processes::setNiceness(long pid, int priority) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->setNiceness(pid, priority); } bool Processes::setScheduler(long pid, KSysGuard::Process::Scheduler priorityClass, int priority) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->setScheduler(pid, priorityClass, priority); } bool Processes::setIoNiceness(long pid, KSysGuard::Process::IoPriorityClass priorityClass, int priority) { d->mAbstractProcesses->errorCode = Unknown; if(d->mUsingHistoricalData) { d->mAbstractProcesses->errorCode = NotSupported; return false; } return d->mAbstractProcesses->setIoNiceness(pid, priorityClass, priority); } bool Processes::supportsIoNiceness() { if(d->mUsingHistoricalData) return false; return d->mAbstractProcesses->supportsIoNiceness(); } long long Processes::totalPhysicalMemory() { return d->mAbstractProcesses->totalPhysicalMemory(); } long Processes::numberProcessorCores() { return d->mAbstractProcesses->numberProcessorCores(); } void Processes::answerReceived( int id, const QList& answer ) { KSysGuard::ProcessesRemote *processes = qobject_cast(d->mAbstractProcesses); if(processes) processes->answerReceived(id, answer); } QList< QPair > Processes::historiesAvailable() const { if(!d->mIsLocalHost) return QList< QPair >(); if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(); return d->mHistoricProcesses->historiesAvailable(); } void Processes::useCurrentData() { if(d->mUsingHistoricalData) { delete d->mHistoricProcesses; - d->mHistoricProcesses = NULL; + d->mHistoricProcesses = nullptr; connect( d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); d->mUsingHistoricalData = false; } } bool Processes::setViewingTime(const QDateTime &when) { d->mAbstractProcesses->errorCode = Unknown; if(!d->mIsLocalHost) { d->mAbstractProcesses->errorCode = NotSupported; return false; } if(!d->mUsingHistoricalData) { if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(); disconnect( d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); connect( d->mHistoricProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated); d->mUsingHistoricalData = true; } return d->mHistoricProcesses->setViewingTime(when); } bool Processes::loadHistoryFile(const QString &filename) { d->mAbstractProcesses->errorCode = Unknown; if(!d->mIsLocalHost) { d->mAbstractProcesses->errorCode = NotSupported; return false; } if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(false); return d->mHistoricProcesses->loadHistoryFile(filename); } QString Processes::historyFileName() const { if(!d->mIsLocalHost || !d->mHistoricProcesses) return QString::null; return d->mHistoricProcesses->historyFileName(); } QDateTime Processes::viewingTime() const { if(!d->mIsLocalHost || !d->mHistoricProcesses) return QDateTime(); return d->mHistoricProcesses->viewingTime(); } bool Processes::isHistoryAvailable() const { if(!d->mIsLocalHost) return false; if(!d->mHistoricProcesses) d->mHistoricProcesses = new ProcessesATop(); return d->mHistoricProcesses->isHistoryAvailable(); } } diff --git a/processcore/processes.h b/processcore/processes.h index c3d9ead..2dba69e 100644 --- a/processcore/processes.h +++ b/processcore/processes.h @@ -1,268 +1,268 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 PROCESSES_H_ #define PROCESSES_H_ #include "process.h" #include #include namespace KSysGuard { /** * This class retrieves the processes currently running in an OS independent way. * * To use, do something like: * * \code * #include "processes.h> * #include "process.h> * * KSysGuard::Processes *processes = new KSysGuard::Processes() * QHash processlist = processes->getProcesses(); * foreach( Process * process, processlist) { * kDebug() << "Process with pid " << process->pid() << " is called " << process->name; * } * delete processes; * processes = NULL; * \endcode * * @author John Tapsell */ #ifdef Q_WS_WIN class Processes : public QObject #else class Q_DECL_EXPORT Processes : public QObject #endif { Q_OBJECT public: - Processes(const QString &hostname = QString::null, QObject * parent = 0); + Processes(const QString &hostname = QString::null, QObject * parent = nullptr); ~Processes() override; enum UpdateFlag { StandardInformation = 1, IOStatistics = 2, XMemory = 4 }; Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag) enum Error { Unknown = 0, InvalidPid, InvalidParameter, InsufficientPermissions, ProcessDoesNotExistOrZombie, NotSupported }; /** * Update all the process information. After calling this, /proc or equivalent is scanned and * the signals processChanged, etc are emitted. * * Set updateDuration to whatever time period that you update, in milliseconds. * For example, if you update every 2000ms, set this to 2000. That way it won't update * more often than needed. */ - void updateAllProcesses(long updateDurationMS = 0, Processes::UpdateFlags updateFlags = 0); + void updateAllProcesses(long updateDurationMS = 0, Processes::UpdateFlags updateFlags = nullptr); /** * Return information for one specific process. Call getProcess(0) to get the * fake process used as the top most parent for all processes. * This doesn't fetch any new information and so returns almost instantly. * Call updateAllProcesses() to actually fetch the process information. */ Process *getProcess(long pid) const; /** * Get the error code for the last command that failed. */ Error lastError() const; /** * Kill the specified process. You may not have the privilege to kill the process. * The process may also chose to ignore the command. Send the SIGKILL signal to kill * the process immediately. You may lose any unsaved data. * * @returns Successful or not in killing the process */ bool killProcess(long pid); /** * Send the specified named POSIX signal to the process given. * * For example, to indicate for process 324 to STOP do: * \code * #include * ... * * KSysGuard::Processes::sendSignal(23, SIGSTOP); * \endcode * */ bool sendSignal(long pid, int sig); /** * Set the priority for a process. This is from 19 (very nice, lowest priority) to * -20 (highest priority). The default value for a process is 0. * * @return false if you do not have permission to set the priority */ bool setNiceness(long pid, int priority); /** * Set the scheduler for a process. This is defined according to POSIX.1-2001 * See "man sched_setscheduler" for more information. * * @p priorityClass One of SCHED_FIFO, SCHED_RR, SCHED_OTHER, and SCHED_BATCH * @p priority Set to 0 for SCHED_OTHER and SCHED_BATCH. Between 1 and 99 for SCHED_FIFO and SCHED_RR * @return false if you do not have permission to set the priority */ bool setScheduler(long pid, KSysGuard::Process::Scheduler priorityClass, int priority); /** * Set the io priority for a process. This is from 7 (very nice, lowest io priority) to * 0 (highest priority). The default value is determined as: io_nice = (cpu_nice + 20) / 5. * * @return false if you do not have permission to set the priority */ bool setIoNiceness(long pid, KSysGuard::Process::IoPriorityClass priorityClass, int priority); /** * Returns true if ionice is supported on this system */ bool supportsIoNiceness(); /** * Return the internal pointer of all the processes. The order of the processes * is guaranteed to never change. Call updateAllProcesses() first to actually * update the information. */ const QList< Process *> &getAllProcesses() const; /** * Return the number of processes. Call updateAllProcesses() to actually * update the information. * * This is equivalent to getAllProcesses().count() */ int processCount() const; /** * Return the total amount of physical memory in KB. This is fast (just a system call) * Returns 0 on error */ long long totalPhysicalMemory(); /** * Return the number of processor cores enabled. * (A system can disable processors. Disabled processors are not counted here). * This is fast (just a system call) */ long numberProcessorCores(); /** Update/add process for given pid immediately */ bool updateOrAddProcess( long pid); /** Whether we can get historic process and system data */ bool isHistoryAvailable() const; /** Stop using historical data and use the most recent up-to-date data */ void useCurrentData(); /** Return a list of end times and intervals for all the available history */ QList< QPair > historiesAvailable() const; /** Use historical process data closest to the given date-time. * Returns false if it is outside the range available or there is a problem * getting the data. */ bool setViewingTime(const QDateTime &when); QDateTime viewingTime() const; bool loadHistoryFile(const QString &filename); QString historyFileName() const; public Q_SLOTS: /** The abstract processes has updated its list of processes */ void processesUpdated(); Q_SIGNALS: /** The data for a process has changed. * if @p onlyTotalCpu is set, only the total cpu usage has been updated. * process->changes contains a bit field indicating what has changed since the last time this was emitted * for this process */ void processChanged( KSysGuard::Process *process, bool onlyTotalCpu); /** * This indicates we are about to add a process in the model. * The process already has the pid, ppid and tree_parent set up. */ void beginAddProcess( KSysGuard::Process *process); /** * We have finished inserting a process */ void endAddProcess(); /** * This indicates we are about to remove a process in the model. Emit the appropriate signals */ void beginRemoveProcess( KSysGuard::Process *process); /** * We have finished removing a process */ void endRemoveProcess(); /** * This indicates we are about move a process from one parent to another. */ void beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent); /** * We have finished moving the process */ void endMoveProcess(); protected: class Private; Private *d; private: inline void deleteProcess(long pid); bool updateProcess( Process *process, long ppid); bool updateProcessInfo(Process *ps); bool addProcess(long pid, long ppid); Q_SIGNALS: /** For a remote machine, we rely on being able to communicate with ksysguardd. * This must be dealt with by the program including this widget. It must listen to our * 'runCommand' signal, and run the given command, with the given id. */ void runCommand(const QString &command, int id); public: /** For a remote machine, we rely on being able to communicate with ksysguardd. * The programming using this must call this slot when an answer is received from ksysguardd, * in response to a runCommand request. The id identifies the answer */ void answerReceived( int id, const QList& answer ); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Processes::UpdateFlags) } #endif diff --git a/processcore/processes_atop_p.cpp b/processcore/processes_atop_p.cpp index cf0e224..ba17be2 100644 --- a/processcore/processes_atop_p.cpp +++ b/processcore/processes_atop_p.cpp @@ -1,375 +1,375 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "processes_atop_p.h" #include "process.h" #include "atop_p.h" #include "processcore_debug.h" #include #include #include #include #include #include #include #include namespace KSysGuard { class ProcessesATop::Private { public: Private(); ~Private(); QFile atopLog; bool ready; bool loadDataForHistory(int index); bool loadHistoryFile(const QString &filename); RawHeader rh; RawRecord rr; // SStat sstats; PStat *pstats; QList pids; //This is a list of process pid's, in the exact same order as pstats QString lastError; QList historyOffsets; //< The file offset where each history is stored QList< QPair > historyTimes; //< The end time for each history record and its interval, probably in order from oldest to newest int currentlySelectedIndex; }; ProcessesATop::Private::Private() : ready(false), - pstats(NULL), + pstats(nullptr), currentlySelectedIndex(-1) { } ProcessesATop::Private::~Private() { } QString ProcessesATop::historyFileName() const { return d->atopLog.fileName(); } bool ProcessesATop::loadHistoryFile(const QString &filename) { return d->loadHistoryFile(filename); } bool ProcessesATop::Private::loadHistoryFile(const QString &filename) { atopLog.setFileName(filename); ready = false; currentlySelectedIndex = -1; if(!atopLog.exists()) { lastError = "File " + filename + " does not exist"; return false; } if(!atopLog.open(QIODevice::ReadOnly)) { lastError = "Could not open file" + filename; return false; } int sizeRead = atopLog.read((char*)(&rh), sizeof(RawHeader)); if(sizeRead != sizeof(RawHeader)) { lastError = "Could not read header from file" + filename; return false; } if(rh.magic != ATOPLOGMAGIC) { lastError = "File " + filename + " does not contain raw atop/atopsar output (wrong magic number)"; return false; } if (/*rh.sstatlen != sizeof(SStat) ||*/ rh.pstatlen != sizeof(PStat) || rh.rawheadlen != sizeof(RawHeader) || rh.rawreclen != sizeof(RawRecord) ) { lastError = "File " + filename + " has incompatible format"; if (rh.aversion & 0x8000) { lastError = QStringLiteral("(created by version %1.%2. This program understands the format written by version 1.23") .arg((rh.aversion >> 8) & 0x7f).arg(rh.aversion & 0xff); } return false; } /* Read the first data header */ int offset = atopLog.pos(); historyTimes.clear(); historyOffsets.clear(); while( !atopLog.atEnd() && atopLog.read((char*)(&rr), sizeof(RawRecord)) == sizeof(RawRecord) ) { historyOffsets << offset; historyTimes << QPair(QDateTime::fromTime_t(rr.curtime), rr.interval); offset += sizeof(RawRecord) + rr.scomplen + rr.pcomplen; atopLog.seek(offset); } if(currentlySelectedIndex >= historyOffsets.size()) currentlySelectedIndex = historyOffsets.size() - 1; ready = true; return true; } bool ProcessesATop::Private::loadDataForHistory(int index) { delete [] pstats; - pstats = NULL; + pstats = nullptr; atopLog.seek(historyOffsets.at(index)); /*Read the first data header */ if( atopLog.read((char*)(&rr), sizeof(RawRecord)) != sizeof(RawRecord) ) { lastError = QStringLiteral("Could not read data header"); return false; } if( historyTimes.at(index).first != QDateTime::fromTime_t(rr.curtime) || historyTimes.at(index).second != rr.interval) { lastError = QStringLiteral("INTERNAL ERROR WITH loadDataForHistory"); ready = false; return false; } atopLog.seek(atopLog.pos() + rr.scomplen); QByteArray processRecord; processRecord.resize(rr.pcomplen); // qToBigEndian( rr.pcomplen, (uchar*)processRecord.data() ); unsigned int dataRead = 0; do { int ret = atopLog.read( processRecord.data() + dataRead, rr.pcomplen - dataRead); if(ret == -1) { lastError = QStringLiteral("Stream interrupted while being read"); return false; } dataRead += ret; } while(dataRead < rr.pcomplen); Q_ASSERT(dataRead == rr.pcomplen); //Q_ASSERT( (index + 1 ==historyTimes.count()) || atopLog.pos() == historyTimes.at(index+1)); pstats = new PStat[ rr.nlist ]; unsigned long uncompressedLength= sizeof(struct PStat) * rr.nlist; int ret = uncompress((Byte *)pstats, &uncompressedLength, (Byte *)processRecord.constData(), rr.pcomplen); if(ret != Z_OK && ret != Z_STREAM_END && ret != Z_NEED_DICT) { switch(ret) { case Z_MEM_ERROR: lastError = QStringLiteral("Could not uncompress record data due to lack of memory"); break; case Z_BUF_ERROR: lastError = QStringLiteral("Could not uncompress record data due to lack of room in buffer"); break; case Z_DATA_ERROR: lastError = QStringLiteral("Could not uncompress record data due to corrupted data"); break; default: lastError = "Could not uncompress record data due to unexpected error: " + QString::number(ret); break; } delete [] pstats; - pstats = NULL; + pstats = nullptr; return false; } pids.clear(); for(uint i = 0; i < rr.nlist; i++) { pids << pstats[i].gen.pid; } return true; } ProcessesATop::ProcessesATop(bool loadDefaultFile) : d(new Private()) { if(loadDefaultFile) loadHistoryFile(QStringLiteral("/var/log/atop.log")); } bool ProcessesATop::isHistoryAvailable() const { return d->ready; } long ProcessesATop::getParentPid(long pid) { int index = d->pids.indexOf(pid); if(index < 0) return 0; return d->pstats[index].gen.ppid; } bool ProcessesATop::updateProcessInfo( long pid, Process *process) { int index = d->pids.indexOf(pid); if(index < 0) return false; PStat &p = d->pstats[index]; process->setParentPid(p.gen.ppid); process->setUid(p.gen.ruid); process->setEuid(p.gen.ruid); process->setSuid(p.gen.ruid); process->setFsuid(p.gen.ruid); process->setGid(p.gen.rgid); process->setEgid(p.gen.rgid); process->setSgid(p.gen.rgid); process->setFsgid(p.gen.rgid); process->setTracerpid(-1); process->setNumThreads(p.gen.nthr); // process->setTty process->setUserTime(p.cpu.utime * 100/d->rh.hertz);//check - divide by interval maybe? process->setSysTime(p.cpu.stime * 100/d->rh.hertz); //check process->setUserUsage(process->userTime() / d->rr.interval); process->setSysUsage(process->sysTime() / d->rr.interval); process->setNiceLevel(p.cpu.nice); // process->setscheduler(p.cpu.policy); process->setVmSize(p.mem.vmem); process->setVmRSS(p.mem.rmem); process->vmSizeChange() = p.mem.vgrow; process->vmRSSChange() = p.mem.rgrow; process->setVmURSS(0); process->vmURSSChange() = 0; /* Fill in name and command */ QString name = QString::fromUtf8(p.gen.name, qstrnlen(p.gen.name,PNAMLEN)); QString command = QString::fromUtf8(p.gen.cmdline, qstrnlen(p.gen.cmdline,CMDLEN)); //cmdline separates parameters with the NULL character if(!command.isEmpty()) { if(command.startsWith(name)) { int index = command.indexOf(QChar('\0')); name = command.left(index); } command.replace('\0', ' '); } process->setName(name); process->setCommand(command); /* Fill in state */ switch(p.gen.state) { case 'E': process->setStatus(Process::Ended); break; case 'R': process->setStatus(Process::Running); break; case 'S': process->setStatus(Process::Sleeping); break; case 'D': process->setStatus(Process::DiskSleep); break; case 'Z': process->setStatus(Process::Zombie); break; case 'T': process->setStatus(Process::Stopped); break; case 'W': process->setStatus(Process::Paging); break; default: process->setStatus(Process::OtherStatus); break; } return true; } QDateTime ProcessesATop::viewingTime() const { if(!d->ready) return QDateTime(); return d->historyTimes.at(d->currentlySelectedIndex).first; } bool ProcessesATop::setViewingTime(const QDateTime &when) { QPair tmpWhen(when, 0); QList< QPair >::iterator i = qUpperBound(d->historyTimes.begin(), d->historyTimes.end(), tmpWhen); if(i->first == when || (i->first > when && i->first.addSecs(-i->second) <= when)) { //We found the time :) d->currentlySelectedIndex = i - d->historyTimes.begin(); bool success = d->loadDataForHistory(d->currentlySelectedIndex); if(!success) qCWarning(LIBKSYSGUARD) << d->lastError; return success; } return false; } QList< QPair > ProcessesATop::historiesAvailable() const { return d->historyTimes; } QSet ProcessesATop::getAllPids( ) { return d->pids.toSet(); } bool ProcessesATop::sendSignal(long pid, int sig) { Q_UNUSED(pid); Q_UNUSED(sig); errorCode = Processes::NotSupported; return false; } bool ProcessesATop::setNiceness(long pid, int priority) { Q_UNUSED(pid); Q_UNUSED(priority); errorCode = Processes::NotSupported; return false; } bool ProcessesATop::setScheduler(long pid, int priorityClass, int priority) { Q_UNUSED(pid); Q_UNUSED(priorityClass); Q_UNUSED(priority); errorCode = Processes::NotSupported; return false; } bool ProcessesATop::setIoNiceness(long pid, int priorityClass, int priority) { Q_UNUSED(pid); Q_UNUSED(priorityClass); Q_UNUSED(priority); errorCode = Processes::NotSupported; return false; } bool ProcessesATop::supportsIoNiceness() { return false; } long long ProcessesATop::totalPhysicalMemory() { return 0; } ProcessesATop::~ProcessesATop() { delete d; } } diff --git a/processcore/processes_linux_p.cpp b/processcore/processes_linux_p.cpp index d36cbda..55146bf 100644 --- a/processcore/processes_linux_p.cpp +++ b/processcore/processes_linux_p.cpp @@ -1,717 +1,717 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "processes_local_p.h" #include "process.h" #include #include #include #include #include #include //for sysconf #include //for kill and setNice #include #include #include #include #include #include //for ionice #include #include //for getsched #include #define PROCESS_BUFFER_SIZE 1000 /* For ionice */ extern int sys_ioprio_set(int, int, int); extern int sys_ioprio_get(int, int); #define HAVE_IONICE /* Check if this system has ionice */ #if !defined(SYS_ioprio_get) || !defined(SYS_ioprio_set) /* All new kernels have SYS_ioprio_get and _set defined, but for the few that do not, here are the definitions */ #if defined(__i386__) #define __NR_ioprio_set 289 #define __NR_ioprio_get 290 #elif defined(__ppc__) || defined(__powerpc__) #define __NR_ioprio_set 273 #define __NR_ioprio_get 274 #elif defined(__x86_64__) #define __NR_ioprio_set 251 #define __NR_ioprio_get 252 #elif defined(__ia64__) #define __NR_ioprio_set 1274 #define __NR_ioprio_get 1275 #else #ifdef __GNUC__ #warning "This architecture does not support IONICE. Disabling ionice feature." #endif #undef HAVE_IONICE #endif /* Map these to SYS_ioprio_get */ #define SYS_ioprio_get __NR_ioprio_get #define SYS_ioprio_set __NR_ioprio_set #endif /* !SYS_ioprio_get */ /* Set up ionice functions */ #ifdef HAVE_IONICE #define IOPRIO_WHO_PROCESS 1 #define IOPRIO_CLASS_SHIFT 13 /* Expose the kernel calls to userspace via syscall * See man ioprio_set and man ioprio_get for information on these functions */ static int ioprio_set(int which, int who, int ioprio) { return syscall(SYS_ioprio_set, which, who, ioprio); } static int ioprio_get(int which, int who) { return syscall(SYS_ioprio_get, which, who); } #endif namespace KSysGuard { class ProcessesLocal::Private { public: Private() { mProcDir = opendir( "/proc" );} ~Private(); inline bool readProcStatus(const QString &dir, Process *process); inline bool readProcStat(const QString &dir, Process *process); inline bool readProcStatm(const QString &dir, Process *process); inline bool readProcCmdline(const QString &dir, Process *process); inline bool getNiceness(long pid, Process *process); inline bool getIOStatistics(const QString &dir, Process *process); QFile mFile; char mBuffer[PROCESS_BUFFER_SIZE+1]; //used as a buffer to read data into DIR* mProcDir; }; ProcessesLocal::Private::~Private() { closedir(mProcDir); } ProcessesLocal::ProcessesLocal() : d(new Private()) { } bool ProcessesLocal::Private::readProcStatus(const QString &dir, Process *process) { mFile.setFileName(dir + "status"); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ process->setUid(0); process->setGid(0); process->setTracerpid(-1); process->setNumThreads(0); int size; int found = 0; //count how many fields we found while( (size = mFile.readLine( mBuffer, sizeof(mBuffer))) > 0) { //-1 indicates an error switch( mBuffer[0]) { case 'N': if((unsigned int)size > sizeof("Name:") && qstrncmp(mBuffer, "Name:", sizeof("Name:")-1) == 0) { if(process->command().isEmpty()) process->setName(QString::fromLocal8Bit(mBuffer + sizeof("Name:")-1, size-sizeof("Name:")+1).trimmed()); if(++found == 5) goto finish; } break; case 'U': if((unsigned int)size > sizeof("Uid:") && qstrncmp(mBuffer, "Uid:", sizeof("Uid:")-1) == 0) { qlonglong uid; qlonglong euid; qlonglong suid; qlonglong fsuid; sscanf(mBuffer + sizeof("Uid:") -1, "%Ld %Ld %Ld %Ld", &uid, &euid, &suid, &fsuid); process->setUid(uid); process->setEuid(euid); process->setSuid(suid); process->setFsuid(fsuid); if(++found == 5) goto finish; } break; case 'G': if((unsigned int)size > sizeof("Gid:") && qstrncmp(mBuffer, "Gid:", sizeof("Gid:")-1) == 0) { qlonglong gid, egid, sgid, fsgid; sscanf(mBuffer + sizeof("Gid:")-1, "%Ld %Ld %Ld %Ld", &gid, &egid, &sgid, &fsgid); process->setGid(gid); process->setEgid(egid); process->setSgid(sgid); process->setFsgid(fsgid); if(++found == 5) goto finish; } break; case 'T': if((unsigned int)size > sizeof("TracerPid:") && qstrncmp(mBuffer, "TracerPid:", sizeof("TracerPid:")-1) == 0) { process->setTracerpid(atol(mBuffer + sizeof("TracerPid:")-1)); if (process->tracerpid() == 0) process->setTracerpid(-1); if(++found == 5) goto finish; } else if((unsigned int)size > sizeof("Threads:") && qstrncmp(mBuffer, "Threads:", sizeof("Threads:")-1) == 0) { process->setNumThreads(atol(mBuffer + sizeof("Threads:")-1)); if(++found == 5) goto finish; } break; default: break; } } finish: mFile.close(); return true; } long ProcessesLocal::getParentPid(long pid) { if (pid <= 0) return -1; d->mFile.setFileName("/proc/" + QString::number(pid) + "/stat"); if(!d->mFile.open(QIODevice::ReadOnly)) return -1; /* process has terminated in the meantime */ int size; //amount of data read in if( (size = d->mFile.readLine( d->mBuffer, sizeof(d->mBuffer))) <= 0) { //-1 indicates nothing read d->mFile.close(); return -1; } d->mFile.close(); char *word = d->mBuffer; //The command name is the second parameter, and this ends with a closing bracket. So find the last //closing bracket and start from there word = strrchr(word, ')'); if (!word) return -1; word++; //Nove to the space after the last ")" int current_word = 1; while(true) { if(word[0] == ' ' ) { if(++current_word == 3) break; } else if(word[0] == 0) { return -1; //end of data - serious problem } word++; } long ppid = atol(++word); if (ppid == 0) return -1; return ppid; } bool ProcessesLocal::Private::readProcStat(const QString &dir, Process *ps) { QString filename = dir + "stat"; // As an optimization, if the last file read in was stat, then we already have this info in memory if(mFile.fileName() != filename) { mFile.setFileName(filename); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ if( mFile.readLine( mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read mFile.close(); return false; } mFile.close(); } char *word = mBuffer; //The command name is the second parameter, and this ends with a closing bracket. So find the last //closing bracket and start from there word = strrchr(word, ')'); if (!word) return false; word++; //Nove to the space after the last ")" int current_word = 1; //We've skipped the process ID and now at the end of the command name char status='\0'; unsigned long long vmSize = 0; unsigned long long vmRSS = 0; while(current_word < 23) { if(word[0] == ' ' ) { ++current_word; switch(current_word) { case 2: //status status=word[1]; // Look at the first letter of the status. // We analyze this after the while loop break; case 6: //ttyNo { int ttyNo = atoi(word+1); int major = ttyNo >> 8; int minor = ttyNo & 0xff; switch(major) { case 136: ps->setTty(QByteArray("pts/") + QByteArray::number(minor)); break; case 5: ps->setTty(QByteArray("tty")); case 4: if(minor < 64) ps->setTty(QByteArray("tty") + QByteArray::number(minor)); else ps->setTty(QByteArray("ttyS") + QByteArray::number(minor-64)); break; default: ps->setTty(QByteArray()); } } break; case 13: //userTime ps->setUserTime(atoll(word+1)); break; case 14: //sysTime ps->setSysTime(atoll(word+1)); break; case 18: //niceLevel ps->setNiceLevel(atoi(word+1)); /*Or should we use getPriority instead? */ break; case 21: // startTime ps->setStartTime(atoll(word+1)); break; case 22: //vmSize vmSize = atoll(word+1); break; case 23: //vmRSS vmRSS = atoll(word+1); break; default: break; } } else if(word[0] == 0) { return false; //end of data - serious problem } word++; } /* There was a "(ps->vmRss+3) * sysconf(_SC_PAGESIZE)" here in the original ksysguard code. I have no idea why! After comparing it to * meminfo and other tools, this means we report the RSS by 12 bytes differently compared to them. So I'm removing the +3 * to be consistent. NEXT TIME COMMENT STRANGE THINGS LIKE THAT! :-) * * Update: I think I now know why - the kernel allocates 3 pages for * tracking information about each the process. This memory isn't * included in vmRSS..*/ ps->setVmRSS(vmRSS * (sysconf(_SC_PAGESIZE) / 1024)); /*convert to KiB*/ ps->setVmSize(vmSize / 1024); /* convert to KiB */ switch( status) { case 'R': ps->setStatus(Process::Running); break; case 'S': ps->setStatus(Process::Sleeping); break; case 'D': ps->setStatus(Process::DiskSleep); break; case 'Z': ps->setStatus(Process::Zombie); break; case 'T': ps->setStatus(Process::Stopped); break; case 'W': ps->setStatus(Process::Paging); break; default: ps->setStatus(Process::OtherStatus); break; } return true; } bool ProcessesLocal::Private::readProcStatm(const QString &dir, Process *process) { #ifdef _SC_PAGESIZE mFile.setFileName(dir + "statm"); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ if( mFile.readLine( mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read mFile.close(); return 0; } mFile.close(); int current_word = 0; char *word = mBuffer; while(true) { if(word[0] == ' ' ) { if(++current_word == 2) //number of pages that are shared break; } else if(word[0] == 0) { return false; //end of data - serious problem } word++; } long shared = atol(word+1); /* we use the rss - shared to find the amount of memory just this app uses */ process->setVmURSS(process->vmRSS() - (shared * sysconf(_SC_PAGESIZE) / 1024)); #else process->setVmURSS(0); #endif return true; } bool ProcessesLocal::Private::readProcCmdline(const QString &dir, Process *process) { if(!process->command().isNull()) return true; //only parse the cmdline once. This function takes up 25% of the CPU time :-/ mFile.setFileName(dir + "cmdline"); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ QTextStream in(&mFile); process->setCommand(in.readAll()); //cmdline separates parameters with the NULL character if(!process->command().isEmpty()) { //extract non-truncated name from cmdline int zeroIndex = process->command().indexOf(QChar('\0')); int processNameStart = process->command().lastIndexOf(QChar('/'), zeroIndex); if(processNameStart == -1) processNameStart = 0; else processNameStart++; QString nameFromCmdLine = process->command().mid(processNameStart, zeroIndex - processNameStart); if(nameFromCmdLine.startsWith(process->name())) process->setName(nameFromCmdLine); process->command().replace('\0', ' '); } mFile.close(); return true; } bool ProcessesLocal::Private::getNiceness(long pid, Process *process) { int sched = sched_getscheduler(pid); switch(sched) { case (SCHED_OTHER): process->setScheduler(KSysGuard::Process::Other); break; case (SCHED_RR): process->setScheduler(KSysGuard::Process::RoundRobin); break; case (SCHED_FIFO): process->setScheduler(KSysGuard::Process::Fifo); break; #ifdef SCHED_IDLE case (SCHED_IDLE): process->setScheduler(KSysGuard::Process::SchedulerIdle); #endif #ifdef SCHED_BATCH case (SCHED_BATCH): process->setScheduler(KSysGuard::Process::Batch); break; #endif default: process->setScheduler(KSysGuard::Process::Other); } if(sched == SCHED_FIFO || sched == SCHED_RR) { struct sched_param param; if(sched_getparam(pid, ¶m) == 0) process->setNiceLevel(param.sched_priority); else process->setNiceLevel(0); //Error getting scheduler parameters. } #ifdef HAVE_IONICE int ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); /* Returns from 0 to 7 for the iopriority, and -1 if there's an error */ if(ioprio == -1) { process->setIoniceLevel(-1); process->setIoPriorityClass(KSysGuard::Process::None); return false; /* Error. Just give up. */ } process->setIoniceLevel(ioprio & 0xff); /* Bottom few bits are the priority */ process->setIoPriorityClass((KSysGuard::Process::IoPriorityClass)(ioprio >> IOPRIO_CLASS_SHIFT)); /* Top few bits are the class */ return true; #else return false; /* Do nothing, if we do not support this architecture */ #endif } bool ProcessesLocal::Private::getIOStatistics(const QString &dir, Process *process) { QString filename = dir + "io"; // As an optimization, if the last file read in was io, then we already have this info in memory mFile.setFileName(filename); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ if( mFile.read( mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read mFile.close(); return false; } mFile.close(); int current_word = 0; //count from 0 char *word = mBuffer; while(current_word < 6 && word[0] != 0) { if(word[0] == ' ' ) { qlonglong number = atoll(word+1); switch(current_word++) { case 0: //rchar - characters read process->setIoCharactersRead(number); break; case 1: //wchar - characters written process->setIoCharactersWritten(number); break; case 2: //syscr - read syscall process->setIoReadSyscalls(number); break; case 3: //syscw - write syscall process->setIoWriteSyscalls(number); break; case 4: //read_bytes - bytes actually read from I/O process->setIoCharactersActuallyRead(number); break; case 5: //write_bytes - bytes actually written to I/O process->setIoCharactersActuallyWritten(number); default: break; } } word++; } return true; } bool ProcessesLocal::updateProcessInfo( long pid, Process *process) { bool success = true; QString dir = "/proc/" + QString::number(pid) + '/'; if(!d->readProcStat(dir, process)) success = false; if(!d->readProcStatus(dir, process)) success = false; if(!d->readProcStatm(dir, process)) success = false; if(!d->readProcCmdline(dir, process)) success = false; if(!d->getNiceness(pid, process)) success = false; if(mUpdateFlags.testFlag(Processes::IOStatistics) && !d->getIOStatistics(dir, process)) success = false; return success; } QSet ProcessesLocal::getAllPids( ) { QSet pids; - if(d->mProcDir==NULL) return pids; //There's not much we can do without /proc + if(d->mProcDir==nullptr) return pids; //There's not much we can do without /proc struct dirent* entry; rewinddir(d->mProcDir); while ( ( entry = readdir( d->mProcDir ) ) ) if ( entry->d_name[ 0 ] >= '0' && entry->d_name[ 0 ] <= '9' ) pids.insert(atol( entry->d_name )); return pids; } bool ProcessesLocal::sendSignal(long pid, int sig) { errno = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } if (kill( (pid_t)pid, sig )) { switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } //Kill failed return false; } return true; } bool ProcessesLocal::setNiceness(long pid, int priority) { errno = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } if (setpriority( PRIO_PROCESS, pid, priority )) { switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EACCES: case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } //set niceness failed return false; } return true; } bool ProcessesLocal::setScheduler(long pid, int priorityClass, int priority) { errno = 0; if(priorityClass == KSysGuard::Process::Other || priorityClass == KSysGuard::Process::Batch || priorityClass == KSysGuard::Process::SchedulerIdle) priority = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } struct sched_param params; params.sched_priority = priority; int policy; switch(priorityClass) { case (KSysGuard::Process::Other): policy = SCHED_OTHER; break; case (KSysGuard::Process::RoundRobin): policy = SCHED_RR; break; case (KSysGuard::Process::Fifo): policy = SCHED_FIFO; break; #ifdef SCHED_IDLE case (KSysGuard::Process::SchedulerIdle): policy = SCHED_IDLE; break; #endif #ifdef SCHED_BATCH case (KSysGuard::Process::Batch): policy = SCHED_BATCH; break; #endif default: errorCode = Processes::NotSupported; return false; } if (sched_setscheduler( pid, policy, ¶ms) != 0) { switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } return false; } return true; } bool ProcessesLocal::setIoNiceness(long pid, int priorityClass, int priority) { errno = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } #ifdef HAVE_IONICE if (ioprio_set(IOPRIO_WHO_PROCESS, pid, priority | priorityClass << IOPRIO_CLASS_SHIFT) == -1) { //set io niceness failed switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } return false; } return true; #else errorCode = Processes::NotSupported; return false; #endif } bool ProcessesLocal::supportsIoNiceness() { #ifdef HAVE_IONICE return true; #else return false; #endif } long long ProcessesLocal::totalPhysicalMemory() { //Try to get the memory via sysconf. Note the cast to long long to try to avoid a long overflow //Should we use sysconf(_SC_PAGESIZE) or getpagesize() ? #ifdef _SC_PHYS_PAGES return ((long long)sysconf(_SC_PHYS_PAGES)) * (sysconf(_SC_PAGESIZE)/1024); #else //This is backup code in case this is not defined. It should never fail on a linux system. d->mFile.setFileName("/proc/meminfo"); if(!d->mFile.open(QIODevice::ReadOnly)) return 0; int size; while( (size = d->mFile.readLine( d->mBuffer, sizeof(d->mBuffer))) > 0) { //-1 indicates an error switch( d->mBuffer[0]) { case 'M': if((unsigned int)size > sizeof("MemTotal:") && qstrncmp(d->mBuffer, "MemTotal:", sizeof("MemTotal:")-1) == 0) { d->mFile.close(); return atoll(d->mBuffer + sizeof("MemTotal:")-1); } } } return 0; // Not found. Probably will never happen #endif } ProcessesLocal::~ProcessesLocal() { delete d; } } diff --git a/processui/ProcessFilter.cpp b/processui/ProcessFilter.cpp index 7225452..9dab5cd 100644 --- a/processui/ProcessFilter.cpp +++ b/processui/ProcessFilter.cpp @@ -1,148 +1,148 @@ /* KSysGuard, the KDE System Guard Copyright (c) 2006-2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "processcore/processcore_debug.h" /* For getuid() */ #include #include #include #include #include "ProcessModel.h" #include "ProcessModel_p.h" #include "ProcessFilter.h" bool ProcessFilter::filterAcceptsRow( int source_row, const QModelIndex & source_parent ) const { if( (mFilter == AllProcesses || mFilter == AllProcessesInTreeForm) && filterRegExp().isEmpty()) return true; //Shortcut for common case ProcessModel *model = static_cast(sourceModel()); const KSysGuard::Process *process; if(model->isSimpleMode()) { if(source_parent.isValid()) { qCDebug(LIBKSYSGUARD) << "Serious error with data. In simple mode, there should be no children"; return true; } process = model->getProcessAtIndex(source_row); } else { - KSysGuard::Process *parent_process = NULL; + KSysGuard::Process *parent_process = nullptr; if(source_parent.isValid()) { parent_process = reinterpret_cast(source_parent.internalPointer()); Q_ASSERT(parent_process); } else { //if(!model->isSimpleMode()) { parent_process = model->getProcess(-1); //Get our 'special' process which should have the root init child Q_ASSERT(parent_process); //} } if(!model->isSimpleMode() && source_row >= parent_process->children().size()) { qCDebug(LIBKSYSGUARD) << "Serious error with data. Source row requested for a non existent row. Requested " << source_row << " of " << parent_process->children().size() << " for " << parent_process->pid(); return true; } process = parent_process->children().at(source_row); } Q_ASSERT(process); long uid = process->uid(); long euid = process->euid(); bool accepted = true; switch(mFilter) { case AllProcesses: case AllProcessesInTreeForm: break; case SystemProcesses: if( uid >= 100 && model->canUserLogin(uid)) accepted = false; break; case UserProcesses: if( (uid < 100 || !model->canUserLogin(uid)) && (euid < 100 || !model->canUserLogin(euid))) accepted = false; break; case OwnProcesses: { long ownuid = getuid(); if(uid != ownuid && process->suid() != ownuid && process->fsuid() != ownuid && euid != ownuid) accepted = false; break; } case ProgramsOnly: if(process->tty().isEmpty()) { if(!model->hasGUIWindow(process->pid())) accepted = false; } else { // login and getty kinda _are_ the tty, so I do not really count them as 'programs'. So make a special case and hide them // Their ppid are 1 (init) so by checking we try to avoid false matches, and speed up checking overall QString name = process->name().section(' ', 0,0); if(process->parentPid() == 1 && (name == QLatin1String("login") || name.endsWith(QLatin1String("getty")))) accepted = false; } break; default: break; } if(accepted) { if(filterRegExp().isEmpty()) return true; //Allow the user to search by PID if(QString::number(process->pid()).contains(filterRegExp())) return true; //None of our tests have rejected it. Pass it on to qsortfilterproxymodel's filter if(QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) return true; } //We did not accept this row at all. //If we are in flat mode, then give up now if(mFilter != AllProcessesInTreeForm) return false; //one of our children might be accepted, so accept this row if our children are accepted. QModelIndex source_index = sourceModel()->index(source_row, 0, source_parent); for(int i = 0 ; i < sourceModel()->rowCount(source_index); i++) { if(filterAcceptsRow(i, source_index)) return true; } return false; } bool ProcessFilter::lessThan(const QModelIndex &left, const QModelIndex &right) const { if(right.isValid() && left.isValid()) { Q_ASSERT(left.model()); Q_ASSERT(right.model()); const ProcessModel *model = static_cast(left.model()); return model->lessThan(left, right); } return QSortFilterProxyModel::lessThan(left,right); } void ProcessFilter::setFilter(State filter) { mFilter = filter; filterChanged();//Tell the proxy view to refresh all its information } diff --git a/processui/ProcessFilter.h b/processui/ProcessFilter.h index a36d607..c64782d 100644 --- a/processui/ProcessFilter.h +++ b/processui/ProcessFilter.h @@ -1,63 +1,63 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 PROCESSFILTER_H_ #define PROCESSFILTER_H_ #include #include class QModelIndex; #ifdef Q_OS_WIN // this workaround is needed to make krunner link under msvc // please keep it this way even if you port this library to have a _export.h header file #define KSYSGUARD_EXPORT #else #define KSYSGUARD_EXPORT Q_DECL_EXPORT #endif class KSYSGUARD_EXPORT ProcessFilter : public QSortFilterProxyModel { Q_OBJECT Q_ENUMS(State) public: enum State {AllProcesses=0,AllProcessesInTreeForm, SystemProcesses, UserProcesses, OwnProcesses, ProgramsOnly}; - ProcessFilter(QObject *parent=0) : QSortFilterProxyModel(parent) {mFilter = AllProcesses;} + ProcessFilter(QObject *parent=nullptr) : QSortFilterProxyModel(parent) {mFilter = AllProcesses;} ~ProcessFilter() override {} bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; State filter() const {return mFilter; } public Q_SLOTS: void setFilter(State index); protected: bool filterAcceptsRow( int source_row, const QModelIndex & source_parent ) const override; State mFilter; }; #endif diff --git a/processui/ProcessModel.cpp b/processui/ProcessModel.cpp index b5ebe35..e4e64b1 100644 --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -1,2179 +1,2179 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006-2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "ProcessModel.h" #include "ProcessModel_p.h" #include "timeutil.h" #include "processcore/processes.h" #include "processcore/process.h" #include "processcore/processcore_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HEADING_X_ICON_SIZE 16 #define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000 #define GET_OWN_ID #ifdef GET_OWN_ID /* For getuid*/ #include #include #endif #ifdef HAVE_XRES #include #endif extern QApplication* Qapp; static QString formatByteSize(qlonglong amountInKB, int units) { enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB }; static QString kString = i18n("%1 K", QString::fromLatin1("%1")); static QString mString = i18n("%1 M", QString::fromLatin1("%1")); static QString gString = i18n("%1 G", QString::fromLatin1("%1")); static QString tString = i18n("%1 T", QString::fromLatin1("%1")); static QString pString = i18n("%1 P", QString::fromLatin1("%1")); double amount; if (units == UnitsAuto) { if (amountInKB < 1024.0*0.9) units = UnitsKB; // amount < 0.9 MiB == KiB else if (amountInKB < 1024.0*1024.0*0.9) units = UnitsMB; // amount < 0.9 GiB == MiB else if (amountInKB < 1024.0*1024.0*1024.0*0.9) units = UnitsGB; // amount < 0.9 TiB == GiB else if (amountInKB < 1024.0*1024.0*1024.0*1024.0*0.9) units = UnitsTB; // amount < 0.9 PiB == TiB else units = UnitsPB; } switch(units) { case UnitsKB: return kString.arg(QLocale().toString(amountInKB)); case UnitsMB: amount = amountInKB/1024.0; return mString.arg(QLocale().toString(amount, 'f', 1)); case UnitsGB: amount = amountInKB/(1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return gString.arg(QLocale().toString(amount, 'f', 1)); case UnitsTB: amount = amountInKB/(1024.0*1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return tString.arg(QLocale().toString(amount, 'f', 1)); case UnitsPB: amount = amountInKB/(1024.0*1024.0*1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return pString.arg(QLocale().toString(amount, 'f', 1)); default: return QLatin1String(""); // error } } ProcessModelPrivate::ProcessModelPrivate() : mBlankPixmap(HEADING_X_ICON_SIZE,1) { mBlankPixmap.fill(QColor(0,0,0,0)); mSimple = true; mIsLocalhost = true; mMemTotal = -1; mNumProcessorCores = 1; - mProcesses = NULL; + mProcesses = nullptr; mShowChildTotals = true; mShowCommandLineOptions = false; mShowingTooltips = true; mNormalizeCPUUsage = true; mIoInformation = ProcessModel::ActualBytes; #ifdef HAVE_XRES mHaveXRes = false; #endif mHaveTimer = false, mTimerId = -1, mMovingRow = false; mRemovingRow = false; mInsertingRow = false; #if HAVE_X11 mIsX11 = QX11Info::isPlatformX11(); #else mIsX11 = false; #endif } ProcessModelPrivate::~ProcessModelPrivate() { #if HAVE_X11 qDeleteAll(mPidToWindowInfo); #endif delete mProcesses; - mProcesses = NULL; + mProcesses = nullptr; } ProcessModel::ProcessModel(QObject* parent, const QString &host) : QAbstractItemModel(parent), d(new ProcessModelPrivate) { d->q=this; #ifdef HAVE_XRES if (d->mIsX11) { int event, error, major, minor; d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor); } #endif if(host.isEmpty() || host == QLatin1String("localhost")) { d->mHostName = QString(); d->mIsLocalhost = true; } else { d->mHostName = host; d->mIsLocalhost = false; } setupHeader(); d->setupProcesses(); #if HAVE_X11 d->setupWindows(); #endif d->mUnits = UnitsKB; d->mIoUnits = UnitsKB; } bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { //Because we need to sort Descendingly by default for most of the headings, we often return left > right KSysGuard::Process *processLeft = reinterpret_cast< KSysGuard::Process * > (left.internalPointer()); KSysGuard::Process *processRight = reinterpret_cast< KSysGuard::Process * > (right.internalPointer()); Q_ASSERT(processLeft); Q_ASSERT(processRight); Q_ASSERT(left.column() == right.column()); switch(left.column()) { case HeadingUser: { /* Sorting by user will be the default and the most common. We want to sort in the most useful way that we can. We need to return a number though. This code is based on that sorting ascendingly should put the current user at the top First the user we are running as should be at the top. Then any other users in the system. Then at the bottom the 'system' processes. We then sort by cpu usage to sort by that, then finally sort by memory usage */ /* First, place traced processes at the very top, ignoring any other sorting criteria */ if(processLeft->tracerpid() >= 0) return true; if(processRight->tracerpid() >= 0) return false; /* Sort by username. First group into own user, normal users, system users */ if(processLeft->uid() != processRight->uid()) { //We primarily sort by username if(d->mIsLocalhost) { int ownUid = getuid(); if(processLeft->uid() == ownUid) return true; //Left is our user, right is not. So left is above right if(processRight->uid() == ownUid) return false; //Left is not our user, right is. So right is above left } bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid()); bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid()); if(isLeftSystemUser && !isRightSystemUser) return false; //System users are less than non-system users if(!isLeftSystemUser && isRightSystemUser) return true; //They are either both system users, or both non-system users. //So now sort by username return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false); } /* 2nd sort order - Graphics Windows */ //Both columns have the same user. Place processes with windows at the top if(processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow()) return true; if(!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow()) return false; /* 3rd sort order - CPU Usage */ int leftCpu, rightCpu; if(d->mSimple || !d->mShowChildTotals) { leftCpu = processLeft->userUsage() + processLeft->sysUsage(); rightCpu = processRight->userUsage() + processRight->sysUsage(); } else { leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); } if(leftCpu != rightCpu) return leftCpu > rightCpu; /* 4th sort order - Memory Usage */ qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); return memoryLeft > memoryRight; } case HeadingCPUUsage: { int leftCpu, rightCpu; if(d->mSimple || !d->mShowChildTotals) { leftCpu = processLeft->userUsage() + processLeft->sysUsage(); rightCpu = processRight->userUsage() + processRight->sysUsage(); } else { leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); } return leftCpu > rightCpu; } case HeadingCPUTime: { return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime()); } case HeadingMemory: { qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); return memoryLeft > memoryRight; } case HeadingStartTime: { return processLeft->startTime() > processRight->startTime(); } case HeadingXMemory: return processLeft->pixmapBytes() > processRight->pixmapBytes(); case HeadingVmSize: return processLeft->vmSize() > processRight->vmSize(); case HeadingSharedMemory: { qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0; qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0; return memoryLeft > memoryRight; } case HeadingPid: return processLeft->pid() > processRight->pid(); case HeadingNiceness: //Sort by scheduler first if(processLeft->scheduler() != processRight->scheduler()) { if(processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo) return true; if(processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo) return false; if(processLeft->scheduler() == KSysGuard::Process::Other) return true; if(processRight->scheduler() == KSysGuard::Process::Other) return false; if(processLeft->scheduler() == KSysGuard::Process::Batch) return true; } if(processLeft->niceLevel() == processRight->niceLevel()) return processLeft->pid() < processRight->pid(); //Subsort by pid if the niceLevel is the same return processLeft->niceLevel() < processRight->niceLevel(); case HeadingTty: { if(processLeft->tty() == processRight->tty()) return processLeft->pid() < processRight->pid(); //Both ttys are the same. Sort by pid if(processLeft->tty().isEmpty()) return false; //Only left is empty (since they aren't the same) else if(processRight->tty().isEmpty()) return true; //Only right is empty //Neither left or right is empty. The tty string is like "tty10" so split this into "tty" and "10" //and sort by the string first, then sort by the number QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$")); QRegExp regexpRight(regexpLeft); if(regexpLeft.indexIn(processLeft->tty()) == -1 || regexpRight.indexIn(processRight->tty()) == -1) return processLeft->tty() < processRight->tty(); int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1)); if(nameMatch < 0) return true; if(nameMatch > 0) return false; return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt(); } case HeadingIoRead: switch(d->mIoInformation) { case ProcessModel::Bytes: return processLeft->ioCharactersRead() > processRight->ioCharactersRead(); case ProcessModel::Syscalls: return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls(); case ProcessModel::ActualBytes: return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead(); case ProcessModel::BytesRate: return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate(); case ProcessModel::SyscallsRate: return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate(); case ProcessModel::ActualBytesRate: return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate(); } case HeadingIoWrite: switch(d->mIoInformation) { case ProcessModel::Bytes: return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten(); case ProcessModel::Syscalls: return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls(); case ProcessModel::ActualBytes: return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten(); case ProcessModel::BytesRate: return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate(); case ProcessModel::SyscallsRate: return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate(); case ProcessModel::ActualBytesRate: return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate(); } } //Sort by the display string if we do not have an explicit sorting here return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString(); } ProcessModel::~ProcessModel() { delete d; } KSysGuard::Processes *ProcessModel::processController() const { return d->mProcesses; } #if HAVE_X11 void ProcessModelPrivate::windowRemoved(WId wid) { WindowInfo *window = mWIdToWindowInfo.take(wid); if(!window) return; qlonglong pid = window->pid; QMultiHash::iterator i = mPidToWindowInfo.find(pid); while (i != mPidToWindowInfo.end() && i.key() == pid) { if(i.value()->wid == wid) { i = mPidToWindowInfo.erase(i); break; } else i++; } delete window; //Update the model so that it redraws and resorts KSysGuard::Process *process = mProcesses->getProcess(pid); if(!process) return; int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); emit q->dataChanged(index2, index2); } #endif #if HAVE_X11 void ProcessModelPrivate::setupWindows() { if (!mIsX11) { return; } connect( KWindowSystem::self(), SIGNAL(windowChanged(WId,uint)), this, SLOT(windowChanged(WId,uint))); connect( KWindowSystem::self(), &KWindowSystem::windowAdded, this, &ProcessModelPrivate::windowAdded); connect( KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &ProcessModelPrivate::windowRemoved); //Add all the windows that KWin is managing - i.e. windows that the user can see const QList windows = KWindowSystem::windows(); for (auto it = windows.begin(); it != windows.end(); ++it ) { updateWindowInfo(*it, ~0u, true); } } #endif #ifdef HAVE_XRES bool ProcessModelPrivate::updateXResClientData() { if (!mIsX11) { return false; } XResClient *clients; int count; XResQueryClients(QX11Info::display(), &count, &clients); mXResClientResources.clear(); for (int i=0; i < count; i++) mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask); if(clients) XFree(clients); return true; } void ProcessModelPrivate::queryForAndUpdateAllXWindows() { if (!mIsX11) { return; } updateXResClientData(); Window *children, dummy; unsigned int count; Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count); if(!result) return; if(!updateXResClientData()) return; for (uint i=0; i < count; ++i) { WId wid = children[i]; QMap::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid)); if(iter == mXResClientResources.end()) continue; //We couldn't find it this time :-/ if(-iter.key() != (qlonglong)(wid & ~iter.value())) continue; //Already added this window //Get the PID for this window if we do not know it NETWinInfo info( QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid ); qlonglong pid = info.pid(); if(!pid) continue; //We found a window with this client mXResClientResources.erase(iter); KSysGuard::Process *process = mProcesses->getProcess(pid); if(!process) return; //shouldn't really happen.. maybe race condition etc unsigned long previousPixmapBytes = process->pixmapBytes(); //Now update the pixmap bytes for this window bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes()); if(!success) process->pixmapBytes() = 0; if(previousPixmapBytes != process->pixmapBytes()) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process); emit q->dataChanged(index, index); } } if(children) XFree((char*)children); } #endif void ProcessModelPrivate::setupProcesses() { if(mProcesses) { #ifdef Q_WS_X11_DISABLE mWIdToWindowInfo.clear(); mPidToWindowInfo.clear(); #endif delete mProcesses; - mProcesses = 0; + mProcesses = nullptr; q->beginResetModel(); q->endResetModel(); } mProcesses = new KSysGuard::Processes(mHostName); connect( mProcesses, &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged); connect( mProcesses, &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow); connect( mProcesses, &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow); connect( mProcesses, &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow); connect( mProcesses, &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow); connect( mProcesses, &KSysGuard::Processes::beginMoveProcess, this, &ProcessModelPrivate::beginMoveProcess); connect( mProcesses, &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow); mNumProcessorCores = mProcesses->numberProcessorCores(); if(mNumProcessorCores < 1) mNumProcessorCores=1; //Default to 1 if there was an error getting the number } #if HAVE_X11 void ProcessModelPrivate::windowChanged(WId wid, unsigned int properties) { updateWindowInfo(wid, properties, false); } void ProcessModelPrivate::windowAdded(WId wid) { updateWindowInfo(wid, ~0u, true); } void ProcessModelPrivate::updateWindowInfo(WId wid, unsigned int properties, bool newWindow) { if (!mIsX11) { return; } properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon); if(!properties) return; //Nothing interesting changed WindowInfo *w = mWIdToWindowInfo.value(wid); if(!w && !newWindow) return; //We do not have a record of this window and this is not a new window if(properties == NET::WMIcon) { if(w) w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true); return; } /* Get PID for window */ NETWinInfo info( QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::Properties(properties) & ~NET::WMIcon ); if(!w) { //We know that this must be a newWindow qlonglong pid = info.pid(); if(!(properties & NET::WMPid && pid)) return; //No PID for the window - this happens if the process did not set _NET_WM_PID //If we are to get the PID only, we are only interested in the XRes info for this, //so don't bother if we already have this info if(properties == NET::WMPid && mPidToWindowInfo.contains(pid)) return; w = new WindowInfo(wid, pid); mPidToWindowInfo.insertMulti(pid, w); mWIdToWindowInfo.insert(wid, w); } if(w && (properties & NET::WMIcon)) w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true); if(properties & NET::WMVisibleName && info.visibleName()) w->name = QString::fromUtf8(info.visibleName()); else if(properties & NET::WMName) w->name = QString::fromUtf8(info.name()); else if(properties & (NET::WMName | NET::WMVisibleName)) w->name.clear(); KSysGuard::Process *process = mProcesses->getProcess(w->pid); if(!process) { return; //This happens when a new window is detected before we've read in the process } int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); if(!process->hasManagedGuiWindow()) { process->hasManagedGuiWindow() = true; //Since this is the first window for a process, invalidate HeadingName so that //if we are sorting by name this gets taken into account QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process); emit q->dataChanged(index1, index1); } QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); emit q->dataChanged(index2, index2); } #endif void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags) { if(updateFlags != KSysGuard::Processes::XMemory) { d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags); if(d->mMemTotal <= 0) d->mMemTotal = d->mProcesses->totalPhysicalMemory(); } #ifdef HAVE_XRES //Add all the rest of the windows if(d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory)) d->queryForAndUpdateAllXWindows(); #endif } QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const { switch( status) { case KSysGuard::Process::Running: return i18n("- Process is doing some work."); case KSysGuard::Process::Sleeping: return i18n("- Process is waiting for something to happen."); case KSysGuard::Process::Stopped: return i18n("- Process has been stopped. It will not respond to user input at the moment."); case KSysGuard::Process::Zombie: return i18n("- Process has finished and is now dead, but the parent process has not cleaned up."); case KSysGuard::Process::Ended: // return i18n("- Process has finished and no longer exists."); default: return QString(); } } KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const { Q_ASSERT(d->mSimple); return d->mProcesses->getAllProcesses().at(index); } int ProcessModel::rowCount(const QModelIndex &parent) const { if(d->mSimple) { if(parent.isValid()) return 0; //In flat mode, none of the processes have children return d->mProcesses->processCount(); } //Deal with the case that we are showing it as a tree KSysGuard::Process *process; if(parent.isValid()) { if(parent.column() != 0) return 0; //For a treeview we say that only the first column has children process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0 } else { process = d->mProcesses->getProcess(-1); } Q_ASSERT(process); int num_rows = process->children().count(); return num_rows; } int ProcessModel::columnCount ( const QModelIndex & ) const { return d->mHeadings.count(); } bool ProcessModel::hasChildren ( const QModelIndex & parent = QModelIndex() ) const { if(d->mSimple) { if(parent.isValid()) return 0; //In flat mode, none of the processes have children return !d->mProcesses->getAllProcesses().isEmpty(); } //Deal with the case that we are showing it as a tree KSysGuard::Process *process; if(parent.isValid()) { if(parent.column() != 0) return false; //For a treeview we say that only the first column has children process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0 } else { process = d->mProcesses->getProcess(-1); } Q_ASSERT(process); bool has_children = !process->children().isEmpty(); Q_ASSERT((rowCount(parent) > 0) == has_children); return has_children; } QModelIndex ProcessModel::index ( int row, int column, const QModelIndex & parent ) const { if(row<0) return QModelIndex(); if(column<0 || column >= d->mHeadings.count() ) return QModelIndex(); if(d->mSimple) { if( parent.isValid()) return QModelIndex(); if( d->mProcesses->processCount() <= row) return QModelIndex(); return createIndex( row, column, d->mProcesses->getAllProcesses().at(row)); } //Deal with the case that we are showing it as a tree - KSysGuard::Process *parent_process = 0; + KSysGuard::Process *parent_process = nullptr; if(parent.isValid()) //not valid for init or children without parents, so use our special item with pid of 0 parent_process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); else parent_process = d->mProcesses->getProcess(-1); Q_ASSERT(parent_process); if(parent_process->children().count() > row) return createIndex(row,column, parent_process->children()[row]); else { return QModelIndex(); } } bool ProcessModel::isSimpleMode() const { return d->mSimple; } void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); if (!process->timeKillWasSent().isNull()) { int elapsed = process->timeKillWasSent().elapsed(); if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { if (!mPidsToUpdate.contains(process->pid())) mPidsToUpdate.append(process->pid()); QModelIndex index1 = q->createIndex(row, 0, process); QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process); emit q->dataChanged(index1, index2); if (!mHaveTimer) { mHaveTimer = true; mTimerId = startTimer(100); } } } int totalUpdated = 0; Q_ASSERT(row != -1); //Something has gone very wrong if(onlyTotalCpu) { if(mShowChildTotals) { //Only the total cpu usage changed, so only update that QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); emit q->dataChanged(index, index); } return; } else { if(process->changes() == KSysGuard::Process::Nothing) { return; //Nothing changed } if(process->changes() & KSysGuard::Process::Uids) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Tty) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process); emit q->dataChanged(index, index); } if(process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status) || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingCPUTime, process); emit q->dataChanged(index, index); //Because of our sorting, changing usage needs to also invalidate the User column index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::NiceLevels) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::VmSize) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process); emit q->dataChanged(index, index); } if(process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process); emit q->dataChanged(index, index); QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process); emit q->dataChanged(index2, index2); //Because of our sorting, changing usage needs to also invalidate the User column index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Name) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Command) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Login) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::IO) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingIoWrite, process); emit q->dataChanged(index, index); } } } void ProcessModelPrivate::beginInsertRow( KSysGuard::Process *process) { Q_ASSERT(process); Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); mInsertingRow = true; #if HAVE_X11 process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid()); #endif if(mSimple) { int row = mProcesses->processCount(); q->beginInsertRows( QModelIndex(), row, row ); return; } //Deal with the case that we are showing it as a tree int row = process->parent()->children().count(); QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0); //Only here can we actually change the model. First notify the view/proxy models then modify q->beginInsertRows(parentModelIndex, row, row); } void ProcessModelPrivate::endInsertRow() { Q_ASSERT(!mRemovingRow); Q_ASSERT(mInsertingRow); Q_ASSERT(!mMovingRow); mInsertingRow = false; q->endInsertRows(); } void ProcessModelPrivate::beginRemoveRow( KSysGuard::Process *process ) { Q_ASSERT(process); Q_ASSERT(process->pid() >= 0); Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); mRemovingRow = true; if(mSimple) { return q->beginRemoveRows(QModelIndex(), process->index(), process->index()); } else { int row = process->parent()->children().indexOf(process); if(row == -1) { qCDebug(LIBKSYSGUARD) << "A serious problem occurred in remove row."; mRemovingRow = false; return; } return q->beginRemoveRows(q->getQModelIndex(process->parent(), 0), row, row); } } void ProcessModelPrivate::endRemoveRow() { Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); if(!mRemovingRow) return; mRemovingRow = false; q->endRemoveRows(); } void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent) { Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); if(mSimple) return; //We don't need to move processes when in simple mode mMovingRow = true; int current_row = process->parent()->children().indexOf(process); Q_ASSERT(current_row != -1); int new_row = new_parent->children().count(); QModelIndex sourceParent = q->getQModelIndex( process->parent(), 0); QModelIndex destinationParent = q->getQModelIndex( new_parent, 0 ); mMovingRow = q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row); Q_ASSERT(mMovingRow); } void ProcessModelPrivate::endMoveRow() { Q_ASSERT(!mInsertingRow); Q_ASSERT(!mRemovingRow); if(!mMovingRow) return; mMovingRow = false; q->endMoveRows(); } QModelIndex ProcessModel::getQModelIndex( KSysGuard::Process *process, int column) const { Q_ASSERT(process); int pid = process->pid(); if (pid == -1) return QModelIndex(); //pid -1 is our fake process meaning the very root (never drawn). To represent that, we return QModelIndex() which also means the top element int row = 0; if(d->mSimple) { row = process->index(); } else { row = process->parent()->children().indexOf(process); } Q_ASSERT(row != -1); return createIndex(row, column, process); } QModelIndex ProcessModel::parent ( const QModelIndex & index ) const { if(!index.isValid()) return QModelIndex(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_ASSERT(process); if(d->mSimple) return QModelIndex(); else return getQModelIndex(process->parent(), 0); } QVariant ProcessModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation != Qt::Horizontal) return QVariant(); if(section < 0 || section >= d->mHeadings.count()) return QVariant(); //is this needed? switch( role ) { case Qt::TextAlignmentRole: { switch(section) { case HeadingPid: case HeadingMemory: case HeadingXMemory: case HeadingSharedMemory: case HeadingIoRead: case HeadingIoWrite: case HeadingVmSize: case HeadingNiceness: // return QVariant(Qt::AlignRight); case HeadingUser: case HeadingCPUUsage: return QVariant(Qt::AlignCenter); } return QVariant(); } case Qt::ToolTipRole: { if(!d->mShowingTooltips) return QVariant(); switch(section) { case HeadingName: return i18n("The process name."); case HeadingUser: return i18n("The user who owns this process."); case HeadingTty: return i18n("The controlling terminal on which this process is running."); case HeadingNiceness: return i18n("The priority with which this process is being run. For the normal scheduler, this ranges from 19 (very nice, least priority) to -19 (top priority)."); case HeadingCPUUsage: if(d->mNumProcessorCores == 1) return i18n("The current CPU usage of the process."); else // i18n: %1 is always greater than 1, so do not worry about // nonsensical verbosity of the singular part. if(d->mNormalizeCPUUsage) return i18np("The current total CPU usage of the process, divided by the %1 processor core in the machine.", "The current total CPU usage of the process, divided by the %1 processor cores in the machine.", d->mNumProcessorCores); else return i18n("The current total CPU usage of the process."); case HeadingCPUTime: return i18n("The total user and system time that this process has been running for, displayed as minutes:seconds."); case HeadingVmSize: return i18n("This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so on. This number is almost meaningless."); case HeadingMemory: return i18n("This is the amount of real physical memory that this process is using by itself, and approximates the Private memory usage of the process.
It does not include any swapped out memory, nor the code size of its shared libraries.
This is often the most useful figure to judge the memory use of a program. See What's This for more information.
"); case HeadingSharedMemory: return i18n("This is approximately the amount of real physical memory that this process's shared libraries are using.
This memory is shared among all processes that use this library.
"); case HeadingStartTime: return i18n("The elapsed time since the process was started."); case HeadingCommand: return i18n("The command with which this process was launched."); case HeadingXMemory: return i18n("The amount of pixmap memory that this process is using."); case HeadingXTitle: return i18n("The title of any windows that this process is showing."); case HeadingPid: return i18n("The unique Process ID that identifies this process."); case HeadingIoRead: return i18n("The number of bytes read. See What's This for more information."); case HeadingIoWrite: return i18n("The number of bytes written. See What's This for more information."); default: return QVariant(); } } case Qt::WhatsThisRole: { switch(section) { case HeadingName: return i18n("Technical information: The kernel process name is a maximum of 8 characters long, so the full command is examined. If the first word in the full command line starts with the process name, the first word of the command line is shown, otherwise the process name is used."); case HeadingUser: return i18n("The user who owns this process. If the effective, setuid etc user is different, the user who owns the process will be shown, followed by the effective user. The ToolTip contains the full information.

" "" "" "" "" #ifdef Q_OS_LINUX "" #endif "
Login Name/GroupThe username of the Real User/Group who created this process
Effective User/GroupThe process is running with privileges of the Effective User/Group. This is shown if different from the real user.
Setuid User/GroupThe saved username of the binary. The process can escalate its Effective User/Group to the Setuid User/Group.
File System User/GroupAccesses to the filesystem are checked with the File System User/Group. This is a Linux specific call. See setfsuid(2) for more information.
"); case HeadingVmSize: return i18n("This is the size of allocated address space - not memory, but address space. This value in practice means next to nothing. When a process requests a large memory block from the system but uses only a small part of it, the real usage will be low, VIRT will be high.

Technical information: This is VmSize in proc/*/status and VIRT in top."); case HeadingMemory: return i18n("Technical information: This is an approximation of the Private memory usage, calculated as VmRSS - Shared, from /proc/*/statm. This tends to underestimate the true Private memory usage of a process (by not including i/o backed memory pages), but is the best estimation that is fast to determine. This is sometimes known as URSS (Unique Resident Set Size). For an individual process, see \"Detailed Memory Information\" for a more accurate, but slower, calculation of the true Private memory usage."); case HeadingCPUUsage: return i18n("The CPU usage of a process and all of its threads."); case HeadingCPUTime: return i18n("The total system and user time that a process and all of its threads have been running on the CPU for. This can be greater than the wall clock time if the process has been across multiple CPU cores."); case HeadingSharedMemory: return i18n("Technical information: This is an approximation of the Shared memory, called SHR in top. It is the number of pages that are backed by a file (see kernel Documentation/filesystems/proc.txt). For an individual process, see \"Detailed Memory Information\" for a more accurate, but slower, calculation of the true Shared memory usage."); case HeadingStartTime: return i18n("Technical information: The underlying value (clock ticks since system boot) is retrieved from /proc/[pid]/stat"); case HeadingCommand: return i18n("Technical information: This is from /proc/*/cmdline"); case HeadingXMemory: return i18n("Technical information: This is the amount of memory used by the Xorg process for images for this process. This is memory used in addition to Memory and Shared Memory.
Technical information: This only counts the pixmap memory, and does not include resource memory used by fonts, cursors, glyphsets etc. See the xrestop program for a more detailed breakdown."); case HeadingXTitle: return i18n("Technical information: For each X11 window, the X11 property _NET_WM_PID is used to map the window to a PID. If a process' windows are not shown, then that application incorrectly is not setting _NET_WM_PID."); case HeadingPid: return i18n("Technical information: This is the Process ID. A multi-threaded application is treated a single process, with all threads sharing the same PID. The CPU usage etc will be the total, accumulated, CPU usage of all the threads."); case HeadingIoRead: case HeadingIoWrite: return i18n("This column shows the IO statistics for each process. The tooltip provides the following information:
" "" "" "" "" "" "" "" "
Characters ReadThe number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which this process passed to read() and pread(). It includes things like tty IO and it is unaffected by whether or not actual physical disk IO was required (the read might have been satisfied from pagecache).
Characters WrittenThe number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats apply here as with Characters Read.
Read SyscallsThe number of read I/O operations, i.e. syscalls like read() and pread().
Write SyscallsThe number of write I/O operations, i.e. syscalls like write() and pwrite().
Actual Bytes ReadThe number of bytes which this process really did cause to be fetched from the storage layer. Done at the submit_bio() level, so it is accurate for block-backed filesystems. This may not give sensible values for NFS and CIFS filesystems.
Actual Bytes WrittenAttempt to count the number of bytes which this process caused to be sent to the storage layer. This is done at page-dirtying time.

" "The number in brackets shows the rate at which each value is changing, determined from taking the difference between the previous value and the new value, and dividing by the update interval.

" "Technical information: This data is collected from /proc/*/io and is documented further in Documentation/accounting and Documentation/filesystems/proc.txt in the kernel source."); default: return QVariant(); } } case Qt::DisplayRole: return d->mHeadings[section]; default: return QVariant(); } } void ProcessModel::setSimpleMode(bool simple) { if(d->mSimple == simple) return; emit layoutAboutToBeChanged (); d->mSimple = simple; int flatrow; int treerow; QList flatIndexes; QList treeIndexes; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { flatrow = process->index(); treerow = process->parent()->children().indexOf(process); flatIndexes.clear(); treeIndexes.clear(); for(int i=0; i < columnCount(); i++) { flatIndexes << createIndex(flatrow, i, process); treeIndexes << createIndex(treerow, i, process); } if(d->mSimple) //change from tree mode to flat mode changePersistentIndexList(treeIndexes, flatIndexes); else // change from flat mode to tree mode changePersistentIndexList(flatIndexes, treeIndexes); } emit layoutChanged(); } bool ProcessModel::canUserLogin(long uid ) const { if(uid == 65534) { //nobody user return false; } if(!d->mIsLocalhost) return true; //We only deal with localhost. Just always return true for non localhost int canLogin = d->mUidCanLogin.value(uid, -1); //Returns 0 if we cannot login, 1 if we can, and the default is -1 meaning we don't know if(canLogin != -1) return canLogin; //We know whether they can log in //We got the default, -1, so we don't know. Look it up KUser user(uid); if(!user.isValid()) { //for some reason the user isn't recognised. This might happen under certain security situations. //Just return true to be safe d->mUidCanLogin[uid] = 1; return true; } QString shell = user.shell(); if(shell == QLatin1String("/bin/false") ) //FIXME - add in any other shells it could be for false { d->mUidCanLogin[uid] = 0; return false; } d->mUidCanLogin[uid] = 1; return true; } QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const { QString userTooltip; if(!mIsLocalhost) { return xi18nc("@info:tooltip", "Login Name: %1", getUsernameForUser(ps->uid(), true)); } else { KUser user(ps->uid()); if(!user.isValid()) userTooltip += xi18nc("@info:tooltip", "This user is not recognized for some reason."); else { if(!user.property(KUser::FullName).isValid()) userTooltip += xi18nc("@info:tooltip", "%1", user.property(KUser::FullName).toString()); userTooltip += xi18nc("@info:tooltip", "Login Name: %1 (uid: %2)", user.loginName(), QString::number(ps->uid())); if(!user.property(KUser::RoomNumber).isValid()) userTooltip += xi18nc("@info:tooltip", " Room Number: %1", user.property(KUser::RoomNumber).toString()); if(!user.property(KUser::WorkPhone).isValid()) userTooltip += xi18nc("@info:tooltip", " Work Phone: %1", user.property(KUser::WorkPhone).toString()); } } if( (ps->uid() != ps->euid() && ps->euid() != -1) || (ps->uid() != ps->suid() && ps->suid() != -1) || (ps->uid() != ps->fsuid() && ps->fsuid() != -1)) { if(ps->euid() != -1) userTooltip += xi18nc("@info:tooltip", "Effective User: %1", getUsernameForUser(ps->euid(), true)); if(ps->suid() != -1) userTooltip += xi18nc("@info:tooltip", "Setuid User: %1", getUsernameForUser(ps->suid(), true)); if(ps->fsuid() != -1) userTooltip += xi18nc("@info:tooltip", "File System User: %1", getUsernameForUser(ps->fsuid(), true)); userTooltip += QLatin1String("
"); } if(ps->gid() != -1) { userTooltip += xi18nc("@info:tooltip", "Group: %1", getGroupnameForGroup(ps->gid())); if( (ps->gid() != ps->egid() && ps->egid() != -1) || (ps->gid() != ps->sgid() && ps->sgid() != -1) || (ps->gid() != ps->fsgid() && ps->fsgid() != -1)) { if(ps->egid() != -1) userTooltip += xi18nc("@info:tooltip", "Effective Group: %1", getGroupnameForGroup(ps->egid())); if(ps->sgid() != -1) userTooltip += xi18nc("@info:tooltip", "Setuid Group: %1", getGroupnameForGroup(ps->sgid())); if(ps->fsgid() != -1) userTooltip += xi18nc("@info:tooltip", "File System Group: %1", getGroupnameForGroup(ps->fsgid())); } } return userTooltip; } QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const { return i18nc("Short description of a process. PID, name, user", "%1: %2, owned by user %3", (long)(process->pid()), process->name(), d->getUsernameForUser(process->uid(), false)); } QString ProcessModelPrivate::getGroupnameForGroup(long gid) const { if(mIsLocalhost) { QString groupname = KUserGroup(gid).name(); if(!groupname.isEmpty()) return i18nc("Group name and group id", "%1 (gid: %2)", groupname, gid); } return QString::number(gid); } QString ProcessModelPrivate::getUsernameForUser(long uid, bool withuid) const { QString &username = mUserUsername[uid]; if(username.isNull()) { if(!mIsLocalhost) { username = QLatin1String(""); //empty, but not null } else { KUser user(uid); if(!user.isValid()) username = QLatin1String(""); else username = user.loginName(); } } if(username.isEmpty()) return QString::number(uid); if(withuid) return i18nc("User name and user id", "%1 (uid: %2)", username, (long int)uid); return username; } QVariant ProcessModel::data(const QModelIndex &index, int role) const { //This function must be super duper ultra fast because it's called thousands of times every few second :( //I think it should be optomised for role first, hence the switch statement (fastest possible case) KFormat format; if (!index.isValid()) { return QVariant(); } if (index.column() >= d->mHeadings.count()) { return QVariant(); } switch (role){ case Qt::DisplayRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); switch(index.column()) { case HeadingName: if(d->mShowCommandLineOptions) return process->name(); else return process->name().section(' ', 0,0); case HeadingPid: return (qlonglong)process->pid(); case HeadingUser: if(!process->login().isEmpty()) return process->login(); if(process->uid() == process->euid()) return d->getUsernameForUser(process->uid(), false); else return QString(d->getUsernameForUser(process->uid(), false) + ", " + d->getUsernameForUser(process->euid(), false)); case HeadingNiceness: switch(process->scheduler()) { case KSysGuard::Process::Other: return process->niceLevel(); case KSysGuard::Process::SchedulerIdle: return i18nc("scheduler", "Idle"); //neither static nor dynamic priority matter case KSysGuard::Process::Batch: return i18nc("scheduler", "(Batch) %1", process->niceLevel()); //only dynamic priority matters case KSysGuard::Process::RoundRobin: return i18nc("Round robin scheduler", "RR %1", process->niceLevel()); case KSysGuard::Process::Fifo: if(process->niceLevel() == 99) return i18nc("Real Time scheduler", "RT"); else return i18nc("First in first out scheduler", "FIFO %1", process->niceLevel()); case KSysGuard::Process::Interactive: return i18nc("scheduler", "(IA) %1", process->niceLevel()); } case HeadingTty: return process->tty(); case HeadingCPUUsage: { double total; if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage() + process->totalSysUsage(); else total = process->userUsage() + process->sysUsage(); if(d->mNormalizeCPUUsage) total = total / d->mNumProcessorCores; if(total < 1 && process->status() != KSysGuard::Process::Sleeping && process->status() != KSysGuard::Process::Running && process->status() != KSysGuard::Process::Ended) return process->translatedStatus(); //tell the user when the process is a zombie or stopped if(total < 0.5) return ""; return QString(QString::number((int)(total+0.5)) + '%'); } case HeadingCPUTime: { qlonglong seconds = (process->userTime() + process->sysTime())/100; return QStringLiteral("%1:%2").arg(seconds/60).arg((int)seconds%60, 2, 10, QLatin1Char('0')); } case HeadingMemory: if(process->vmURSS() == -1) { //If we don't have the URSS (the memory used by only the process, not the shared libraries) //then return the RSS (physical memory used by the process + shared library) as the next best thing return formatMemoryInfo(process->vmRSS(), d->mUnits, true); } else { return formatMemoryInfo(process->vmURSS(), d->mUnits, true); } case HeadingVmSize: return formatMemoryInfo(process->vmSize(), d->mUnits, true); case HeadingSharedMemory: if(process->vmRSS() - process->vmURSS() <= 0 || process->vmURSS() == -1) return QVariant(QVariant::String); return formatMemoryInfo(process->vmRSS() - process->vmURSS(), d->mUnits); case HeadingStartTime: { // NOTE: the next 6 lines are the same as in the next occurence of 'case HeadingStartTime:' => keep in sync or remove duplicate code const auto clockTicksSinceSystemBoot = process->startTime(); const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); // see man proc or http://superuser.com/questions/101183/what-is-a-cpu-tick const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); return TimeUtil::secondsToHumanElapsedString(relativeStartTime); } case HeadingCommand: { return process->command().replace('\n',' '); // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text // QString command = process->command; // command.replace(process->name, "" + process->name + ""); // return "" + command; } case HeadingIoRead: { switch(d->mIoInformation) { case ProcessModel::Bytes: //divide by 1024 to convert to kB return formatMemoryInfo(process->ioCharactersRead() / 1024, d->mIoUnits, true); case ProcessModel::Syscalls: if( process->ioReadSyscalls()) return QString::number(process->ioReadSyscalls()); break; case ProcessModel::ActualBytes: return formatMemoryInfo(process->ioCharactersActuallyRead() / 1024, d->mIoUnits, true); case ProcessModel::BytesRate: if( process->ioCharactersReadRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersReadRate() / 1024, d->mIoUnits, true)); break; case ProcessModel::SyscallsRate: if( process->ioReadSyscallsRate()) return QString::number(process->ioReadSyscallsRate()); break; case ProcessModel::ActualBytesRate: if( process->ioCharactersActuallyReadRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyReadRate() / 1024, d->mIoUnits, true)); break; } return QVariant(); } case HeadingIoWrite: { switch(d->mIoInformation) { case ProcessModel::Bytes: return formatMemoryInfo(process->ioCharactersWritten() / 1024, d->mIoUnits, true); case ProcessModel::Syscalls: if( process->ioWriteSyscalls()) return QString::number(process->ioWriteSyscalls()); break; case ProcessModel::ActualBytes: return formatMemoryInfo(process->ioCharactersActuallyWritten() / 1024, d->mIoUnits, true); case ProcessModel::BytesRate: if(process->ioCharactersWrittenRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersWrittenRate() / 1024, d->mIoUnits, true)); break; case ProcessModel::SyscallsRate: if( process->ioWriteSyscallsRate()) return QString::number(process->ioWriteSyscallsRate()); break; case ProcessModel::ActualBytesRate: if(process->ioCharactersActuallyWrittenRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyWrittenRate() / 1024, d->mIoUnits, true)); break; } return QVariant(); } #if HAVE_X11 case HeadingXMemory: return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true); case HeadingXTitle: { if(!process->hasManagedGuiWindow()) return QVariant(QVariant::String); WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QVariant(QVariant::String); else return w->name; } #endif default: return QVariant(); } break; } case Qt::ToolTipRole: { if(!d->mShowingTooltips) return QVariant(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); QString tracer; if(process->tracerpid() >= 0) { KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid()); if(process_tracer) //it is possible for this to be not the case in certain race conditions tracer = xi18nc("tooltip. name,pid ","This process is being debugged by %1 (%2)", process_tracer->name(), (long int)process->tracerpid()); } switch(index.column()) { case HeadingName: { /* It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put * a picture in a tooltip :( QIcon icon; if(mPidToWindowInfo.contains(process->pid())) { WId wid; wid = mPidToWindowInfo[process->pid()].wid; icon = KWindowSystem::icon(wid); } if(icon.isValid()) { tooltip = i18n(""); firstrow = false; } for(int i = 0; i < d->mHeadings.size(); i++) { if(firstrow) { QString heading = d->mHeadings[i]; textHtmlHeaders += ""; if(i) { textCsvHeaders += ','; textPlainHeaders += QLatin1String(", "); } textPlainHeaders += heading; heading.replace('"', QLatin1String("\"\"")); textCsvHeaders += '"' + heading + '"'; } QModelIndex index2 = createIndex(index.row(), i, reinterpret_cast< KSysGuard::Process * > (index.internalPointer())); QString display = data(index2, PlainValueRole).toString(); if(i) { textCsv += ','; textPlain += QLatin1String(", "); } textHtml += ""; textPlain += display; display.replace('"',QLatin1String("\"\"")); textCsv += '"' + display + '"'; } } } textHtml = "
%1", icon); } */ QString tooltip; if(process->parentPid() == -1) { //Give a quick explanation of init and kthreadd if(process->name() == QLatin1String("init") || process->name() == QLatin1String("systemd")) { tooltip = xi18nc("@info:tooltip", "%1The parent of all other processes and cannot be killed.Process ID: %2", process->name(), (long int)process->pid()); } else if(process->name() == QLatin1String("kthreadd")) { tooltip = xi18nc("@info:tooltip", "KThreaddManages kernel threads. The children processes run in the kernel, controlling hard disk access, etc."); } else { tooltip = xi18nc("@info:tooltip","%1Process ID: %2",process->name(), (long int)process->pid()); } } else { KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parentPid()); if(parent_process) { //In race conditions, it's possible for this process to not exist tooltip = xi18nc("@info:tooltip", "%1" "Process ID: %2" "Parent: %3" "Parent's ID: %4", process->name(), (long int)process->pid(), parent_process->name(), (long int)process->parentPid()); } else { tooltip = xi18nc("@info:tooltip", "%1" "Process ID: %2" "Parent's ID: %3", process->name(), (long int)process->pid(), (long int)process->parentPid()); } } if(process->numThreads() >= 1) tooltip += xi18nc("@info:tooltip", "Number of threads: %1", process->numThreads()); if(!process->command().isEmpty()) { tooltip += xi18nc("@info:tooltip", "Command: %1", process->command()); } if(!process->tty().isEmpty()) tooltip += xi18nc("@info:tooltip", "Running on: %1", QString(process->tty())); if(!tracer.isEmpty()) return QStringLiteral("%1
%2").arg(tooltip).arg(tracer); return tooltip; } case HeadingStartTime: { // NOTE: the next 6 lines are the same as in the previous occurence of 'case HeadingStartTime:' => keep in sync or remove duplicate code const auto clockTicksSinceSystemBoot = process->startTime(); const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); return xi18nc("@info:tooltip", "Clock ticks since system boot: %1" "Seconds since system boot: %2 (System boot time: %3)" "Absolute start time: %4" "Relative start time: %5", clockTicksSinceSystemBoot, secondsSinceSystemBoot, systemBootTime.toString(), absoluteStartTime.toString(), TimeUtil::secondsToHumanElapsedString(relativeStartTime)); } case HeadingCommand: { QString tooltip = xi18nc("@info:tooltip", "This process was run with the following command:" "%1", process->command()); if(!process->tty().isEmpty()) tooltip += xi18nc("@info:tooltip", "Running on: %1", QString(process->tty())); if (!tracer.isEmpty()) { return QStringLiteral("%1
%2").arg(tooltip).arg(tracer); } return tooltip; } case HeadingUser: { QString tooltip = d->getTooltipForUser(process); if(tracer.isEmpty()) { return tooltip; } return tooltip + "
" + tracer; } case HeadingNiceness: { QString tooltip; switch(process->scheduler()) { case KSysGuard::Process::Other: case KSysGuard::Process::Batch: case KSysGuard::Process::Interactive: tooltip = xi18nc("@info:tooltip", "Nice level: %1 (%2)", process->niceLevel(), process->niceLevelAsString()); break; case KSysGuard::Process::RoundRobin: case KSysGuard::Process::Fifo: tooltip = xi18nc("@info:tooltip", "This is a real time process." "Scheduler priority: %1", process->niceLevel()); break; case KSysGuard::Process::SchedulerIdle: break; //has neither dynamic (niceness) or static (scheduler priority) priotiy } if(process->scheduler() != KSysGuard::Process::Other) tooltip += xi18nc("@info:tooltip", "Scheduler: %1", process->schedulerAsString()); if(process->ioPriorityClass() != KSysGuard::Process::None) { if((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) && process->ioniceLevel() != -1) tooltip += xi18nc("@info:tooltip", "I/O Nice level: %1 (%2)", process->ioniceLevel(), process->ioniceLevelAsString()); tooltip += xi18nc("@info:tooltip", "I/O Class: %1", process->ioPriorityClassAsString()); } if(tracer.isEmpty()) return tooltip; return QString(tooltip + "
" + tracer); } case HeadingCPUUsage: case HeadingCPUTime: { int divideby = (d->mNormalizeCPUUsage?d->mNumProcessorCores:1); QString tooltip = xi18nc("@info:tooltip", "Process status: %1 %2" "User CPU usage: %3%" "System CPU usage: %4%", /* Please do not add here - the tooltip is appended to */ process->translatedStatus(), d->getStatusDescription(process->status()), (float)(process->userUsage()) / divideby, (float)(process->sysUsage()) / divideby); if(process->numThreads() >= 1) tooltip += xi18nc("@info:tooltip", "Number of threads: %1", process->numThreads()); if(process->numChildren() > 0) { tooltip += xi18nc("@info:tooltip", "Number of children: %1" "Total User CPU usage: %2%" "Total System CPU usage: %3%" "Total CPU usage: %4%", process->numChildren(), (float)(process->totalUserUsage())/ divideby, (float)(process->totalSysUsage()) / divideby, (float)(process->totalUserUsage() + process->totalSysUsage()) / divideby); } if(process->userTime() > 0) tooltip += xi18nc("@info:tooltip", "CPU time spent running as user: %1 seconds", process->userTime() / 100.0, 0, 'f', 1); if(process->sysTime() > 0) tooltip += xi18nc("@info:tooltip", "CPU time spent running in kernel: %1 seconds", process->sysTime() / 100.0, 0, 'f', 1); if(process->niceLevel() != 0) tooltip += xi18nc("@info:tooltip", "Nice level: %1 (%2)", process->niceLevel(), process->niceLevelAsString() ); if(process->ioPriorityClass() != KSysGuard::Process::None) { if((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) && process->ioniceLevel() != -1) tooltip += xi18nc("@info:tooltip", "I/O Nice level: %1 (%2)", process->ioniceLevel(), process->ioniceLevelAsString() ); tooltip += xi18nc("@info:tooltip", "I/O Class: %1", process->ioPriorityClassAsString() ); } if(!tracer.isEmpty()) return QString(tooltip + "
" + tracer); return tooltip; } case HeadingVmSize: { return QVariant(); } case HeadingMemory: { QString tooltip; if(process->vmURSS() != -1) { //We don't have information about the URSS, so just fallback to RSS if(d->mMemTotal > 0) tooltip += xi18nc("@info:tooltip", "Memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmURSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), process->vmURSS() * 100 / d->mMemTotal); else tooltip += xi18nc("@info:tooltip", "Memory usage: %1
", format.formatByteSize(process->vmURSS() * 1024)); } if(d->mMemTotal > 0) tooltip += xi18nc("@info:tooltip", "RSS Memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmRSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), process->vmRSS() * 100 / d->mMemTotal); else tooltip += xi18nc("@info:tooltip", "RSS Memory usage: %1", format.formatByteSize(process->vmRSS() * 1024)); return tooltip; } case HeadingSharedMemory: { if(process->vmURSS() == -1) { return xi18nc("@info:tooltip", "Your system does not seem to have this information available to be read."); } if(d->mMemTotal >0) return xi18nc("@info:tooltip", "Shared library memory usage: %1 out of %2 (%3 %)", format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024), format.formatByteSize(d->mMemTotal * 1024), (process->vmRSS() - process->vmURSS()) * 100 / d->mMemTotal); else return xi18nc("@info:tooltip", "Shared library memory usage: %1", format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024)); } case HeadingIoWrite: case HeadingIoRead: { //FIXME - use the formatByteRate functions when added return kxi18nc("@info:tooltip", "Characters read: %1 (%2 KiB/s)" "Characters written: %3 (%4 KiB/s)" "Read syscalls: %5 (%6 s⁻¹)" "Write syscalls: %7 (%8 s⁻¹)" "Actual bytes read: %9 (%10 KiB/s)" "Actual bytes written: %11 (%12 KiB/s)") .subs(format.formatByteSize(process->ioCharactersRead())) .subs(QString::number(process->ioCharactersReadRate() / 1024)) .subs(format.formatByteSize(process->ioCharactersWritten())) .subs(QString::number(process->ioCharactersWrittenRate() / 1024)) .subs(QString::number(process->ioReadSyscalls())) .subs(QString::number(process->ioReadSyscallsRate())) .subs(QString::number(process->ioWriteSyscalls())) .subs(QString::number(process->ioWriteSyscallsRate())) .subs(format.formatByteSize(process->ioCharactersActuallyRead())) .subs(QString::number(process->ioCharactersActuallyReadRate() / 1024)) .subs(format.formatByteSize(process->ioCharactersActuallyWritten())) .subs(QString::number(process->ioCharactersActuallyWrittenRate() / 1024)) .toString(); } case HeadingXTitle: { #if HAVE_X11 QString tooltip; QList values = d->mPidToWindowInfo.values(process->pid()); if(values.isEmpty()) return QVariant(QVariant::String); for(int i = 0; i < values.size(); i++) { if(!values.at(i)->name.isEmpty()) tooltip += "
  • " + values.at(i)->name + "
  • "; } if(!tooltip.isEmpty()) return QString("

      " + tooltip + "
    "); #endif return QVariant(QVariant::String); } default: return QVariant(QVariant::String); } } case Qt::TextAlignmentRole: switch(index.column() ) { case HeadingUser: case HeadingCPUUsage: return QVariant(Qt::AlignCenter); case HeadingNiceness: case HeadingCPUTime: case HeadingStartTime: case HeadingPid: case HeadingMemory: case HeadingXMemory: case HeadingSharedMemory: case HeadingVmSize: case HeadingIoWrite: case HeadingIoRead: return QVariant(Qt::AlignRight | Qt::AlignVCenter); default: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } case UidRole: { if(index.column() != 0) return QVariant(); //If we query with this role, then we want the raw UID for this. KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); return process->uid(); } case PlainValueRole: //Used to return a plain value. For copying to a clipboard etc { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); switch(index.column()) { case HeadingName: return process->name(); case HeadingPid: return (qlonglong)process->pid(); case HeadingUser: if(!process->login().isEmpty()) return process->login(); if(process->uid() == process->euid()) return d->getUsernameForUser(process->uid(), false); else return QString(d->getUsernameForUser(process->uid(), false) + ", " + d->getUsernameForUser(process->euid(), false)); case HeadingNiceness: return process->niceLevel(); case HeadingTty: return process->tty(); case HeadingCPUUsage: { double total; if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage() + process->totalSysUsage(); else total = process->userUsage() + process->sysUsage(); if(d->mNormalizeCPUUsage) return total / d->mNumProcessorCores; else return total; } case HeadingCPUTime: return (qlonglong)(process->userTime() + process->sysTime()); case HeadingMemory: if(process->vmRSS() == 0) return QVariant(QVariant::String); if(process->vmURSS() == -1) { return (qlonglong)process->vmRSS(); } else { return (qlonglong)process->vmURSS(); } case HeadingVmSize: return (qlonglong)process->vmSize(); case HeadingSharedMemory: if(process->vmRSS() - process->vmURSS() < 0 || process->vmURSS() == -1) return QVariant(QVariant::String); return (qlonglong)(process->vmRSS() - process->vmURSS()); case HeadingStartTime: return process->startTime(); // 2015-01-03, gregormi: can maybe be replaced with something better later case HeadingCommand: return process->command(); case HeadingIoRead: switch(d->mIoInformation) { case ProcessModel::Bytes: return process->ioCharactersRead(); case ProcessModel::Syscalls: return process->ioReadSyscalls(); case ProcessModel::ActualBytes: return process->ioCharactersActuallyRead(); case ProcessModel::BytesRate: return (qlonglong)process->ioCharactersReadRate(); case ProcessModel::SyscallsRate: return (qlonglong)process->ioReadSyscallsRate(); case ProcessModel::ActualBytesRate: return (qlonglong)process->ioCharactersActuallyReadRate(); } case HeadingIoWrite: switch(d->mIoInformation) { case ProcessModel::Bytes: return process->ioCharactersWritten(); case ProcessModel::Syscalls: return process->ioWriteSyscalls(); case ProcessModel::ActualBytes: return process->ioCharactersActuallyWritten(); case ProcessModel::BytesRate: return (qlonglong)process->ioCharactersWrittenRate(); case ProcessModel::SyscallsRate: return (qlonglong)process->ioWriteSyscallsRate(); case ProcessModel::ActualBytesRate: return (qlonglong)process->ioCharactersActuallyWrittenRate(); } case HeadingXMemory: return (qulonglong)process->pixmapBytes(); #if HAVE_X11 case HeadingXTitle: { WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QString(); return w->name; } #endif default: return QVariant(); } break; } #if HAVE_X11 case WindowIdRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return (int)0; else return (int)w->wid; } #endif case PercentageRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_CHECK_PTR(process); switch(index.column()) { case HeadingCPUUsage: { float cpu; if(d->mSimple || !d->mShowChildTotals) cpu = process->userUsage() + process->sysUsage(); else cpu = process->totalUserUsage() + process->totalSysUsage(); cpu = cpu / 100.0; if(!d->mNormalizeCPUUsage) return cpu; return cpu / d->mNumProcessorCores; } case HeadingMemory: if(d->mMemTotal <= 0) return -1; if(process->vmURSS() != -1) return float(process->vmURSS()) / d->mMemTotal; else return float(process->vmRSS()) / d->mMemTotal; case HeadingSharedMemory: if(process->vmURSS() == -1 || d->mMemTotal <= 0) return -1; return float(process->vmRSS() - process->vmURSS())/d->mMemTotal; default: return -1; } } case Qt::DecorationRole: { if(index.column() == HeadingName) { #if HAVE_X11 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(!process->hasManagedGuiWindow()) { if(d->mSimple) //When not in tree mode, we need to pad the name column where we do not have an icon return QIcon(d->mBlankPixmap); else //When in tree mode, the padding looks bad, so do not pad in this case return QVariant(); } WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(w && !w->icon.isNull()) return w->icon; return QIcon(d->mBlankPixmap); #else return QVariant(); #endif } else if (index.column() == HeadingCPUUsage) { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Stopped || process->status() == KSysGuard::Process::Zombie) { // QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small, // KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), // 0L, true); } } return QVariant(); } case Qt::BackgroundRole: { if (index.column() != HeadingUser) { if (!d->mHaveTimer) //If there is no timer, then no processes are being killed, so no point looking for one return QVariant(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if (!process->timeKillWasSent().isNull()) { int elapsed = process->timeKillWasSent().elapsed(); if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {//Only show red for about 7 seconds int transparency = 255 - elapsed*250/MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS; KColorScheme scheme(QPalette::Active, KColorScheme::Selection); QBrush brush = scheme.background(KColorScheme::NegativeBackground); QColor color = brush.color(); color.setAlpha(transparency); brush.setColor(color); return brush; } } return QVariant(); } KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Ended) { return QColor(Qt::lightGray); } if(process->tracerpid() >= 0) { //It's being debugged, so probably important. Let's mark it as such return QColor(Qt::yellow); } if(d->mIsLocalhost && process->uid() == getuid()) { //own user return QColor(0, 208, 214, 50); } if(process->uid() < 100 || !canUserLogin(process->uid())) return QColor(218, 220,215, 50); //no color for system tasks //other users return QColor(2, 154, 54, 50); } case Qt::FontRole: { if(index.column() == HeadingCPUUsage) { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->userUsage() == 0) { QFont font; font.setItalic(true); return font; } } return QVariant(); } default: //This is a very very common case, so the route to this must be very minimal return QVariant(); } return QVariant(); //never get here, but make compiler happy } bool ProcessModel::hasGUIWindow(qlonglong pid) const { #if HAVE_X11 return d->mPidToWindowInfo.contains(pid); #else return false; #endif } bool ProcessModel::isLocalhost() const { return d->mIsLocalhost; } void ProcessModel::setupHeader() { //These must be in the same order that they are in the header file QStringList headings; headings << i18nc("process heading", "Name"); headings << i18nc("process heading", "Username"); headings << i18nc("process heading", "PID"); headings << i18nc("process heading", "TTY"); headings << i18nc("process heading", "Niceness"); // xgettext: no-c-format headings << i18nc("process heading", "CPU %"); headings << i18nc("process heading", "CPU Time"); headings << i18nc("process heading", "IO Read"); headings << i18nc("process heading", "IO Write"); headings << i18nc("process heading", "Virtual Size"); headings << i18nc("process heading", "Memory"); headings << i18nc("process heading", "Shared Mem"); headings << i18nc("process heading", "Relative Start Time"); headings << i18nc("process heading", "Command"); #if HAVE_X11 if (d->mIsX11) { headings << i18nc("process heading", "X11 Memory"); headings << i18nc("process heading", "Window Title"); } #endif if(d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings beginInsertColumns(QModelIndex(), 0, headings.count()-1); { d->mHeadings = headings; } endInsertColumns(); } else { // This was called to retranslate the headings. Just use the new translations and call headerDataChanged Q_ASSERT(d->mHeadings.count() == headings.count()); d->mHeadings = headings; headerDataChanged(Qt::Horizontal, 0 , headings.count()-1); } } void ProcessModel::retranslateUi() { setupHeader(); } KSysGuard::Process *ProcessModel::getProcess(qlonglong pid) { return d->mProcesses->getProcess(pid); } bool ProcessModel::showTotals() const { return d->mShowChildTotals; } void ProcessModel::setShowTotals(bool showTotals) //slot { if(showTotals == d->mShowChildTotals) return; d->mShowChildTotals = showTotals; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { if(process->numChildren() > 0) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingCPUUsage, process); emit dataChanged(index, index); } } } qlonglong ProcessModel::totalMemory() const { return d->mMemTotal; } void ProcessModel::setUnits(Units units) { if(d->mUnits == units) return; d->mUnits = units; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingXMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingSharedMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingVmSize, process); emit dataChanged(index, index); } } ProcessModel::Units ProcessModel::units() const { return (Units) d->mUnits; } void ProcessModel::setIoUnits(Units units) { if(d->mIoUnits == units) return; d->mIoUnits = units; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingIoRead, process); emit dataChanged(index, index); index = createIndex(row, HeadingIoWrite, process); emit dataChanged(index, index); } } ProcessModel::Units ProcessModel::ioUnits() const { return (Units) d->mIoUnits; } void ProcessModel::setIoInformation( ProcessModel::IoInformation ioInformation ) { d->mIoInformation = ioInformation; } ProcessModel::IoInformation ProcessModel::ioInformation() const { return d->mIoInformation; } QString ProcessModel::formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero) const { //We cache the result of i18n for speed reasons. We call this function //hundreds of times, every second or so if(returnEmptyIfValueIsZero && amountInKB == 0) return QString(); static QString percentageString = i18n("%1%", QString::fromLatin1("%1")); if (units == UnitsPercentage) { if(d->mMemTotal == 0) return QLatin1String(""); //memory total not determined yet. Shouldn't happen, but don't crash if it does float percentage = amountInKB*100.0/d->mMemTotal; if(percentage < 0.1) percentage = 0.1; return percentageString.arg(percentage, 0, 'f', 1); } else return formatByteSize(amountInKB, units); } QString ProcessModel::hostName() const { return d->mHostName; } QStringList ProcessModel::mimeTypes() const { QStringList types; types << QStringLiteral("text/plain"); types << QStringLiteral("text/csv"); types << QStringLiteral("text/html"); return types; } QMimeData *ProcessModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QString textCsv; QString textCsvHeaders; QString textPlain; QString textPlainHeaders; QString textHtml; QString textHtmlHeaders; QString display; int firstColumn = -1; bool firstrow = true; foreach (const QModelIndex &index, indexes) { if (index.isValid()) { if(firstColumn == -1) firstColumn = index.column(); else if(firstColumn != index.column()) continue; else { textCsv += '\n'; textPlain += '\n'; textHtml += QLatin1String("
    " + heading + "" + QString(display).toHtmlEscaped() + "
    " + textHtmlHeaders + "" + textHtml + "
    "; textCsv = textCsvHeaders + '\n' + textCsv; textPlain = textPlainHeaders + '\n' + textPlain; mimeData->setText(textPlain); mimeData->setHtml(textHtml); mimeData->setData(QStringLiteral("text/csv"), textCsv.toUtf8()); return mimeData; } Qt::ItemFlags ProcessModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; //Would this ever happen? KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Ended) return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable; else return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; } bool ProcessModel::isShowCommandLineOptions() const { return d->mShowCommandLineOptions; } void ProcessModel::setShowCommandLineOptions(bool showCommandLineOptions) { d->mShowCommandLineOptions = showCommandLineOptions; } bool ProcessModel::isShowingTooltips() const { return d->mShowingTooltips; } void ProcessModel::setShowingTooltips(bool showTooltips) { d->mShowingTooltips = showTooltips; } bool ProcessModel::isNormalizedCPUUsage() const { return d->mNormalizeCPUUsage; } void ProcessModel::setNormalizedCPUUsage(bool normalizeCPUUsage) { d->mNormalizeCPUUsage = normalizeCPUUsage; } void ProcessModelPrivate::timerEvent( QTimerEvent * event ) { Q_UNUSED(event); foreach (qlonglong pid, mPidsToUpdate) { KSysGuard::Process *process = mProcesses->getProcess(pid); if (process && !process->timeKillWasSent().isNull() && process->timeKillWasSent().elapsed() < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index1 = q->createIndex(row, 0, process); QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process); emit q->dataChanged(index1, index2); } else { mPidsToUpdate.removeAll(pid); } } if (mPidsToUpdate.isEmpty()) { mHaveTimer = false; killTimer(mTimerId); mTimerId = -1; } } diff --git a/processui/ProcessModel.h b/processui/ProcessModel.h index bfa3ed8..81f71d8 100644 --- a/processui/ProcessModel.h +++ b/processui/ProcessModel.h @@ -1,205 +1,205 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 PROCESSMODEL_H_ #define PROCESSMODEL_H_ #include #include namespace KSysGuard { class Processes; class Process; } class ProcessModelPrivate; #ifdef Q_OS_WIN // this workaround is needed to make krunner link under msvc // please keep it this way even if you port this library to have a _export.h header file #define KSYSGUARD_EXPORT #else #define KSYSGUARD_EXPORT Q_DECL_EXPORT #endif class KSYSGUARD_EXPORT ProcessModel : public QAbstractItemModel { Q_OBJECT Q_ENUMS(Units) public: - ProcessModel(QObject* parent = 0, const QString &host = QString() ); + ProcessModel(QObject* parent = nullptr, const QString &host = QString() ); ~ProcessModel() override; /* Functions for our Model for QAbstractItemModel*/ int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount ( const QModelIndex & parent = QModelIndex() ) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const override; QModelIndex parent ( const QModelIndex & index ) const override; bool hasChildren ( const QModelIndex & parent) const override; /** Returns if (left < right), used by the sort-filter proxy model to sort the columns */ bool lessThan( const QModelIndex & left, const QModelIndex & right) const; /* Functions for drag and drop and copying to clipboard, inherited from QAbstractItemModel */ QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; /* Functions for setting the model */ /** Setup the column headings by inserting the appropriate headings into the model. * Can be called more than once to retranslate the headings if the system language changes. */ void setupHeader(); /** Update data. You can pass in the time between updates to only update if there hasn't * been an update within the last @p updateDurationMSecs milliseconds. 0 indicate to update * regardless of when the last update was. * The updateFlags indicates what to additional update, as well as the usual details. */ void update(long updateDurationMSecs = 0, KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::IOStatistics); /** Return a string with the pid of the process and the name of the process. E.g. 13343: ksysguard */ QString getStringForProcess(KSysGuard::Process *process) const; KSysGuard::Process *getProcess(qlonglong pid); /** This is used from ProcessFilter to get the process at a given index when in flat mode */ KSysGuard::Process *getProcessAtIndex(int index) const; /** Returns whether this user can log in or not. * @see mUidCanLogin */ bool canUserLogin(long uid) const; /** In simple mode, everything is flat, with no icons, few if any colors, no xres etc. * This can be changed at any time. It is a fairly quick operation. Basically it resets the model */ void setSimpleMode(bool simple); /** In simple mode, everything is flat, with no icons, few if any colors, no xres etc */ bool isSimpleMode() const; /** Returns the total amount of physical memory in the machine. */ qlonglong totalMemory() const; /** This returns a QModelIndex for the given process. It has to look up the parent for this pid, find the offset this * pid is from the parent, and return that. It's not that slow, but does involve a couple of hash table lookups. */ QModelIndex getQModelIndex ( KSysGuard::Process *process, int column) const; /** Whether this is showing the processes for the current machine */ bool isLocalhost() const; /** The host name that this widget is showing the processes of */ QString hostName() const; /** Whether this process has a GUI window */ bool hasGUIWindow(qlonglong pid) const; /** Returns for process controller pointer for this model */ KSysGuard::Processes *processController() const; //The processes instance /** Convenience function to get the number of processes. * * Equivalent to processController->processCount() */ int processCount() const { return processController()->processCount(); } /** The headings in the model. The order here is the order that they are shown * in. If you change this, make sure you also change the * setup header function, and make sure you increase PROCESSHEADERVERSION. This will ensure * that old saved settings won't be used */ #define PROCESSHEADERVERSION 6 enum { HeadingName=0, HeadingUser, HeadingPid, HeadingTty, HeadingNiceness, HeadingCPUUsage, HeadingCPUTime, HeadingIoRead, HeadingIoWrite, HeadingVmSize, HeadingMemory, HeadingSharedMemory, HeadingStartTime, HeadingCommand, HeadingXMemory, HeadingXTitle }; enum { UidRole = Qt::UserRole, SortingValueRole, WindowIdRole, PlainValueRole, PercentageRole }; bool showTotals() const; /** When displaying memory sizes, this is the units it should be displayed in */ enum Units { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB, UnitsPercentage }; /** Set the units memory sizes etc should be displayed in */ void setUnits(Units units); /** The units memory sizes etc should be displayed in */ Units units() const; /** Set the I/O units sizes etc should be displayed in */ void setIoUnits(Units units); /** The units I/O sizes etc should be displayed in */ Units ioUnits() const; enum IoInformation { Bytes, Syscalls, ActualBytes, BytesRate, SyscallsRate, ActualBytesRate }; /** Set the information to show in the Io Read and Io Write columns */ void setIoInformation( IoInformation ioInformation ); /** The information to show in the Io Read and Io Write columns */ IoInformation ioInformation() const; /** Take an amount in kb, and return a string in the units set by setUnits() */ QString formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero = false) const; /** Whether to show the command line options in the process name column */ bool isShowCommandLineOptions() const; /** Set whether to show the command line options in the process name column */ void setShowCommandLineOptions(bool showCommandLineOptions); /** Whether to show tooltips when the mouse hovers over a process */ bool isShowingTooltips() const; /** Set whether to show tooltips when the mouse hovers over a process */ void setShowingTooltips(bool showTooltips); /** Whether to divide CPU usage by the number of CPUs */ bool isNormalizedCPUUsage() const; /** Set whether to divide CPU usage by the number of CPUs */ void setNormalizedCPUUsage(bool normalizeCPUUsage); /** Retranslate the GUI, for when the system language changes */ void retranslateUi(); public Q_SLOTS: /** Whether to show the total cpu for the process plus all of its children */ void setShowTotals(bool showTotals); private: ProcessModelPrivate* const d; friend class ProcessModelPrivate; }; #endif diff --git a/processui/ksysguardprocesslist.cpp b/processui/ksysguardprocesslist.cpp index 3216316..345c666 100644 --- a/processui/ksysguardprocesslist.cpp +++ b/processui/ksysguardprocesslist.cpp @@ -1,1473 +1,1473 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999 - 2001 Chris Schlaeger Copyright (c) 2006 - 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "ksysguardprocesslist.h" #include "../config-ksysguard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For SIGTERM #include #include #include #include #include #include #include #include "ReniceDlg.h" #include "ui_ProcessWidgetUI.h" #include "scripting.h" #include #include //Trolltech have a testing class for classes that inherit QAbstractItemModel. If you want to run with this run-time testing enabled, put the modeltest.* files in this directory and uncomment the next line //#define DO_MODELCHECK #ifdef DO_MODELCHECK #include "modeltest.h" #endif class ProgressBarItemDelegate : public QStyledItemDelegate { public: ProgressBarItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override { QStyleOptionViewItemV4 option = opt; initStyleOption(&option,index); float percentage = index.data(ProcessModel::PercentageRole).toFloat(); if (percentage >= 0) drawPercentageDisplay(painter,option, percentage); else QStyledItemDelegate::paint(painter, option, index); } private: inline void drawPercentageDisplay(QPainter *painter, QStyleOptionViewItemV4 &option, float percentage) const { QStyle *style = option.widget ? option.widget->style() : QApplication::style(); // draw the background style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget); QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) cg = QPalette::Inactive; //Now draw our percentage thingy const QRect &rect = option.rect; int size = qMin(percentage,1.0f) * rect.width(); if(size > 2 ) { //make sure the line will have a width of more than 1 pixel painter->setPen(Qt::NoPen); QColor color = option.palette.color(cg, QPalette::Link); color.setAlpha(50); painter->fillRect( rect.x(), rect.y(), size, rect.height(), color); } // draw the text if (!option.text.isEmpty()) { QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(option.palette.color(cg, QPalette::Text)); } painter->setFont(option.font); QTextOption textOption; textOption.setWrapMode(QTextOption::ManualWrap); textOption.setTextDirection(option.direction); textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment)); painter->drawText(textRect, option.text, textOption); } // draw the focus rect if (option.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = style->subElementRect(QStyle::SE_ItemViewItemFocusRect, &option, option.widget); o.state |= QStyle::State_KeyboardFocusChange; o.state |= QStyle::State_Item; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget); } } }; struct KSysGuardProcessListPrivate { KSysGuardProcessListPrivate(KSysGuardProcessList* q, const QString &hostName) - : mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(NULL), mUpdateTimer(NULL) + : mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(nullptr), mUpdateTimer(nullptr) { - mScripting = NULL; + mScripting = nullptr; mNeedToExpandInit = false; mNumItemsSelected = -1; mResortCountDown = 2; //The items added initially will be already sorted, but without CPU info. On the second refresh we will have CPU usage, so /then/ we can resort renice = new QAction(i18np("Set Priority...", "Set Priority...", 1), q); renice->setShortcut(Qt::Key_F8); selectParent = new QAction(i18n("Jump to Parent Process"), q); selectTracer = new QAction(i18n("Jump to Process Debugging This One"), q); window = new QAction(i18n("Show Application Window"), q); resume = new QAction(i18n("Resume Stopped Process"), q); terminate = new QAction(i18np("End Process", "End Processes", 1), q); terminate->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); terminate->setShortcut(Qt::Key_Delete); kill = new QAction(i18np("Forcibly Kill Process", "Forcibly Kill Processes", 1), q); kill->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); kill->setShortcut(Qt::SHIFT + Qt::Key_Delete); sigStop = new QAction(i18n("Suspend (STOP)"), q); sigCont = new QAction(i18n("Continue (CONT)"), q); sigHup = new QAction(i18n("Hangup (HUP)"), q); sigInt = new QAction(i18n("Interrupt (INT)"), q); sigTerm = new QAction(i18n("Terminate (TERM)"), q); sigKill = new QAction(i18n("Kill (KILL)"), q); sigUsr1 = new QAction(i18n("User 1 (USR1)"), q); sigUsr2 = new QAction(i18n("User 2 (USR2)"), q); //Set up '/' as a shortcut to jump to the quick search text widget jumpToSearchFilter = new QAction(i18n("Focus on Quick Search"), q); jumpToSearchFilter->setShortcuts(QList() << QKeySequence::Find << '/'); } - ~KSysGuardProcessListPrivate() { delete mUi; mUi = NULL; } + ~KSysGuardProcessListPrivate() { delete mUi; mUi = nullptr; } /** The number rows and their children for the given parent in the mFilterModel model */ int totalRowCount(const QModelIndex &parent) const; /** Helper function to setup 'action' with the given pids */ void setupKAuthAction(KAuth::Action &action, const QList & pids) const; /** fire a timer event if we are set to use our internal timer*/ void fireTimerEvent(); /** The process model. This contains all the data on all the processes running on the system */ ProcessModel mModel; /** The process filter. The mModel is connected to this, and this filter model connects to the view. This lets us * sort the view and filter (by using the combo box or the search line) */ ProcessFilter mFilterModel; /** The graphical user interface for this process list widget, auto-generated by Qt Designer */ Ui::ProcessWidget *mUi; /** The context menu when you right click on a process */ QMenu *mProcessContextMenu; /** A timer to call updateList() every mUpdateIntervalMSecs. * NULL is mUpdateIntervalMSecs is <= 0. */ QTimer *mUpdateTimer; /** The time to wait, in milliseconds, between updating the process list */ int mUpdateIntervalMSecs; /** Number of items that are selected */ int mNumItemsSelected; /** Class to deal with the scripting. NULL if scripting is disabled */ Scripting *mScripting; /** A counter to mark when to resort, so that we do not resort on every update */ int mResortCountDown; bool mNeedToExpandInit; QAction *renice; QAction *terminate; QAction *kill; QAction *selectParent; QAction *selectTracer; QAction *jumpToSearchFilter; QAction *window; QAction *resume; QAction *sigStop; QAction *sigCont; QAction *sigHup; QAction *sigInt; QAction *sigTerm; QAction *sigKill; QAction *sigUsr1; QAction *sigUsr2; }; KSysGuardProcessList::KSysGuardProcessList(QWidget* parent, const QString &hostName) : QWidget(parent), d(new KSysGuardProcessListPrivate(this, hostName)) { qRegisterMetaType >(); qDBusRegisterMetaType >(); d->mUpdateIntervalMSecs = 0; //Set process to not update manually by default d->mUi->setupUi(this); d->mFilterModel.setSourceModel(&d->mModel); d->mUi->treeView->setModel(&d->mFilterModel); #ifdef DO_MODELCHECK new ModelTest(&d->mModel, this); #endif d->mUi->treeView->setItemDelegate(new ProgressBarItemDelegate(d->mUi->treeView)); d->mUi->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(d->mUi->treeView->header(), &QWidget::customContextMenuRequested, this, &KSysGuardProcessList::showColumnContextMenu); d->mProcessContextMenu = new QMenu(d->mUi->treeView); d->mUi->treeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(d->mUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showProcessContextMenu(QPoint))); d->mUi->treeView->header()->setSectionsClickable(true); d->mUi->treeView->header()->setSortIndicatorShown(true); d->mUi->treeView->header()->setCascadingSectionResizes(false); connect(d->mUi->btnKillProcess, &QAbstractButton::clicked, this, &KSysGuardProcessList::killSelectedProcesses); connect(d->mUi->txtFilter, &QLineEdit::textChanged, this, &KSysGuardProcessList::filterTextChanged); connect(d->mUi->cmbFilter, SIGNAL(currentIndexChanged(int)), this, SLOT(setStateInt(int))); connect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren); connect(d->mUi->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KSysGuardProcessList::selectionChanged); connect(&d->mFilterModel, &QAbstractItemModel::rowsInserted, this, &KSysGuardProcessList::rowsInserted); connect(&d->mFilterModel, &QAbstractItemModel::rowsRemoved, this, &KSysGuardProcessList::processListChanged); setMinimumSize(sizeHint()); d->mFilterModel.setFilterKeyColumn(-1); /* Hide various columns by default, to reduce information overload */ d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmSize); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingNiceness); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingTty); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingStartTime); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCommand); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingPid); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCPUTime); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoRead); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoWrite); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingXMemory); // NOTE! After this is all setup, the settings for the header are restored // from the user's last run. (in restoreHeaderState) // So making changes here only affects the default settings. To // test changes temporarily, comment out the lines in restoreHeaderState. // When you are happy with the changes and want to commit, increase the // value of PROCESSHEADERVERSION. This will force the header state // to be reset back to the defaults for all users. d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingCPUUsage, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingCPUUsage)); d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingMemory)); d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingSharedMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingSharedMemory)); d->mUi->treeView->header()->setSectionResizeMode(0, QHeaderView::Interactive); d->mUi->treeView->header()->setStretchLastSection(true); //Process names can have mixed case. Make the filter case insensitive. d->mFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive); d->mFilterModel.setSortCaseSensitivity(Qt::CaseInsensitive); d->mUi->txtFilter->installEventFilter(this); d->mUi->treeView->installEventFilter(this); d->mUi->treeView->setDragEnabled(true); d->mUi->treeView->setDragDropMode(QAbstractItemView::DragOnly); //Sort by username by default d->mUi->treeView->sortByColumn(ProcessModel::HeadingUser, Qt::AscendingOrder); // Add all the actions to the main widget, and get all the actions to call actionTriggered when clicked QSignalMapper *signalMapper = new QSignalMapper(this); QList actions; actions << d->renice << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window << d->jumpToSearchFilter; actions << d->resume << d->sigStop << d->sigCont << d->sigHup << d->sigInt << d->sigTerm << d->sigKill << d->sigUsr1 << d->sigUsr2; foreach(QAction *action, actions) { addAction(action); connect(action, SIGNAL(triggered(bool)), signalMapper, SLOT(map())); signalMapper->setMapping(action, action); } connect(signalMapper, SIGNAL(mapped(QObject*)), SLOT(actionTriggered(QObject*))); retranslateUi(); d->mUi->btnKillProcess->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); d->mUi->btnKillProcess->setToolTip(i18n("End the selected process. Warning - you may lose unsaved work.
    Right click on a process to send other signals.
    See What's This for technical information.
    To target a specific window to kill, press Ctrl+Alt+Esc at any time.")); } KSysGuardProcessList::~KSysGuardProcessList() { delete d; } QTreeView *KSysGuardProcessList::treeView() const { return d->mUi->treeView; } QLineEdit *KSysGuardProcessList::filterLineEdit() const { return d->mUi->txtFilter; } ProcessFilter::State KSysGuardProcessList::state() const { return d->mFilterModel.filter(); } void KSysGuardProcessList::setStateInt(int state) { setState((ProcessFilter::State) state); d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex()); } void KSysGuardProcessList::setState(ProcessFilter::State state) { //index is the item the user selected in the combo box d->mFilterModel.setFilter(state); d->mModel.setSimpleMode( (state != ProcessFilter::AllProcessesInTreeForm) ); d->mUi->cmbFilter->setCurrentIndex( (int)state); if(isVisible()) expandInit(); } void KSysGuardProcessList::filterTextChanged(const QString &newText) { d->mFilterModel.setFilterRegExp(newText.trimmed()); if(isVisible()) expandInit(); d->mUi->btnKillProcess->setEnabled( d->mUi->treeView->selectionModel()->hasSelection() ); d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex()); } int KSysGuardProcessList::visibleProcessesCount() const { //This assumes that all the visible rows are processes. This is true currently, but might not be //true if we add support for showing threads etc if(d->mModel.isSimpleMode()) return d->mFilterModel.rowCount(); return d->totalRowCount(QModelIndex()); } int KSysGuardProcessListPrivate::totalRowCount(const QModelIndex &parent ) const { int numRows = mFilterModel.rowCount(parent); int total = numRows; for (int i = 0; i < numRows; ++i) { QModelIndex index = mFilterModel.index(i, 0,parent); //if it has children add the total if (mFilterModel.hasChildren(index)) total += totalRowCount(index); } return total; } void KSysGuardProcessListPrivate::setupKAuthAction(KAuth::Action &action, const QList & pids) const { action.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper")); int processCount = pids.count(); for(int i = 0; i < processCount; i++) { action.addArgument(QStringLiteral("pid%1").arg(i), pids[i]); } action.addArgument(QStringLiteral("pidcount"), processCount); } void KSysGuardProcessList::selectionChanged() { int numSelected = d->mUi->treeView->selectionModel()->selectedRows().size(); if(numSelected == d->mNumItemsSelected) return; d->mNumItemsSelected = numSelected; d->mUi->btnKillProcess->setEnabled( numSelected != 0 ); d->renice->setText(i18np("Set Priority...", "Set Priority...", numSelected)); d->kill->setText(i18np("Forcibly Kill Process", "Forcibly Kill Processes", numSelected)); d->terminate->setText(i18ncp("Context menu", "End Process", "End Processes", numSelected)); } void KSysGuardProcessList::showProcessContextMenu(const QModelIndex &index) { if(!index.isValid()) return; QRect rect = d->mUi->treeView->visualRect(index); QPoint point(rect.x() + rect.width()/4, rect.y() + rect.height()/2 ); showProcessContextMenu(point); } void KSysGuardProcessList::showProcessContextMenu(const QPoint &point) { d->mProcessContextMenu->clear(); QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); int numProcesses = selectedIndexes.size(); if(numProcesses == 0) { //No processes selected, so no process context menu //Check just incase we have no columns visible. In which case show the column context menu //so that users can unhide columns if there are no columns visible for(int i = 0; i < d->mFilterModel.columnCount(); ++i) { if(!d->mUi->treeView->header()->isSectionHidden(i)) return; } showColumnContextMenu(point); return; } QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0)); KSysGuard::Process *process = reinterpret_cast (realIndex.internalPointer()); //If the selected process is a zombie, do not bother offering renice and kill options bool showSignalingEntries = numProcesses != 1 || process->status() != KSysGuard::Process::Zombie; if(showSignalingEntries) { d->mProcessContextMenu->addAction(d->renice); QMenu *signalMenu = d->mProcessContextMenu->addMenu(i18n("Send Signal")); signalMenu->addAction(d->sigStop); signalMenu->addAction(d->sigCont); signalMenu->addAction(d->sigHup); signalMenu->addAction(d->sigInt); signalMenu->addAction(d->sigTerm); signalMenu->addAction(d->sigKill); signalMenu->addAction(d->sigUsr1); signalMenu->addAction(d->sigUsr2); } if(numProcesses == 1 && process->parentPid() > 1) { //As a design decision, I do not show the 'Jump to parent process' option when the //parent is just 'init'. KSysGuard::Process *parent_process = d->mModel.getProcess(process->parentPid()); if(parent_process) { //it should not be possible for this process to not exist, but check just incase QString parent_name = parent_process->name(); d->selectParent->setText(i18n("Jump to Parent Process (%1)", parent_name)); d->mProcessContextMenu->addAction(d->selectParent); } } if(numProcesses == 1 && process->tracerpid() >= 0) { //If the process is being debugged, offer to select it d->mProcessContextMenu->addAction(d->selectTracer); } if (numProcesses == 1 && !d->mModel.data(realIndex, ProcessModel::WindowIdRole).isNull()) { d->mProcessContextMenu->addAction(d->window); } if(numProcesses == 1 && process->status() == KSysGuard::Process::Stopped) { //If the process is stopped, offer to resume it d->mProcessContextMenu->addAction(d->resume); } if(numProcesses == 1 && d->mScripting) { foreach(QAction *action, d->mScripting->actions()) { d->mProcessContextMenu->addAction(action); } } if (showSignalingEntries) { d->mProcessContextMenu->addSeparator(); d->mProcessContextMenu->addAction(d->terminate); if (numProcesses == 1 && !process->timeKillWasSent().isNull()) d->mProcessContextMenu->addAction(d->kill); } d->mProcessContextMenu->popup(d->mUi->treeView->viewport()->mapToGlobal(point)); } void KSysGuardProcessList::actionTriggered(QObject *object) { if(!isVisible()) //Ignore triggered actions if we are not visible! return; //Reset the text back to normal d->selectParent->setText(i18n("Jump to Parent Process")); QAction *result = qobject_cast(object); - if(result == 0) { + if(result == nullptr) { //Escape was pressed. Do nothing. } else if(result == d->renice) { reniceSelectedProcesses(); } else if(result == d->terminate) { sendSignalToSelectedProcesses(SIGTERM, true); } else if(result == d->kill) { sendSignalToSelectedProcesses(SIGKILL, true); } else if(result == d->selectParent) { QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); int numProcesses = selectedIndexes.size(); if(numProcesses == 0) return; //No processes selected QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0)); KSysGuard::Process *process = reinterpret_cast (realIndex.internalPointer()); if(process) selectAndJumpToProcess(process->parentPid()); } else if(result == d->selectTracer) { QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); int numProcesses = selectedIndexes.size(); if(numProcesses == 0) return; //No processes selected QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0)); KSysGuard::Process *process = reinterpret_cast (realIndex.internalPointer()); if(process) selectAndJumpToProcess(process->tracerpid()); } else if(result == d->window) { QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); int numProcesses = selectedIndexes.size(); if(numProcesses == 0) return; //No processes selected foreach( const QModelIndex &index, selectedIndexes) { QModelIndex realIndex = d->mFilterModel.mapToSource(index); QVariant widVar= d->mModel.data(realIndex, ProcessModel::WindowIdRole); if( !widVar.isNull() ) { int wid = widVar.toInt(); KWindowSystem::activateWindow(wid); } } } else if(result == d->jumpToSearchFilter) { d->mUi->txtFilter->setFocus(); } else { int sig; if(result == d->resume || result == d->sigCont) sig = SIGCONT; //Despite the function name, this sends a signal, rather than kill it. Silly unix :) else if(result == d->sigStop) sig = SIGSTOP; else if(result == d->sigHup) sig = SIGHUP; else if(result == d->sigInt) sig = SIGINT; else if(result == d->sigTerm) sig = SIGTERM; else if(result == d->sigKill) sig = SIGKILL; else if(result == d->sigUsr1) sig = SIGUSR1; else if(result == d->sigUsr2) sig = SIGUSR2; else return; sendSignalToSelectedProcesses(sig, false); } } void KSysGuardProcessList::selectAndJumpToProcess(int pid) { KSysGuard::Process *process = d->mModel.getProcess(pid); if(!process) return; QModelIndex sourceIndex = d->mModel.getQModelIndex(process, 0); QModelIndex filterIndex = d->mFilterModel.mapFromSource( sourceIndex ); if(!filterIndex.isValid() && !d->mUi->txtFilter->text().isEmpty()) { //The filter is preventing us from finding the parent. Clear the filter //(It could also be the combo box - should we deal with that case as well?) d->mUi->txtFilter->clear(); filterIndex = d->mFilterModel.mapFromSource( sourceIndex ); } d->mUi->treeView->clearSelection(); d->mUi->treeView->setCurrentIndex(filterIndex); d->mUi->treeView->scrollTo( filterIndex, QAbstractItemView::PositionAtCenter); } void KSysGuardProcessList::showColumnContextMenu(const QPoint &point){ QMenu menu; QAction *action; int num_headings = d->mFilterModel.columnCount(); int index = d->mUi->treeView->header()->logicalIndexAt(point); if(index >= 0) { bool anyOtherVisibleColumns = false; for(int i = 0; i < num_headings; ++i) { if(i != index && !d->mUi->treeView->header()->isSectionHidden(i)) { anyOtherVisibleColumns = true; break; } } if(anyOtherVisibleColumns) { //selected a column. Give the option to hide it action = new QAction(&menu); action->setData(-index-1); //We set data to be negative (and minus 1) to hide a column, and positive to show a column action->setText(i18n("Hide Column '%1'", d->mFilterModel.headerData(index, Qt::Horizontal, Qt::DisplayRole).toString())); menu.addAction(action); if(d->mUi->treeView->header()->sectionsHidden()) { menu.addSeparator(); } } } if(d->mUi->treeView->header()->sectionsHidden()) { for(int i = 0; i < num_headings; ++i) { if(d->mUi->treeView->header()->isSectionHidden(i)) { #ifndef HAVE_XRES if(i == ProcessModel::HeadingXMemory) continue; #endif action = new QAction(&menu); action->setText(i18n("Show Column '%1'", d->mFilterModel.headerData(i, Qt::Horizontal, Qt::DisplayRole).toString())); action->setData(i); //We set data to be negative (and minus 1) to hide a column, and positive to show a column menu.addAction(action); } } } - QAction *actionAuto = NULL; - QAction *actionKB = NULL; - QAction *actionMB = NULL; - QAction *actionGB = NULL; - QAction *actionPercentage = NULL; - QAction *actionShowCmdlineOptions = NULL; - QAction *actionShowTooltips = NULL; - QAction *actionNormalizeCPUUsage = NULL; - - QAction *actionIoCharacters = NULL; - QAction *actionIoSyscalls = NULL; - QAction *actionIoActualCharacters = NULL; - QAction *actionIoShowRate = NULL; + QAction *actionAuto = nullptr; + QAction *actionKB = nullptr; + QAction *actionMB = nullptr; + QAction *actionGB = nullptr; + QAction *actionPercentage = nullptr; + QAction *actionShowCmdlineOptions = nullptr; + QAction *actionShowTooltips = nullptr; + QAction *actionNormalizeCPUUsage = nullptr; + + QAction *actionIoCharacters = nullptr; + QAction *actionIoSyscalls = nullptr; + QAction *actionIoActualCharacters = nullptr; + QAction *actionIoShowRate = nullptr; bool showIoRate = false; if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) showIoRate = d->mModel.ioInformation() == ProcessModel::BytesRate || d->mModel.ioInformation() == ProcessModel::SyscallsRate || d->mModel.ioInformation() == ProcessModel::ActualBytesRate; if( index == ProcessModel::HeadingVmSize || index == ProcessModel::HeadingMemory || index == ProcessModel::HeadingXMemory || index == ProcessModel::HeadingSharedMemory || ( (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) && d->mModel.ioInformation() != ProcessModel::Syscalls)) { //If the user right clicks on a column that contains a memory size, show a toggle option for displaying //the memory in different units. e.g. "2000 k" or "2 m" menu.addSeparator()->setText(i18n("Display Units")); QActionGroup *unitsGroup = new QActionGroup(&menu); /* Automatic (human readable)*/ actionAuto = new QAction(&menu); actionAuto->setText(i18n("Mixed")); actionAuto->setCheckable(true); menu.addAction(actionAuto); unitsGroup->addAction(actionAuto); /* Kilobytes */ actionKB = new QAction(&menu); actionKB->setText((showIoRate)?i18n("Kilobytes per second"):i18n("Kilobytes")); actionKB->setCheckable(true); menu.addAction(actionKB); unitsGroup->addAction(actionKB); /* Megabytes */ actionMB = new QAction(&menu); actionMB->setText((showIoRate)?i18n("Megabytes per second"):i18n("Megabytes")); actionMB->setCheckable(true); menu.addAction(actionMB); unitsGroup->addAction(actionMB); /* Gigabytes */ actionGB = new QAction(&menu); actionGB->setText((showIoRate)?i18n("Gigabytes per second"):i18n("Gigabytes")); actionGB->setCheckable(true); menu.addAction(actionGB); unitsGroup->addAction(actionGB); ProcessModel::Units currentUnit; if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) { currentUnit = d->mModel.ioUnits(); } else { actionPercentage = new QAction(&menu); actionPercentage->setText(i18n("Percentage")); actionPercentage->setCheckable(true); menu.addAction(actionPercentage); unitsGroup->addAction(actionPercentage); currentUnit = d->mModel.units(); } switch(currentUnit) { case ProcessModel::UnitsAuto: actionAuto->setChecked(true); break; case ProcessModel::UnitsKB: actionKB->setChecked(true); break; case ProcessModel::UnitsMB: actionMB->setChecked(true); break; case ProcessModel::UnitsGB: actionGB->setChecked(true); break; case ProcessModel::UnitsPercentage: actionPercentage->setChecked(true); break; default: break; } unitsGroup->setExclusive(true); } else if(index == ProcessModel::HeadingName) { menu.addSeparator(); actionShowCmdlineOptions = new QAction(&menu); actionShowCmdlineOptions->setText(i18n("Display command line options")); actionShowCmdlineOptions->setCheckable(true); actionShowCmdlineOptions->setChecked(d->mModel.isShowCommandLineOptions()); menu.addAction(actionShowCmdlineOptions); } else if(index == ProcessModel::HeadingCPUUsage) { menu.addSeparator(); actionNormalizeCPUUsage = new QAction(&menu); actionNormalizeCPUUsage->setText(i18n("Divide CPU usage by number of CPUs")); actionNormalizeCPUUsage->setCheckable(true); actionNormalizeCPUUsage->setChecked(d->mModel.isNormalizedCPUUsage()); menu.addAction(actionNormalizeCPUUsage); } if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) { menu.addSeparator()->setText(i18n("Displayed Information")); QActionGroup *ioInformationGroup = new QActionGroup(&menu); actionIoCharacters = new QAction(&menu); actionIoCharacters->setText(i18n("Characters read/written")); actionIoCharacters->setCheckable(true); menu.addAction(actionIoCharacters); ioInformationGroup->addAction(actionIoCharacters); actionIoSyscalls = new QAction(&menu); actionIoSyscalls->setText(i18n("Number of Read/Write operations")); actionIoSyscalls->setCheckable(true); menu.addAction(actionIoSyscalls); ioInformationGroup->addAction(actionIoSyscalls); actionIoActualCharacters = new QAction(&menu); actionIoActualCharacters->setText(i18n("Bytes actually read/written")); actionIoActualCharacters->setCheckable(true); menu.addAction(actionIoActualCharacters); ioInformationGroup->addAction(actionIoActualCharacters); actionIoShowRate = new QAction(&menu); actionIoShowRate->setText(i18n("Show I/O rate")); actionIoShowRate->setCheckable(true); actionIoShowRate->setChecked(showIoRate); menu.addAction(actionIoShowRate); switch(d->mModel.ioInformation()) { case ProcessModel::Bytes: case ProcessModel::BytesRate: actionIoCharacters->setChecked(true); break; case ProcessModel::Syscalls: case ProcessModel::SyscallsRate: actionIoSyscalls->setChecked(true); break; case ProcessModel::ActualBytes: case ProcessModel::ActualBytesRate: actionIoActualCharacters->setChecked(true); break; default: break; } } menu.addSeparator(); actionShowTooltips = new QAction(&menu); actionShowTooltips->setCheckable(true); actionShowTooltips->setChecked(d->mModel.isShowingTooltips()); actionShowTooltips->setText(i18n("Show Tooltips")); menu.addAction(actionShowTooltips); QAction *result = menu.exec(d->mUi->treeView->header()->mapToGlobal(point)); if(!result) return; //Menu cancelled if(result == actionAuto) { if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) d->mModel.setIoUnits(ProcessModel::UnitsAuto); else d->mModel.setUnits(ProcessModel::UnitsAuto); return; } else if(result == actionKB) { if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) d->mModel.setIoUnits(ProcessModel::UnitsKB); else d->mModel.setUnits(ProcessModel::UnitsKB); return; } else if(result == actionMB) { if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) d->mModel.setIoUnits(ProcessModel::UnitsMB); else d->mModel.setUnits(ProcessModel::UnitsMB); return; } else if(result == actionGB) { if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) d->mModel.setIoUnits(ProcessModel::UnitsGB); else d->mModel.setUnits(ProcessModel::UnitsGB); return; } else if(result == actionPercentage) { d->mModel.setUnits(ProcessModel::UnitsPercentage); return; } else if(result == actionShowCmdlineOptions) { d->mModel.setShowCommandLineOptions(actionShowCmdlineOptions->isChecked()); return; } else if(result == actionNormalizeCPUUsage) { d->mModel.setNormalizedCPUUsage(actionNormalizeCPUUsage->isChecked()); return; } else if(result == actionShowTooltips) { d->mModel.setShowingTooltips(actionShowTooltips->isChecked()); return; } else if(result == actionIoCharacters) { d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes); return; } else if(result == actionIoSyscalls) { d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls); return; } else if(result == actionIoActualCharacters) { d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes); return; } else if(result == actionIoShowRate) { showIoRate = actionIoShowRate->isChecked(); switch(d->mModel.ioInformation()) { case ProcessModel::Bytes: case ProcessModel::BytesRate: d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes); break; case ProcessModel::Syscalls: case ProcessModel::SyscallsRate: d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls); break; case ProcessModel::ActualBytes: case ProcessModel::ActualBytesRate: d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes); break; default: break; } } int i = result->data().toInt(); //We set data to be negative to hide a column, and positive to show a column if(i < 0) d->mUi->treeView->hideColumn(-1-i); else { d->mUi->treeView->showColumn(i); updateList(); d->mUi->treeView->resizeColumnToContents(i); d->mUi->treeView->resizeColumnToContents(d->mFilterModel.columnCount()); } menu.deleteLater(); } void KSysGuardProcessList::expandAllChildren(const QModelIndex &parent) { //This is called when the user expands a node. This then expands all of its //children. This will trigger this function again recursively. QModelIndex sourceParent = d->mFilterModel.mapToSource(parent); for(int i = 0; i < d->mModel.rowCount(sourceParent); i++) { d->mUi->treeView->expand(d->mFilterModel.mapFromSource(d->mModel.index(i,0, sourceParent))); } } void KSysGuardProcessList::rowsInserted(const QModelIndex & parent, int start, int end ) { if(d->mModel.isSimpleMode() || parent.isValid()) { emit processListChanged(); return; //No tree or not a root node - no need to expand init } disconnect(&d->mFilterModel, &QAbstractItemModel::rowsInserted, this, &KSysGuardProcessList::rowsInserted); //It is a root node that we just inserted - expand it bool expanded = false; for(int i = start; i <= end; i++) { QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex()); if(!d->mUi->treeView->isExpanded(index)) { if(!expanded) { disconnect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren); expanded = true; } d->mUi->treeView->expand(index); d->mNeedToExpandInit = true; } } if(expanded) connect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren); connect(&d->mFilterModel, &QAbstractItemModel::rowsInserted, this, &KSysGuardProcessList::rowsInserted); emit processListChanged(); } void KSysGuardProcessList::expandInit() { if(d->mModel.isSimpleMode()) return; //No tree - no need to expand init bool expanded = false; for(int i = 0; i < d->mFilterModel.rowCount(QModelIndex()); i++) { QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex()); if(!d->mUi->treeView->isExpanded(index)) { if(!expanded) { disconnect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren); expanded = true; } d->mUi->treeView->expand(index); } } if(expanded) connect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren); } void KSysGuardProcessList::hideEvent ( QHideEvent * event ) //virtual protected from QWidget { //Stop updating the process list if we are hidden if(d->mUpdateTimer) d->mUpdateTimer->stop(); //stop any scripts running, to save on memory if(d->mScripting) d->mScripting->stopAllScripts(); QWidget::hideEvent(event); } void KSysGuardProcessList::showEvent ( QShowEvent * event ) //virtual protected from QWidget { //Start updating the process list again if we are shown again updateList(); QHeaderView *header = d->mUi->treeView->header(); d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder()); QWidget::showEvent(event); } void KSysGuardProcessList::changeEvent( QEvent * event ) { if (event->type() == QEvent::LanguageChange) { d->mModel.retranslateUi(); d->mUi->retranslateUi(this); retranslateUi(); } QWidget::changeEvent(event); } void KSysGuardProcessList::retranslateUi() { d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcesses, QIcon::fromTheme(QStringLiteral("view-process-all"))); d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcessesInTreeForm, QIcon::fromTheme(QStringLiteral("view-process-all-tree"))); d->mUi->cmbFilter->setItemIcon(ProcessFilter::SystemProcesses, QIcon::fromTheme(QStringLiteral("view-process-system"))); d->mUi->cmbFilter->setItemIcon(ProcessFilter::UserProcesses, QIcon::fromTheme(QStringLiteral("view-process-users"))); d->mUi->cmbFilter->setItemIcon(ProcessFilter::OwnProcesses, QIcon::fromTheme(QStringLiteral("view-process-own"))); d->mUi->cmbFilter->setItemIcon(ProcessFilter::ProgramsOnly, QIcon::fromTheme(QStringLiteral("view-process-all"))); } void KSysGuardProcessList::updateList() { if(isVisible()) { KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::StandardInformation; if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoRead) || !d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoWrite)) updateFlags |= KSysGuard::Processes::IOStatistics; if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingXMemory)) updateFlags |= KSysGuard::Processes::XMemory; d->mModel.update(d->mUpdateIntervalMSecs, updateFlags); if(d->mUpdateTimer) d->mUpdateTimer->start(d->mUpdateIntervalMSecs); emit updated(); if (QToolTip::isVisible() && qApp->topLevelAt(QCursor::pos()) == window()) { QWidget *w = d->mUi->treeView->viewport(); if(w->geometry().contains(d->mUi->treeView->mapFromGlobal( QCursor::pos() ))) { QHelpEvent event(QEvent::ToolTip, w->mapFromGlobal( QCursor::pos() ), QCursor::pos()); qApp->notify(w, &event); } } if(--d->mResortCountDown <= 0) { d->mResortCountDown = 2; //resort every second time //resort now QHeaderView *header= d->mUi->treeView->header(); d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder()); } if( d->mNeedToExpandInit ) { expandInit(); d->mNeedToExpandInit = false; } } } int KSysGuardProcessList::updateIntervalMSecs() const { return d->mUpdateIntervalMSecs; } void KSysGuardProcessList::setUpdateIntervalMSecs(int intervalMSecs) { if(intervalMSecs == d->mUpdateIntervalMSecs) return; d->mUpdateIntervalMSecs = intervalMSecs; if(intervalMSecs <= 0) { //no point keep the timer around if we aren't updating automatically delete d->mUpdateTimer; - d->mUpdateTimer = NULL; + d->mUpdateTimer = nullptr; return; } if(!d->mUpdateTimer) { //intervalMSecs is a valid time, so set up a timer d->mUpdateTimer = new QTimer(this); d->mUpdateTimer->setSingleShot(true); connect(d->mUpdateTimer, &QTimer::timeout, this, &KSysGuardProcessList::updateList); if(isVisible()) d->mUpdateTimer->start(d->mUpdateIntervalMSecs); } else d->mUpdateTimer->setInterval(d->mUpdateIntervalMSecs); } bool KSysGuardProcessList::reniceProcesses(const QList &pids, int niceValue) { QList< long long> unreniced_pids; for (int i = 0; i < pids.size(); ++i) { bool success = d->mModel.processController()->setNiceness(pids.at(i), niceValue); if(!success) { unreniced_pids << pids.at(i); } } if(unreniced_pids.isEmpty()) return true; //All processes were reniced successfully if(!d->mModel.isLocalhost()) return false; //We can't use kauth to renice non-localhost processes KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.renice")); action.setParentWidget(window()); d->setupKAuthAction( action, unreniced_pids); action.addArgument(QStringLiteral("nicevalue"), niceValue); KAuth::ExecuteJob *job = action.execute(); if (job->exec()) { updateList(); } else if (!job->exec()) { KMessageBox::sorry(this, i18n("You do not have the permission to renice the process and there " "was a problem trying to run as root. Error %1 %2", job->error(), job->errorString())); return false; } return true; } QList KSysGuardProcessList::selectedProcesses() const { QList processes; QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); for(int i = 0; i < selectedIndexes.size(); ++i) { KSysGuard::Process *process = reinterpret_cast (d->mFilterModel.mapToSource(selectedIndexes.at(i)).internalPointer()); processes << process; } return processes; } void KSysGuardProcessList::reniceSelectedProcesses() { QList pids; QPointer reniceDlg; { QList processes = selectedProcesses(); QStringList selectedAsStrings; if (processes.isEmpty()) { KMessageBox::sorry(this, i18n("You must select a process first.")); return; } int sched = -2; int iosched = -2; foreach(KSysGuard::Process *process, processes) { pids << process->pid(); selectedAsStrings << d->mModel.getStringForProcess(process); if(sched == -2) sched = (int)process->scheduler(); else if(sched != -1 && sched != (int)process->scheduler()) sched = -1; //If two processes have different schedulers, disable the cpu scheduler stuff if(iosched == -2) iosched = (int)process->ioPriorityClass(); else if(iosched != -1 && iosched != (int)process->ioPriorityClass()) iosched = -1; //If two processes have different schedulers, disable the cpu scheduler stuff } int firstPriority = processes.first()->niceLevel(); int firstIOPriority = processes.first()->ioniceLevel(); bool supportsIoNice = d->mModel.processController()->supportsIoNiceness(); if(!supportsIoNice) { iosched = -2; firstIOPriority = -2; } reniceDlg = new ReniceDlg(d->mUi->treeView, selectedAsStrings, firstPriority, sched, firstIOPriority, iosched); if(reniceDlg->exec() == QDialog::Rejected) { delete reniceDlg; return; } } //Because we've done into ReniceDlg, which calls processEvents etc, our processes list is no //longer valid QList renicePids; QList changeCPUSchedulerPids; QList changeIOSchedulerPids; foreach (long long pid, pids) { KSysGuard::Process *process = d->mModel.getProcess(pid); if (!process) continue; switch(reniceDlg->newCPUSched) { case -2: case -1: //Invalid, not changed etc. break; //So do nothing case KSysGuard::Process::Other: case KSysGuard::Process::Fifo: if(reniceDlg->newCPUSched != (int)process->scheduler()) { changeCPUSchedulerPids << pid; renicePids << pid; } else if(reniceDlg->newCPUPriority != process->niceLevel()) renicePids << pid; break; case KSysGuard::Process::RoundRobin: case KSysGuard::Process::Batch: if(reniceDlg->newCPUSched != (int)process->scheduler() || reniceDlg->newCPUPriority != process->niceLevel()) { changeCPUSchedulerPids << pid; } break; } switch(reniceDlg->newIOSched) { case -2: case -1: //Invalid, not changed etc. break; //So do nothing case KSysGuard::Process::None: if(reniceDlg->newIOSched != (int)process->ioPriorityClass()) { // Unfortunately linux doesn't actually let us set the ioniceness back to none after being set to something else if(process->ioPriorityClass() != KSysGuard::Process::BestEffort || reniceDlg->newIOPriority != process->ioniceLevel()) changeIOSchedulerPids << pid; } break; case KSysGuard::Process::Idle: if(reniceDlg->newIOSched != (int)process->ioPriorityClass()) { changeIOSchedulerPids << pid; } break; case KSysGuard::Process::BestEffort: if(process->ioPriorityClass() == KSysGuard::Process::None && reniceDlg->newIOPriority == (process->niceLevel() + 20)/5) break; //Don't set to BestEffort if it's on None and the nicelevel wouldn't change case KSysGuard::Process::RealTime: if(reniceDlg->newIOSched != (int)process->ioPriorityClass() || reniceDlg->newIOPriority != process->ioniceLevel()) { changeIOSchedulerPids << pid; } break; } } if(!changeCPUSchedulerPids.isEmpty()) { Q_ASSERT(reniceDlg->newCPUSched >= 0); if(!changeCpuScheduler(changeCPUSchedulerPids, (KSysGuard::Process::Scheduler) reniceDlg->newCPUSched, reniceDlg->newCPUPriority)) { delete reniceDlg; return; } } if(!renicePids.isEmpty()) { Q_ASSERT(reniceDlg->newCPUPriority <= 20 && reniceDlg->newCPUPriority >= -20); if(!reniceProcesses(renicePids, reniceDlg->newCPUPriority)) { delete reniceDlg; return; } } if(!changeIOSchedulerPids.isEmpty()) { if(!changeIoScheduler(changeIOSchedulerPids, (KSysGuard::Process::IoPriorityClass) reniceDlg->newIOSched, reniceDlg->newIOPriority)) { delete reniceDlg; return; } } delete reniceDlg; updateList(); } bool KSysGuardProcessList::changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority) { if(newIoSched == KSysGuard::Process::None) newIoSched = KSysGuard::Process::BestEffort; if(newIoSched == KSysGuard::Process::Idle) newIoSchedPriority = 0; QList< long long> unchanged_pids; for (int i = 0; i < pids.size(); ++i) { bool success = d->mModel.processController()->setIoNiceness(pids.at(i), newIoSched, newIoSchedPriority); if(!success) { unchanged_pids << pids.at(i); } } if(unchanged_pids.isEmpty()) return true; if(!d->mModel.isLocalhost()) return false; //We can't use kauth to affect non-localhost processes KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.changeioscheduler")); action.setParentWidget(window()); d->setupKAuthAction( action, unchanged_pids); action.addArgument(QStringLiteral("ioScheduler"), (int)newIoSched); action.addArgument(QStringLiteral("ioSchedulerPriority"), newIoSchedPriority); KAuth::ExecuteJob *job = action.execute(); if (job->exec()) { updateList(); } else if (!job->exec()) { KMessageBox::sorry(this, i18n("You do not have the permission to change the I/O priority of the process and there " "was a problem trying to run as root. Error %1 %2", job->error(), job->errorString())); return false; } return true; } bool KSysGuardProcessList::changeCpuScheduler(const QList< long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority) { if(newCpuSched == KSysGuard::Process::Other || newCpuSched == KSysGuard::Process::Batch) newCpuSchedPriority = 0; QList< long long> unchanged_pids; for (int i = 0; i < pids.size(); ++i) { bool success = d->mModel.processController()->setScheduler(pids.at(i), newCpuSched, newCpuSchedPriority); if(!success) { unchanged_pids << pids.at(i); } } if(unchanged_pids.isEmpty()) return true; if(!d->mModel.isLocalhost()) return false; //We can't use KAuth to affect non-localhost processes KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.changecpuscheduler")); action.setParentWidget(window()); d->setupKAuthAction( action, unchanged_pids); action.addArgument(QStringLiteral("cpuScheduler"), (int)newCpuSched); action.addArgument(QStringLiteral("cpuSchedulerPriority"), newCpuSchedPriority); KAuth::ExecuteJob *job = action.execute(); if (job->exec()) { updateList(); } else if (!job->exec()) { KMessageBox::sorry(this, i18n("You do not have the permission to change the CPU Scheduler for the process and there " "was a problem trying to run as root. Error %1 %2", job->error(), job->errorString())); return false; } return true; } bool KSysGuardProcessList::killProcesses(const QList< long long> &pids, int sig) { QList< long long> unkilled_pids; for (int i = 0; i < pids.size(); ++i) { bool success = d->mModel.processController()->sendSignal(pids.at(i), sig); // If we failed due to a reason other than insufficient permissions, elevating to root can't // help us if(!success && (d->mModel.processController()->lastError() == KSysGuard::Processes::InsufficientPermissions || d->mModel.processController()->lastError() == KSysGuard::Processes::Unknown)) { unkilled_pids << pids.at(i); } } if(unkilled_pids.isEmpty()) return true; if(!d->mModel.isLocalhost()) return false; //We can't elevate privileges to kill non-localhost processes KAuth::Action action(QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal")); action.setParentWidget(window()); d->setupKAuthAction( action, unkilled_pids); action.addArgument(QStringLiteral("signal"), sig); KAuth::ExecuteJob *job = action.execute(); if (job->exec()) { updateList(); } else if (!job->exec()) { KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there " "was a problem trying to run as root. Error %1 %2", job->error(), job->errorString())); return false; } return true; } void KSysGuardProcessList::killSelectedProcesses() { sendSignalToSelectedProcesses(SIGTERM, true); } void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm) { QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); QStringList selectedAsStrings; QList< long long> selectedPids; QList processes = selectedProcesses(); foreach(KSysGuard::Process *process, processes) { selectedPids << process->pid(); if (!confirm) continue; QString name = d->mModel.getStringForProcess(process); selectedAsStrings << name; } if (selectedPids.isEmpty()) { if (confirm) KMessageBox::sorry(this, i18n("You must select a process first.")); return; } else if (confirm && (sig == SIGTERM || sig == SIGKILL)) { int count = selectedAsStrings.count(); QString msg; QString title; QString dontAskAgainKey; QString closeButton; if (sig == SIGTERM) { msg = i18np("Are you sure you want to end this process? Any unsaved work may be lost.", "Are you sure you want to end these %1 processes? Any unsaved work may be lost", count); title = i18ncp("Dialog title", "End Process", "End %1 Processes", count); dontAskAgainKey = QStringLiteral("endconfirmation"); closeButton = i18n("End"); } else if (sig == SIGKILL) { msg = i18np("Are you sure you want to immediately and forcibly kill this process? Any unsaved work may be lost.", "Are you sure you want to immediately and forcibly kill these %1 processes? Any unsaved work may be lost", count); title = i18ncp("Dialog title", "Forcibly Kill Process", "Forcibly Kill %1 Processes", count); dontAskAgainKey = QStringLiteral("killconfirmation"); closeButton = i18n("Kill"); } int res = KMessageBox::warningContinueCancelList(this, msg, selectedAsStrings, title, KGuiItem(closeButton, QStringLiteral("process-stop")), KStandardGuiItem::cancel(), dontAskAgainKey); if (res != KMessageBox::Continue) return; } //We have shown a GUI dialog box, which processes events etc. //So processes is NO LONGER VALID if (!killProcesses(selectedPids, sig)) return; if (sig == SIGTERM || sig == SIGKILL) { foreach (long long pid, selectedPids) { KSysGuard::Process *process = d->mModel.getProcess(pid); if (process) process->timeKillWasSent().start(); d->mUi->treeView->selectionModel()->clearSelection(); } } updateList(); } bool KSysGuardProcessList::showTotals() const { return d->mModel.showTotals(); } void KSysGuardProcessList::setShowTotals(bool showTotals) //slot { d->mModel.setShowTotals(showTotals); } ProcessModel::Units KSysGuardProcessList::units() const { return d->mModel.units(); } void KSysGuardProcessList::setUnits(ProcessModel::Units unit) { d->mModel.setUnits(unit); } void KSysGuardProcessList::saveSettings(KConfigGroup &cg) { /* Note that the ksysguard program does not use these functions. It saves the settings itself to an xml file instead */ cg.writeEntry("units", (int)(units())); cg.writeEntry("ioUnits", (int)(d->mModel.ioUnits())); cg.writeEntry("ioInformation", (int)(d->mModel.ioInformation())); cg.writeEntry("showCommandLineOptions", d->mModel.isShowCommandLineOptions()); cg.writeEntry("normalizeCPUUsage", d->mModel.isNormalizedCPUUsage()); cg.writeEntry("showTooltips", d->mModel.isShowingTooltips()); cg.writeEntry("showTotals", showTotals()); cg.writeEntry("filterState", (int)(state())); cg.writeEntry("updateIntervalMSecs", updateIntervalMSecs()); cg.writeEntry("headerState", d->mUi->treeView->header()->saveState()); //If we change, say, the header between versions of ksysguard, then the old headerState settings will not be valid. //The version property lets us keep track of which version we are cg.writeEntry("version", PROCESSHEADERVERSION); } void KSysGuardProcessList::loadSettings(const KConfigGroup &cg) { /* Note that the ksysguard program does not use these functions. It saves the settings itself to an xml file instead */ setUnits((ProcessModel::Units) cg.readEntry("units", (int)ProcessModel::UnitsKB)); d->mModel.setIoUnits((ProcessModel::Units) cg.readEntry("ioUnits", (int)ProcessModel::UnitsKB)); d->mModel.setIoInformation((ProcessModel::IoInformation) cg.readEntry("ioInformation", (int)ProcessModel::ActualBytesRate)); d->mModel.setShowCommandLineOptions(cg.readEntry("showCommandLineOptions", false)); d->mModel.setNormalizedCPUUsage(cg.readEntry("normalizeCPUUsage", true)); d->mModel.setShowingTooltips(cg.readEntry("showTooltips", true)); setShowTotals(cg.readEntry("showTotals", true)); setStateInt(cg.readEntry("filterState", (int)ProcessFilter::AllProcesses)); setUpdateIntervalMSecs(cg.readEntry("updateIntervalMSecs", 2000)); int version = cg.readEntry("version", 0); if(version == PROCESSHEADERVERSION) { //If the header has changed, the old settings are no longer valid. Only restore if version is the same restoreHeaderState(cg.readEntry("headerState", QByteArray())); } } void KSysGuardProcessList::restoreHeaderState(const QByteArray & state) { d->mUi->treeView->header()->restoreState(state); } bool KSysGuardProcessList::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if(obj == d->mUi->treeView) { if(keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { d->mUi->treeView->selectionModel()->select(d->mUi->treeView->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows); showProcessContextMenu(d->mUi->treeView->currentIndex()); return true; } else if(keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine) || keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage)) { if (d->mUi->treeView->selectionModel()->selectedRows().size() == 1 && d->mUi->treeView->selectionModel()->selectedRows().first().row() == 0) { // when first row is selected, pressing up or pgup moves to the textfield d->mUi->txtFilter->setFocus(); return true; } } else if (!keyEvent->text().isEmpty() && keyEvent->key() != Qt::Key_Tab && (!keyEvent->modifiers() || keyEvent->modifiers() == Qt::ShiftModifier)) { // move to textfield and forward keyevent if user starts typing from treeview d->mUi->txtFilter->setFocus(); QApplication::sendEvent(d->mUi->txtFilter, event); return true; } } else { Q_ASSERT(obj == d->mUi->txtFilter); if (d->mUi->treeView->model()->rowCount() == 0) { // treeview is empty, do nothing return false; } if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { // pressing enter will send enter to the first row in the list // the focusin eventfilter will make sure the first row is selected if there was // no previous selection d->mUi->treeView->setFocus(); QApplication::sendEvent(d->mUi->treeView, event); return true; } else if(keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine) || keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage)) { // attempting to move down by down-key or pgdown, or pressing enter will move focus // to the treeview d->mUi->treeView->setFocus(); return true; } } } return false; } ProcessModel *KSysGuardProcessList::processModel() { return &d->mModel; } void KSysGuardProcessList::setKillButtonVisible(bool visible) { d->mUi->btnKillProcess->setVisible(visible); } bool KSysGuardProcessList::isKillButtonVisible() const { return d->mUi->btnKillProcess->isVisible(); } bool KSysGuardProcessList::scriptingEnabled() const { return !!d->mScripting; } void KSysGuardProcessList::setScriptingEnabled(bool enabled) { if(!!d->mScripting == enabled) return; //Nothing changed if(!enabled) { delete d->mScripting; - d->mScripting = NULL; + d->mScripting = nullptr; } else { d->mScripting = new Scripting(this); d->mScripting->hide(); } } diff --git a/processui/ksysguardprocesslist.h b/processui/ksysguardprocesslist.h index 70334b1..9e04f19 100644 --- a/processui/ksysguardprocesslist.h +++ b/processui/ksysguardprocesslist.h @@ -1,235 +1,235 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 _KSysGuardProcessList_h_ #define _KSysGuardProcessList_h_ #include #include #include #include "ProcessModel.h" #include "ProcessFilter.h" #include class QShowEvent; class QHideEvent; class QLineEdit; class QTreeView; struct KSysGuardProcessListPrivate; /** * This widget implements a process list page. Besides the process * list which is implemented as a ProcessList, it contains two * combo boxes and two buttons. The combo boxes are used to set the * update rate and the process filter. The buttons are used to force * an immediate update and to kill a process. */ class Q_DECL_EXPORT KSysGuardProcessList : public QWidget { Q_OBJECT Q_PROPERTY( bool showTotalsInTree READ showTotals WRITE setShowTotals ) Q_PROPERTY( ProcessFilter::State state READ state WRITE setState ) Q_PROPERTY( int updateIntervalMSecs READ updateIntervalMSecs WRITE setUpdateIntervalMSecs ) Q_PROPERTY( ProcessModel::Units units READ units WRITE setUnits ) Q_PROPERTY( bool killButtonVisible READ isKillButtonVisible WRITE setKillButtonVisible ) Q_PROPERTY( bool scriptingEnabled READ scriptingEnabled WRITE setScriptingEnabled ) Q_ENUMS( ProcessFilter::State ) Q_ENUMS( ProcessModel::Units ) public: - KSysGuardProcessList(QWidget* parent = NULL, const QString &hostName = QString()); + KSysGuardProcessList(QWidget* parent = nullptr, const QString &hostName = QString()); ~KSysGuardProcessList() override; QLineEdit *filterLineEdit() const; QTreeView *treeView() const; /** Returns which processes we are currently filtering for and the way in which we show them. * @see setState() */ ProcessFilter::State state() const; /** Returns the number of milliseconds that have to elapse before updating the list of processes. * If this is 0, the processes will not be automatically updated. */ int updateIntervalMSecs() const; /** Whether the widget will show child totals for CPU and Memory etc usage */ bool showTotals() const; /** The units to display memory sizes etc in. E.g. kb/mb/gb */ ProcessModel::Units units() const; /** Returns a list of the processes that have been selected by the user. */ QList selectedProcesses() const; /** Returns the number of processes currently being displayed * * To get the total number processes, visible or not, use processModel()-> * */ int visibleProcessesCount() const; /** Save the current state of the widget to the given config group * * @param[in] cg Config group to add these settings to * */ void saveSettings(KConfigGroup &cg); /** Load the saved state of the widget from the given config group */ void loadSettings(const KConfigGroup &cg); /** Returns the process model used. Use with caution. */ ProcessModel *processModel(); /** Restore the headings to the given state. */ void restoreHeaderState(const QByteArray & state); /** @returns whether the Kill Process button is visible. */ bool isKillButtonVisible() const; /** @param visible defines whether the Kill Process button is shown or not. */ void setKillButtonVisible(bool visible); /** Whether scripting support is enabled. * * Default is false. */ bool scriptingEnabled() const; /** Set whether scripting support is enabled. * * Default is false. */ void setScriptingEnabled(bool enabled); Q_SIGNALS: /** Emitted when the display has been updated */ void updated(); void processListChanged(); public Q_SLOTS: /** Inform the view that the user has changed the selection */ void selectionChanged(); /** Send a kill signal to all the processes that the user has selected. Pops up a dialog box to confirm with the user */ void killSelectedProcesses(); /** Send a signal to all the processes that the user has selected. * @p confirm - If true, pops up a dialog box to confirm with the user */ void sendSignalToSelectedProcesses(int sig, bool confirm); /** Send a signal to a list of given processes. * @p pids A list of PIDs that should be sent the signal * @p sig The signal to send. * @return Whether the kill went ahead. True if successful or user cancelled. False if there was a problem */ bool killProcesses(const QList< long long> &pids, int sig); /** Renice all the processes that the user has selected. Pops up a dialog box to ask for the nice value and confirm */ void reniceSelectedProcesses(); /** Change the CPU scheduler for the given of processes to the given scheduler, with the given scheduler priority. * If the scheduler is Other or Batch, @p newCpuSchedPriority is ignored. * @return Whether the cpu scheduler changing went ahead. True if successful or user cancelled. False if there was a problem */ bool changeCpuScheduler(const QList< long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority); /** Change the I/O scheduler for the given of processes to the given scheduler, with the given scheduler priority. * If the scheduler is Other or Batch, @p newCpuSchedPriority is ignored. * @return Whether the cpu scheduler changing went ahead. True if successful or user cancelled. False if there was a problem */ bool changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority); /** Renice the processes given to the given niceValue. * @return Whether the kill went ahead. True if successful or user cancelled. False if there was a problem * */ bool reniceProcesses(const QList &pids, int niceValue); /** Fetch new process information and redraw the display */ void updateList(); /** Set which processes we are currently filtering for and the way in which we show them. */ void setState(ProcessFilter::State state); /** Set the number of milliseconds that have to elapse before updating the list of processes. * If this is set to 0, the process list will not be automatically updated and the owner can call * updateList() manually. */ void setUpdateIntervalMSecs(int intervalMSecs); /** Set whether to show child totals for CPU and Memory etc usage */ void setShowTotals(bool showTotals); /** Focus on a particular process, and select it */ void selectAndJumpToProcess(int pid); /** The units to display memory sizes etc in. */ void setUnits(ProcessModel::Units unit); /** Row was just inserted in the filter model */ void rowsInserted ( const QModelIndex & parent, int start, int end ); private Q_SLOTS: /** Expand all the children, recursively, of the node given. Pass an empty QModelIndex to expand all the top level children */ void expandAllChildren(const QModelIndex &parent); /** Expand init to show its children, but not the sub children processes. */ void expandInit(); /** Display a context menu for the column headings allowing the user to show or hide columns. */ void showColumnContextMenu(const QPoint &point); /** Display a context menu for the given process allowing the user to kill etc the process */ void showProcessContextMenu(const QModelIndex &index); /** Display a context menu for the selected processes allowing the user to kill etc the process */ void showProcessContextMenu(const QPoint &point); /** Set state from combo box int value */ void setStateInt(int state); /** Called when the text in the gui filter text box has changed */ void filterTextChanged(const QString &newText); /** Called when one of the actions (kill, renice etc) is clicked etc */ void actionTriggered(QObject *object); protected: /** Inherit QWidget::showEvent(QShowEvent *) to enable the timer, for updates, when visible */ void showEvent(QShowEvent*) override; /** Inherit QWidget::hideEvent(QShowEvent *) to disable the timer, for updates, when not visible */ void hideEvent(QHideEvent*) override; /** Capture any change events sent to this widget. In particular QEvent::LanguageChange */ void changeEvent ( QEvent * event ) override; bool eventFilter(QObject *obj, QEvent *event) override; /** Retranslate the Ui as needed */ void retranslateUi(); private: KSysGuardProcessListPrivate* const d; }; Q_DECLARE_METATYPE( long long ) Q_DECLARE_METATYPE( QList ) #endif diff --git a/processui/scripting.cpp b/processui/scripting.cpp index afdf949..9cb1658 100644 --- a/processui/scripting.cpp +++ b/processui/scripting.cpp @@ -1,232 +1,232 @@ /* KSysGuard, the KDE System Guard Copyright (c) 2009 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "scripting.h" #include #include #include #include #include #include #include #include #include #include "processes.h" #include "ksysguardprocesslist.h" #include #include #include #include #include #include #if HAVE_QTWEBKITWIDGETS #include #include #endif class ScriptingHtmlDialog : public QDialog { public: ScriptingHtmlDialog(QWidget *parent) : QDialog(parent) { QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); #if HAVE_QTWEBKITWIDGETS QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(&m_webView); layout->addWidget(buttonBox); setLayout(layout); (void)minimumSizeHint(); //Force the dialog to be laid out now layout->setContentsMargins(0,0,0,0); m_webView.settings()->setOfflineStoragePath(QString()); m_webView.settings()->setObjectCacheCapacities(0,0,0); m_webView.settings()->setAttribute(QWebSettings::PluginsEnabled, false); m_webView.settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, false); - m_webView.page()->setNetworkAccessManager(NULL); //Disable talking to remote servers + m_webView.page()->setNetworkAccessManager(nullptr); //Disable talking to remote servers m_webView.page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded); m_webView.page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded); // inject a style sheet that follows system colors, otherwise we might end up with black text on dark gray background const QString styleSheet = QStringLiteral( "body { background: %1; color: %2; }" \ "a { color: %3; }" \ "a:visited { color: %4; } " ).arg(palette().background().color().name(), palette().text().color().name(), palette().link().color().name(), palette().linkVisited().color().name()); // you can only provide a user style sheet url, so we turn it into a data url here const QUrl dataUrl(QStringLiteral("data:text/css;charset=utf-8;base64,") + QString::fromLatin1(styleSheet.toUtf8().toBase64())); m_webView.settings()->setUserStyleSheetUrl(dataUrl); #endif } #if HAVE_QTWEBKITWIDGETS QWebView *webView() { return &m_webView; } protected: QWebView m_webView; #endif }; ProcessObject::ProcessObject(ProcessModel *model, int pid) { mModel = model; mPid = pid; } bool ProcessObject::fileExists(const QString &filename) { QFileInfo fileInfo(filename); return fileInfo.exists(); } QString ProcessObject::readFile(const QString &filename) { QFile file(filename); if(!file.open(QIODevice::ReadOnly)) return QString(); QTextStream stream(&file); QString contents = stream.readAll(); file.close(); return contents; } Scripting::Scripting(KSysGuardProcessList * parent) : QWidget(parent), mProcessList(parent) { - mScriptingHtmlDialog = NULL; + mScriptingHtmlDialog = nullptr; loadContextMenu(); } void Scripting::runScript(const QString &path, const QString &name) { //Record the script name and path for use in the script helper functions mScriptPath = path; mScriptName = name; #if HAVE_QTWEBKITWIDGETS QUrl fileName = QUrl::fromLocalFile(path + "index.html"); if(!mScriptingHtmlDialog) { mScriptingHtmlDialog = new ScriptingHtmlDialog(this); connect(mScriptingHtmlDialog, &QDialog::rejected, this, &Scripting::stopAllScripts); QAction *refreshAction = new QAction(QStringLiteral("refresh"), mScriptingHtmlDialog); refreshAction->setShortcut(QKeySequence::Refresh); connect(refreshAction, &QAction::triggered, this, &Scripting::refreshScript); mScriptingHtmlDialog->addAction(refreshAction); QAction *zoomInAction = KStandardAction::zoomIn(this, SLOT(zoomIn()), mScriptingHtmlDialog); mScriptingHtmlDialog->addAction(zoomInAction); QAction *zoomOutAction = KStandardAction::zoomOut(this, SLOT(zoomOut()), mScriptingHtmlDialog); mScriptingHtmlDialog->addAction(zoomOutAction); } //Make the process information available to the script mScriptingHtmlDialog->webView()->load(fileName); mScriptingHtmlDialog->show(); connect(mScriptingHtmlDialog->webView()->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared, this, &Scripting::setupJavascriptObjects); setupJavascriptObjects(); #else QMessageBox::critical(this, i18n("QtWebKitWidgets not available"), i18n("KSysGuard library was compiled without QtWebKitWidgets, please contact your distribution.")); #endif } #if HAVE_QTWEBKITWIDGETS void Scripting::zoomIn() { QWebView *webView = mScriptingHtmlDialog->webView(); webView->setZoomFactor( webView->zoomFactor() * 1.1 ); } void Scripting::zoomOut() { QWebView *webView = mScriptingHtmlDialog->webView(); if(webView->zoomFactor() > 0.1) //Prevent it getting too small webView->setZoomFactor( webView->zoomFactor() / 1.1 ); } void Scripting::refreshScript() { //Call any refresh function, if it exists mProcessList->processModel()->update(0, KSysGuard::Processes::XMemory); if(mScriptingHtmlDialog && mScriptingHtmlDialog->webView() && mScriptingHtmlDialog->webView()->page() && mScriptingHtmlDialog->webView()->page()->mainFrame()) { mScriptingHtmlDialog->webView()->page()->mainFrame()->evaluateJavaScript(QStringLiteral("refresh();")); } } void Scripting::setupJavascriptObjects() { mProcessList->processModel()->update(0, KSysGuard::Processes::XMemory); mProcessObject = new ProcessObject(mProcessList->processModel(), mPid); mScriptingHtmlDialog->webView()->page()->mainFrame()->addToJavaScriptWindowObject(QStringLiteral("process"), mProcessObject, QWebFrame::ScriptOwnership); } #endif void Scripting::stopAllScripts() { if (mScriptingHtmlDialog) mScriptingHtmlDialog->deleteLater(); - mScriptingHtmlDialog = NULL; - mProcessObject = NULL; + mScriptingHtmlDialog = nullptr; + mProcessObject = nullptr; mScriptPath.clear(); mScriptName.clear(); } void Scripting::loadContextMenu() { //Clear any existing actions qDeleteAll(mActions); mActions.clear(); QStringList scripts; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("ksysguard/scripts/"), QStandardPaths::LocateDirectory); Q_FOREACH (const QString& dir, dirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*.desktop"), QDir::NoFilter, QDirIterator::Subdirectories); while (it.hasNext()) { scripts.append(it.next()); } } foreach(const QString &script, scripts) { KDesktopFile desktopFile(script); if(!desktopFile.name().isEmpty() && !desktopFile.noDisplay()) { QAction *action = new QAction(desktopFile.readName(), this); action->setToolTip(desktopFile.readComment()); action->setIcon(QIcon(desktopFile.readIcon())); QString scriptPath = script; scriptPath.truncate(scriptPath.lastIndexOf('/')); action->setProperty("scriptPath", QString(scriptPath + QLatin1Char('/'))); connect(action, &QAction::triggered, this, &Scripting::runScriptSlot); mProcessList->addAction(action); mActions << action; } } } void Scripting::runScriptSlot() { QAction *action = static_cast(sender()); //All the files for the script should be in the scriptPath QString path = action->property("scriptPath").toString(); QList selectedProcesses = mProcessList->selectedProcesses(); if(selectedProcesses.isEmpty()) return; mPid = selectedProcesses[0]->pid(); runScript(path, action->text()); } diff --git a/signalplotter/kgraphicssignalplotter.h b/signalplotter/kgraphicssignalplotter.h index 1c26591..0d68cf3 100644 --- a/signalplotter/kgraphicssignalplotter.h +++ b/signalplotter/kgraphicssignalplotter.h @@ -1,462 +1,462 @@ /* This file is part of the KDE project Copyright (c) 2006 - 2009 John Tapsell This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KGRAPHICSSIGNALPLOTTER_H #define KGRAPHICSSIGNALPLOTTER_H #include #include #include #include #include #include class QGraphicsSceneResizeEvent; class KGraphicsSignalPlotterPrivate; //Make sure only declare KLocalizedString once #ifndef KSIGNALPLOTTER_H Q_DECLARE_METATYPE(KLocalizedString) #endif /** \class KGraphicsSignalPlotter * \brief The KGraphicsSignalPlotter graphics widget draws a real time graph of data that updates continually. * * Features include: * \li Points are joined by a bezier curve. * \li Lines are anti-aliased * \li Background can be set as a specified SVG * \li The lines can be reordered * \li Uses as little memory and CPU as possible * \li Graph can be smoothed using the formula (value * 2 + last_value)/3 * \li Can cope with positive/negative infinity and NaN values. * * Example usage: * \code * KGraphicsSignalPlotter *s = KGraphicsSignalPlotter(parent); * s->addBeam(Qt::blue); * s->addBeam(Qt::green); * QList data; * data << 4.0 << 5.0; * s->addSample(data); * \endcode * * Note that the number of horizontal lines is calculated automatically based on the axis font size, even if the axis labels are not shown. * * Smoothing looks very nice visually and is enabled by default. It can be disabled with setSmoothGraph(). * * \image KSignalPlotter.png Example KGraphicsSignalPlotter with two beams */ class Q_DECL_EXPORT KGraphicsSignalPlotter : public QGraphicsWidget { Q_OBJECT Q_PROPERTY(qreal minimumValue READ minimumValue WRITE setMinimumValue) Q_PROPERTY(qreal maximumValue READ maximumValue WRITE setMaximumValue) Q_PROPERTY(bool useAutoRange READ useAutoRange WRITE setUseAutoRange) Q_PROPERTY(KLocalizedString unit READ unit WRITE setUnit) Q_PROPERTY(qreal scaleDownBy READ scaleDownBy WRITE setScaleDownBy) Q_PROPERTY(uint horizontalScale READ horizontalScale WRITE setHorizontalScale) Q_PROPERTY(bool showHorizontalLines READ showHorizontalLines WRITE setShowHorizontalLines) Q_PROPERTY(bool showVerticalLines READ showVerticalLines WRITE setShowVerticalLines) Q_PROPERTY(bool verticalLinesScroll READ verticalLinesScroll WRITE setVerticalLinesScroll) Q_PROPERTY(uint verticalLinesDistance READ verticalLinesDistance WRITE setVerticalLinesDistance) Q_PROPERTY(bool showAxis READ showAxis WRITE setShowAxis) Q_PROPERTY(QString svgBackground READ svgBackground WRITE setSvgBackground) Q_PROPERTY(bool thinFrame READ thinFrame WRITE setThinFrame) Q_PROPERTY(int maxAxisTextWidth READ maxAxisTextWidth WRITE setMaxAxisTextWidth) Q_PROPERTY(bool smoothGraph READ smoothGraph WRITE setSmoothGraph) Q_PROPERTY(bool stackGraph READ stackGraph WRITE setStackGraph) Q_PROPERTY(int fillOpacity READ fillOpacity WRITE setFillOpacity) public: - KGraphicsSignalPlotter( QGraphicsItem *parent = 0); + KGraphicsSignalPlotter( QGraphicsItem *parent = nullptr); ~KGraphicsSignalPlotter() override; /** \brief Add a new line to the graph plotter, with the specified color. * * Note that the order you add the beams in must be the same order that * the beam data is given in (Unless you reorder the beams). * * \param color Color of beam - does not have to be unique. */ void addBeam( const QColor &color ); /** \brief Add data to the graph, and advance the graph by one time period. * * The data must be given as a list in the same order that the beams were * added (or consequently reordered). If samples.count() != numBeams(), * a warning is printed and the data discarded. * * To indicate that no data is available for a given beam, set its value to * (non-signalling) NaN. * * For example: * * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::red); * s->addBeam(Qt::green); * s->addBeam(Qt::blue); * signalPlotter->addSample( QList() << std::numeric_limits::quiet_NaN() << 1.0/0 << 10.0 ); * \endcode * * This indicates that no data is available yet for red (so the beam will not be drawn for this section), * that's it positive infinity for green, and 10 for blue. * * Infinity is handled by drawing a straight line up to the top or bottom of the display, and does not affect the range. * For the above example, the displayed range would now be 0 to 10. */ void addSample( const QList &samples ); /** \brief Reorder the beams into the order given. * * For example: * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::blue); * s->addBeam(Qt::green); * s->addBeam(Qt::red); * QList neworder; * neworder << 2 << 0 << 1; * s->reorderBeams( newOrder); * //Now the order is red, blue then green * \endcode * * The size of the \p newOrder list must be equal to the result of numBeams(). * \param newOrder New order of beams. */ void reorderBeams( const QList& newOrder ); /** \brief Removes the beam at the specified index. * * This causes the graph to be redrawn with the specified beam completely * removed. */ void removeBeam( int index ); /** \brief Get the color of the beam at the specified index. * * For example: * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::blue); * s->addBeam(Qt::green); * s->addBeam(Qt::red); * * QColor color = s->beamColor(0); //returns blue * \endcode * * \sa setBeamColor() */ QColor beamColor( int index ) const; /** \brief Set the color of the beam at the specified index. * * \sa beamColor() */ void setBeamColor( int index, const QColor &color ); /** \brief Returns the number of beams. */ int numBeams() const; /** \brief Set the axis units with a localized string. * * The localized string must contain a placeholder "%1" which is substituted for the value. * The plural form (ki18np) can be used if the unit string changes depending on the number (for example * "1 second", "2 seconds"). * * For example: * * \code * KSignalPlotter plotter; * plotter.setUnit( ki18ncp("Units", "%1 second", "%1 seconds") ); * QString formattedString = plotter.valueAsString(3.4); //returns "3.4 seconds" * \endcode * * Typically a new unit would be set when setScaleDownBy is called. * Note that even the singular should use "%1 second" instead of "1 second", so that a value of -1 works correctly. * * \see unit(), setScaleDownBy() */ void setUnit( const KLocalizedString &unit ); /** \brief The localizable units used on the vertical axis of the graph. * * The returns the localizable string set with setUnit(). * * Default is the string "%1" - i.e. to just display the number. * * \see setUnit */ KLocalizedString unit() const; /** \brief Scale all the values down by the given amount. * * This is useful when the data is given in, say, kilobytes, but you set * the units as megabytes. Thus you would have to call this with @p value * set to 1024. This affects all the data already entered. * * Typically this is followed by calling setUnit() to set the display axis * units. Default value is 1. */ void setScaleDownBy( qreal value ); /** \brief Amount scaled down by. * * \sa setScaleDownBy */ qreal scaleDownBy() const; /** \brief Set whether to scale the graph automatically beyond the given range. * * If true, the range on vertical axis is automatically expanded from the * data available, expanding beyond the range set by changeRange() if data * values are outside of this range. * * Regardless whether this is set of not, the range of the vertical axis * will never be less than the range given by maximumValue() and minimumvalue(). * * \param value Whether to scale beyond the given range. Default is true. * * \sa useAutoRange */ void setUseAutoRange( bool value ); /** \brief Whether the vertical axis range is set automatically. */ bool useAutoRange() const; /** \brief Change the minimum and maximum values drawn on the graph. * * Note that these values are sanitised. For example, if you * set the minimum as 3, and the maximum as 97, then the graph * would be drawn between 0 and 100. The algorithm to determine * this "nice range" attempts to minimize the number of non-zero * digits. * * If autoRange() is true, then this range is taking as a 'hint'. * The range will never be smaller than the given range, but can grow * if there are values larger than the given range. * * This is equivalent to calling * \code * setMinimumValue(min); * setMaximumValue(max); * \endcode * * \sa setMinimumValue(), setMaximumValue(), minimumValue(), maximumValue() */ void changeRange( qreal min, qreal max ); /** \brief Set the min value hint for the vertical axis. * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ void setMinimumValue( qreal min ); /** \brief Get the min value hint for the vertical axis. * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ qreal minimumValue() const; /** \brief Set the max value hint for the vertical axis. * * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ void setMaximumValue( qreal max ); /** \brief Get the maximum value hint for the vertical axis. * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ qreal maximumValue() const; /** \brief Get the current maximum value on the y-axis. * * This will never be lower than maximumValue(), and if autoRange() is true, * it will be equal or larger (due to rounding up to make it a nice number) * than the highest value being shown. */ qreal currentMaximumRangeValue() const; /** \brief Get the current minimum value on the y-axis. * * This will never be lower than minimumValue(), and if autoRange() is true, * it will be equal or larger (due to rounding up to make it a nice number) * than the highest value being shown. */ qreal currentMinimumRangeValue() const; /** \brief Set the number of pixels horizontally between data points. * Default is 6. */ void setHorizontalScale( uint scale ); /** \brief The number of pixels horizontally between data points. * Default is 6. */ int horizontalScale() const; /** \brief Set whether to draw the vertical grid lines. * Default is false. */ void setShowVerticalLines( bool value ); /** \brief Whether to draw the vertical grid lines. * Default is false. */ bool showVerticalLines() const; /** \brief Set the horizontal distance, in pixels, between the vertical grid lines. * Must be a distance of 1 or more. * Default is 30 pixels. */ void setVerticalLinesDistance( uint distance ); /** \brief The horizontal distance, in pixels, between the vertical grid lines. * Default is 30 pixels. */ uint verticalLinesDistance() const; /** \brief Set whether the vertical lines move with the data. * Default is true. This has no effect is showVerticalLines is false. */ void setVerticalLinesScroll( bool value ); /** \brief Whether the vertical lines move with the data. * Default is true. This has no effect is showVerticalLines is false. */ bool verticalLinesScroll() const; /** \brief Set whether to draw the horizontal grid lines. * Default is true. */ void setShowHorizontalLines( bool value ); /** \brief Whether to draw the horizontal grid lines. * Default is true. */ bool showHorizontalLines() const; /** \brief The number of decimal places used for the axis labels * * This is calculated based on the current range */ int currentAxisPrecision() const; /** \brief Set whether to show the vertical axis labels. * * Default is true. * \sa showAxis(), setAxisFont(), setAxisFontColor(), setMaxAxisTextWidth() */ void setShowAxis( bool show ); /** \brief Whether to show the vertical axis labels. * * Default is true. * \sa setShowAxis(), axisFont(), axisFontColor(), maxAxisTextWidth() */ bool showAxis() const; /** \brief Set the filename of the SVG background. * * Set to empty (default) to disable again. */ void setSvgBackground( const QString &filename ); /** \brief The filename of the SVG background. */ QString svgBackground() const; /** \brief Return the last value that we have for the given beam index. * * \return last value, or 0 if not known. */ qreal lastValue( int index) const; /** \brief Return a translated string for the last value at the given index. * * Returns, for example, "34 %" or "100 KB" for the given beam index, * using the last value set for the beam, using the given precision. * * If precision is -1 (the default) then if @p value is greater than 99.5, no decimal figures are shown, * otherwise if @p value is greater than 0.995, 1 decimal figure is used, otherwise 2. */ QString lastValueAsString( int index, int precision = -1) const; /** \brief Return a translated string for the given value. * * Returns, for example, "34 %" or "100 KB" for the given value in unscaled units. * * If precision is -1 (the default) then if @p value is greater than 99.5, no decimal figures are shown, * otherwise if @p value is greater than 0.995, 1 decimal figure is used, otherwise 2. * * For example: * \code * KSignalPlotter plotter; * plotter.setUnit( ki18ncp("Units", "1 hour", "%1 hours") ); * plotter.scaleDownBy( 60 ); //The input will be in seconds, and there's 60 seconds in an hour * QString formattedString = plotter.valueAsString(150); //returns "2.5 hours" * \endcode * */ QString valueAsString( qreal value, int precision = -1) const; /** \brief Set the distance between the left of the widget and the left of the plotting region. * * For example: * \code * int axisTextWidth = fontMetrics().width(i18nc("Largest axis title", "99999 XXXX")); * plotter->setMaxAxisTextWidth(axisTextWidth); * \endcode * * If this is 0, the default, then the text will be shown inside the plotting area. */ void setMaxAxisTextWidth(int maxAxisTextWidth); /** \brief Get the distance between the left of the widget and the left of the plotting region. */ int maxAxisTextWidth() const; /** \brief Set whether to smooth the graph by averaging the points. * * This uses the formula: (value*2 + last_value)/3. * Default is true. */ void setSmoothGraph(bool smooth); /** \brief Whether to smooth the graph by averaging the points. * * This uses the formula: (value*2 + last_value)/3. * Default is true. */ bool smoothGraph() const; /** \brief Set whether to stack the beams on top of each other. * * Default is false */ void setStackGraph(bool stack); /** \brief Whether to stack the beams on top of each other. * * Default is false */ bool stackGraph() const; /** \brief Alpha value for filling the area underneath the graph lines. * * Set to 0 to disable filling the graph, and 255 for a solid fill. Default is 20*/ void setFillOpacity(int fill); /** \brief Alpha value for filling the area underneath the graph lines. */ int fillOpacity() const; /* Whether to show a thin line on the left and bottom of the widget, for a slight 3D effect */ bool thinFrame() const; /* Set whether to show a thin line on the left and bottom of the widget, for a slight 3D effect */ void setThinFrame(bool thinFrame); Q_SIGNALS: /** When the axis has changed this signal is emitted. */ void axisScaleChanged(); protected: /* Reimplemented */ void resizeEvent( QGraphicsSceneResizeEvent* event ) override; - void paint( QPainter * p, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 ) override; + void paint( QPainter * p, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; QSizeF sizeHint( Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override; void changeEvent ( QEvent * event ) override; QPainterPath opaqueArea() const override; private: KGraphicsSignalPlotterPrivate * const d; friend class KGraphicsSignalPlotterPrivate; }; #endif diff --git a/signalplotter/ksignalplotter.h b/signalplotter/ksignalplotter.h index 86bad0f..d497405 100644 --- a/signalplotter/ksignalplotter.h +++ b/signalplotter/ksignalplotter.h @@ -1,462 +1,462 @@ /* This file is part of the KDE project Copyright (c) 2006 - 2009 John Tapsell This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSIGNALPLOTTER_H #define KSIGNALPLOTTER_H #include #include #include #include #include #include class QPaintEvent; class QResizeEvent; class KSignalPlotterPrivate; //Make sure only declare KLocalizedString once #ifndef KGRAPHICSSIGNALPLOTTER_H Q_DECLARE_METATYPE(KLocalizedString) #endif /** \class KSignalPlotter * \brief The KSignalPlotter widget draws a real time graph of data that updates continually. * * Features include: * \li Points are joined by a bezier curve. * \li Lines are anti-aliased * \li Background can be set as a specified SVG * \li The lines can be reordered * \li Uses as little memory and CPU as possible * \li Graph can be smoothed using the formula (value * 2 + last_value)/3 * \li Can cope with positive/negative infinity and NaN values. * * Example usage: * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::blue); * s->addBeam(Qt::green); * QList data; * data << 4.0 << 5.0; * s->addSample(data); * \endcode * * Note that the number of horizontal lines is calculated automatically based on the axis font size, even if the axis labels are not shown. * * Smoothing looks very nice visually and is enabled by default. It can be disabled with setSmoothGraph(). * * \image KSignalPlotter.png Example KSignalPlotter with two beams */ class Q_DECL_EXPORT KSignalPlotter : public QWidget { Q_OBJECT Q_PROPERTY(qreal minimumValue READ minimumValue WRITE setMinimumValue) Q_PROPERTY(qreal maximumValue READ maximumValue WRITE setMaximumValue) Q_PROPERTY(bool useAutoRange READ useAutoRange WRITE setUseAutoRange) Q_PROPERTY(KLocalizedString unit READ unit WRITE setUnit) Q_PROPERTY(qreal scaleDownBy READ scaleDownBy WRITE setScaleDownBy) Q_PROPERTY(uint horizontalScale READ horizontalScale WRITE setHorizontalScale) Q_PROPERTY(bool showHorizontalLines READ showHorizontalLines WRITE setShowHorizontalLines) Q_PROPERTY(bool showVerticalLines READ showVerticalLines WRITE setShowVerticalLines) Q_PROPERTY(bool verticalLinesScroll READ verticalLinesScroll WRITE setVerticalLinesScroll) Q_PROPERTY(uint verticalLinesDistance READ verticalLinesDistance WRITE setVerticalLinesDistance) Q_PROPERTY(bool showAxis READ showAxis WRITE setShowAxis) Q_PROPERTY(QString svgBackground READ svgBackground WRITE setSvgBackground) Q_PROPERTY(bool thinFrame READ thinFrame WRITE setThinFrame) Q_PROPERTY(int maxAxisTextWidth READ maxAxisTextWidth WRITE setMaxAxisTextWidth) Q_PROPERTY(bool smoothGraph READ smoothGraph WRITE setSmoothGraph) Q_PROPERTY(bool stackGraph READ stackGraph WRITE setStackGraph) Q_PROPERTY(int fillOpacity READ fillOpacity WRITE setFillOpacity) public: - KSignalPlotter( QWidget *parent = 0); + KSignalPlotter( QWidget *parent = nullptr); ~KSignalPlotter() override; /** \brief Add a new line to the graph plotter, with the specified color. * * Note that the order you add the beams in must be the same order that * the beam data is given in (Unless you reorder the beams). * * \param color Color of beam - does not have to be unique. */ void addBeam( const QColor &color ); /** \brief Add data to the graph, and advance the graph by one time period. * * The data must be given as a list in the same order that the beams were * added (or consequently reordered). If samples.count() != numBeams(), * a warning is printed and the data discarded. * * To indicate that no data is available for a given beam, set its value to * (non-signalling) NaN. * * For example: * * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::red); * s->addBeam(Qt::green); * s->addBeam(Qt::blue); * signalPlotter->addSample( QList() << std::numeric_limits::quiet_NaN() << 1.0/0 << 10.0 ); * \endcode * * This indicates that no data is available yet for red (so the beam will not be drawn for this section), * that's it positive infinity for green, and 10 for blue. * * Infinity is handled by drawing a straight line up to the top or bottom of the display, and does not affect the range. * For the above example, the displayed range would now be 0 to 10. */ void addSample( const QList &samples ); /** \brief Reorder the beams into the order given. * * For example: * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::blue); * s->addBeam(Qt::green); * s->addBeam(Qt::red); * QList neworder; * neworder << 2 << 0 << 1; * s->reorderBeams( newOrder); * //Now the order is red, blue then green * \endcode * * The size of the \p newOrder list must be equal to the result of numBeams(). * \param newOrder New order of beams. */ void reorderBeams( const QList& newOrder ); /** \brief Removes the beam at the specified index. * * This causes the graph to be redrawn with the specified beam completely * removed. */ void removeBeam( int index ); /** \brief Get the color of the beam at the specified index. * * For example: * \code * KSignalPlotter *s = KSignalPlotter(parent); * s->addBeam(Qt::blue); * s->addBeam(Qt::green); * s->addBeam(Qt::red); * * QColor color = s->beamColor(0); //returns blue * \endcode * * \sa setBeamColor() */ QColor beamColor( int index ) const; /** \brief Set the color of the beam at the specified index. * * \sa beamColor() */ void setBeamColor( int index, const QColor &color ); /** \brief Returns the number of beams. */ int numBeams() const; /** \brief Set the axis units with a localized string. * * The localized string must contain a placeholder "%1" which is substituted for the value. * The plural form (ki18np) can be used if the unit string changes depending on the number (for example * "1 second", "2 seconds"). * * For example: * * \code * KSignalPlotter plotter; * plotter.setUnit( ki18ncp("Units", "%1 second", "%1 seconds") ); * QString formattedString = plotter.valueAsString(3.4); //returns "3.4 seconds" * \endcode * * Typically a new unit would be set when setScaleDownBy is called. * Note that even the singular should use "%1 second" instead of "1 second", so that a value of -1 works correctly. * * \see unit(), setScaleDownBy() */ void setUnit( const KLocalizedString &unit ); /** \brief The localizable units used on the vertical axis of the graph. * * The returns the localizable string set with setUnit(). * * Default is the string "%1" - i.e. to just display the number. * * \see setUnit */ KLocalizedString unit() const; /** \brief Scale all the values down by the given amount. * * This is useful when the data is given in, say, kilobytes, but you set * the units as megabytes. Thus you would have to call this with @p value * set to 1024. This affects all the data already entered. * * Typically this is followed by calling setUnit() to set the display axis * units. Default value is 1. */ void setScaleDownBy( qreal value ); /** \brief Amount scaled down by. * * \sa setScaleDownBy */ qreal scaleDownBy() const; /** \brief Set whether to scale the graph automatically beyond the given range. * * If true, the range on vertical axis is automatically expanded from the * data available, expanding beyond the range set by changeRange() if data * values are outside of this range. * * Regardless whether this is set of not, the range of the vertical axis * will never be less than the range given by maximumValue() and minimumvalue(). * * \param value Whether to scale beyond the given range. Default is true. * * \sa useAutoRange */ void setUseAutoRange( bool value ); /** \brief Whether the vertical axis range is set automatically. */ bool useAutoRange() const; /** \brief Change the minimum and maximum values drawn on the graph. * * Note that these values are sanitised. For example, if you * set the minimum as 3, and the maximum as 97, then the graph * would be drawn between 0 and 100. The algorithm to determine * this "nice range" attempts to minimize the number of non-zero * digits. * * If autoRange() is true, then this range is taking as a 'hint'. * The range will never be smaller than the given range, but can grow * if there are values larger than the given range. * * This is equivalent to calling * \code * setMinimumValue(min); * setMaximumValue(max); * \endcode * * \sa setMinimumValue(), setMaximumValue(), minimumValue(), maximumValue() */ void changeRange( qreal min, qreal max ); /** \brief Set the min value hint for the vertical axis. * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ void setMinimumValue( qreal min ); /** \brief Get the min value hint for the vertical axis. * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ qreal minimumValue() const; /** \brief Set the max value hint for the vertical axis. * * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ void setMaximumValue( qreal max ); /** \brief Get the maximum value hint for the vertical axis. * * \sa changeRange(), minimumValue(), setMaximumValue(), maximumValue() */ qreal maximumValue() const; /** \brief Get the current maximum value on the y-axis. * * This will never be lower than maximumValue(), and if autoRange() is true, * it will be equal or larger (due to rounding up to make it a nice number) * than the highest value being shown. */ qreal currentMaximumRangeValue() const; /** \brief Get the current minimum value on the y-axis. * * This will never be lower than minimumValue(), and if autoRange() is true, * it will be equal or larger (due to rounding up to make it a nice number) * than the highest value being shown. */ qreal currentMinimumRangeValue() const; /** \brief Set the number of pixels horizontally between data points. * Default is 6. */ void setHorizontalScale( uint scale ); /** \brief The number of pixels horizontally between data points. * Default is 6. */ int horizontalScale() const; /** \brief Set whether to draw the vertical grid lines. * Default is false. */ void setShowVerticalLines( bool value ); /** \brief Whether to draw the vertical grid lines. * Default is false. */ bool showVerticalLines() const; /** \brief Set the horizontal distance, in pixels, between the vertical grid lines. * Must be a distance of 1 or more. * Default is 30 pixels. */ void setVerticalLinesDistance( uint distance ); /** \brief The horizontal distance, in pixels, between the vertical grid lines. * Default is 30 pixels. */ uint verticalLinesDistance() const; /** \brief Set whether the vertical lines move with the data. * Default is true. This has no effect is showVerticalLines is false. */ void setVerticalLinesScroll( bool value ); /** \brief Whether the vertical lines move with the data. * Default is true. This has no effect is showVerticalLines is false. */ bool verticalLinesScroll() const; /** \brief Set whether to draw the horizontal grid lines. * Default is true. */ void setShowHorizontalLines( bool value ); /** \brief Whether to draw the horizontal grid lines. * Default is true. */ bool showHorizontalLines() const; /** \brief The number of decimal places used for the axis labels * * This is calculated based on the current range */ int currentAxisPrecision() const; /** \brief Set whether to show the vertical axis labels. * * Default is true. * \sa showAxis(), setAxisFont(), setAxisFontColor(), setMaxAxisTextWidth() */ void setShowAxis( bool show ); /** \brief Whether to show the vertical axis labels. * * Default is true. * \sa setShowAxis(), axisFont(), axisFontColor(), maxAxisTextWidth() */ bool showAxis() const; /** \brief Set the filename of the SVG background. * * Set to empty (default) to disable again. */ void setSvgBackground( const QString &filename ); /** \brief The filename of the SVG background. */ QString svgBackground() const; /** \brief Return the last value that we have for the given beam index. * * \return last value, or 0 if not known. */ qreal lastValue( int index) const; /** \brief Return a translated string for the last value at the given index. * * Returns, for example, "34 %" or "100 KB" for the given beam index, * using the last value set for the beam, using the given precision. * * If precision is -1 (the default) then if @p value is greater than 99.5, no decimal figures are shown, * otherwise if @p value is greater than 0.995, 1 decimal figure is used, otherwise 2. */ QString lastValueAsString( int index, int precision = -1) const; /** \brief Return a translated string for the given value. * * Returns, for example, "34 %" or "100 KB" for the given value in unscaled units. * * If precision is -1 (the default) then if @p value is greater than 99.5, no decimal figures are shown, * otherwise if @p value is greater than 0.995, 1 decimal figure is used, otherwise 2. * * For example: * \code * KSignalPlotter plotter; * plotter.setUnit( ki18ncp("Units", "1 hour", "%1 hours") ); * plotter.scaleDownBy( 60 ); //The input will be in seconds, and there's 60 seconds in an hour * QString formattedString = plotter.valueAsString(150); //returns "2.5 hours" * \endcode * */ QString valueAsString( qreal value, int precision = -1) const; /** \brief Set the distance between the left of the widget and the left of the plotting region. * * For example: * \code * int axisTextWidth = fontMetrics().width(i18nc("Largest axis title", "99999 XXXX")); * plotter->setMaxAxisTextWidth(axisTextWidth); * \endcode * * If this is 0, the default, then the text will be shown inside the plotting area. */ void setMaxAxisTextWidth(int maxAxisTextWidth); /** \brief Get the distance between the left of the widget and the left of the plotting region. */ int maxAxisTextWidth() const; /** \brief Set whether to smooth the graph by averaging the points. * * This uses the formula: (value*2 + last_value)/3. * Default is true. */ void setSmoothGraph(bool smooth); /** \brief Whether to smooth the graph by averaging the points. * * This uses the formula: (value*2 + last_value)/3. * Default is true. */ bool smoothGraph() const; /** \brief Set whether to stack the beams on top of each other. * * Default is false */ void setStackGraph(bool stack); /** \brief Whether to stack the beams on top of each other. * * Default is false */ bool stackGraph() const; /** \brief Alpha value for filling the area underneath the graph lines. * * Set to 0 to disable filling the graph, and 255 for a solid fill. Default is 20*/ void setFillOpacity(int fill); /** \brief Alpha value for filling the area underneath the graph lines. */ int fillOpacity() const; /* Whether to show a thin line on the left and bottom of the widget, for a slight 3D effect */ bool thinFrame() const; /* Set whether to show a thin line on the left and bottom of the widget, for a slight 3D effect */ void setThinFrame(bool thinFrame); Q_SIGNALS: /** When the axis has changed this signal is emitted. */ void axisScaleChanged(); protected: /* Reimplemented */ void resizeEvent( QResizeEvent* ) override; void paintEvent( QPaintEvent* ) override; QSize sizeHint() const override; void changeEvent ( QEvent * event ) override; private: KSignalPlotterPrivate * const d; friend class KSignalPlotterPrivate; }; #endif