Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
3df0c75
feat: confluence - enable temp dir
almostearthling Feb 14, 2026
2972c03
temp: begin supporting MCRT aka confluence
almostearthling Feb 18, 2026
4cc180f
temp: move Lua library to a separate file
almostearthling Feb 18, 2026
d56877d
temp: fix Lua library source
almostearthling Feb 18, 2026
7552af7
temp: add MCRT initializer in Lua
almostearthling Feb 20, 2026
f3f4dc9
temp: define the items that enable confluence
almostearthling Feb 21, 2026
847d3d8
temp: complete utilities to implement MCRT
almostearthling Feb 22, 2026
c88551e
temp: install Lua library at startup
almostearthling Feb 22, 2026
6eb7d26
temp: implement confluent cond like an extra item
almostearthling Feb 22, 2026
a2ec898
Merge branch 'development' into confluence
almostearthling Feb 28, 2026
48da7ca
temp: start shaping the MCRT condition form
almostearthling Feb 28, 2026
0f0f927
temp(ui): implement MCRT features in forms
almostearthling Mar 1, 2026
b85ff67
temp: set confluent conditions for specific form
almostearthling Mar 3, 2026
3173e48
Merge branch 'main' into confluence
almostearthling Mar 4, 2026
deb6c8a
Merge branch 'main' into confluence
almostearthling Mar 4, 2026
d3366a7
temp: implement saving of MCRT configuration
almostearthling Mar 4, 2026
0060f96
temp: refactoring and fixes
almostearthling Mar 4, 2026
dfffe24
temp: update widgets in confluent condition form
almostearthling Mar 5, 2026
fb1e890
temp: remove redundant underscore in MCRT names
almostearthling Mar 5, 2026
88f8206
temp: partly fix bugs in Lua confluence library
almostearthling Mar 6, 2026
dd53ae8
temp: fix confluence Lua library issues
almostearthling Mar 6, 2026
288e04e
tenmp: remove useless backslash
almostearthling Mar 6, 2026
6d4d1ca
temp: fix the confluence cond creation process
almostearthling Mar 8, 2026
40aef86
temp: fix confluent conditions form issues
almostearthling Mar 8, 2026
1db76da
temp: enclose most error prone Lua code in `pcall`
almostearthling Mar 9, 2026
071023a
temp: exploit whenever sync primitives for Lua
almostearthling Apr 1, 2026
7dbfef7
amend: Lua confluence init resets sharedstate
almostearthling Apr 2, 2026
419ae3c
amend: add checks for new whenever options
almostearthling Apr 3, 2026
b1a24c3
feat: working MCRT confluence using shared states
almostearthling Apr 5, 2026
c2d070d
fix: Lua init script path saved as literal in config
almostearthling Apr 5, 2026
cd91531
Merge branch 'development' into confluence
almostearthling Apr 5, 2026
41e1c50
style: reformat modified source code
almostearthling Apr 5, 2026
3e3fd52
fix(i18n): update translations for confluence
almostearthling Apr 6, 2026
d3b8747
refactor: reorganize initialization sequence
almostearthling Apr 6, 2026
bdb31c9
fix: use condition availability to enable MCRT
almostearthling Apr 7, 2026
92616d0
build: update pyproject.toml format
almostearthling Apr 7, 2026
3da62eb
Merge branch 'development' into confluence
almostearthling Apr 7, 2026
9fbcc35
temp: use static Lua library
almostearthling Apr 7, 2026
91d8cb3
temp: try to better specify package data
almostearthling Apr 7, 2026
4c1d3ba
refactor: provide Lua MCRT lib as static resource
almostearthling Apr 7, 2026
07d89af
style: accept many linter suggestions
almostearthling Apr 8, 2026
2002235
fix: introduce a hidden startup condition
almostearthling Apr 8, 2026
970360f
style: remove unused imports
almostearthling Apr 8, 2026
7918b41
fix(ui): disable confluence for confluence conditions
almostearthling Apr 8, 2026
dba823d
fix(ui): adjust timestamp width in history box
almostearthling Apr 8, 2026
d3b924d
feat: update tomlkit to version 0.14
almostearthling Apr 8, 2026
e524665
build: bump version for distribution purposes
almostearthling Apr 8, 2026
4201536
fix: update reported version as well
almostearthling Apr 8, 2026
73dddf5
fix(ui): human readable description of MCRT conditions
almostearthling Apr 8, 2026
3aaed1d
docs: describe new MCRT features and update images
almostearthling Apr 8, 2026
276c8e1
docs: update documentation about frontends
almostearthling Apr 9, 2026
c78ac35
style: more implemented linter suggestions
almostearthling Apr 9, 2026
7e7d3f7
style: explain ignored lint warnings
almostearthling Apr 9, 2026
a8d0c34
feat: separate private section in configuration
almostearthling Apr 9, 2026
841e53a
build: bump version for latest adjustments
almostearthling Apr 9, 2026
231d9d0
build: update revision in resource strings
almostearthling Apr 9, 2026
89125c1
style: remove explicit `return None` hint
almostearthling Apr 9, 2026
89cf5d5
fix: check that an item can be deleted
almostearthling Apr 10, 2026
95fb13e
fix(ui): fixes in string resources and I18N
almostearthling Apr 10, 2026
3d839dd
fix(ui): let the UI decide how to break long text
almostearthling Apr 10, 2026
e00e58f
fix(i18n): better German translation
almostearthling Apr 10, 2026
410acf3
build: bump version for new release
almostearthling Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/cfgapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# entry point for the configuration application, which is also reachable
# using the system tray menu when using the tray resident application
def main(root) -> None:
def main(root):
# not setting the root of form_Config() informs that this is the
# configuration app, thus no `Reload` button should be displayed
form = form_Config()
Expand Down
40 changes: 35 additions & 5 deletions lib/configurator/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,61 @@
from tomlkit import document, comment, item, aot
from time import strftime

