diff --git a/AppEntry.cpp b/AppEntry.cpp index 2536e7a..de10806 100644 --- a/AppEntry.cpp +++ b/AppEntry.cpp @@ -6,7 +6,7 @@ #include "AppEntry.h" #include -AppEntry::AppEntry(std::string ti, const std::time_t sT, const std::time_t eT, const long long i) +AppEntry::AppEntry(std::string ti, const TimePoint sT, const TimePoint eT, const long long i) : id(i) , title(std::move(ti)) , startTime(sT) @@ -34,22 +34,22 @@ std::string AppEntry::getTitle() const return title; } -void AppEntry::setStartTime(std::time_t sT) +void AppEntry::setStartTime(AppEntry::TimePoint sT) { startTime = sT; } -std::time_t AppEntry::getStartTime() const +AppEntry::TimePoint AppEntry::getStartTime() const { return startTime; } -void AppEntry::setEndTime(std::time_t eT) +void AppEntry::setEndTime(AppEntry::TimePoint eT) { endTime = eT; } -std::time_t AppEntry::getEndTime() const +AppEntry::TimePoint AppEntry::getEndTime() const { return endTime; } diff --git a/AppEntry.h b/AppEntry.h index 3fffec5..28c730a 100644 --- a/AppEntry.h +++ b/AppEntry.h @@ -3,6 +3,7 @@ #include #include +#include /** * @class AppEntry @@ -13,8 +14,12 @@ */ class AppEntry { public: + // Use std::chrono::system_clock::time_point for time tracking + using TimePoint = std::chrono::system_clock::time_point; + // alternate ctor - defaults to an empty AppEntry. - explicit AppEntry(std::string ti = "", std::time_t sT = 0, std::time_t eT = 0, long long i = 0); + explicit AppEntry(std::string ti = "", TimePoint sT = std::chrono::system_clock::now(), TimePoint + eT = std::chrono::system_clock::now(), long long i = 0); // defaulted copy ctor AppEntry(const AppEntry& copy) = default; @@ -29,14 +34,14 @@ class AppEntry { void setTitle(const std::string& t); [[nodiscard]] std::string getTitle() const; - void setStartTime(std::time_t sT); - [[nodiscard]] std::time_t getStartTime() const; + void setStartTime(TimePoint sT); + [[nodiscard]] TimePoint getStartTime() const; - void setEndTime(std::time_t sT); - [[nodiscard]] std::time_t getEndTime() const; + void setEndTime(TimePoint sT); + [[nodiscard]] TimePoint getEndTime() const; // returns whether title is empty - bool isEmpty() const; + [[nodiscard]] bool isEmpty() const; private: // id of AppEntry @@ -44,9 +49,9 @@ class AppEntry { // title of AppEntry std::string title; // startTime of Window Visitation - std::time_t startTime; + TimePoint startTime; // endTime of Window Visitation - std::time_t endTime; + TimePoint endTime; }; #endif // APPENTRY_H diff --git a/AppTracker.cpp b/AppTracker.cpp index 79ab381..0a5845e 100644 --- a/AppTracker.cpp +++ b/AppTracker.cpp @@ -6,103 +6,59 @@ #include "AppTracker.h" #include "AppEntry.h" #include +#include +AppTracker::AppTracker() +{ #ifdef _WIN32 -#include + windowTracker = std::make_unique(); +#elif __linux__ + windowTracker = std::make_unique(); +#else + throw std::runtime_error("Unsupported OS"); #endif +} -AppTracker::AppTracker() - : dbManager(DatabaseManager()) - , tracking(true) +std::ostream& operator<<(std::ostream& os, AppTracker& a) { + std::vector appEntries = a.getAppEntries(); + + std::for_each(appEntries.begin(), appEntries.end(), [&os](AppEntry& entry) { + const std::time_t t_s = std::chrono::system_clock::to_time_t(entry.getStartTime()); + const std::time_t t_e = std::chrono::system_clock::to_time_t(entry.getEndTime()); + os << "ID: " << entry.getID() << ", Title: " << entry.getTitle() + << ", Start Time: " << std::ctime(&t_s) << ", End Time: " << std::ctime(&t_e) + << '\n'; + }); + return os; } void AppTracker::startTracking() { -#ifdef _WIN32 - startTrackingWindows(); -#elif __linux - startTrackingLinux(); -#else - throw std::runtime_error("Operating Systems other than Windows have not been supported yet!"); -#endif + windowTracker->startTracking(); } void AppTracker::stopTracking() { - tracking = false; + windowTracker->stopTracking(); } std::vector AppTracker::getAppEntries() { - return dbManager.getAppEntries(); + return windowTracker->getAppEntries(); } void AppTracker::clearTracking() { - dbManager.clearData(); + windowTracker->clearTracking(); } DatabaseManager& AppTracker::getDatabaseManager() { - return dbManager; + return windowTracker->getDatabaseManager(); } bool AppTracker::getTrackingBool() const { - return tracking; -} -#ifdef _WIN32 -void AppTracker::startTrackingWindows() -{ - char windowTitle[256]; - AppEntry prevEntry; - - while (tracking) { - HWND hwnd = GetForegroundWindow(); - GetWindowText(hwnd, windowTitle, sizeof(windowTitle)); - std::string title(windowTitle); - time_t now = time(nullptr); - // convert now to string form - char dt[26]; - - errno_t err = ctime_s(dt, sizeof(dt), &now); - - if (err) { - throw std::runtime_error("Error converting time"); - } - - clock_t curTime = clock(); - - if (!title.empty()) { - // handle the first entry - if (prevEntry.isEmpty()) { - std::cout << "Starting time for " << title << " : " << dt; - - prevEntry.setTitle(title); - prevEntry.setStartTime(clock()); - } else if (prevEntry.getTitle() != title) { - prevEntry.setEndTime(curTime); - std::cout << "Ending time for " << prevEntry.getTitle() << " : " << dt; - std::cout << "Duration for " << prevEntry.getTitle() << " : " - << (prevEntry.getEndTime() - prevEntry.getStartTime()) - / (long long)CLOCKS_PER_SEC - << std::endl; - - // insert the appEntry - dbManager.insertData(prevEntry); - - // figure out the time spent at the website - std::cout << "Starting time for " << title << " : " << dt << std::endl; - prevEntry.setTitle(title); - prevEntry.setStartTime(curTime); - } - } - Sleep(1000); // Check every second, check if this should be changed - } -} -#elif __linux -void AppTracker::startTrackingLinux(){ - throw std::runtime_error("linux is not implemented yet!"); + return windowTracker->getTrackingBool(); } -#endif \ No newline at end of file diff --git a/AppTracker.h b/AppTracker.h index 5f4e897..95225ea 100644 --- a/AppTracker.h +++ b/AppTracker.h @@ -1,7 +1,9 @@ #ifndef APPTRACKER_H #define APPTRACKER_H +#include #include "DatabaseManager.h" +#include "IWindowInterface.h" /** * @class AppTracker @@ -14,6 +16,9 @@ class AppTracker { // default ctor. AppTracker(); + // operator overload that prints results + friend std::ostream& operator<<(std::ostream& os, AppTracker& a); + // startTracking Method - throws error if OS is not windows. controlled by // tracking boolean void startTracking(); @@ -34,18 +39,7 @@ class AppTracker { [[nodiscard]] bool getTrackingBool() const; private: - // DatabaseManager for the AppTracker - DatabaseManager dbManager; - // boolean for tracking - bool tracking; -#ifdef _WIN32 - // startTracking Method - windows implementation - void startTrackingWindows(); -#elif __linux__ - void startTrackingLinux(); - // TODO: Linux implementation - // TODO: MacOS implementation -#endif + std::unique_ptr windowTracker; }; #endif // APPTRACKER_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 30e4c5c..9978275 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,10 +35,19 @@ include_directories(${googletest_SOURCE_DIR}/googletest/include) add_executable(desktop_screentime main.cpp AppTracker.cpp DatabaseManager.cpp - AppEntry.cpp) + AppEntry.cpp + IWindowInterface.cpp +) ## Link SQLite3 to the executable ## -target_link_libraries(desktop_screentime PRIVATE ${SQLite3_LIBRARIES} pthread) +# Set compiler flags +if(MSVC) + # Using Microsoft Visual C++ + target_link_libraries(desktop_screentime PRIVATE ${SQLite3_LIBRARIES}) +else() + # Assuming GCC or Clang + target_link_libraries(desktop_screentime PRIVATE ${SQLite3_LIBRARIES} pthread) +endif() target_include_directories(desktop_screentime PRIVATE ${SQLite3_INCLUDE_DIRS}) # Testing Executable @@ -51,6 +60,7 @@ set(SOURCE_FILES AppTracker.cpp DatabaseManager.cpp AppEntry.cpp + IWindowInterface.cpp ) # Make the project root directory the working directory when we run diff --git a/DatabaseManager.cpp b/DatabaseManager.cpp index 13b03d0..e814194 100644 --- a/DatabaseManager.cpp +++ b/DatabaseManager.cpp @@ -25,14 +25,17 @@ DatabaseManager::DatabaseManager() void DatabaseManager::insertData(AppEntry& appEntry) { + auto startEpoch = std::chrono::system_clock::to_time_t(appEntry.getStartTime()); + auto endEpoch = std::chrono::system_clock::to_time_t(appEntry.getEndTime()); + const char* insertSql = "INSERT INTO AppUsage (title, startTime, endTime) VALUES (?, ?, ?)"; sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(db, insertSql, -1, &stmt, nullptr); if (rc == SQLITE_OK) { sqlite3_bind_text(stmt, 1, appEntry.getTitle().c_str(), -1, SQLITE_TRANSIENT); - sqlite3_bind_int64(stmt, 2, static_cast(appEntry.getStartTime())); - sqlite3_bind_int64(stmt, 3, static_cast(appEntry.getEndTime())); + sqlite3_bind_int64(stmt, 2, startEpoch); + sqlite3_bind_int64(stmt, 3, endEpoch); rc = sqlite3_step(stmt); if (rc == SQLITE_DONE) { @@ -55,9 +58,11 @@ void DatabaseManager::queryData() { auto currEntries = getAppEntries(); std::for_each(appEntries.begin(), appEntries.end(), [](AppEntry& entry) { + const std::time_t t_s = std::chrono::system_clock::to_time_t(entry.getStartTime()); + const std::time_t t_e = std::chrono::system_clock::to_time_t(entry.getEndTime()); std::cout << "ID: " << entry.getID() << ", Title: " << entry.getTitle() - << ", Start Time: " << entry.getStartTime() - << ", End Time: " << entry.getEndTime() << std::endl; + << ", Start Time: " << std::ctime(&t_s) << ", End Time: " << std::ctime(&t_e) + << '\n'; }); } @@ -90,14 +95,14 @@ std::vector DatabaseManager::getAppEntries() while (sqlite3_step(stmt) == SQLITE_ROW) { int id = sqlite3_column_int(stmt, 0); const char* title = reinterpret_cast(sqlite3_column_text(stmt, 1)); - auto startTime = static_cast(sqlite3_column_int64(stmt, 2)); - auto endTime = static_cast(sqlite3_column_int64(stmt, 3)); + auto startEpoch = sqlite3_column_int64(stmt, 2); + auto endEpoch = sqlite3_column_int64(stmt, 3); AppEntry entry; entry.setID(id); entry.setTitle(std::string(title)); - entry.setStartTime(startTime); - entry.setEndTime(endTime); + entry.setStartTime(std::chrono::system_clock::from_time_t(startEpoch)); + entry.setEndTime(std::chrono::system_clock::from_time_t(endEpoch)); entries.push_back(entry); } diff --git a/IWindowInterface.cpp b/IWindowInterface.cpp new file mode 100644 index 0000000..bf4c628 --- /dev/null +++ b/IWindowInterface.cpp @@ -0,0 +1,157 @@ +/** + * @file IWindowInterface.cpp + * @brief Implementation of AppTracker.h + * @author Syed Ali + */ + +#include "IWindowInterface.h" +#include +#include +#include +#ifdef _WIN32 +#include +//#elif __linux__ +//#include +//#include +#endif + +IWindowInterface::IWindowInterface(): dbManager(DatabaseManager()), tracking(true) {} + +void IWindowInterface::stopTracking() +{ + tracking = false; +} + +std::vector IWindowInterface::getAppEntries() +{ + return dbManager.getAppEntries(); +} + +void IWindowInterface::clearTracking() +{ + dbManager.clearData(); +} + +DatabaseManager& IWindowInterface::getDatabaseManager() +{ + return dbManager; +} + +bool IWindowInterface::getTrackingBool() const +{ + return tracking; +} + + + +#ifdef _WIN32 +void WindowsTracker::startTracking() +{ + char windowTitle[256]; + AppEntry prevEntry; + + while (tracking) { + HWND hwnd = GetForegroundWindow(); + GetWindowText(hwnd, windowTitle, sizeof(windowTitle)); + std::string title(windowTitle); +// time_t now = time(nullptr); +// // convert now to string form +// char dt[26]; +// +// errno_t err = ctime_s(dt, sizeof(dt), &now); +// +// if (err) { +// throw std::runtime_error("Error converting time"); +// } + + auto curTime = std::chrono::system_clock::now(); + const std::time_t t_c = std::chrono::system_clock::to_time_t(curTime); + + if (!title.empty()) { + // handle the first entry + if (prevEntry.isEmpty()) { + std::cout << "Starting time for " << title << " : " << std::ctime(&t_c); + + prevEntry.setTitle(title); + prevEntry.setStartTime(curTime); + } else if (prevEntry.getTitle() != title) { + prevEntry.setEndTime(curTime); + // Calculate the duration in seconds + auto duration = std::chrono::duration_cast + (prevEntry.getEndTime() - prevEntry.getStartTime()); + + + std::cout << "Ending time for " << prevEntry.getTitle() << " : " << std::ctime(&t_c); + std::cout << "Duration for " << prevEntry.getTitle() << " : " + << duration.count() << " seconds" << std::endl; + + // insert the appEntry + dbManager.insertData(prevEntry); + + // figure out the time spent at the website + std::cout << "Starting time for " << title << " : " << std::ctime(&t_c) << std::endl; + prevEntry.setTitle(title); + prevEntry.setStartTime(curTime); + } + } + Sleep(1000); // Check every second, check if this should be changed + } +} +#endif + +void LinuxTracker::startTracking() { + throw std::runtime_error("not implemented yet - linux"); +} +/* +#ifdef _WIN32 + // startTracking Method - windows implementation + void startTrackingWindows(); +#elif __linux__ + // startTracking Method - linux implementation + void startTrackingLinux(); + // TODO: Linux implementation + // TODO: MacOS implementation +#endif + + + */ + + + +/* + #if __linux__ +std::string getTrackingScreenOS(Display* display) +{ + Display* display = XOpenDisplay(nullptr); + if (!display) { + throw std::runtime_error("Cannot open display"); + } + Window window; + char* title = nullptr; + int status = XGetInputFocus(display, &window, &revert_to_return); + + if (status == Success && window != None) { + XFetchName(display, window, &title); + } + + std::string windowTitle(title ? title : ""); + XFree(title); + return windowTitle; +} +void AppTracker::startTrackingLinux() { + Display* display = XOpenDisplay(nullptr); + if (!display) { + throw std::runtime_error("Cannot open display"); + } + + while (tracking) { + std::string title = getActiveWindowTitle(display); + // ... rest of the tracking logic + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + XCloseDisplay(display); +} +#endif + + */ \ No newline at end of file diff --git a/IWindowInterface.h b/IWindowInterface.h new file mode 100644 index 0000000..fe484cf --- /dev/null +++ b/IWindowInterface.h @@ -0,0 +1,50 @@ +// +// Created by syeda on 12/25/2023. +// TODO COMMENTS + +#ifndef WINDOWTRACKER_H +#define WINDOWTRACKER_H + +#include "DatabaseManager.h" + +// factory pattern +class IWindowInterface { +public: + IWindowInterface(); + virtual ~IWindowInterface() = default; + + virtual void startTracking() = 0; + + // sets tracking boolean to false + virtual void stopTracking(); + + // return a vector of all the AppEntries from the AppUsage table + virtual std::vector getAppEntries(); + + // clear the tracking entries + virtual void clearTracking(); + + // returns a reference dbManager + virtual DatabaseManager& getDatabaseManager(); + + // gets tracking boolean + [[nodiscard]] virtual bool getTrackingBool() const; +protected: + // DatabaseManager for the AppTracker + DatabaseManager dbManager; + // boolean for tracking + bool tracking; + +}; + +class WindowsTracker : public IWindowInterface { +public: + void startTracking() override; +}; + +class LinuxTracker : public IWindowInterface { +public: + void startTracking() override; +}; + +#endif //WINDOWTRACKER_H diff --git a/main.cpp b/main.cpp index c06beac..17848b9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,15 +1,16 @@ #include "AppTracker.h" -#include #include #include -std::atomic trackingActive(true); // Global flag to control tracking AppTracker appTracker; void startTrackingWrapper() { - - appTracker.startTracking(); // Pass the flag to the tracking function + try { + appTracker.startTracking(); + } catch (std::runtime_error& e) { + throw std::runtime_error(e.what()); + } } int main() @@ -30,6 +31,7 @@ int main() } std::cout << "Tracking stopped." << std::endl; + std::cout << appTracker << std::endl; appTracker.clearTracking(); return 0; } \ No newline at end of file