├── .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 | |  |  |
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 |  | 
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 |
--------------------------------------------------------------------------------