from ..utility import is_private_item_name
from ..i18n.strings import UI_APP


# the comment text constants for the configuration file are defined here
COMMENT_PUBLIC_SECTION = "global parameters and user defined items"
COMMENT_PRIVATE_ITEMS = "private items: please do not modify below this line"


# all items have an `as_table()` utility that converts them to TOML tables
def write_whenever_config(filename, tasks, conditions, events, globals) -> None:
# this writer separates private items from user created ones
def write_whenever_config(filename, tasks, conditions, events, globals):
head = document()
doc = document()
priv = document()
mod_time = strftime("%Y-%m-%d @%H:%M:%S")
doc.add(comment(f"{UI_APP}: {mod_time}"))
head.add(comment(f"{UI_APP}: {mod_time}"))
head.add(comment(COMMENT_PUBLIC_SECTION))
for k in globals:
if globals[k] is not None:
doc.add(k, item(globals[k]))
priv.add(comment(COMMENT_PRIVATE_ITEMS))
p = aot()
t = aot()
for elem in tasks:
t.append(elem.as_table())
if is_private_item_name(elem.name):
p.append(elem.as_table())
else:
t.append(elem.as_table())
doc.append("task", t)
priv.append("task", p)
p = aot()
t = aot()
for elem in conditions:
t.append(elem.as_table())
if is_private_item_name(elem.name):
p.append(elem.as_table())
else:
t.append(elem.as_table())
doc.append("condition", t)
priv.append("condition", p)
p = aot()
t = aot()
for elem in events:
t.append(elem.as_table())
if is_private_item_name(elem.name):
p.append(elem.as_table())
else:
t.append(elem.as_table())
doc.append("event", t)
priv.append("event", p)
with open(filename, "w") as f:
f.write(head.as_string())
f.write("\n")
f.write(doc.as_string())
f.write("\n")
f.write(priv.as_string())


