summaryrefslogtreecommitdiff
path: root/overlays
diff options
context:
space:
mode:
Diffstat (limited to 'overlays')
-rw-r--r--overlays/batman-adv.nix15
-rw-r--r--overlays/keepassxc/database-open-dialog.patch129
-rw-r--r--overlays/keepassxc/default.nix8
-rw-r--r--overlays/mako.nix5
-rw-r--r--overlays/matrix-synapse.nix4
-rw-r--r--overlays/swayosd/default.nix30
-rw-r--r--overlays/swayosd/exponential.patch57
-rwxr-xr-xoverlays/worktime/worktime/__main__.py190
8 files changed, 370 insertions, 68 deletions
diff --git a/overlays/batman-adv.nix b/overlays/batman-adv.nix
deleted file mode 100644
index cce7dc4f..00000000
--- a/overlays/batman-adv.nix
+++ /dev/null
@@ -1,15 +0,0 @@
1{ final, prev, sources, ... }: {
2 linuxPackages_latest = prev.linuxPackages_latest.extend (self: super: {
3 batman_adv = super.batman_adv.overrideAttrs (oldAttrs: {
4 version = "${sources.batman-adv.version}-${self.kernel.version}";
5 inherit (sources.batman-adv) src;
6 });
7 });
8
9 linuxPackages_6_2 = prev.linuxPackages_6_2.extend (self: super: {
10 batman_adv = super.batman_adv.overrideAttrs (oldAttrs: {
11 version = "${sources.batman-adv.version}-${self.kernel.version}";
12 inherit (sources.batman-adv) src;
13 });
14 });
15}
diff --git a/overlays/keepassxc/database-open-dialog.patch b/overlays/keepassxc/database-open-dialog.patch
new file mode 100644
index 00000000..dff84846
--- /dev/null
+++ b/overlays/keepassxc/database-open-dialog.patch
@@ -0,0 +1,129 @@
1diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
2index 60412b5a..c0497d91 100644
3--- a/src/browser/BrowserService.cpp
4+++ b/src/browser/BrowserService.cpp
5@@ -249,7 +249,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
6 return result;
7 }
8
9- auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
10+ auto dialogResult = MessageBox::warning(nullptr,
11 tr("KeePassXC - Create a new group"),
12 tr("A request for creating a new group \"%1\" has been received.\n"
13 "Do you want to create this group?\n")
14@@ -422,7 +422,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& entriesToConfirm,
15
16 m_dialogActive = true;
17 updateWindowState();
18- BrowserAccessControlDialog accessControlDialog(m_currentDatabaseWidget);
19+ BrowserAccessControlDialog accessControlDialog{};
20
21 connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &accessControlDialog, SLOT(reject()));
22
23@@ -512,7 +512,7 @@ QString BrowserService::storeKey(const QString& key)
24 QString id;
25
26 do {
27- QInputDialog keyDialog(m_currentDatabaseWidget);
28+ QInputDialog keyDialog{};
29 connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &keyDialog, SLOT(reject()));
30 keyDialog.setWindowTitle(tr("KeePassXC - New key association request"));
31 keyDialog.setLabelText(tr("You have received an association request for the following database:\n%1\n\n"
32@@ -535,7 +535,7 @@ QString BrowserService::storeKey(const QString& key)
33
34 contains = db->metadata()->customData()->contains(CustomData::BrowserKeyPrefix + id);
35 if (contains) {
36- dialogResult = MessageBox::warning(m_currentDatabaseWidget,
37+ dialogResult = MessageBox::warning(nullptr,
38 tr("KeePassXC - Overwrite existing key?"),
39 tr("A shared encryption key with the name \"%1\" "
40 "already exists.\nDo you want to overwrite it?")
41@@ -595,7 +595,7 @@ QJsonObject BrowserService::showPasskeysRegisterPrompt(const QJsonObject& public
42 const auto existingEntries = getPasskeyEntriesWithUserHandle(rpId, userId, keyList);
43
44 raiseWindow();
45- BrowserPasskeysConfirmationDialog confirmDialog(m_currentDatabaseWidget);
46+ BrowserPasskeysConfirmationDialog confirmDialog{};
47 confirmDialog.registerCredential(username, rpId, existingEntries, timeout);
48
49 auto dialogResult = confirmDialog.exec();
50@@ -612,7 +612,7 @@ QJsonObject BrowserService::showPasskeysRegisterPrompt(const QJsonObject& public
51 // If no entry is selected, show the import dialog for manual entry selection
52 auto selectedEntry = confirmDialog.getSelectedEntry();
53 if (!selectedEntry) {
54- PasskeyImporter passkeyImporter(m_currentDatabaseWidget);
55+ PasskeyImporter passkeyImporter{};
56 const auto result = passkeyImporter.showImportDialog(db,
57 nullptr,
58 origin,
59@@ -683,7 +683,7 @@ QJsonObject BrowserService::showPasskeysAuthenticationPrompt(const QJsonObject&
60 const auto timeout = publicKeyOptions["timeout"].toInt();
61
62 raiseWindow();
63- BrowserPasskeysConfirmationDialog confirmDialog(m_currentDatabaseWidget);
64+ BrowserPasskeysConfirmationDialog confirmDialog{};
65 confirmDialog.authenticateCredential(entries, rpId, timeout);
66 auto dialogResult = confirmDialog.exec();
67 if (dialogResult == QDialog::Accepted) {
68@@ -760,7 +760,7 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
69
70 // Ask confirmation if entry already contains a Passkey
71 if (entry->hasPasskey()) {
72- if (MessageBox::question(m_currentDatabaseWidget,
73+ if (MessageBox::question(nullptr,
74 tr("KeePassXC - Update passkey"),
75 tr("Entry already has a passkey.\nDo you want to overwrite the passkey in %1 - %2?")
76 .arg(entry->title(), passkeyUtils()->getUsernameFromEntry(entry)),
77@@ -873,7 +873,7 @@ bool BrowserService::updateEntry(const EntryParameters& entryParameters, const Q
78 MessageBox::Button dialogResult = MessageBox::No;
79 if (!browserSettings()->alwaysAllowUpdate()) {
80 raiseWindow();
81- dialogResult = MessageBox::question(m_currentDatabaseWidget,
82+ dialogResult = MessageBox::question(nullptr,
83 tr("KeePassXC - Update Entry"),
84 tr("Do you want to update the information in %1 - %2?")
85 .arg(QUrl(entryParameters.siteUrl).host(), username),
86@@ -909,7 +909,7 @@ bool BrowserService::deleteEntry(const QString& uuid)
87 return false;
88 }
89
90- auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
91+ auto dialogResult = MessageBox::warning(nullptr,
92 tr("KeePassXC - Delete entry"),
93 tr("A request for deleting entry \"%1\" has been received.\n"
94 "Do you want to delete the entry?\n")
95@@ -1536,7 +1536,7 @@ QSharedPointer<Database> BrowserService::selectedDatabase()
96 }
97 }
98
99- BrowserEntrySaveDialog browserEntrySaveDialog(m_currentDatabaseWidget);
100+ BrowserEntrySaveDialog browserEntrySaveDialog{};
101 int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_currentDatabaseWidget);
102 if (openDatabaseCount > 1) {
103 int res = browserEntrySaveDialog.exec();
104diff --git a/src/fdosecrets/objects/Prompt.cpp b/src/fdosecrets/objects/Prompt.cpp
105index e89cd499..347c98b8 100644
106--- a/src/fdosecrets/objects/Prompt.cpp
107+++ b/src/fdosecrets/objects/Prompt.cpp
108@@ -313,7 +313,7 @@ namespace FdoSecrets
109 if (!entries.isEmpty()) {
110 QString app = tr("%1 (PID: %2)").arg(client->name()).arg(client->pid());
111 auto ac = new AccessControlDialog(
112- findWindow(m_windowId), entries, app, client->processInfo(), AuthOption::Remember);
113+ nullptr, entries, app, client->processInfo(), AuthOption::Remember);
114 connect(ac, &AccessControlDialog::finished, this, &UnlockPrompt::itemUnlockFinished);
115 connect(ac, &AccessControlDialog::finished, ac, &AccessControlDialog::deleteLater);
116 ac->open();
117diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
118index 805d4eab..4836199e 100644
119--- a/src/gui/DatabaseTabWidget.cpp
120+++ b/src/gui/DatabaseTabWidget.cpp
121@@ -41,7 +41,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
122 : QTabWidget(parent)
123 , m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
124 , m_dbWidgetPendingLock(nullptr)
125- , m_databaseOpenDialog(new DatabaseOpenDialog(this))
126+ , m_databaseOpenDialog(new DatabaseOpenDialog())
127 , m_databaseOpenInProgress(false)
128 {
129 auto* tabBar = new QTabBar(this);
diff --git a/overlays/keepassxc/default.nix b/overlays/keepassxc/default.nix
new file mode 100644
index 00000000..46b3a459
--- /dev/null
+++ b/overlays/keepassxc/default.nix
@@ -0,0 +1,8 @@
1{ final, prev, ... }:
2{
3 keepassxc = prev.keepassxc.overrideAttrs (oldAttrs: {
4 patches = (oldAttrs.patches or []) ++ prev.lib.optional (prev.lib.versionAtLeast oldAttrs.version "2.7.9") [
5 ./database-open-dialog.patch
6 ];
7 });
8}
diff --git a/overlays/mako.nix b/overlays/mako.nix
new file mode 100644
index 00000000..1c1464fb
--- /dev/null
+++ b/overlays/mako.nix
@@ -0,0 +1,5 @@
1{ final, prev, sources, ... }: {
2 mako = prev.mako.overrideAttrs (oldAttrs: {
3 inherit (sources.mako) version src;
4 });
5}
diff --git a/overlays/matrix-synapse.nix b/overlays/matrix-synapse.nix
deleted file mode 100644
index 59b2c6da..00000000
--- a/overlays/matrix-synapse.nix
+++ /dev/null
@@ -1,4 +0,0 @@
1{ final, prev, ... }:
2{
3 matrix-synapse-unwrapped = prev.matrix-synapse-unwrapped.overridePythonAttrs { doCheck = false; };
4}
diff --git a/overlays/swayosd/default.nix b/overlays/swayosd/default.nix
new file mode 100644
index 00000000..28c8f1b9
--- /dev/null
+++ b/overlays/swayosd/default.nix
@@ -0,0 +1,30 @@
1{ final, prev, sources, ... }: {
2 swayosd = prev.swayosd.overrideAttrs (oldAttrs: rec {
3 inherit (sources.swayosd) version src;
4 cargoDeps = prev.rustPlatform.fetchCargoTarball {
5 inherit (oldAttrs) pname;
6 inherit version src;
7 hash = "sha256-Anrk8p76HKZcNavYdi9l1oYahduLrb7Lf7knQK7Hy5E=";
8 };
9 nativeBuildInputs = with final; [
10 wrapGAppsHook4
11 pkg-config
12 meson
13 rustc
14 cargo
15 ninja
16 rustPlatform.cargoSetupHook
17 ];
18 buildInputs = with final; [
19 gtk4-layer-shell
20 libevdev
21 libinput
22 libpulseaudio
23 udev
24 sassc
25 ];
26 patches = (oldAttrs.patches or []) ++ [
27 ./exponential.patch
28 ];
29 });
30}
diff --git a/overlays/swayosd/exponential.patch b/overlays/swayosd/exponential.patch
new file mode 100644
index 00000000..eb90d739
--- /dev/null
+++ b/overlays/swayosd/exponential.patch
@@ -0,0 +1,57 @@
1diff --git a/src/brightness_backend/brightnessctl.rs b/src/brightness_backend/brightnessctl.rs
2index ccb0e11..740fdb6 100644
3--- a/src/brightness_backend/brightnessctl.rs
4+++ b/src/brightness_backend/brightnessctl.rs
5@@ -107,10 +107,21 @@ impl VirtualDevice {
6 }
7 }
8
9- fn set_percent(&mut self, mut val: u32) -> anyhow::Result<()> {
10- val = val.clamp(0, 100);
11- self.current = self.max.map(|max| val * max / 100);
12- let _: String = self.run(("set", &*format!("{val}%")))?;
13+ fn val_to_percent(&mut self, val: u32) -> u32 {
14+ return ((val as f64 / self.get_max() as f64).powf(0.25) * 100_f64).round() as u32;
15+ }
16+ fn percent_to_val(&mut self, perc: u32) -> u32 {
17+ return ((perc as f64 / 100_f64).powf(4_f64) * self.get_max() as f64).round() as u32;
18+ }
19+
20+ fn set_percent(&mut self, val: u32) -> anyhow::Result<()> {
21+ let new = self.percent_to_val(val);
22+ self.set_val(new)
23+ }
24+ fn set_val(&mut self, val: u32) -> anyhow::Result<()> {
25+ let curr = val.clamp(0, self.get_max());
26+ self.current = Some(curr);
27+ let _: String = self.run(("set", &*format!("{curr}")))?;
28 Ok(())
29 }
30 }
31@@ -134,20 +145,18 @@ impl BrightnessBackend for BrightnessCtl {
32
33 fn lower(&mut self, by: u32) -> anyhow::Result<()> {
34 let curr = self.get_current();
35- let max = self.get_max();
36-
37- let curr = curr * 100 / max;
38+ let mut new = self.device.val_to_percent(curr).saturating_sub(by);
39+ new = self.device.percent_to_val(new).min(curr.saturating_sub(1));
40
41- self.device.set_percent(curr.saturating_sub(by))
42+ self.device.set_val(new)
43 }
44
45 fn raise(&mut self, by: u32) -> anyhow::Result<()> {
46 let curr = self.get_current();
47- let max = self.get_max();
48-
49- let curr = curr * 100 / max;
50+ let mut new = self.device.val_to_percent(curr) + by;
51+ new = self.device.percent_to_val(new).max(curr + 1);
52
53- self.device.set_percent(curr + by)
54+ self.device.set_val(new)
55 }
56
57 fn set(&mut self, val: u32) -> anyhow::Result<()> {
diff --git a/overlays/worktime/worktime/__main__.py b/overlays/worktime/worktime/__main__.py
index 362c8da4..4eee5dc2 100755
--- a/overlays/worktime/worktime/__main__.py
+++ b/overlays/worktime/worktime/__main__.py
@@ -23,7 +23,7 @@ import argparse
23from copy import deepcopy 23from copy import deepcopy
24 24
25import sys 25import sys
26from sys import stderr 26from sys import stderr, stdout
27 27
28from tabulate import tabulate 28from tabulate import tabulate
29 29
@@ -38,6 +38,7 @@ from collections import defaultdict
38 38
39import jsonpickle 39import jsonpickle
40from hashlib import blake2s 40from hashlib import blake2s
41import json
41 42
42class TogglAPISection(Enum): 43class TogglAPISection(Enum):
43 TOGGL = '/api/v9' 44 TOGGL = '/api/v9'
@@ -223,6 +224,7 @@ class Worktime(object):
223 leave_budget = dict() 224 leave_budget = dict()
224 time_per_day = None 225 time_per_day = None
225 workdays = None 226 workdays = None
227 pull_forward = dict()
226 228
227 @staticmethod 229 @staticmethod
228 @cache 230 @cache
@@ -390,7 +392,7 @@ class Worktime(object):
390 if e.errno != 2: 392 if e.errno != 2:
391 raise e 393 raise e
392 394
393 pull_forward = dict() 395 self.time_per_day = lambda day: timedelta(hours = hours_per_week(day)) / len(self.workdays) - (holidays[day] if day in holidays else timedelta())
394 396
395 start_day = self.start_date.date() 397 start_day = self.start_date.date()
396 end_day = self.end_date.date() 398 end_day = self.end_date.date()
@@ -418,21 +420,19 @@ class Worktime(object):
418 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break 420 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
419 else: 421 else:
420 if d >= self.end_date.date(): 422 if d >= self.end_date.date():
421 pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta())) 423 self.pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta()))
422 except IOError as e: 424 except IOError as e:
423 if e.errno != 2: 425 if e.errno != 2:
424 raise e 426 raise e
425 427
426 self.days_to_work = dict() 428 self.days_to_work = dict()
427 429
428 if pull_forward: 430 if self.pull_forward:
429 end_day = max(end_day, max(list(pull_forward))) 431 end_day = max(end_day, max(list(self.pull_forward)))
430 432
431 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: 433 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
432 if day.isoweekday() in self.workdays: 434 if day.isoweekday() in self.workdays:
433 time_to_work = self.time_per_day(day) 435 time_to_work = self.time_per_day(day)
434 if day in holidays.keys():
435 time_to_work -= holidays[day]
436 if time_to_work > timedelta(): 436 if time_to_work > timedelta():
437 self.days_to_work[day] = time_to_work 437 self.days_to_work[day] = time_to_work
438 438
@@ -470,17 +470,17 @@ class Worktime(object):
470 self.extra_days_to_work[self.now.date()] = timedelta() 470 self.extra_days_to_work[self.now.date()] = timedelta()
471 471
472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta()) 472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta())
473 for day in [d for d in list(pull_forward) if d > self.end_date.date()]: 473 for day in [d for d in list(self.pull_forward) if d > self.end_date.date()]:
474 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 474 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
476 days_forward = days_forward.union(extra_days_forward) 476 days_forward = days_forward.union(extra_days_forward)
477 477
478 extra_day_time_left = timedelta() 478 extra_day_time_left = timedelta()
479 for extra_day in extra_days_forward: 479 for extra_day in extra_days_forward:
480 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 480 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
481 extra_day_time_left += day_time 481 extra_day_time_left += day_time
482 extra_day_time = min(extra_day_time_left, pull_forward[day]) 482 extra_day_time = min(extra_day_time_left, self.pull_forward[day])
483 time_forward = pull_forward[day] - extra_day_time 483 time_forward = self.pull_forward[day] - extra_day_time
484 if extra_day_time_left > timedelta(): 484 if extra_day_time_left > timedelta():
485 for extra_day in extra_days_forward: 485 for extra_day in extra_days_forward:
486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
@@ -518,7 +518,14 @@ def format_days(worktime, days, date_format=None):
518 return ', '.join(map(lambda group: ','.join(map(format_group, group)), groups)) 518 return ', '.join(map(lambda group: ','.join(map(format_group, group)), groups))
519 519
520 520
521def worktime(**args): 521def tooltip_timedelta(td):
522 if td < timedelta(seconds = 0):
523 return "-" + tooltip_timedelta(-td)
524 mm, ss = divmod(td.total_seconds(), 60)
525 hh, mm = divmod(mm, 60)
526 return "%d:%02d:%02d" % (hh, mm, ss)
527
528def worktime(pull_forward_cutoff, waybar, **args):
522 worktime = Worktime(**args) 529 worktime = Worktime(**args)
523 530
524 def format_worktime(worktime): 531 def format_worktime(worktime):
@@ -557,24 +564,41 @@ def worktime(**args):
557 return f"{indicator}{difference_string}" 564 return f"{indicator}{difference_string}"
558 else: 565 else:
559 difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1)) 566 difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1))
560 if worktime.now_is_workday: 567 return difference_string
561 return difference_string 568
562 else: 569 out_class = "running" if worktime.running_entry else "stopped"
563 return f"({difference_string})" 570 difference = worktime.time_to_work - worktime.time_worked
564 571 if worktime.running_entry and -min(timedelta(milliseconds=0), difference) > sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0)) or not worktime.running_entry and max(timedelta(milliseconds=0), difference) > worktime.time_per_day(worktime.now.date()) and worktime.now_is_workday:
565 if worktime.time_pulled_forward >= timedelta(minutes = 15): 572 out_class = "over"
573 pull_forward_sum = sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))
574 if pull_forward_sum >= min(pull_forward_cutoff, timedelta(seconds = 1)):
566 worktime_no_pulled_forward = deepcopy(worktime) 575 worktime_no_pulled_forward = deepcopy(worktime)
567 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward 576 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
568 worktime_no_pulled_forward.time_pulled_forward = timedelta() 577 worktime_no_pulled_forward.time_pulled_forward = timedelta()
578 worktime_no_pulled_forward.pull_forward = dict()
579 worktime.time_to_work += pull_forward_sum
569 580
570 difference_string = format_worktime(worktime)
571 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward) 581 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward)
572 582
573 print(f"{difference_string_no_pulled_forward}…{difference_string}") 583 tooltip = tooltip_timedelta(worktime_no_pulled_forward.time_to_work - worktime_no_pulled_forward.time_worked) + "…" + tooltip_timedelta(difference + pull_forward_sum)
584 if pull_forward_sum >= pull_forward_cutoff:
585 out_text = f"{difference_string_no_pulled_forward}…{format_worktime(worktime)}"
586 else:
587 out_text = format_worktime(worktime)
588 else:
589 tooltip = tooltip_timedelta(difference)
590 out_text = format_worktime(worktime)
591
592 if waybar:
593 json.dump({"text": out_text, "class": out_class, "tooltip": tooltip}, stdout)
574 else: 594 else:
575 print(format_worktime(worktime)) 595 print(out_text)
576 596
577def time_worked(now, **args): 597def pull_forward(**args):
598 worktime = Worktime(**args)
599 print(tooltip_timedelta(sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))))
600
601def time_worked(now, waybar, **args):
578 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 602 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
579 if now.time() == time(): 603 if now.time() == time():
580 now = now + timedelta(days = 1) 604 now = now + timedelta(days = 1)
@@ -584,33 +608,62 @@ def time_worked(now, **args):
584 608
585 worked = now.time_worked - then.time_worked 609 worked = now.time_worked - then.time_worked
586 610
611 out_text = None
612 out_class = "running" if now.running_entry else "stopped"
613 tooltip = tooltip_timedelta(worked)
614 target_time = max(then.time_per_day(then.now.date()), now.time_per_day(now.now.date())) if then.time_per_day(then.now.date()) and now.time_per_day(now.now.date()) else (then.time_per_day(then.now.date()) if then.time_per_day(then.now.date()) else now.time_per_day(now.now.date()));
615 difference = target_time - worked
616 difference_pull_forward = difference + now.time_pulled_forward
617 if now.running_entry and difference_pull_forward < timedelta(seconds=0):
618 out_class = "over"
587 if args['do_round']: 619 if args['do_round']:
588 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5)) 620 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5))
589 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60) 621 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60)
590 sign = '' if total_minutes_difference >= 0 else '-' 622 sign = '' if total_minutes_difference >= 0 else '-'
591
592 difference_string = f"{sign}"
593 if hours_difference != 0:
594 difference_string += f"{hours_difference}h"
595 if hours_difference == 0 or minutes_difference != 0:
596 difference_string += f"{minutes_difference}m"
597
598 clockout_time = None
599 clockout_difference = None
600 if then.now_is_workday or now.now_is_workday:
601 target_time = max(then.time_per_day(then.now.date()), now.time_per_day(now.now.date())) if then.time_per_day(then.now.date()) and now.time_per_day(now.now.date()) else (then.time_per_day(then.now.date()) if then.time_per_day(then.now.date()) else now.time_per_day(now.now.date()));
602 difference = target_time - worked
603 clockout_difference = 5 * ceil(difference / timedelta(minutes = 5))
604 clockout_time = now.now + difference
605 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1)
606 clockout_time = clockout_time.replace(second = 0, microsecond = 0)
607 623
608 if now.running_entry and clockout_time and clockout_difference >= 0: 624 difference_string = f"{sign}"
609 print(f"{difference_string}/{clockout_time:%H:%M}") 625 if hours_difference != 0:
610 else: 626 difference_string += f"{hours_difference}h"
611 print(difference_string) 627 if hours_difference == 0 or minutes_difference != 0:
628 difference_string += f"{minutes_difference}m"
629
630 def round_clockout_time(difference):
631 clockout_time = None
632 clockout_difference = None
633 exact_clockout_time = None
634 if then.now_is_workday or now.now_is_workday:
635 clockout_difference = 5 * ceil(difference / timedelta(minutes = 5))
636 clockout_time = now.now + difference
637 exact_clockout_time = clockout_time
638 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1)
639 clockout_time = clockout_time.replace(second = 0, microsecond = 0)
640
641 return clockout_time, exact_clockout_time, clockout_difference
642
643 clockout_time, exact_clockout_time, clockout_difference = round_clockout_time(difference)
644 clockout_time_pull_forward, exact_clockout_time_pull_forward, clockout_difference_pull_forward = round_clockout_time(difference_pull_forward)
645 clockout_pull_forward_sum, exact_clockout_pull_forward_sum, _ = round_clockout_time(now.time_to_work - now.time_worked + sum(now.pull_forward.values(), start=timedelta(milliseconds=0)))
646
647 if now.running_entry and clockout_time and (clockout_difference >= 0 or clockout_difference_pull_forward >= 0):
648 out_text = f"{difference_string}/{clockout_time:%H:%M}"
649 tooltip = f"{tooltip_timedelta(worked)}/{exact_clockout_time:%H:%M:%S}"
650
651 if clockout_pull_forward_sum >= clockout_time_pull_forward and clockout_time_pull_forward != clockout_time:
652 out_text += f"…{clockout_time_pull_forward:%H:%M}"
653 if exact_clockout_pull_forward_sum >= exact_clockout_time_pull_forward and exact_clockout_time_pull_forward != exact_clockout_time:
654 tooltip += f"…{exact_clockout_time_pull_forward:%H:%M:%S}"
655 else:
656 out_text = difference_string
612 else: 657 else:
613 print(worked) 658 out_text = str(worked)
659
660 if not now.now_is_workday:
661 out_text = f'({out_text})'
662
663 if waybar:
664 json.dump({"text": out_text, "class": out_class, "tooltip": tooltip}, stdout)
665 else:
666 print(out_text)
614 667
615def diff(now, **args): 668def diff(now, **args):
616 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 669 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
@@ -798,6 +851,38 @@ def classification(classification_name, table, table_format, **args):
798def main(): 851def main():
799 def isotime(s): 852 def isotime(s):
800 return datetime.fromisoformat(s).replace(tzinfo=tzlocal()) 853 return datetime.fromisoformat(s).replace(tzinfo=tzlocal())
854 def duration_minutes(s):
855 return timedelta(minutes = float(s))
856
857 def set_default_subparser(self, name, args=None, positional_args=0):
858 """default subparser selection. Call after setup, just before parse_args()
859 name: is the name of the subparser to call by default
860 args: if set is the argument list handed to parse_args()
861
862 , tested with 2.7, 3.2, 3.3, 3.4
863 it works with 2.6 assuming argparse is installed
864 """
865 subparser_found = False
866 for arg in sys.argv[1:]:
867 if arg in ['-h', '--help']: # global help if no subparser
868 break
869 else:
870 for x in self._subparsers._actions:
871 if not isinstance(x, argparse._SubParsersAction):
872 continue
873 for sp_name in x._name_parser_map.keys():
874 if sp_name in sys.argv[1:]:
875 subparser_found = True
876 if not subparser_found:
877 # insert default in last position before global positional
878 # arguments, this implies no global options are specified after
879 # first positional argument
880 if args is None:
881 sys.argv.insert(len(sys.argv) - positional_args, name)
882 else:
883 args.insert(len(args) - positional_args, name)
884
885 argparse.ArgumentParser.set_default_subparser = set_default_subparser
801 886
802 config = Worktime.config() 887 config = Worktime.config()
803 888
@@ -807,9 +892,13 @@ def main():
807 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false') 892 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false')
808 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false') 893 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false')
809 subparsers = parser.add_subparsers(help = 'Subcommands') 894 subparsers = parser.add_subparsers(help = 'Subcommands')
810 parser.set_defaults(cmd = worktime) 895 worktime_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked'])
811 time_worked_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked', 'today']) 896 worktime_parser.add_argument('--pull-forward-cutoff', dest = 'pull_forward_cutoff', metavar = 'MINUTES', type = duration_minutes, default = timedelta(minutes = 15))
897 worktime_parser.add_argument('--waybar', action='store_true')
898 worktime_parser.set_defaults(cmd = worktime)
899 time_worked_parser = subparsers.add_parser('today')
812 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false') 900 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false')
901 time_worked_parser.add_argument('--waybar', action='store_true')
813 time_worked_parser.set_defaults(cmd = time_worked) 902 time_worked_parser.set_defaults(cmd = time_worked)
814 diff_parser = subparsers.add_parser('diff') 903 diff_parser = subparsers.add_parser('diff')
815 diff_parser.set_defaults(cmd = diff) 904 diff_parser.set_defaults(cmd = diff)
@@ -827,6 +916,9 @@ def main():
827 classification_parser.add_argument('--table', action = 'store_true') 916 classification_parser.add_argument('--table', action = 'store_true')
828 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid') 917 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid')
829 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name)) 918 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name))
919 pull_forward_parser = subparsers.add_parser('pull-forward')
920 pull_forward_parser.set_defaults(cmd = pull_forward)
921 parser.set_default_subparser('time_worked')
830 args = parser.parse_args() 922 args = parser.parse_args()
831 923
832 args.cmd(**vars(args)) 924 args.cmd(**vars(args))