├── .gitignore ├── update ├── uninstall ├── LICENSE ├── src ├── Watcher │ ├── afk.py │ ├── get_windows.py │ ├── time_operations.py │ ├── watch_log.py │ ├── analysis.py │ └── commands.py └── bin │ └── watcher ├── install ├── misc └── export_to_2.0 ├── README.md └── CONTRIBUTING.md /.gitignore: -------------------------------------------------------------------------------- 1 | src/Watcher/__pycache__/* 2 | -------------------------------------------------------------------------------- /update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo rm -rf /usr/share/Watcher/* 3 | sudo cp -r ./src/Watcher/* /usr/share/Watcher/ 4 | 5 | sudo cp -r ./src/bin/watcher /usr/local/bin/ 6 | sudo chmod +x /usr/local/bin/watcher 7 | echo "[✔] Updating Finished." 8 | -------------------------------------------------------------------------------- /uninstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo rm -rf /usr/share/Watcher/ 3 | sudo rm -rf /usr/local/bin/watcher 4 | 5 | echo "Do you want to delete your raw_data files?? y or n (default: n)" 6 | read yes_or_no 7 | 8 | if [[ $yes_or_no = y ]]; then 9 | rm -rf ~/.cache/Watcher/ 10 | else 11 | echo "Nice Decision either way you might use this app in future" 12 | fi 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Waishnav Deore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Watcher/afk.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | # checks if currently in afk mode, only returns true once 5 | def get_afk_status(afk_active, timeout): 6 | if (is_afk(timeout)): 7 | return True 8 | elif returned_from_afk(afk_active, timeout): 9 | return True 10 | else: 11 | return False 12 | 13 | def returned_from_afk(afk_active, timeout): 14 | has_returned = (afk_active and not (is_afk(timeout))) 15 | return has_returned 16 | 17 | def is_afk(timeout): 18 | timeout = timeout * 60 * 1000 - 100 # minimizing 100 milisec error 19 | #If the AFK feature is installed 20 | time_since_last_input = int(os.popen("xprintidle").read()[:-1]) 21 | if (time_since_last_input > timeout): 22 | video_playback = os.popen("""pacmd list-sink-inputs | grep -w state | grep -i 'RUNNING'""").read() 23 | # if playback is not running as well as user is AFK 24 | if "RUNNING" in video_playback: 25 | return False 26 | # if playback is running is background as well as user is AFK 27 | else: 28 | return True 29 | return False 30 | 31 | # testing out 32 | if __name__ == "__main__": 33 | while True: 34 | time.sleep(1) 35 | print(is_afk(0.05)) 36 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git clone https://github.com/Waishnav/Watcher -b v2.0 && cd Watcher 3 | 4 | echo "[✔] First of all Thanks for dropping by!." 5 | sleep 1s 6 | echo "[✔] And...FYI Watcher uses very less resources like almost 10 MBs." 7 | sleep 1s 8 | echo "[✔] Also some of the features are in development AFK is one of them" 9 | sleep 2s 10 | echo "[✔] So let's start installation process... " 11 | 12 | sudo cp -r ./src/Watcher /usr/share/ 13 | echo "[✔] Copying Watcher to /usr/share/" 14 | sudo cp -r ./src/bin/watcher /usr/local/bin/ 15 | echo "[✔] Copying watcher executable to /usr/local/bin/" 16 | sudo chmod +x /usr/local/bin/watcher 17 | echo "[✔] Making it executable by giving it permission" 18 | 19 | # making directory for log-files (where all you daily logs are stored) 20 | mkdir -p ~/.cache/Watcher/ 21 | echo "[✔] To store raw_data making directory as ~/.cache/Watcher" 22 | mkdir -p ~/.cache/Watcher/daily_data/ 23 | mkdir -p ~/.cache/Watcher/Analysis/ 24 | 25 | # deleting folowing lines "[ -f /etc/xprofile ] && . /etc/xprofile/" and "[ -f ~/.xprofile ] && . ~/.xprofile" 26 | if [ -f "$HOME/.xinitrc" ]; 27 | then 28 | sed -i '/xprofile/d' ~/.xinitrc 29 | # checking wherther is ~/.xprofile is sourced in ~/.xintrc or not 30 | count1=$(grep -c "xprofile" $HOME/.xinitrc) 31 | else 32 | count1=0 33 | fi 34 | # checking whether user have used watcher before as well as xprofile filed setup or not 35 | if [ -f "$HOME/.xprofile" ]; 36 | then 37 | count2=$(grep -c "watcher --start" $HOME/.xprofile) 38 | else 39 | count2=0 40 | fi 41 | 42 | echo "[✔] Making sure that it will run at startup of the system" 43 | 44 | if [ $count2 = 0 ]; 45 | then 46 | echo "watcher --start &" >> $HOME/.xprofile 47 | fi 48 | 49 | if [ -f "$HOME/.xinitrc" ]; 50 | then 51 | if [ $count1 = 0 ] ; 52 | then 53 | echo "source ~/.xprofile" >> $HOME/.xinitrc 54 | fi 55 | fi 56 | 57 | echo "Now you can use Watcher after restart. And see you around" 58 | echo "If anything goes wrong do PR or open issue" 59 | echo '''If you wanna just give a try (without restart), You can start taking logs (run watch_log.py) by 'watcher --start' ''' 60 | -------------------------------------------------------------------------------- /src/Watcher/get_windows.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # get title name of app that user working on 4 | def active_window_title(): 5 | active_window_title = os.popen('''xprop -id $(xdotool getwindowfocus) WM_NAME''').read()[19:-2] 6 | a = active_window_title.find('"') 7 | active_window_title = active_window_title[a+1:] 8 | if "XGetWindowProperty[_NET_ACTIVE_WINDOW] failed" in active_window_title: 9 | active_window_title = "" 10 | if "\n" in active_window_title: 11 | active_window_title = "Unknown" 12 | return active_window_title 13 | 14 | # get classname of app that user working on 15 | def active_window(): 16 | actv_id = os.popen("xdotool getwindowfocus").read()[:-1] 17 | if len(actv_id) == 4: 18 | active_window = "" 19 | else: 20 | active_window = os.popen("xprop -id $(xdotool getwindowfocus) | grep CLASS ").read() 21 | if active_window != "": 22 | active_window = active_window[19:-1].replace('''"''', "").split(", ")[1] 23 | 24 | if "XGetWindowProperty[_NET_ACTIVE_WINDOW] failed" in active_window: 25 | active_window = "" 26 | if "\n" in active_window: 27 | active_window = "Unknown" 28 | # check whether user is using nvim or vim 29 | active_window = active_window.capitalize() 30 | aw_title = active_window_title() 31 | terminals = ["Kitty", "Alacritty", "Terminator", "Tilda", "Guake", "Yakuake", "Roxterm", "Eterm", "Rxvt", "Xterm", "Tilix", "Lxterminal", "Konsole", "St", "Gnome-terminal", "Xfce4-terminal", "Terminology", "Extraterm", "Mate-terminal"] 32 | if active_window in terminals: 33 | try: 34 | if "nvim" in aw_title: 35 | active_window = "NeoVim" 36 | elif "vim" in aw_title: 37 | active_window = "Vim" 38 | except TypeError: 39 | None 40 | return active_window 41 | 42 | # returns true if user has move to next app which is not the same as previous 43 | def previous_window(array_of_window, active_window): 44 | array_of_window.remove(active_window) 45 | array_of_window.append(active_window) 46 | return array_of_window[-2] 47 | 48 | if __name__ == "__main__": 49 | print(active_window()) 50 | -------------------------------------------------------------------------------- /src/Watcher/time_operations.py: -------------------------------------------------------------------------------- 1 | def time_difference(a,b): # b - a 2 | hr = int(b[0:2])-int(a[0:2]) 3 | mn = int(b[3:5])-int(a[3:5]) 4 | sec = int(b[6:8])-int(a[6:8]) 5 | if mn < 0 and sec < 0: 6 | hr = hr - 1 7 | mn = 60 + mn - 1 8 | sec = 60 + sec 9 | if hr < 0: 10 | hr = hr + 24 11 | 12 | elif mn < 0 and sec >= 0: 13 | hr = hr - 1 14 | mn = 60 + mn 15 | if hr < 0: 16 | hr = hr + 24 17 | elif sec < 0 and mn > 0: 18 | sec = 60 + sec 19 | mn = mn - 1 20 | if hr < 0: 21 | hr = hr + 24 22 | elif sec < 0 and mn == 0: 23 | hr = hr - 1 24 | mn = 59 25 | sec = 60 + sec 26 | 27 | hr = str(hr).zfill(2) 28 | mn = str(mn).zfill(2) 29 | sec = str(sec).zfill(2) 30 | result = hr + ":" + mn + ":" + sec 31 | 32 | return result 33 | 34 | def time_addition(a,b): 35 | hr = int(b[0:2]) + int(a[0:2]) 36 | mn = int(b[3:5]) + int(a[3:5]) 37 | sec = int(b[6:8]) + int(a[6:8]) 38 | if mn >= 60 and sec >= 60: 39 | hr = hr + 1 40 | mn = mn - 60 + 1 41 | sec = sec - 60 42 | elif mn >= 60: 43 | hr = hr + 1 44 | mn = mn - 60 45 | elif sec >= 60: 46 | mn = mn + 1 47 | sec = sec - 60 48 | 49 | hr = str(hr).zfill(2) 50 | mn = str(mn).zfill(2) 51 | sec = str(sec).zfill(2) 52 | result = hr + ":" + mn + ":" + sec 53 | return result 54 | 55 | def format_time(t): 56 | result = t[0:2] + 'h ' + t[3:5] + 'm ' + t[6::] + 's' 57 | #if int(t[0:2]) == 0: 58 | # result = t[3:5] + 'm ' + t[6::] + 's' 59 | # if int(t[3:5]) == 0: 60 | # result = t[6::] + 's' 61 | #else: 62 | # result = t[0:2] + 'h ' + t[3:5] + 'm ' + t[6::] + 's' 63 | return result 64 | 65 | def convert_into_sec(t): 66 | sec = int(t[0:2])*3600 + int(t[3:5])*60 + int(t[6::]) 67 | return sec 68 | 69 | def convert(sec): 70 | sec = int(sec) 71 | sec = sec % (24 * 3600) 72 | hr = sec // 3600 73 | sec %= 3600 74 | mn = sec // 60 75 | sec %= 60 76 | hr = str(hr).zfill(2) 77 | mn = str(mn).zfill(2) 78 | sec = str(sec).zfill(2) 79 | 80 | result = hr + ":" + mn + ":" + sec 81 | return result 82 | 83 | -------------------------------------------------------------------------------- /misc/export_to_2.0: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import sys 4 | sys.path.insert(0, "/usr/share/Watcher/") 5 | import csv 6 | from watch_log import get_date 7 | import datetime 8 | import time_operations as to 9 | 10 | def extract_data(date): 11 | user = os.getlogin() 12 | path = "/home/" + user +"/.cache/Watcher/raw_data/" 13 | filename = path + date + ".csv" 14 | 15 | l = list() # l = list of (app_name, time spent on app on that session) 16 | d = dict() 17 | 18 | if os.path.isfile(filename): 19 | with open(filename, 'r') as file: 20 | reader = csv.reader(file) 21 | for row in reader: 22 | for column in row: 23 | l.append([column[18::],column[9:17]]) 24 | d.update({column[18::]: "O"}) 25 | else: 26 | None 27 | 28 | d = list(d) # list of app opened on that day 29 | return d, l 30 | 31 | # creating dictionary to store time spend on each applicaitons on that particular day 32 | def final_report(window_opened, time_spent): 33 | report = dict() 34 | 35 | for i in window_opened: # i is applications name 36 | time = '00:00:00' 37 | for j in time_spent: # j is list of applicaions_name and time_spent in particular session 38 | if i == j[0]: 39 | time = to.time_addition(j[1], time) 40 | report.update({i:time}) 41 | 42 | #print(report) 43 | if "User-logged-in" in report.keys(): 44 | report.pop("User-logged-in") 45 | if "AFK" in report.keys(): 46 | report.pop("AFK") 47 | if "Unknown" in report.keys(): 48 | report.pop("Unknown") 49 | # sort report dictonary in decreasing order of Usages 50 | sorted_values = [] 51 | for x,y in report.items(): 52 | sorted_values.append(to.convert_into_sec(y)) 53 | 54 | sorted_values.sort(reverse=True) 55 | sorted_report = dict() 56 | 57 | for i in sorted_values: 58 | for x, y in report.items(): 59 | if to.convert_into_sec(y) == i: 60 | sorted_report.update({x:y}) 61 | 62 | return sorted_report 63 | 64 | def export_to_new(date): 65 | window_opened, time_spent = extract_data(date) 66 | sorted_report = final_report(window_opened, time_spent) 67 | filename = "/home/"+os.getlogin()+"/.cache/Watcher/daily_data/"+date+".csv" 68 | overwrite_Data = [] 69 | with open(filename, 'w') as csvfile: 70 | for x,y in sorted_report.items(): 71 | overwrite_Data.append([y,x]) 72 | 73 | csvwriter = csv.writer(csvfile, delimiter='\t') 74 | csvwriter.writerows(overwrite_Data) 75 | 76 | del sorted_report 77 | del overwrite_Data 78 | 79 | path = "/home/" + os.getlogin() +"/.cache/Watcher/raw_data/" 80 | for file in os.listdir(path): 81 | export_to_new(file[:-4]) 82 | 83 | 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Watcher 2 | 3 | ### Minimal Open source Screen-Time Tracker (CLI-app) 4 | 5 | Note: codebase in the python is being under migration in golang, you can checkout `go-migrate` branch for more info. 6 | 7 | 8 | 9 | 10 | ## Table of Contents 11 | 12 | - [About](#about) 13 | - [Gallery](#gallery) 14 | - [Installation](#installation) 15 | - [Want to Contribute](#want-to-contribute) 16 | - [Todo](#to-do) 17 | 18 | 19 | ## About 20 | 21 | Watcher is CLI-app (at this moment) which helps you to get perspective about your Screen-time 22 | 23 | ## Gallery 24 | 25 | 26 | | Day Summary | Week Summary | 27 | | :-------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------: | 28 | | ![](https://github.com/user-attachments/assets/d7513cf1-f53e-429e-a1de-748bf4e0f094) | ![](https://github.com/user-attachments/assets/d44089ed-836b-4eee-b053-7f3adb850ec1) | 29 | 30 | Funfact: You might be thinking how can someone has 14 hrs of screen time in a single day, Well ! short ans is AFK-feature is not implemented yet... Most of the time I left my laptop as it is so it also counts that AFK time as Screen-time 31 | 32 | ## Installation 33 | 34 | - Note: Install [`xprintidle`](https://github.com/g0hl1n/xprintidle) and [`xdotool`](https://github.com/jordansissel/xdotool) on your system ( the only dependancies other than python3 ). Install [`python3`](https://www.python.org/downloads/) if not installed in your machine. 35 | - First, Install the following dependancy `xprintidle` and `xdotool` 36 | 37 | ```bash 38 | $ sudo [package-manager] install xprintidle xdotool 39 | ``` 40 | 41 | - Second, Copy the Following Command and paste in terminal 42 | 43 | ```bash 44 | $ bash <(curl -s https://raw.githubusercontent.com/Waishnav/Watcher/main/install) 45 | ``` 46 | 47 | - Then run install script 48 | 49 | ```bash 50 | $ chmod +x ./install && ./install 51 | ``` 52 | 53 | ### Want to Contribute 54 | If you are interseted in contibuting checkout [CONTRIBUTING.md](https://github.com/Waishnav/Watcher/blob/main/CONTRIBUTING.md) 55 | 56 | You can currently contribute to one of the three projects listed below throughout the HACTOBERFEST. 57 | - [Watcher Website](https://github.com/Waishnav/Watcher-web) (made with React) 58 | - [Watcher v1.0](https://github.com/Waishnav/Watcher/tree/v1.0) (No real time updates in logfile) 59 | - [Watcher v2.0](https://github.com/Waishnav/Watcher/tree/v2.0) (Real time stats in logfile) 60 | 61 | To contribute, clone the relevant branch anywhere you wish to. 62 | 63 | ## To-do 64 | 65 | - [x] AFK feature 66 | - [ ] GUI only if got 300 stars Probably [Tauri App](https://github.com/tauri-apps/tauri). 67 | -------------------------------------------------------------------------------- /src/Watcher/watch_log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import time 4 | import get_windows as x 5 | import afk as y 6 | from time_operations import time_difference, time_addition, convert 7 | 8 | # get current time whenever the function is called 9 | def get_time(): 10 | t = os.popen('''date +"%T"''').read() 11 | return t[0:-1] 12 | 13 | # get date of today 14 | def get_date(): 15 | d = os.popen('''date +"%Y-%m-%d"''').read() 16 | return d[0:-1] 17 | 18 | def update_csv(date, Data): 19 | filename = os.environ['HOME']+"/.cache/Watcher/daily_data/"+date+".csv" 20 | overwrite_Data = [] 21 | with open(filename, 'w') as csvfile: 22 | for x,y in Data.items(): 23 | overwrite_Data.append([y,x]) 24 | 25 | csvwriter = csv.writer(csvfile, delimiter='\t') 26 | csvwriter.writerows(overwrite_Data) 27 | 28 | # Expected Behaviour == if date got changed then append line in new csv file after initializing the csv file 29 | # also if usr is AFK then append line 30 | def import_data(file): 31 | with open(file, 'r') as f: 32 | raw_data = f.readlines() 33 | data = dict() 34 | #l = [] 35 | for x in raw_data: 36 | x = x.split('\t') 37 | a = {x[1][:-1]:x[0]} 38 | #l.append(x[1][:-1]) 39 | data.update(a) 40 | return data 41 | 42 | 43 | # TODO: AFK feature devlopement (it will be developed after completing alpha product (after whole project up end running) 44 | 45 | def log_creation(): 46 | filename = os.environ['HOME']+"/.cache/Watcher/daily_data/"+get_date()+".csv" 47 | if not(os.path.isfile(filename)): 48 | creat_file = os.environ['HOME']+"/.cache/Watcher/daily_data/"+get_date()+".csv" 49 | with open(creat_file, 'w') as fp: 50 | pass 51 | 52 | afk = False 53 | afkTimeout = 1 # timeout in minutes 54 | data = import_data(filename) 55 | while True: 56 | date = get_date() 57 | filename = os.environ['HOME']+"/.cache/Watcher/daily_data/"+date+".csv" 58 | afk = y.is_afk(afkTimeout) 59 | print(data) 60 | 61 | if not(afk): 62 | active_window = x.active_window() 63 | usage = data.get(active_window) 64 | if usage == None: 65 | usage = "00:00:00" 66 | 67 | time.sleep(1) 68 | if y.is_afk(afkTimeout): 69 | afk_time = int(round(int(os.popen("xprintidle").read()[:-1])/1000, 0)) 70 | usage = time_difference(convert(afk_time), usage) 71 | 72 | usage = time_addition("00:00:01", usage) 73 | data.update({active_window : usage}) 74 | if os.path.isfile(os.environ['HOME']+"/.cache/Watcher/daily_data/"+get_date()+".csv"): 75 | update_csv(get_date(), data) 76 | elif not(os.path.isfile(os.environ['HOME']+"/.cache/Watcher/daily_data/"+get_date()+".csv")): 77 | new_filename = os.environ['HOME']+"/.cache/Watcher/daily_data/"+get_date()+".csv" 78 | with open(new_filename, 'w') as fp: 79 | pass 80 | 81 | data.clear() 82 | 83 | 84 | if __name__ == "__main__": 85 | log_creation() 86 | #afk_time = int(round(int(os.popen("xprintidle").read()[:-1])/1000, 0)) 87 | #print(afk_time) 88 | 89 | -------------------------------------------------------------------------------- /src/Watcher/analysis.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | from watch_log import get_date 4 | import datetime 5 | import time_operations as to 6 | 7 | # creating dictionary to store time spend on each applicaitons on that particular day 8 | def final_report(date): 9 | path = os.environ['HOME'] +"/.cache/Watcher/daily_data/" 10 | filename = path + date + ".csv" 11 | 12 | report = dict() 13 | if os.path.isfile(filename): 14 | with open(filename, 'r') as f: 15 | raw_data = f.readlines() 16 | for x in raw_data: 17 | x = x.split('\t') 18 | a = {x[1][:-1]:x[0]} 19 | report.update(a) 20 | 21 | #print(report) 22 | if "Unknown" in report.keys(): 23 | report.pop("Unknown") 24 | return report 25 | 26 | def sort_data(report): 27 | # sort report dictonary in decreasing order of Usages 28 | sorted_values = [] 29 | for x,y in report.items(): 30 | sorted_values.append(to.convert_into_sec(y)) 31 | 32 | sorted_values.sort(reverse=True) 33 | sorted_report = dict() 34 | 35 | for i in sorted_values: 36 | for x, y in report.items(): 37 | if to.convert_into_sec(y) == i: 38 | sorted_report.update({x:y}) 39 | del report 40 | del sorted_values 41 | return sorted_report 42 | 43 | 44 | def get_sunday_of_week(week): 45 | year = int(week[4:]) 46 | week = int(week[1:3]) 47 | first = datetime.date(year, 1, 1) 48 | base = 1 if first.isocalendar()[1] == 1 else 8 49 | return first + datetime.timedelta(days=base - first.isocalendar()[2] + 7 * (week - 1)) + datetime.timedelta(days=6.9) 50 | 51 | # getting dates of particular week for week summary 52 | def week_dates(theday=datetime.date.today()): 53 | weekday = theday.isoweekday() - 1 54 | # The start of the week (Monday) 55 | start = theday - datetime.timedelta(days=weekday) 56 | # build a simple range 57 | dates = [start + datetime.timedelta(days=d) for d in range(weekday + 1)] 58 | dates = [str(d) for d in dates] 59 | return dates 60 | 61 | def weekday_from_date(date): 62 | day = os.popen('''date -d "'''+ date + '''" +%a''').read() 63 | return day[0:-1] 64 | 65 | def weekly_logs(week = str(os.popen('''date +"W%V-%Y"''').read()[0:-1])): 66 | user = os.environ['HOME'] 67 | filename = user+"/.cache/Watcher/Analysis/"+week+".csv" 68 | with open(filename, "w") as csvfile: 69 | csvwriter = csv.writer(csvfile, delimiter='\t') 70 | 71 | if os.popen('''date +"W%V-%Y"''').read()[0:-1] == week: 72 | dates = week_dates() 73 | else: 74 | dates = week_dates(get_sunday_of_week(week)) 75 | 76 | for i in dates: 77 | Total_screen_time = "00:00:00" 78 | for x, y in final_report(i).items(): 79 | Total_screen_time = to.time_addition(y, Total_screen_time) 80 | csvwriter.writerow([weekday_from_date(i), Total_screen_time]) 81 | 82 | all_data = dict() 83 | for i in dates: 84 | for x, y in final_report(i).items(): 85 | usage = all_data.get(x) 86 | if usage == None: 87 | usage = "00:00:00" 88 | y = to.time_addition(usage, y) 89 | all_data.update({x:y}) 90 | 91 | all_data = sort_data(all_data) 92 | for x, y in all_data.items(): 93 | csvwriter.writerow([y, x]) 94 | 95 | #testing 96 | if __name__ == "__main__": 97 | weekly_logs("W29-2022") 98 | 99 | -------------------------------------------------------------------------------- /src/bin/watcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import sys 4 | sys.path.insert(0, "/usr/share/Watcher/") 5 | import watch_log as x 6 | from commands import Color 7 | import commands as cmd 8 | from analysis import weekly_logs 9 | 10 | def help_option(): 11 | print(Color.BLUE("Watcher") + " - Minimal open source screen-time tracker\n") 12 | print(Color.YELLOW("USAGE:")+"\n\t watcher [OPTION]\n") 13 | print(Color.YELLOW("OPTIONS")) 14 | print("\t-ds, --day-summary Shows today's screen-time and app-usage") 15 | print("\t-ds [date] Shows that day's screen-time and app-usage\n\t\t\t\t\t (if date is not given then date=today's date)") 16 | print("\t-ws, --week-summary Shows screen-time of each day of that running week") 17 | print("\t-ws [week] Shows screen-time of each day of that week given by you") 18 | print("\t-s, --start It starts taking logs afterwards in following directory ~/.cache/Watcher/raw_data/") 19 | print(Color.YELLOW("\nEXAMPLE COMMANDS")) 20 | print("\twatcher -ds 2022-01-31") 21 | print("\twatcher -ws W01-2022") 22 | print("\nFor more information see github repo: "+ Color.BLUE("https://github.com/Waishnav/Watcher") +" and Don't forget to star the repo") 23 | 24 | def wrong_option(): 25 | print(Color.RED("Wrong")+" [OPTION] choosen. Have a look at the Options!!\n") 26 | print(Color.YELLOW("OPTIONS")) 27 | print("\t-ds or --day-summary [date] Displays where you have spend your time of that day") 28 | print("\t-ws or --week-summary [week] Displays screen-time of each day of week") 29 | print(Color.YELLOW("\nEXAMPLE COMMANDS")) 30 | print("\twatcher -ds 2022-01-31") 31 | print("\twatcher -ws W01-2022") 32 | print("\nFor more information see github repo: "+ Color.BLUE("https://github.com/Waishnav/Watcher") +" and Don't forget to star the repo") 33 | 34 | 35 | #print("▒▒▒\t▒▒▒\n███") 36 | arg = sys.argv 37 | if len(arg) == 2: 38 | if arg[1] == "-ds" or arg[1] == "--day-summary": 39 | cmd.daily_summary() 40 | elif arg[1] == "-ws" or arg[1] == "--week-summary": 41 | weekly_logs() 42 | cmd.week_summary() 43 | elif arg[1] == "-h" or arg[1] == "--help": 44 | help_option() 45 | elif arg[1] == "--start" or arg[1] == "-s": 46 | print("Log creations started... \nif you wanna stop it, use keyboard-shortcut (Ctrl+Shift+C or Ctrl+C)") 47 | x.log_creation() 48 | elif arg[1] == "--version" or arg[1] == "-v": 49 | print("Version: 2.0.0") 50 | else: 51 | wrong_option() 52 | 53 | elif len(arg) == 3: 54 | if arg[1] == "-ds" or arg[1] == "--day-summary": 55 | if len(arg[2]) == 10: 56 | cmd.daily_summary(str(arg[2])) 57 | elif arg[2] == "--yestarday" or arg[2] == "-y": 58 | cmd.daily_summary(os.popen("""date -d "1 day ago" '+%Y-%m-%d'""").read()[:-1]) 59 | else: 60 | wrong_option() 61 | elif arg[1] == "-ws" or arg[1] == "--week-summary": 62 | if len(arg[2]) == 8: 63 | weekly_logs(str(arg[2])) 64 | cmd.week_summary(str(arg[2])) 65 | elif arg[2] == "--previous_week" or arg[2] == "-pw": 66 | prev_week=os.popen("""date -d 'last week' '+W%W-%Y'""").read()[:-1] 67 | weekly_logs(prev_week) 68 | cmd.week_summary(prev_week) 69 | else: 70 | wrong_option() 71 | else: 72 | wrong_option() 73 | 74 | else: 75 | wrong_option() 76 | #parser = argparse.ArgumentParser(description="Minimal open source screen-time calulator for digitally wellbeing") 77 | #parser.add_argument() 78 | #args = parser.parse_args() 79 | #print(args) 80 | 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | ----------------------- 3 | How Watcher works exactly ? 4 | ================= 5 | 6 | First let's understand how it works. 7 | 8 | `./install` file install's all the py scripts and make Watcher to run on startup. 9 | 10 | ### **v1.0** 11 | 12 | - **[afk.py](https://github.com/Waishnav/Watcher/blob/v1.0/src/Watcher/afk.py)** : As name sugested it records the time for being afk it will count afk time when no input is given within 1 min . Also it ignores if their is video playback. 13 | 14 | - **[analysis.py](https://github.com/Waishnav/Watcher/blob/v1.0/src/Watcher/analysis.py)** : It gives the report for the week summary and day summary of the usage. 15 | - **[commands.py](https://github.com/Waishnav/Watcher/blob/v1.0/src/Watcher/commands.py)** : This is used to display the data that analysis.py has analysed. Additionally, it contains all of the command outputs; in essence, this file is where the user interacts with the Wacther. 16 | 17 | - **[get_windows.py](https://github.com/Waishnav/Watcher/blob/v1.0/src/Watcher/get_windows.py)** : It gives the title/name of the Active Window/App. 18 | 19 | - **[time_operations.py](https://github.com/Waishnav/Watcher/blob/v1.0/src/Watcher/time_operations.py)** : It converts the raw time data into time how much the app is used. Also format produce the time in [HH:MM:SS](https://docs.oracle.com/cd/E41183_01/DR/Time_Formats.html) format. 20 | 21 | - **[watch.log](https://github.com/Waishnav/Watcher/blob/v1.0/src/Watcher/watch_log.py)** : This is where the logs file are created it creates two kind of log files for Week analysis and Day analysis, log files for Day analysis are created in [Date].csv format and for week W[week_no.]_[year].py format. 22 | 23 | 24 | ### **v2.0** 25 | 26 | #### Main Difference between [v1.0](https://github.com/Waishnav/Watcher/tree/v1.0/src/Watcher) and [v2.0](https://github.com/Waishnav/Watcher/tree/v2.0/src/Watcher) is the Represntation of the csv and the Algorithm (The way in which it calculate and present the usage time) is **optimized**. 27 | 28 | - #### The file which is changed mainly is [analysis.py](https://github.com/Waishnav/Watcher/blob/v2.0/src/Watcher/analysis.py) and the [watch_log.py](https://github.com/Waishnav/Watcher/blob/v2.0/src/Watcher/watch_log.py). 29 | 30 | #### In v1.0 their is multiple session of time created even for the same window/apps but in v2.0 this is optimized and been removed the multiple session for each apps. 31 | ## Screenshots (csv files) 32 | v1.0 | v2.0 33 | :-------------------------:|:-------------------------: 34 | ![](https://user-images.githubusercontent.com/83799380/194891764-f45c529c-d29d-4d14-96fc-bce0e80becb5.png) | ![](https://user-images.githubusercontent.com/83799380/194891867-a49df66a-5ae0-4b9b-9aa7-b6d6681c4574.png) 35 | 36 | ----------------------- 37 | How can you Contribute to this cool project ? 38 | ================= 39 | 40 | ### Table of Contents 41 | 42 | - [Getting started](https://github.com/Waishnav/Watcher) 43 | - [Work on Landing page](https://github.com/Waishnav/Watcher-web) 44 | - Landing website which helps people to gain clearity about thier screen-time and its importance. 45 | - It is made with the help of React-framework 46 | - [Solve v1.0 issues](https://github.com/Waishnav/Watcher/tree/v1.0) 47 | - There are some bugs in the v1.0 some of them are small some might be big. so you can solve them and improve v1.0. 48 | - [Improve v2.0](https://github.com/Waishnav/Watcher/tree/v2.0) 49 | - v2.0 is improvised version of previous algorithm test it and give us feedback in discussion section. 50 | - [How you can help](#how-you-can-help) 51 | - [Questions?](#questions) 52 | 53 | 54 | ### How you can help 55 | 56 | There are many ways to contribute to Watcher: 57 | 58 | - Work on issues labeled [`good first issue`] or [`help wanted`][help wanted], these are especially suited for new contributors. 59 | - Fix [`bugs`]. 60 | - Implement new features. NOTE: before implementing and doing PR do discuss with the community with opening issue. 61 | - Look among the [requested features][requested features] on the forum. 62 | 63 | - Write documentation. 64 | 65 | 66 | 67 | 68 | ## Commit message guidelines 69 | 70 | When writing commit messages try to follow [Conventional Commits](https://www.conventionalcommits.org/). It is not a strict requirement (to minimize overhead for new contributors) but it is encouraged. 71 | 72 | The format is: 73 | 74 | ``` 75 | [optional scope]: 76 | 77 | [optional body] 78 | 79 | [optional footer] 80 | ``` 81 | 82 | Where `type` can be one of: `feat, fix, chore, ci, docs, style, refactor, perf, test` 83 | 84 | Examples: 85 | 86 | ``` 87 | - feat: added ability to sort by duration 88 | - fix: fixes incorrect week number (#407) 89 | - docs: improved query documentation 90 | ``` 91 | 92 | 93 | ## Questions? 94 | 95 | If you have any questions, you can: 96 | 97 | - Create new discussion revolve around your question. [GitHub Discussions](https://github.com/Waishnav/Watcher/discussions). 98 | - (as a last resort/if needed) Email me (Maintainer): [waishnavdeore@gmail.com](mailto:waishnavdeore@gmail.com) 99 | 100 | 101 | [github discussions]: https://github.com/Waishnav/Watcher/discussions. 102 | -------------------------------------------------------------------------------- /src/Watcher/commands.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import datetime 4 | from watch_log import get_date 5 | import analysis as anls 6 | import time_operations as to 7 | 8 | class Color: 9 | 10 | def GREY(text): 11 | return '\033[90m' + text + '\033[0m' 12 | 13 | def BLUE(text): 14 | return '\033[34m' + text + '\033[0m' 15 | 16 | def GREEN(text): 17 | return '\033[32m' + text + '\033[0m' 18 | 19 | def YELLOW(text): 20 | return '\033[33m' + text + '\033[0m' 21 | 22 | def RED(text): 23 | return '\033[31m' + text + '\033[0m' 24 | 25 | def PURPLE(text): 26 | return '\033[35m' + text + '\033[0m' 27 | 28 | def DARKCYAN(text): 29 | return '\033[36m' + text + '\033[0m' 30 | 31 | def BOLD(text): 32 | return '\033[1m' + text + '\033[0m' 33 | 34 | def UNDERLINE(text): 35 | return '\033[4m' + text + '\033[0m' 36 | 37 | def daily_summary(date = get_date()): 38 | Total_screen_time = "00:00:00" 39 | for x,y in anls.final_report(date).items(): 40 | Total_screen_time = to.time_addition(y, Total_screen_time) 41 | 42 | if date == get_date(): 43 | if len(to.format_time(Total_screen_time)) == 3: 44 | print(Color.YELLOW("\n Today's Screen-Time\t\t ") + Color.BLUE(f'{to.format_time(Total_screen_time):>16}')) 45 | elif len(to.format_time(Total_screen_time)) == 7: 46 | print(Color.YELLOW("\n Today's Screen-Time\t\t ") + Color.BLUE(f'{to.format_time(Total_screen_time):>11}')) 47 | elif len(to.format_time(Total_screen_time)) == 11: 48 | print(Color.YELLOW("\n Today's Screen-Time\t\t ") + Color.BLUE(to.format_time(Total_screen_time))) 49 | elif date == os.popen("""date -d "1 day ago" '+%Y-%m-%d'""").read()[:-1]: 50 | if len(to.format_time(Total_screen_time)) == 3: 51 | print(Color.YELLOW("\n Yestarday's Screen-Time\t ") + Color.BLUE(f'{to.format_time(Total_screen_time):>16}')) 52 | elif len(to.format_time(Total_screen_time)) == 7: 53 | print(Color.YELLOW("\n Yestarday's Screen-Time\t ") + Color.BLUE(f'{to.format_time(Total_screen_time):>11}')) 54 | elif len(to.format_time(Total_screen_time)) == 11: 55 | print(Color.YELLOW("\n Yestarday's Screen-Time\t ") + Color.BLUE(to.format_time(Total_screen_time))) 56 | else: 57 | if len(to.format_time(Total_screen_time)) == 3: 58 | print(Color.YELLOW("\n "+date+"'s Screen-Time\t ") + Color.BLUE(f'{to.format_time(Total_screen_time):>6}')) 59 | elif len(to.format_time(Total_screen_time)) == 7: 60 | print(Color.YELLOW("\n "+ date+ "'s Screen-Time\t ") + Color.BLUE(f'{to.format_time(Total_screen_time):>1}')) 61 | elif len(to.format_time(Total_screen_time)) == 11: 62 | print(Color.YELLOW("\n "+date+"'s Screen-Time\t ") + Color.BLUE(to.format_time(Total_screen_time))) 63 | 64 | print(" ────────────────────────────────────────────────") 65 | print(Color.RED(f'{" App Usages":>29}')) 66 | print(" ────────────────────────────────────────────────") 67 | 68 | for x,y in anls.sort_data(anls.final_report(date)).items(): 69 | if x == "": 70 | x = "Home-Screen" 71 | print(" " + Color.GREEN(f'{x:<22}') + '\t ',f'{to.format_time(y):>12}') 72 | 73 | def week_summary(week = os.popen('''date +"W%V-%Y"''').read()[:-1]): 74 | user = os.environ['HOME'] 75 | filename = user+"/.cache/Watcher/Analysis/"+week+".csv" 76 | with open(filename, 'r') as file: 77 | csvreader = csv.reader(file, delimiter='\t') 78 | week_overview = dict() 79 | app_usages = dict() 80 | for row in csvreader: 81 | if len(row[0]) == 3: 82 | week_overview.update({row[0]:row[1]}) # Weekday -- screen-time 83 | else: 84 | app_usages.update({row[1]:row[0]}) # app-name -- usage 85 | 86 | week_screen_time = "00:00:00" 87 | for x, y in week_overview.items(): 88 | week_screen_time = to.time_addition(y, week_screen_time) 89 | 90 | if week == os.popen('''date +"W%V-%Y"''').read()[:-1]: 91 | print(Color.PURPLE("\n Week's screen-time\t\t ") + Color.BLUE(to.format_time(week_screen_time))) 92 | elif week == os.popen("""date -d 'last week' '+W%W-%Y'""").read()[:-1]: 93 | print(Color.PURPLE("\n Previous Week's \t\t ") + Color.BLUE(to.format_time(week_screen_time))) 94 | print(Color.PURPLE(" Screen-Time")) 95 | else: 96 | print(Color.PURPLE("\n "+week[1:3]+ "th week of\t ") + Color.BLUE(to.format_time(week_screen_time))) 97 | print(Color.PURPLE(" "+week[4:] +" screen-time\t ")) 98 | 99 | print(" ────────────────────────────────────────────────") 100 | 101 | for x, y in week_overview.items(): 102 | print(" " + f'{Color.YELLOW(x):>21}' + "\t\t " + Color.BLUE(to.format_time(y))) 103 | 104 | #anls.prints_report(window_opened, time_spent, is_week) 105 | print(" ────────────────────────────────────────────────") 106 | print(Color.RED(f'{" App Usages":>29}')) 107 | print(" ────────────────────────────────────────────────") 108 | for x,y in app_usages.items(): 109 | if x == "": 110 | x = "Home-Screen" 111 | print(" " + Color.GREEN(f'{x:<22}') + '\t ',f'{to.format_time(y):>12}') 112 | 113 | #testing 114 | if __name__ == "__main__": 115 | week_summary("W27-2022") 116 | #daily_summary("2022-07-18") 117 | --------------------------------------------------------------------------------