# end.
6 changes: 3 additions & 3 deletions lib/extra/_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(self, t: items.Table | None = None):

self.updateitem()

def updateitem(self) -> None:
def updateitem(self):
# set base item properties according to specific parameters in `tags`
self.command = "ls"
self.command_arguments = [
Expand Down Expand Up @@ -165,12 +165,12 @@ def __init__(self, tasks_available, item=None):
self._updateform()

# update the form with the specific parameters (usually in the `tags`)
def _updateform(self) -> None:
def _updateform(self):
self.data_set("parameter1", self._item.tags.get("parameter1")) # type: ignore
return super()._updateform()

# update the item from the form elements (usually update `tags`)
def _updatedata(self) -> None:
def _updatedata(self):
self._item.tags["parameter1"] = self.data_get("parameter1") # type: ignore
self._item.updateitem() # type: ignore
return super()._updatedata()
Expand Down
2 changes: 1 addition & 1 deletion lib/extra/c_battery_charging_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def _get_batteries():
batteries.append(str(x))
batteries.sort()
return batteries
except:
except Exception:
return []


Expand Down
2 changes: 1 addition & 1 deletion lib/extra/c_battery_charging_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def _has_battery():
conn = wmi.WMI()
batteries = conn.query(query)
return bool(len(batteries) > 0)
except:
except Exception:
return False


Expand Down
2 changes: 1 addition & 1 deletion lib/extra/c_battery_low_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def _get_batteries():
batteries.append(str(x))
batteries.sort()
return batteries
except:
except Exception:
return []


Expand Down
2 changes: 1 addition & 1 deletion lib/extra/c_battery_low_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _has_battery():
conn = wmi.WMI()
batteries = conn.query(query)
return bool(len(batteries) > 0)
except:
except Exception:
return False


Expand Down
2 changes: 1 addition & 1 deletion lib/extra/c_removabledrive_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def check_tags(cls, tags):
if drive_letter is not None:
if (
not isinstance(drive_letter, str)
and not drive_letter[0] in AVAILABLE_DRIVE_LETTERS
and drive_letter[0] not in AVAILABLE_DRIVE_LETTERS
):
errors.append("drive_letter")
drive_label = tags.get("drive_label")
Expand Down
2 changes: 1 addition & 1 deletion lib/extra/c_session_locked_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def check_tags(cls, tags):
missing.append("check_frequency")
elif (
not isinstance(check_frequency, str)
and not check_frequency in CHECK_EXTRA_DELAY.keys()
and check_frequency not in CHECK_EXTRA_DELAY.keys()
):
errors.append("check_frequency")
if errors or missing:
Expand Down
1 change: 0 additions & 1 deletion lib/extra/i18n/extra_locale.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# get localized versions of strings for extra items UIs


import os
from importlib import import_module

from ...i18n.localizer import get_locale
Expand Down
2 changes: 1 addition & 1 deletion lib/forms/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(self, main=False):


# display a simple about box
def show_about_box(main=False) -> None:
def show_about_box(main=False):
box = AboutBox(main)
box.run()
del box
Expand Down
97 changes: 78 additions & 19 deletions lib/forms/cfgform.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from ttkbootstrap_icons_mat import MatIcon as Icons

from ..i18n.strings import *
from ..icons import APP_ICON32 as APP_ICON
from .ui import *
from .colors import *

from ..repocfg import AppConfig
from ..utility import get_configfile, is_private_item_name
from ..items.item import ALL_AVAILABLE_ITEMS_D
from ..internal import multi_conds_run_task as mcrt

from ..configurator.reader import read_whenever_config
from ..configurator.writer import write_whenever_config
Expand Down Expand Up @@ -209,7 +209,7 @@ def tab_change():
self._updateform()

# update the associated data according to what is in the form
def _updatedata(self) -> None:
def _updatedata(self):
self._itemlistentries = []
# encode the invisible list field with the same signature that is used in
# the _item_ module, so that the corresponding form and item factories can
Expand Down Expand Up @@ -244,7 +244,7 @@ def _updatedata(self) -> None:
)

