nowfocus 0.4.2__py3-none-any.whl → 0.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nowfocus/__main__.py +157 -145
- nowfocus/conf.py +41 -48
- nowfocus/desktop-extras/nowfocus.desktop +1 -1
- nowfocus/example-todo.txt +1 -2
- nowfocus/install.py +92 -0
- nowfocus/session_options.py +1 -1
- nowfocus/task_window.py +5 -5
- nowfocus/utils.py +2 -22
- nowfocus/version_migrator.py +20 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.4.4.dist-info}/METADATA +2 -10
- {nowfocus-0.4.2.dist-info → nowfocus-0.4.4.dist-info}/RECORD +15 -13
- {nowfocus-0.4.2.dist-info → nowfocus-0.4.4.dist-info}/WHEEL +0 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.4.4.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.4.2.dist-info → nowfocus-0.4.4.dist-info}/top_level.txt +0 -0
nowfocus/__main__.py
CHANGED
|
@@ -29,8 +29,6 @@ except Exception as e:
|
|
|
29
29
|
gi.require_version('AppIndicator3', '0.1')
|
|
30
30
|
from gi.repository import AppIndicator3 as appindicator
|
|
31
31
|
|
|
32
|
-
# from dbus_idle import IdleMonitor
|
|
33
|
-
|
|
34
32
|
# Set working dir to file location
|
|
35
33
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
36
34
|
|
|
@@ -62,81 +60,59 @@ class Application(Gtk.Application):
|
|
|
62
60
|
def __init__(self, *args, **kwargs):
|
|
63
61
|
super().__init__(*args, application_id="org.nowfocus.nowfocus", **kwargs)
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
# # To put everything here...
|
|
67
|
-
# # this doesn't work because as soon as an exception occurs ii jumps to the handler and breaks all the following code.
|
|
63
|
+
try:
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# return None
|
|
73
|
-
|
|
74
|
-
self.window = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
self.window = None
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
self.is_running = False
|
|
70
|
+
self.device_in_use = True
|
|
71
|
+
self.UserIdleTime = UserIdleTime()
|
|
72
|
+
self.last_user_idle_check_time = None
|
|
73
|
+
self.session = default_session()
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
self.menu = Gtk.Menu()
|
|
76
|
+
self.menu_tasks = {}
|
|
77
|
+
self.list_menus = {}
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
self.indicator = appindicator.Indicator.new(conf.app_name, os.path.abspath('icon/icon-1.svg'), appindicator.IndicatorCategory.APPLICATION_STATUS)
|
|
80
|
+
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
|
|
81
|
+
self.indicator.set_menu(self.menu)
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
if conf.is_first_load:
|
|
84
|
+
import install
|
|
85
|
+
install.run_first_load_actions()
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
# menu.set_reserve_toggle_size(False) # skip menu left padding, doesn't work
|
|
87
|
+
utils.db_schema_update()
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
utils.db_schema_update()
|
|
89
|
+
self.async_refresh()
|
|
91
90
|
|
|
91
|
+
# utils.db_cleanup()
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
self.async_refresh()
|
|
93
|
+
main_tick_timer = GLib.timeout_add_seconds(1, self.tick)
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
self.resume_session_from_db()
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
self.start_pipe()
|
|
98
|
+
self.open_task_window()
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
# Testing
|
|
101
|
+
# time.sleep(3)
|
|
102
|
+
# self.open_session_options_dialog('test_param')
|
|
103
|
+
# self.open_settings_window() #for testing
|
|
104
|
+
# self.open_new_task_dialog() #for testing
|
|
105
|
+
# self.print_time_totals()
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
signal.signal(signal.SIGINT, self.quit)
|
|
108
|
+
signal.signal(signal.SIGUSR1, self.signal_handler)
|
|
109
|
+
signal.signal(signal.SIGUSR2, self.signal_handler)
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
s = json.loads(db_session[0]['value'])
|
|
107
|
-
s['start_time'] = datetime.strptime(s['start_time'],'%Y-%m-%d %H:%M:%S.%f')
|
|
108
|
-
self.session = s
|
|
109
|
-
self.is_running = True
|
|
110
|
-
dbg("resuming session",s['label'],l=2, s='session')
|
|
111
|
+
Gtk.main()
|
|
111
112
|
except Exception as e:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
117
|
-
self.pipethread.daemon = True
|
|
118
|
-
self.pipethread.start()
|
|
119
|
-
|
|
120
|
-
# Testing
|
|
121
|
-
# time.sleep(3)
|
|
122
|
-
# self.open_session_options_dialog('test_param')
|
|
123
|
-
|
|
124
|
-
# self.open_settings_window() #for testing
|
|
125
|
-
# self.open_task_window() #for testing
|
|
126
|
-
|
|
127
|
-
# time.sleep(2)
|
|
128
|
-
# self.open_new_task_dialog() #for testing
|
|
129
|
-
|
|
130
|
-
# time.sleep(2)
|
|
131
|
-
# self.print_time_totals()
|
|
132
|
-
|
|
133
|
-
# signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
134
|
-
signal.signal(signal.SIGINT, self.quit)
|
|
135
|
-
signal.signal(signal.SIGUSR1, self.signal_handler)
|
|
136
|
-
signal.signal(signal.SIGUSR2, self.signal_handler)
|
|
137
|
-
|
|
138
|
-
Gtk.main()
|
|
139
|
-
|
|
113
|
+
print(conf.app_name +' had a pretty bad error. Please submit the following trace as an issue on the git repo or email it to the developers ')
|
|
114
|
+
traceback.print_tb(e.__traceback__)
|
|
115
|
+
self.quit()
|
|
140
116
|
|
|
141
117
|
|
|
142
118
|
def print_time_totals(self = None, widget = None):
|
|
@@ -177,25 +153,13 @@ class Application(Gtk.Application):
|
|
|
177
153
|
exit()
|
|
178
154
|
|
|
179
155
|
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
156
|
+
def get_seconds_since_user_active(self):
|
|
157
|
+
|
|
158
|
+
if self.last_user_idle_check_time != now().replace(microsecond=0):
|
|
159
|
+
self.last_user_idle_check_time = now().replace(microsecond=0)
|
|
160
|
+
self.seconds_since_user_active = self.UserIdleTime.get()
|
|
183
161
|
|
|
184
|
-
|
|
185
|
-
# Requires xprintidle (sudo apt install xprintidle)
|
|
186
|
-
# idle_time = int(int(subprocess.getoutput('xprintidle')) / 1000)
|
|
187
|
-
|
|
188
|
-
# Version that works, using: https://github.com/bkbilly/dbus_idle
|
|
189
|
-
# but has many deps
|
|
190
|
-
# Requires:
|
|
191
|
-
# sudo apt install meson libdbus-glib-1-dev patchelf
|
|
192
|
-
# pip install dbus-idle
|
|
193
|
-
|
|
194
|
-
# idle_time = int(int(IdleMonitor().get_dbus_idle()) / 1000)
|
|
195
|
-
# return idle_time
|
|
196
|
-
|
|
197
|
-
return self.UserIdleTime.get()
|
|
198
|
-
|
|
162
|
+
return self.seconds_since_user_active
|
|
199
163
|
|
|
200
164
|
|
|
201
165
|
def toggle_do_not_disturb(self, widget):
|
|
@@ -213,81 +177,106 @@ class Application(Gtk.Application):
|
|
|
213
177
|
|
|
214
178
|
def tock(self):
|
|
215
179
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
afk_time = self.seconds_since_user_active()
|
|
220
|
-
# print("Idle time: "+str(afk_time))
|
|
180
|
+
user_inactive_time = self.get_seconds_since_user_active()
|
|
181
|
+
# print("user_inactive_time: "+str(user_inactive_time))
|
|
221
182
|
|
|
222
183
|
dbg('Last todo todo_sync_time', conf.todo_sync_time, 'Time diff',int(time_difference(conf.todo_sync_time)),'Auto refresh interval * 60', (conf.user['todolist_refresh_interval'] * 60), s="todoloading", l=3 )
|
|
223
184
|
|
|
224
|
-
if (int(time_difference(conf.todo_sync_time)) / 60) > conf.user['todolist_refresh_interval'] * 60
|
|
185
|
+
if (int(time_difference(conf.todo_sync_time)) / 60) > conf.user['todolist_refresh_interval'] * 60:
|
|
186
|
+
# TODO: use individual todo_refresh_times
|
|
225
187
|
self.async_refresh()
|
|
226
188
|
|
|
227
|
-
# TODO: use individual todo_refresh_times
|
|
228
|
-
|
|
229
189
|
minutes = (int(self.session['duration']) / 60)
|
|
230
190
|
|
|
231
|
-
if
|
|
191
|
+
if self.check_device_became_inactive():
|
|
192
|
+
self.handle_device_became_inactive()
|
|
193
|
+
|
|
194
|
+
elif self.device_in_use == False or 'do-not-disturb' in self.session:
|
|
195
|
+
# Do nothing
|
|
196
|
+
dbg("Do not disturb is active")
|
|
197
|
+
|
|
198
|
+
elif self.is_running:
|
|
199
|
+
|
|
200
|
+
if self.session['label'] in conf.user['custom_pomodoro_intervals']:
|
|
201
|
+
check = conf.user['custom_pomodoro_intervals'][self.session['label']]
|
|
202
|
+
else:
|
|
203
|
+
check = conf.user['pomodoro_interval']
|
|
204
|
+
|
|
205
|
+
if float(minutes / check ).is_integer():
|
|
206
|
+
notify.Notification.new("Time for a Break?","You've been working on "+self.session['label']+" for "+str(minutes)+" minutes. ", None).show()
|
|
207
|
+
|
|
208
|
+
playsound('sound/bell-xylophone-g.mp3',False)
|
|
209
|
+
|
|
232
210
|
|
|
233
|
-
if
|
|
234
|
-
|
|
211
|
+
if 'target' in self.session:
|
|
212
|
+
t = self.session['target']
|
|
213
|
+
|
|
214
|
+
t['percent'] = round(( (t['starting_value'] + minutes) / t['value'] ) * 100,1)
|
|
215
|
+
print("At", t['percent'], "% of ", t['scope'], " target")
|
|
216
|
+
|
|
217
|
+
if t['type'] == 'max':
|
|
218
|
+
if t['percent'] >= 100:
|
|
219
|
+
|
|
220
|
+
notify.Notification.new("Time is up for this "+t['scope'],"You'r at "+str(round(t['percent']))+"% of your "+str(t['value'])+" minutes in the last "+str(t['within_value'])+" "+ t['within_unit'], None).show()
|
|
221
|
+
|
|
222
|
+
playsound('sound/dinner-bell.mp3',False)
|
|
223
|
+
|
|
224
|
+
elif t['type'] == 'min' and round(t['starting_value'] + minutes) == t['value']:
|
|
225
|
+
notify.Notification.new("Good job on doing "+self.session['label'],"You've reached your target of "+str(t['value'])+" minutes "+str(t['within_value'])+" "+ t['within_unit'], None).show()
|
|
226
|
+
|
|
227
|
+
playsound('sound/xylophone-chord.mp3',False)
|
|
228
|
+
|
|
229
|
+
elif num_is_multiple_of(minutes,conf.user['randomness_interrupt_interval']):
|
|
235
230
|
|
|
236
|
-
|
|
231
|
+
notify.Notification.new("What Am I doing?","Your randomness timer is at "+str(minutes)+" minutes. ", None).show()
|
|
237
232
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
self.open_task_window()
|
|
234
|
+
|
|
235
|
+
playsound('sound/dinner-bell.mp3',False)
|
|
241
236
|
|
|
242
|
-
elif afk_time > 120:
|
|
243
|
-
self.session['duration'] = 0
|
|
244
|
-
self.session['start_time'] = now()
|
|
245
|
-
print("Idle time reset. afk:", afk_time, self.session)
|
|
246
237
|
|
|
238
|
+
def check_device_became_inactive(self):
|
|
239
|
+
if self.get_seconds_since_user_active() > (conf.user['device_not_in_use_threshold'] * 60):
|
|
240
|
+
if self.device_in_use == True:
|
|
241
|
+
self.device_in_use = False
|
|
242
|
+
return True
|
|
247
243
|
else:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if afk_time < 181:
|
|
251
|
-
self.open_task_window(None,{'afk_time':afk_time})
|
|
252
|
-
else:
|
|
253
|
-
if self.session['label'] in conf.user['custom_pomodoro_intervals']:
|
|
254
|
-
check = conf.user['custom_pomodoro_intervals'][self.session['label']]
|
|
255
|
-
else:
|
|
256
|
-
check = conf.user['pomodoro_interval']
|
|
257
|
-
|
|
258
|
-
if float(minutes / check ).is_integer():
|
|
259
|
-
notify.Notification.new("Time for a Break?","You've been working on "+self.session['label']+" for "+str(minutes)+" minutes. ", None).show()
|
|
244
|
+
self.device_in_use = True
|
|
245
|
+
return False
|
|
260
246
|
|
|
261
|
-
playsound('sound/bell-xylophone-g.mp3',False)
|
|
262
247
|
|
|
248
|
+
def handle_device_became_inactive(self):
|
|
249
|
+
self.user_last_inactive = now()
|
|
263
250
|
|
|
264
|
-
|
|
265
|
-
|
|
251
|
+
user_inactive_time = self.get_seconds_since_user_active()
|
|
252
|
+
dbg('device_became_inactive ',l=-1)
|
|
266
253
|
|
|
267
|
-
|
|
268
|
-
print("At", t['percent'], "% of ", t['scope'], " target")
|
|
254
|
+
if self.is_running and 'do-not-disturb' not in self.session:
|
|
269
255
|
|
|
270
|
-
|
|
271
|
-
if t['percent'] >= 100:
|
|
256
|
+
self.open_task_window(None,{'user_inactive_time':user_inactive_time})
|
|
272
257
|
|
|
273
|
-
|
|
258
|
+
else:
|
|
259
|
+
self.session['duration'] = self.session['duration'] - user_inactive_time
|
|
260
|
+
if self.session['duration'] > 60:
|
|
261
|
+
utils.db_save_session(self.session)
|
|
262
|
+
|
|
263
|
+
self.session['duration'] = 0
|
|
264
|
+
self.session['start_time'] = now()
|
|
265
|
+
dbg("Recording:", user_inactive_time, self.session)
|
|
266
|
+
|
|
267
|
+
if 'do-not-disturb' not in self.session and conf.user['randomness_interrupt_interval'] > 0:
|
|
268
|
+
self.open_task_window(None)
|
|
274
269
|
|
|
275
|
-
playsound('sound/dinner-bell.mp3',False)
|
|
276
270
|
|
|
277
|
-
elif t['type'] == 'min' and round(t['starting_value'] + minutes) == t['value']:
|
|
278
|
-
notify.Notification.new("Good job on doing "+self.session['label'],"You've reached your target of "+str(t['value'])+" minutes "+str(t['within_value'])+" "+ t['within_unit'], None).show()
|
|
279
|
-
|
|
280
|
-
playsound('sound/xylophone-chord.mp3',False)
|
|
281
|
-
|
|
282
|
-
|
|
283
271
|
def tick(self):
|
|
284
272
|
|
|
285
273
|
# check for suspend indicated by gap in tick intervals
|
|
286
274
|
time_since_last_tick = round(time_difference(self.session['start_time']) - self.session['duration'])
|
|
287
275
|
if time_since_last_tick > 10:
|
|
276
|
+
|
|
288
277
|
dbg(time_since_last_tick, " seconds since last tick. Probably just woke from suspend. ")
|
|
289
278
|
if self.is_running:
|
|
290
|
-
self.open_task_window(None,{'
|
|
279
|
+
self.open_task_window(None,{'user_inactive_time':time_since_last_tick})
|
|
291
280
|
else:
|
|
292
281
|
self.session['start_time'] = now()
|
|
293
282
|
|
|
@@ -312,7 +301,6 @@ class Application(Gtk.Application):
|
|
|
312
301
|
|
|
313
302
|
if duration > 60 and num_is_multiple_of(duration, 2):
|
|
314
303
|
icon = 'icon-red.svg'
|
|
315
|
-
|
|
316
304
|
else:
|
|
317
305
|
icon = 'icon-1.svg'
|
|
318
306
|
|
|
@@ -321,13 +309,28 @@ class Application(Gtk.Application):
|
|
|
321
309
|
self.indicator.set_icon_full(os.path.abspath('icon/'+icon),label)
|
|
322
310
|
self.indicator.set_label(label,label)
|
|
323
311
|
|
|
312
|
+
if num_is_multiple_of(duration,5): # check every 5 seconds
|
|
313
|
+
if self.device_in_use:
|
|
314
|
+
self.refresh_all_changed_todo_files()
|
|
315
|
+
elif self.get_seconds_since_user_active() < 60:
|
|
316
|
+
self.device_in_use = True
|
|
317
|
+
self.handle_device_became_active()
|
|
318
|
+
else:
|
|
319
|
+
# TODO: incriment a user away timer
|
|
320
|
+
dbg("User is still away")
|
|
324
321
|
|
|
325
|
-
if num_is_multiple_of(self.icon_tick_number,3):
|
|
326
|
-
self.refresh_all_changed_todo_files()
|
|
327
|
-
|
|
328
322
|
return True
|
|
329
323
|
|
|
330
324
|
|
|
325
|
+
def handle_device_became_active(self):
|
|
326
|
+
dbg('device_became_active! ',l=-1)
|
|
327
|
+
self.user_last_active = now()
|
|
328
|
+
|
|
329
|
+
if self.is_running == False:
|
|
330
|
+
self.session['start_time'] = now()
|
|
331
|
+
self.session['duration'] = 0
|
|
332
|
+
|
|
333
|
+
|
|
331
334
|
def refresh_all_changed_todo_files(self):
|
|
332
335
|
for id, todo_config in conf.user['todolists'].items():
|
|
333
336
|
if todo_config['status'] and 'watch_file' in todo_config:
|
|
@@ -352,6 +355,20 @@ class Application(Gtk.Application):
|
|
|
352
355
|
handle_todo_read_error(todo_config,e)
|
|
353
356
|
|
|
354
357
|
|
|
358
|
+
def resume_session_from_db(self):
|
|
359
|
+
try:
|
|
360
|
+
db_session = db_query("SELECT value FROM system WHERE field = 'session'")
|
|
361
|
+
|
|
362
|
+
if db_session:
|
|
363
|
+
s = json.loads(db_session[0]['value'])
|
|
364
|
+
s['start_time'] = datetime.strptime(s['start_time'],'%Y-%m-%d %H:%M:%S.%f')
|
|
365
|
+
self.session = s
|
|
366
|
+
self.is_running = True
|
|
367
|
+
dbg("resuming session",s['label'],l=2, s='session')
|
|
368
|
+
except Exception as e:
|
|
369
|
+
dbg("Error resuming session",e,l=1)
|
|
370
|
+
|
|
371
|
+
|
|
355
372
|
def start_task(self, w = None, task_data_or_id = None, transfer_current_session_time = False):
|
|
356
373
|
|
|
357
374
|
if isinstance(task_data_or_id, dict):
|
|
@@ -431,7 +448,7 @@ class Application(Gtk.Application):
|
|
|
431
448
|
|
|
432
449
|
|
|
433
450
|
def run_task_command(self,command):
|
|
434
|
-
|
|
451
|
+
dbg("running task command:", command,l=-1)
|
|
435
452
|
|
|
436
453
|
self.running_command_task_label = copy.copy(self.session['label'])
|
|
437
454
|
|
|
@@ -736,24 +753,20 @@ class Application(Gtk.Application):
|
|
|
736
753
|
dbg('Signal received',sig,s='signals')
|
|
737
754
|
|
|
738
755
|
if sig == signal.SIGUSR1:
|
|
739
|
-
# try:
|
|
740
|
-
# print('TaskWindow._instance',TaskWindow._instance)
|
|
741
|
-
# if TaskWindow._instance:
|
|
742
|
-
# self.taskwindow.destroy()
|
|
743
|
-
# else:
|
|
744
|
-
# self.open_task_window()
|
|
745
|
-
|
|
746
|
-
# except AttributeError:
|
|
747
756
|
self.open_task_window()
|
|
748
|
-
|
|
749
757
|
elif sig == signal.SIGUSR2:
|
|
750
758
|
self.open_session_options_dialog()
|
|
751
|
-
|
|
752
759
|
else:
|
|
753
760
|
dbg("no handler for received signal",s='signals',l=3)
|
|
754
761
|
|
|
762
|
+
|
|
763
|
+
def start_pipe(self):
|
|
764
|
+
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
765
|
+
self.pipethread.daemon = True
|
|
766
|
+
self.pipethread.start()
|
|
767
|
+
|
|
768
|
+
|
|
755
769
|
def check_pipe(self):
|
|
756
|
-
# print("Listening to pipe at ",conf.pipe)
|
|
757
770
|
|
|
758
771
|
try:
|
|
759
772
|
with open(conf.pipe, "r") as pipe:
|
|
@@ -823,7 +836,6 @@ def startup():
|
|
|
823
836
|
except Exception as e:
|
|
824
837
|
print(e)
|
|
825
838
|
|
|
826
|
-
|
|
827
839
|
try:
|
|
828
840
|
os.mkfifo(conf.pipe)
|
|
829
841
|
dbg("Named pipe created successfully!", s="cli")
|
|
@@ -839,7 +851,7 @@ def startup():
|
|
|
839
851
|
|
|
840
852
|
|
|
841
853
|
except FileExistsError:
|
|
842
|
-
|
|
854
|
+
print("Named pipe exists, application must be running (or improperly shut down.) ")
|
|
843
855
|
|
|
844
856
|
# if args: pass to pipe and exit
|
|
845
857
|
if args.task:
|
nowfocus/conf.py
CHANGED
|
@@ -18,11 +18,14 @@ user_data_dir = GLib.get_user_data_dir()+"/"+app_id
|
|
|
18
18
|
Path(user_data_dir).mkdir(parents=True, exist_ok=True)
|
|
19
19
|
|
|
20
20
|
user_settings_dir = GLib.get_user_config_dir()+"/"+app_id
|
|
21
|
-
|
|
21
|
+
''' Depreciated. use user_data_dir instead '''
|
|
22
22
|
|
|
23
|
+
db_file = user_data_dir+"/data.db"
|
|
24
|
+
settings_file = user_data_dir+"/nowfocus-settings.json"
|
|
23
25
|
debug_level = 1 # dev value
|
|
24
26
|
debug_systems = []
|
|
25
|
-
pipe = "/tmp/"+app_id+"-pipe"
|
|
27
|
+
pipe = "/tmp/"+app_id+"-pipe"
|
|
28
|
+
is_first_load = False
|
|
26
29
|
|
|
27
30
|
# key and type must be the same, (Seems redundant but it's is quite helpful)
|
|
28
31
|
connectors = {
|
|
@@ -59,9 +62,10 @@ prototype_settings = {
|
|
|
59
62
|
"pomodoro_interval": 40,
|
|
60
63
|
"open_task_window_fullscreen": True,
|
|
61
64
|
"show_task_window_sidebars": False,
|
|
62
|
-
"randomness_interrupt_interval":
|
|
65
|
+
"randomness_interrupt_interval":7, #minutes
|
|
66
|
+
"device_not_in_use_threshold":3.5, #minutes
|
|
63
67
|
"default_text": "What am I doing?",
|
|
64
|
-
"todolist_refresh_interval":1,
|
|
68
|
+
"todolist_refresh_interval":1, #hours
|
|
65
69
|
"version":0.4,
|
|
66
70
|
"display_todolist_as_top_level_list":'auto',
|
|
67
71
|
'max_top_level_menu_items':10,
|
|
@@ -69,7 +73,7 @@ prototype_settings = {
|
|
|
69
73
|
'invoice_hourly_rate':0,
|
|
70
74
|
|
|
71
75
|
"custom_pomodoro_intervals": {
|
|
72
|
-
"email":7
|
|
76
|
+
"email":7 #minutes
|
|
73
77
|
},
|
|
74
78
|
|
|
75
79
|
'prompts':'What am I doing?\nWhy am I here?\nWhat could I do?\nWhat do I wish to accomplish?\nWhat is my aim?\nWhat\'s next',
|
|
@@ -84,11 +88,11 @@ prototype_settings = {
|
|
|
84
88
|
},
|
|
85
89
|
|
|
86
90
|
"todolists": {
|
|
87
|
-
"
|
|
88
|
-
"id":"
|
|
91
|
+
"Nowfocus Todo":{
|
|
92
|
+
"id":"Nowfocus Todo",
|
|
89
93
|
"type": "txt",
|
|
90
|
-
"label": "
|
|
91
|
-
"file":
|
|
94
|
+
"label": "Nowfocus Todo",
|
|
95
|
+
"file": user_data_dir+'/nowfocus-todo.txt',
|
|
92
96
|
"timetracker":"Example CSV",
|
|
93
97
|
"status":True
|
|
94
98
|
}
|
|
@@ -98,36 +102,43 @@ prototype_settings = {
|
|
|
98
102
|
"id": "Example CSV",
|
|
99
103
|
"label": "Example CSV",
|
|
100
104
|
"type":"csv",
|
|
101
|
-
"file":
|
|
105
|
+
"file":user_data_dir+'/nowfocus-timetracking-spreadsheet.csv',
|
|
102
106
|
"status":True
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
if os.path.isfile(
|
|
111
|
+
if os.path.isfile(settings_file):
|
|
112
|
+
with open(settings_file, "r") as file:
|
|
113
|
+
settings = json.load(file)
|
|
114
|
+
|
|
115
|
+
# Backward compatibility, check in old settings dir
|
|
116
|
+
elif os.path.isfile(user_settings_dir+"/user_settings.json"):
|
|
108
117
|
with open(user_settings_dir+"/user_settings.json", "r") as file:
|
|
109
|
-
|
|
118
|
+
settings = json.load(file)
|
|
110
119
|
else:
|
|
120
|
+
|
|
111
121
|
print("Setting up initial settings file")
|
|
112
|
-
|
|
122
|
+
is_first_load = True
|
|
123
|
+
settings = {}
|
|
113
124
|
|
|
114
125
|
|
|
115
126
|
user = {}
|
|
116
127
|
|
|
117
|
-
# Merge
|
|
128
|
+
# Merge settings with prototype settings
|
|
118
129
|
for key, val in prototype_settings.items():
|
|
119
130
|
|
|
120
|
-
if
|
|
131
|
+
if settings and key in connectors:
|
|
121
132
|
user[key] = {}
|
|
122
133
|
|
|
123
134
|
# merge and validate todolist and timetracker settings against connector prototypes
|
|
124
|
-
for c_key, c_val in
|
|
135
|
+
for c_key, c_val in settings[key].items():
|
|
125
136
|
|
|
126
137
|
if c_val['type'] in connectors[key]:
|
|
127
138
|
proto = copy.copy(connectors[key][c_val['type']])
|
|
128
139
|
user[key][c_key] = proto
|
|
129
140
|
else:
|
|
130
|
-
print('ERROR: no connector for type '+c_val['type']+' in
|
|
141
|
+
print('ERROR: no connector for type '+c_val['type']+' in settings_file','skipping that connector',c_val)
|
|
131
142
|
continue
|
|
132
143
|
|
|
133
144
|
# print('validate user_settings connector against prototype.','user',c_val,'proto',proto)
|
|
@@ -142,46 +153,28 @@ for key, val in prototype_settings.items():
|
|
|
142
153
|
else:
|
|
143
154
|
print("Adding missing: connection field "+p_field+")")
|
|
144
155
|
|
|
145
|
-
elif key in
|
|
146
|
-
user[key] =
|
|
156
|
+
elif key in settings and type(settings[key]) == type(val):
|
|
157
|
+
user[key] = settings[key]
|
|
147
158
|
else:
|
|
148
159
|
user[key] = val
|
|
149
160
|
|
|
150
161
|
# print(json.dumps(user, indent=4))
|
|
151
162
|
# print(json.dumps(connectors['todolists'], indent=4))
|
|
152
163
|
|
|
153
|
-
# update time_target format
|
|
154
|
-
for id, tt in user['time_targets']['lists'].items():
|
|
155
|
-
if 'within_value' not in tt:
|
|
156
|
-
print("Updating time target to new format ",tt)
|
|
157
|
-
tt['within_value'] = tt['num_days']
|
|
158
|
-
tt['within_unit'] = 'days'
|
|
159
|
-
print(tt)
|
|
160
|
-
if 'status' not in tt:
|
|
161
|
-
tt['status'] = True
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
for id, tt in user['time_targets']['tasks'].items():
|
|
165
|
-
tt
|
|
166
|
-
if 'within_value' not in tt:
|
|
167
|
-
print("Updating time target to new format ",tt)
|
|
168
|
-
tt['within_value'] = tt['num_days']
|
|
169
|
-
tt['within_unit'] = 'days'
|
|
170
|
-
print(tt)
|
|
171
|
-
if 'status' not in tt:
|
|
172
|
-
tt['status'] = True
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
with open(user_settings_dir+"/user_settings.json","w") as settings_file:
|
|
165
|
+
with open(settings_file,"w") as settings_file:
|
|
176
166
|
json.dump(user, settings_file)
|
|
177
167
|
|
|
178
|
-
|
|
179
168
|
todo_connectors = {}
|
|
180
169
|
timetracker_connectors = {}
|
|
181
170
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
171
|
+
try:
|
|
172
|
+
|
|
173
|
+
for id, todolist in user['todolists'].items():
|
|
174
|
+
todo_connectors[todolist['type']] = importlib.import_module('connectors.'+todolist['type'])
|
|
175
|
+
|
|
176
|
+
for id, timetracker in user['timetrackers'].items():
|
|
177
|
+
timetracker_connectors[timetracker['type']] = importlib.import_module('connectors.'+timetracker['type'])
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print("error loading connector")
|
|
180
|
+
print(e)
|
nowfocus/example-todo.txt
CHANGED
nowfocus/install.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
import json
|
|
3
|
+
import importlib
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import copy
|
|
7
|
+
import subprocess, sys
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
import gi
|
|
11
|
+
gi.require_version('Gtk', '3.0')
|
|
12
|
+
from gi.repository import Gtk, GLib, Gdk, Gio
|
|
13
|
+
|
|
14
|
+
# Set working dir to file location
|
|
15
|
+
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
16
|
+
|
|
17
|
+
# Add working dir to path
|
|
18
|
+
sys.path.append(os.path.dirname(__file__))
|
|
19
|
+
|
|
20
|
+
import conf
|
|
21
|
+
from utils import *
|
|
22
|
+
|
|
23
|
+
def run_first_load_actions():
|
|
24
|
+
db_init()
|
|
25
|
+
create_default_timetracking_csv()
|
|
26
|
+
create_default_todo()
|
|
27
|
+
copy_desktop_integration_files()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_default_timetracking_csv():
|
|
31
|
+
target_file = conf.user_data_dir+'/nowfocus-timetracking-spreadsheet.csv'
|
|
32
|
+
|
|
33
|
+
data = 'date,duration,project,task,start time'
|
|
34
|
+
|
|
35
|
+
if not os.path.isfile(target_file):
|
|
36
|
+
with open(target_file, 'w') as file:
|
|
37
|
+
file.writelines(data)
|
|
38
|
+
print("Created default timetracker at "+target_file)
|
|
39
|
+
else:
|
|
40
|
+
print("Default timetracker already exists at "+target_file)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_default_todo():
|
|
44
|
+
source_file = 'example-todo.txt'
|
|
45
|
+
target_file = conf.user_data_dir+'/nowfocus-todo.txt'
|
|
46
|
+
|
|
47
|
+
if not os.path.isfile(target_file):
|
|
48
|
+
with open(source_file, 'r') as file:
|
|
49
|
+
data = file.readlines()
|
|
50
|
+
|
|
51
|
+
with open(target_file, 'w') as file:
|
|
52
|
+
file.writelines(data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def db_init():
|
|
56
|
+
if not os.path.isfile(conf.db_file):
|
|
57
|
+
print('initializing database')
|
|
58
|
+
|
|
59
|
+
db_query("CREATE TABLE lists (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT DEFAULT '', data TEXT DEFAULT '{}');")
|
|
60
|
+
|
|
61
|
+
db_query("CREATE TABLE tasks (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT, data TEXT DEFAULT '{}');")
|
|
62
|
+
|
|
63
|
+
# , tags TEXT
|
|
64
|
+
|
|
65
|
+
db_query("CREATE TABLE sessions (start_time TEXT, duration INTEGER, task_id TEXT, parent_id TEXT, todolist TEXT, extended_label TEXT, notes TEXT, timetracker TEXT);")
|
|
66
|
+
|
|
67
|
+
db_query("CREATE TABLE system (field TEXT PRIMARY KEY NOT NULL, value TEXT);")
|
|
68
|
+
|
|
69
|
+
db_query("INSERT INTO system(field, value) VALUES('db_schema_version', '0.4')")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def copy_desktop_integration_files():
|
|
73
|
+
|
|
74
|
+
home = GLib.get_home_dir()
|
|
75
|
+
|
|
76
|
+
files = (
|
|
77
|
+
(home+'/.local/share/icons/hicolor/scalable/apps','nowfocus.svg'),
|
|
78
|
+
(home+'/.local/share/icons','nowfocus.png'),
|
|
79
|
+
(home+'/.local/share/applications/','nowfocus.desktop'),
|
|
80
|
+
(home+'/.config/autostart/','nowfocus.desktop')
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
for file in files:
|
|
85
|
+
try:
|
|
86
|
+
Path(file[0]).mkdir(parents=True, exist_ok=True)
|
|
87
|
+
shutil.copy("desktop-extras/"+file[1],file[0])
|
|
88
|
+
print("Copied "+file[1]+" to "+file[0])
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print("Error Copying "+file[1]+" to "+file[0],e)
|
|
92
|
+
|
nowfocus/session_options.py
CHANGED
|
@@ -139,7 +139,7 @@ class SessionOptionsDialog(Gtk.Dialog):
|
|
|
139
139
|
|
|
140
140
|
print(' start_time + ', session['start_time'])
|
|
141
141
|
|
|
142
|
-
# Or change session[duration] value
|
|
142
|
+
# Or change session[duration] value to reflect dropped session
|
|
143
143
|
if response == 3:
|
|
144
144
|
# Create custom end time
|
|
145
145
|
custom_end_time = session['start_time'] + timedelta(seconds = (self.duration_input.get_value_as_int() * 60))
|
nowfocus/task_window.py
CHANGED
|
@@ -117,15 +117,15 @@ class TaskWindow(Gtk.Window):
|
|
|
117
117
|
|
|
118
118
|
if passed_data:
|
|
119
119
|
dbg('taskwindow passed_data',passed_data,s='taskwindow')
|
|
120
|
-
if '
|
|
120
|
+
if 'user_inactive_time' in passed_data:
|
|
121
121
|
|
|
122
|
-
last_active_time = datetime.now() - timedelta(seconds=passed_data['
|
|
122
|
+
last_active_time = datetime.now() - timedelta(seconds=passed_data['user_inactive_time'])
|
|
123
123
|
|
|
124
124
|
last_active_str = time.strftime('%H:%M', last_active_time.timetuple())
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
self.header.add(
|
|
126
|
+
user_inactive_label = Gtk.Label()
|
|
127
|
+
user_inactive_label.set_markup("<b>Inactive Since "+" "+str(last_active_str) +"</b>")
|
|
128
|
+
self.header.add(user_inactive_label)
|
|
129
129
|
|
|
130
130
|
pause_then_button = Gtk.Button(label="Finish Then")
|
|
131
131
|
|
nowfocus/utils.py
CHANGED
|
@@ -29,8 +29,6 @@ notify.init(conf.app_name)
|
|
|
29
29
|
|
|
30
30
|
lists = {}
|
|
31
31
|
|
|
32
|
-
db_file = conf.user_data_dir+"/data.db"
|
|
33
|
-
|
|
34
32
|
|
|
35
33
|
def dbg(*data, **kwargs):
|
|
36
34
|
''' Any number of positional args then kwargs
|
|
@@ -318,7 +316,7 @@ def first(i, default = None):
|
|
|
318
316
|
def save_user_settings():
|
|
319
317
|
print("Save updated user_settings")
|
|
320
318
|
dbg(conf.user)
|
|
321
|
-
with open(conf.
|
|
319
|
+
with open(conf.settings_file,"w") as settings_file:
|
|
322
320
|
json.dump(conf.user, settings_file)
|
|
323
321
|
|
|
324
322
|
|
|
@@ -326,8 +324,7 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
|
326
324
|
'''error_handling: 2 = raise error, 1 = notify error, 0 = ignore errors '''
|
|
327
325
|
# print('db_query parameters type: ',type(parameters))
|
|
328
326
|
# print('first(parameters)',first(parameters))
|
|
329
|
-
|
|
330
|
-
c = sqlite3.connect(db_file)
|
|
327
|
+
c = sqlite3.connect(conf.db_file)
|
|
331
328
|
c.row_factory = sqlite3.Row
|
|
332
329
|
try:
|
|
333
330
|
with c:
|
|
@@ -368,23 +365,6 @@ def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
|
368
365
|
return []
|
|
369
366
|
|
|
370
367
|
|
|
371
|
-
def db_init():
|
|
372
|
-
if not os.path.isfile(db_file):
|
|
373
|
-
print('initializing database')
|
|
374
|
-
|
|
375
|
-
db_query("CREATE TABLE lists (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT DEFAULT '', data TEXT DEFAULT '{}');")
|
|
376
|
-
|
|
377
|
-
db_query("CREATE TABLE tasks (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT, data TEXT DEFAULT '{}');")
|
|
378
|
-
|
|
379
|
-
# , tags TEXT
|
|
380
|
-
|
|
381
|
-
db_query("CREATE TABLE sessions (start_time TEXT, duration INTEGER, task_id TEXT, parent_id TEXT, todolist TEXT, extended_label TEXT, notes TEXT, timetracker TEXT);")
|
|
382
|
-
|
|
383
|
-
db_query("CREATE TABLE system (field TEXT PRIMARY KEY NOT NULL, value TEXT);")
|
|
384
|
-
|
|
385
|
-
db_query("INSERT INTO system(field, value) VALUES('db_schema_version', '0.4')")
|
|
386
|
-
|
|
387
|
-
|
|
388
368
|
def db_schema_update():
|
|
389
369
|
|
|
390
370
|
try:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
# update time_target format
|
|
3
|
+
for id, tt in user['time_targets']['lists'].items():
|
|
4
|
+
if 'within_value' not in tt:
|
|
5
|
+
print("Updating time target to new format ",tt)
|
|
6
|
+
tt['within_value'] = tt['num_days']
|
|
7
|
+
tt['within_unit'] = 'days'
|
|
8
|
+
print(tt)
|
|
9
|
+
if 'status' not in tt:
|
|
10
|
+
tt['status'] = True
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
for id, tt in user['time_targets']['tasks'].items():
|
|
14
|
+
if 'within_value' not in tt:
|
|
15
|
+
print("Updating time target to new format ",tt)
|
|
16
|
+
tt['within_value'] = tt['num_days']
|
|
17
|
+
tt['within_unit'] = 'days'
|
|
18
|
+
print(tt)
|
|
19
|
+
if 'status' not in tt:
|
|
20
|
+
tt['status'] = True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nowfocus
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: nowfocus: the open source task-tracking self-control panel.
|
|
5
5
|
Author: AltruistEnterprises
|
|
6
6
|
Project-URL: Homepage, https://www.nowfocus.org
|
|
@@ -112,13 +112,6 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
|
|
|
112
112
|
# Install nowfocus
|
|
113
113
|
pipx install nowfocus
|
|
114
114
|
|
|
115
|
-
# Enter application directory
|
|
116
|
-
cd ~/.local/share/pipx/venvs/nowfocus/lib/python3.12/site-packages/nowfocus/desktop-extras
|
|
117
|
-
|
|
118
|
-
# Copy desktop file and icon to /usr/share
|
|
119
|
-
sudo cp nowfocus.desktop /usr/share/applications/nowfocus.desktop
|
|
120
|
-
sudo cp nowfocus.svg /usr/share/icons/hicolor/scalable/apps/nowfocus.svg
|
|
121
|
-
|
|
122
115
|
# and now Focus!
|
|
123
116
|
nowfocus
|
|
124
117
|
|
|
@@ -130,7 +123,6 @@ nowfocus is a clean, keyboard-driven time management dashboard that flexibly con
|
|
|
130
123
|
```
|
|
131
124
|
|
|
132
125
|
|
|
133
|
-
|
|
134
126
|
3. Add the following command to your startup applications:
|
|
135
127
|
```
|
|
136
128
|
nowfocus --force
|
|
@@ -178,7 +170,7 @@ Open nowfocus **Settings** from the indicator menu or tasks window and connect y
|
|
|
178
170
|
|
|
179
171
|
### Install From Source
|
|
180
172
|
```
|
|
181
|
-
git clone https://codeberg.org/AltruistEnterprises/nowfocus
|
|
173
|
+
git clone https://codeberg.org/AltruistEnterprises/nowfocus.git
|
|
182
174
|
cd nowfocus
|
|
183
175
|
python3 -m venv .venv/nowfocus-build
|
|
184
176
|
source .venv/nowfocus-build/bin/activate
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
nowfocus/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
-
nowfocus/__main__.py,sha256=
|
|
3
|
-
nowfocus/conf.py,sha256=
|
|
4
|
-
nowfocus/example-todo.txt,sha256=
|
|
2
|
+
nowfocus/__main__.py,sha256=hL6A98aanOnczCs8VpeRHhSrM3CanhXujmCqikBZ3B0,33007
|
|
3
|
+
nowfocus/conf.py,sha256=nHJY7EqZfZDE86Y7NZ21EYcaJ3zH75Zsq0jlZySUClc,6711
|
|
4
|
+
nowfocus/example-todo.txt,sha256=o-ZRNiTlSGFbTK9jpdDIi07qHBrpvP0JhBZSk0VlpQU,246
|
|
5
|
+
nowfocus/install.py,sha256=hVgFQmGUTv6VYCpBGr6W92XmUTpJCyxPCAUGaPO3RXY,3012
|
|
5
6
|
nowfocus/new_task_dialog.py,sha256=GG49tOAwXiUKAHeaKnCG8Q3SZlL5pJijRZEcSjMwQng,4350
|
|
6
7
|
nowfocus/session_edit_dialog.py,sha256=V2QWSdNaxsQHRcG28CJBQM2sa45m5RNcu_suQF26mkM,6912
|
|
7
|
-
nowfocus/session_options.py,sha256=
|
|
8
|
+
nowfocus/session_options.py,sha256=mOnyEM3-usKgVvBhESY0TNBcy4FTG-AHP6ETMgB1fQ4,5071
|
|
8
9
|
nowfocus/sessions.csv,sha256=kYpr06yQg_J86NQ4AiYw4RnQchcw3ouPKVYa1lYDUNo,39
|
|
9
10
|
nowfocus/settings.py,sha256=Ufabuqd90tTDlzXB68jsbDz7233CEMsjnmPzaTSEHLI,35150
|
|
10
11
|
nowfocus/styles.css,sha256=PG1SrLkwSSay8M2VKeRcE0UdK54ndsEDFnRLRkmP-9M,510
|
|
11
|
-
nowfocus/task_window.py,sha256=
|
|
12
|
+
nowfocus/task_window.py,sha256=4vDH5Vxse9Y9sxMGoITbNtq2Qhh4ZhnVQHnyt0K-M3I,28417
|
|
12
13
|
nowfocus/user_idle_time.py,sha256=kPZ_bhoBdVMIBJXn2602FUfS4r-u8FnTq5Ze9D5QfHE,2363
|
|
13
|
-
nowfocus/utils.py,sha256=
|
|
14
|
+
nowfocus/utils.py,sha256=YdxsQMAE9jRpfLW8q-KqB9QMtQbiDAqh80srbQvq-Cg,48692
|
|
15
|
+
nowfocus/version_migrator.py,sha256=q8T1C8-DLOwUQUM5IPcMjPbVbsLTO4VsqADlAAXd9gw,628
|
|
14
16
|
nowfocus/connectors/activitywatch.py,sha256=QbkOmjIOiVwccWc2xhhePd0Abww5vEiVpCNjeqOyYGg,921
|
|
15
17
|
nowfocus/connectors/caldav.py,sha256=PeM_9yJC8W17L8Y5AyS75o6GfzTrPoMYKIvetND8T78,5089
|
|
16
18
|
nowfocus/connectors/csv.py,sha256=FwMpHM5lPIT90HKBCQUncpaW7zqFjlHjMwKR0-XWg-4,821
|
|
@@ -22,7 +24,7 @@ nowfocus/connectors/todotxt.py,sha256=QCZjbIhY4Lm37YD0GsKJQUqbj7s3eYmZGgRwUCndZ5
|
|
|
22
24
|
nowfocus/connectors/trello.py,sha256=VqwnvHGXXcljmdf6kRZcE6sfeBQYhped_KVBEBOzWXM,6072
|
|
23
25
|
nowfocus/connectors/txt.py,sha256=iskJsw3dZnI4bIeEDtZCY-aQfKRKtoGATEJ0k13npxI,8125
|
|
24
26
|
nowfocus/connectors/vikunja.py,sha256=Lg1lgF3C5B606CUf3-U8pVtPt7Cp6IKkT5wFN779V8w,10799
|
|
25
|
-
nowfocus/desktop-extras/nowfocus.desktop,sha256=
|
|
27
|
+
nowfocus/desktop-extras/nowfocus.desktop,sha256=PyCDMgm-aicRJkG2p1czLjBBp3BJz8vfLG7TjgJjZfY,297
|
|
26
28
|
nowfocus/desktop-extras/nowfocus.png,sha256=P5rn6-0EAJa2WXf4SJoaNtLRUfiV3LdsOroPKsR6GfA,15148
|
|
27
29
|
nowfocus/desktop-extras/nowfocus.svg,sha256=nps7naZzuhWWuKzQbpvxr9wLyzjmzMPzNHSBQMVetOo,2137
|
|
28
30
|
nowfocus/icon/cancel.png,sha256=Hy9A7KdO13MsXguIgoTI276mECN07rzd6hGbIqxSl7c,506
|
|
@@ -50,9 +52,9 @@ nowfocus/icon/settings.svg,sha256=fgkGJouPPtZLxZn2nr_5pEp9MdhRSRaW9mtdxhJHDuQ,39
|
|
|
50
52
|
nowfocus/sound/bell-xylophone-g.mp3,sha256=1OBcRWvD87AGNcq1uZFR8HqG0nanJykImERfVDVxHD4,53891
|
|
51
53
|
nowfocus/sound/dinner-bell.mp3,sha256=hjjO0xqA4uXpYw9KLwwlBnrVfRhVq1K5OXzwlMXhRn4,113620
|
|
52
54
|
nowfocus/sound/xylophone-chord.mp3,sha256=gwgBSqhMt5PMzT5N03Z6TvDgipQZfnkEz_o81Rq5Z1U,131806
|
|
53
|
-
nowfocus-0.4.
|
|
54
|
-
nowfocus-0.4.
|
|
55
|
-
nowfocus-0.4.
|
|
56
|
-
nowfocus-0.4.
|
|
57
|
-
nowfocus-0.4.
|
|
58
|
-
nowfocus-0.4.
|
|
55
|
+
nowfocus-0.4.4.dist-info/licenses/LICENSE,sha256=fSJzoHs1EOCwEd7FIyokFeGEma7NKmTVEdHkCr5OIV4,35127
|
|
56
|
+
nowfocus-0.4.4.dist-info/METADATA,sha256=f9AbqCZ3JsnqweUnlecxwAy6-Dj9B9qJCTNe51uaVQE,6805
|
|
57
|
+
nowfocus-0.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
58
|
+
nowfocus-0.4.4.dist-info/entry_points.txt,sha256=RbYY19-irSoNVglNeNnL9D36cHft7aKsaEGEYoSH3pA,51
|
|
59
|
+
nowfocus-0.4.4.dist-info/top_level.txt,sha256=3uLd9BwmfarZwqVUxkSJuVwJ8qHzjThte8rt_UYG7tE,9
|
|
60
|
+
nowfocus-0.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|