# update the form fields according to the associated actual data
def _updateform(self) -> None:
def _updateform(self):
self._tv_items.delete(*self._tv_items.get_children())
for entry in self._itemlistentries:
t = entry[2].split(":", 1)[0]
Expand Down Expand Up @@ -293,7 +293,7 @@ def _resetdata(self):
self._changed = False

# load the configuration from a TOML file, only display non-private items
def _load_config(self, fn) -> None:
def _load_config(self, fn):
self._resetdata()
default_globals = self._globals.copy()
tasks, conditions, events, self._globals = read_whenever_config(fn)
Expand Down Expand Up @@ -333,7 +333,7 @@ def _load_config(self, fn) -> None:
self._changed = False

# save the configuration to a TOML file: add private items when required
def _save_config(self, fn) -> None:
def _save_config(self, fn):
# 0. set configuration globals as retrieved from the form
AppConfig.delete("RESET_CONDS_ON_RESUME")
AppConfig.set(
Expand Down Expand Up @@ -367,6 +367,36 @@ def _save_config(self, fn) -> None:
del self._conditions[name]
if name in self._events.keys():
del self._events[name]

# check whether there are MCRT conditions (both confluence and
# confluent) and, if so, create the support items, that is, the initial
# task and condition, and the updater task, otherwise remove them if
# present so that no time and resources are wasted for no reason
n_mcrt_initializer = mcrt.initializer_name()
n_mcrt_initial_cond = mcrt.initial_condition_name()
n_mcrt_updater = mcrt.updater_name()
mcrt_active = False
for cond in self._conditions.values():
if isinstance(cond, mcrt.ConfluenceCondition) or mcrt.is_confluent_cond(
cond
):
mcrt_active = True
break
if mcrt_active:
if n_mcrt_updater not in self._tasks.keys():
self._tasks[n_mcrt_updater] = mcrt.updater()
if n_mcrt_initializer not in self._tasks.keys():
self._tasks[n_mcrt_initializer] = mcrt.initializer()
if n_mcrt_initial_cond not in self._conditions.keys():
self._conditions[n_mcrt_initial_cond] = mcrt.initial_condition()
else:
if n_mcrt_updater in self._tasks.keys():
del self._tasks[n_mcrt_updater]
if n_mcrt_initializer in self._tasks.keys():
del self._tasks[n_mcrt_initializer]
if n_mcrt_initial_cond in self._conditions.keys():
del self._conditions[n_mcrt_initial_cond]

# ...

# finally write the configuration fle
Expand All @@ -379,7 +409,7 @@ def _save_config(self, fn) -> None:
)

# to be called when one of the global parameters has been changed
def _set_changed(self, changed=True) -> None:
def _set_changed(self, changed=True):
self._changed = changed

# check currently loaded items for signature coherence
Expand All @@ -397,27 +427,37 @@ def _check_items(self) -> bool:
return True

# command button reactions
def delete(self) -> None:
def delete(self):
# TODO: do not ignore type error
item_name, _, item_signature = self.data_get("item_selection") # type: ignore
item_type = item_signature.split(":", 1)[0]
if self.messagebox.askyesno(UI_POPUP_T_CONFIRM, UI_POPUP_DELETEITEM_Q):
if item_type == "task":
del self._tasks[item_name]
e = self._tasks[item_name]
if e.can_be_removed(self._conditions):
del self._tasks[item_name]
else:
self.messagebox.showerror(UI_POPUP_T_ERR, UI_POPUP_REFERENCEDTASK)
elif item_type == "cond":
del self._conditions[item_name]
e = self._conditions[item_name]
if e.can_be_removed(self._events):
del self._conditions[item_name]
else:
self.messagebox.showerror(UI_POPUP_T_ERR, UI_POPUP_REFERENCEDCOND)
elif item_type == "event":
del self._events[item_name]
self._updatedata()
self._updateform()
self._changed = True

def save(self) -> None:
def save(self):
self._updatedata()
fn = self.data_get("config_file")
if fn and self._changed:
if os.path.exists(fn):
if self.messagebox.askyesno(UI_POPUP_T_CONFIRM, UI_POPUP_OVERWRITEFILE_Q):
if self.messagebox.askyesno(
UI_POPUP_T_CONFIRM, UI_POPUP_OVERWRITEFILE_Q
):
self._save_config(fn)
self._changed = False
else:
Expand All @@ -426,12 +466,12 @@ def save(self) -> None:

# edit a specific item: to be complete, this is also bound
# to the double click event for an element of the list
def edit(self) -> None:
def edit(self):
selection = self.data_get("item_selection")
if selection is None:
return

item_name, _, item_signature = selection # type: ignore
item_name, _, item_signature = selection # type: ignore
item_type = item_signature.split(":", 1)[0]

# task items
Expand Down Expand Up @@ -473,6 +513,15 @@ def edit(self) -> None:
x for x in self._tasks.keys() if not is_private_item_name(x)
)
e = fform(available_tasks, self._conditions[item_name])
# this is a special case, which has an extra parameter
if item_signature == "cond:lua:mcrt_confluence":
confluent_conds = list(
x
for x in self._conditions.keys()
if not is_private_item_name(x)
and mcrt.is_confluent_cond(self._conditions[x])
)
e.set_available_conditions(confluent_conds)
if e is not None:
new_item = e.run()
if new_item:
Expand All @@ -495,8 +544,7 @@ def edit(self) -> None:
event_conds = list(
x
for x in self._conditions
if self._conditions[x].type == "event"
and not is_private_item_name(x)
if self._conditions[x].type == "event" and not is_private_item_name(x)
)
available_item = ALL_AVAILABLE_ITEMS_D.get(item_signature)
assert available_item is not None
Expand All @@ -518,7 +566,7 @@ def edit(self) -> None:

# create a new item: open the item type selection dialog and, if an
# item type is chosen, open the appropriate form with default values
def new(self) -> None:
def new(self):
e = form_NewItem()
if e is not None:
r = e.run()
Expand All @@ -531,6 +579,15 @@ def new(self) -> None:
x for x in self._tasks.keys() if not is_private_item_name(x)
]
form = form_class(list(available_tasks))
# this is a special case, which has an extra parameter
if form_class is mcrt.form_ConfluenceCondition:
confluent_conds = list(
x
for x in self._conditions.keys()
if not is_private_item_name(x)
and mcrt.is_confluent_cond(self._conditions[x])
)
form.set_available_conditions(confluent_conds)
# note that, since providing a suitable event based
# condition is mandatory for an event, the form will
# refuse to create a new event
Expand Down Expand Up @@ -573,17 +630,19 @@ def new(self) -> None:

# reload the configuration by sending the appropriate message to the
# main (hidden) application form
def reload(self) -> None:
def reload(self):
if self.messagebox.askyesno(UI_POPUP_T_CONFIRM, UI_POPUP_RELOADCONFIG_Q):
if self._app:
self._app.send_event("<<SchedReloadConfig>>")
return super().reload()

# modify the reaction to the quit button so that if the configuration
# has changed the user is asked whether or not he wants to discard it
def exit_close(self) -> None:
def exit_close(self):
if self._changed:
if self.messagebox.askokcancel(UI_POPUP_T_CONFIRM, UI_POPUP_DISCARDCONFIG_Q):
if self.messagebox.askokcancel(
UI_POPUP_T_CONFIRM, UI_POPUP_DISCARDCONFIG_Q
):
return super().exit_close()
else:
return super().exit_close()
Expand Down
Loading
Loading