From 706732d96b028a39032061a0eaf35f1a9639c1c3 Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Mon, 20 Apr 2026 13:41:21 +0200 Subject: [PATCH 1/7] amend: fallback APPDATA on Windows --- lib/utility.py | 52 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/lib/utility.py b/lib/utility.py index c676fb9..0f6bd2a 100644 --- a/lib/utility.py +++ b/lib/utility.py @@ -14,7 +14,7 @@ string, items, exceptions, - ) +) from hashlib import blake2s from base64 import decodebytes as b64_decodeb from io import BytesIO @@ -85,13 +85,24 @@ # operators allowed in WMI and DBus result/parameter checks _WMI_RESULT_CHECK_OPERATORS = ("eq", "neq", "gt", "ge", "lt", "le", "match") -_DBUS_PARAM_CHECK_OPERATORS = ("eq", "neq", "gt", "ge", "lt", "le", "match", "contains", "ncontains") +_DBUS_PARAM_CHECK_OPERATORS = ( + "eq", + "neq", + "gt", + "ge", + "lt", + "le", + "match", + "contains", + "ncontains", +) # check that an operator is correct for either DBus or WMI result checks def is_wmi_operator(s: str) -> bool: return s in _WMI_RESULT_CHECK_OPERATORS + def is_dbus_operator(s: str) -> bool: return s in _DBUS_PARAM_CHECK_OPERATORS @@ -198,18 +209,21 @@ def get_appicon(image: bytes) -> ImageTk.PhotoImage: # determine where configuration is stored by default def get_default_configdir() -> str: if is_windows(): - appdata = os.environ["APPDATA"] - cfgname: str = AppConfig.get("CFGNAME") # type: ignore + appdata = os.getenv("APPDATA") or os.path.join( + os.getenv("USERPROFILE"), # type: ignore + "AppData", + ) + cfgname: str = AppConfig.get("CFGNAME") # type: ignore if AppConfig.get("DEBUG"): cfgname += "_DEBUG" return os.path.join(appdata, cfgname) else: - s: str = AppConfig.get("CFGNAME") # type: ignore + s: str = AppConfig.get("CFGNAME") # type: ignore cfgname = "." + s.lower() if AppConfig.get("DEBUG"): cfgname += "_DEBUG" home = os.path.expanduser("~") - if sys.platform == "darwin": + if is_mac(): return os.path.join(home, "Library", "Application Support", cfgname) elif is_linux(): return os.path.join(home, cfgname) @@ -238,7 +252,7 @@ def get_default_whenever() -> str | None: # determine appdata directory and ensure it exists def get_appdata() -> str: - appdata: str = AppConfig.get("APPDATA") # type: ignore + appdata: str = AppConfig.get("APPDATA") # type: ignore if not os.path.isdir(appdata): try: os.makedirs(appdata) @@ -249,7 +263,7 @@ def get_appdata() -> str: # determine scripts directory and ensure that it exists def get_scriptsdir() -> str: - configdir: str = AppConfig.get("APPDATA") # type: ignore + configdir: str = AppConfig.get("APPDATA") # type: ignore if is_windows(): subdir = "Scripts" else: @@ -264,8 +278,8 @@ def get_scriptsdir() -> str: # determine temp directory and ensure that it exists -def get_tempdir(cleanup: bool=False) -> str: - configdir: str = AppConfig.get("APPDATA") # type: ignore +def get_tempdir(cleanup: bool = False) -> str: + configdir: str = AppConfig.get("APPDATA") # type: ignore if is_windows(): subdir = "Temp" else: @@ -345,7 +359,7 @@ def get_lua_path() -> str: def get_lua_initscript() -> str: init = os.path.join(get_scriptsdir(), _LUA_INIT_NAME) if not os.path.exists(init): - with open(init, 'w') as f: + with open(init, "w") as f: f.write(_LUA_INIT_SCRIPT) return init @@ -484,28 +498,31 @@ def is_whenever_running() -> None | bool: def whenever_has_dbus() -> bool: return bool(AppConfig.get("WHENEVER_HAS_DBUS")) + def whenever_has_wmi() -> bool: return bool(AppConfig.get("WHENEVER_HAS_WMI")) + def whenever_has_lua_sync() -> bool: return bool(AppConfig.get("WHENEVER_HAS_LUASYNC")) + def whenever_has_lua_httpreq() -> bool: return bool(AppConfig.get("WHENEVER_HAS_LUAHTTPREQ")) # return the configuration file path def get_configfile() -> str: - s: str = AppConfig.get("CFGNAME") # type: ignore - d: str = AppConfig.get("APPDATA") # type: ignore + s: str = AppConfig.get("CFGNAME") # type: ignore + d: str = AppConfig.get("APPDATA") # type: ignore basename = "%s.toml" % s.lower() return os.path.join(d, basename) # return the log file path def get_logfile() -> str: - s: str = AppConfig.get("CFGNAME") # type: ignore - d: str = AppConfig.get("APPDATA") # type: ignore + s: str = AppConfig.get("CFGNAME") # type: ignore + d: str = AppConfig.get("APPDATA") # type: ignore basename = "%s.log" % s.lower() return os.path.join(d, basename) @@ -619,9 +636,9 @@ def toml_list_of_literals(los) -> items.Array | None: # in the array, possibly followed by the first non-dashed argument def toml_list_of_command_args(los) -> items.Array | None: if los is not None: - switch_start = ['-', '--'] + switch_start = ["-", "--"] if is_windows(): - switch_start.append('/') + switch_start.append("/") cur_line = [] r = array() for s in los: @@ -649,7 +666,6 @@ def toml_literal(s) -> items.String | None: return toml_try_literal(s) - # clean a caption from non-alphanumeric characters at the end def clean_caption(s) -> str: s = " ".join(s.split()) From df673ff8d7b403a53114ab3a224bf6319895fc8c Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Sun, 3 May 2026 16:18:06 +0200 Subject: [PATCH 2/7] build: better specify pywin32 dependency Initial update towards fixing of #197 --- lib/i18n/strings.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/i18n/strings.py b/lib/i18n/strings.py index d0f2881..6c1bc19 100644 --- a/lib/i18n/strings.py +++ b/lib/i18n/strings.py @@ -15,7 +15,7 @@ CLI_APP = "when" UI_APP_LABEL = "When Automation Tool" UI_APP_COPYRIGHT = "© 2023-2026 Francesco Garosi" -UI_APP_VERSION = "2.1.1" +UI_APP_VERSION = "2.1.2" # other strings that should not be translated UI_WHENEVER = "whenever" diff --git a/pyproject.toml b/pyproject.toml index aeeeb03..8a7a350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "when" -version = "2.1.1" +version = "2.1.2" description = "Interface for the **whenever** automation tool" authors = [ { name = "Francesco Garosi", email = "francesco.garosi@gmail.com" }, @@ -32,7 +32,7 @@ Pygments = "^2.19.2" pygobject = { version = "3.50.0", markers = "sys_platform == 'linux'" } # pygobject = { version = "^3.52.2", markers = "sys_platform == 'linux'" } pystray = "^0.19.5" -pywin32 = { version = "^308", markers = "sys_platform == 'win32'" } +pywin32 = { version = ">=308", markers = "sys_platform == 'win32'" } requests = "^2.32.5" rich = "^13.9.4" semver = "^3.0.4" From 1354e66ff55348bc3e2bc80b8642b398d14e6ef8 Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Sun, 3 May 2026 16:57:03 +0200 Subject: [PATCH 3/7] docs: specify supported Python setups for Windows --- support/docs/install.md | 7 +++++-- support/docs/tutorial.md | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/support/docs/install.md b/support/docs/install.md index 60aa114..09daed9 100644 --- a/support/docs/install.md +++ b/support/docs/install.md @@ -15,7 +15,10 @@ Note that even though a [recent release](https://github.com/almostearthling/when These steps can be followed on both Windows 10 and Windows 11: -1. install **Python** using the standalone installer, and choosing to install it for all users[^1] +1. install **Python** using one of the following methods:[^1] + * the [_winget_](https://learn.microsoft.com/en-us/windows/package-manager/winget) utility, by running `winget install Python` in a terminal window + * the [_Python Install Manager_](https://apps.microsoft.com/detail/9nq7512cxl7t), by running `py install 3` in a terminal window after installing it from the store + * the standalone installer, which can be found at the downloads page on the Python main site 2. install pipx by issuing the command `py -m pip install --user pipx` in a console window: after installation launch `pipx ensurepath` from the command prompt 3. install the latest release of **When**, using **pipx**: @@ -164,4 +167,4 @@ Both the installation of **When** using the **pipx** method, and the installatio [`◀ Main`](main.md) -[^1]: the reason is that, when Python is installed for the current user or via the _App Store_, the APPDATA directory is relocated within the interpreter to a non-canonical location; the problem is under investigation in order to support all installation methods for Python. +[^1]: the reason is that, when Python is installed via the _Microsoft Store_, the _APPDATA_ directory is relocated within the interpreter in order to provide a sandbox for the interpreter: the way of handling file system redirections for packaged applications is [documented](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes#file-system) and there is no way to escape this because such redirections are enabled at low level and real well-known paths are masked. Other installations performed using either _winget_, or the _Python Install Manager_, which in turn is becoming the preferred installation method for Python on Windows, or the standalone Python installer, are not affected this problem. The possibility to support a _Microsoft Store_ based Python setup is under investigation, but will be discouraged in any case: besides all other problems, it will create configuration files that are both hard to find and hard to edit. diff --git a/support/docs/tutorial.md b/support/docs/tutorial.md index be233b0..8a7e36a 100644 --- a/support/docs/tutorial.md +++ b/support/docs/tutorial.md @@ -24,7 +24,7 @@ The examples assume that **When** has been installed using the [suggested method It's sometimes useful to have the ability to trace the configuration of one or more conditions for debug purposes: configuring a condition can sometimes be awful, especially when it is one of the most complex ones (such as a [command](cond_actionrelated.md#command) or a [Lua](cond_actionrelated.md#lua-script) based condition), and you might just want to test your condition with no side effects other than dropping a line to the log. For this I use a simple _trace_ task, consisting in a minimal Lua script: ```lua -log.warn("Trace: *** VERIFIED CONDITION *** `" .. whenever_condition .. "`"); +log.warn("Trace: *** VERIFIED CONDITION *** `" .. whenever_condition .. "`") ``` that exploits the [abilities](lua_considerations.md) of the internal Lua interpreter to From c448a91257c1a445dab0fcf760926fcc7b6204a9 Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Sun, 3 May 2026 18:01:51 +0200 Subject: [PATCH 4/7] fix: support Python when installed from store --- lib/utility.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/utility.py b/lib/utility.py index 0f6bd2a..7662e37 100644 --- a/lib/utility.py +++ b/lib/utility.py @@ -258,6 +258,15 @@ def get_appdata() -> str: os.makedirs(appdata) except Exception: raise OSError(CLI_ERR_DATADIR_UNACCESSIBLE) + # the following is to work around UWP redirection on Windows: not sure + # whether or not it can be of any use on other platforms, so this remains + # Windows specific for the moment + if is_windows(): + realappdata = os.path.realpath(appdata) + if realappdata != appdata: + appdata = realappdata + AppConfig.delete("APPDATA") + AppConfig.set("APPDATA", appdata) return appdata From 5d56f3f8f69f6a790c119a02ce1c40975a81a696 Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Sun, 3 May 2026 18:18:15 +0200 Subject: [PATCH 5/7] docs: describe UWP Python support in When --- support/docs/install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/support/docs/install.md b/support/docs/install.md index 09daed9..4b8799c 100644 --- a/support/docs/install.md +++ b/support/docs/install.md @@ -15,7 +15,7 @@ Note that even though a [recent release](https://github.com/almostearthling/when These steps can be followed on both Windows 10 and Windows 11: -1. install **Python** using one of the following methods:[^1] +1. install **Python**, preferably using one of the following methods:[^1] * the [_winget_](https://learn.microsoft.com/en-us/windows/package-manager/winget) utility, by running `winget install Python` in a terminal window * the [_Python Install Manager_](https://apps.microsoft.com/detail/9nq7512cxl7t), by running `py install 3` in a terminal window after installing it from the store * the standalone installer, which can be found at the downloads page on the Python main site @@ -167,4 +167,4 @@ Both the installation of **When** using the **pipx** method, and the installatio [`◀ Main`](main.md) -[^1]: the reason is that, when Python is installed via the _Microsoft Store_, the _APPDATA_ directory is relocated within the interpreter in order to provide a sandbox for the interpreter: the way of handling file system redirections for packaged applications is [documented](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes#file-system) and there is no way to escape this because such redirections are enabled at low level and real well-known paths are masked. Other installations performed using either _winget_, or the _Python Install Manager_, which in turn is becoming the preferred installation method for Python on Windows, or the standalone Python installer, are not affected this problem. The possibility to support a _Microsoft Store_ based Python setup is under investigation, but will be discouraged in any case: besides all other problems, it will create configuration files that are both hard to find and hard to edit. +[^1]: the reason is that, when Python is installed via the _Microsoft Store_, the _APPDATA_ directory is relocated within the interpreter in order to confine it in a sandbox: the way of handling file system redirections for packaged applications is [documented here](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes#file-system). **When** uses the redirected location to store configuration and data, and **whenever** works anyway, however this type of setup is discouraged because it leads to configuration files that are both hard to read and hard to find. Other installations performed using either _winget_, or the _Python Install Manager_, which in turn is becoming the preferred installation method for Python on Windows, or the standalone Python installer, are not affected by this problem. If a _Microsoft Store_ based setup is really needed, be careful to disable the setting that limits the path length, as it might cause issues because of the intrinsic length of the base path. From c0d629f735c77ec81cf05ecd5ea781d98d67e1fb Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Sun, 3 May 2026 18:28:22 +0200 Subject: [PATCH 6/7] docs: specify PATH issues for Windows setups --- support/docs/install.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/support/docs/install.md b/support/docs/install.md index 4b8799c..7c9198d 100644 --- a/support/docs/install.md +++ b/support/docs/install.md @@ -16,8 +16,8 @@ Note that even though a [recent release](https://github.com/almostearthling/when These steps can be followed on both Windows 10 and Windows 11: 1. install **Python**, preferably using one of the following methods:[^1] - * the [_winget_](https://learn.microsoft.com/en-us/windows/package-manager/winget) utility, by running `winget install Python` in a terminal window - * the [_Python Install Manager_](https://apps.microsoft.com/detail/9nq7512cxl7t), by running `py install 3` in a terminal window after installing it from the store + * the [_winget_](https://learn.microsoft.com/en-us/windows/package-manager/winget) utility, by running `winget install Python` in a terminal window[^2] + * the [_Python Install Manager_](https://apps.microsoft.com/detail/9nq7512cxl7t), by running `py install 3` in a terminal window after installing it from the store[^2] * the standalone installer, which can be found at the downloads page on the Python main site 2. install pipx by issuing the command `py -m pip install --user pipx` in a console window: after installation launch `pipx ensurepath` from the command prompt 3. install the latest release of **When**, using **pipx**: @@ -168,3 +168,4 @@ Both the installation of **When** using the **pipx** method, and the installatio [^1]: the reason is that, when Python is installed via the _Microsoft Store_, the _APPDATA_ directory is relocated within the interpreter in order to confine it in a sandbox: the way of handling file system redirections for packaged applications is [documented here](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes#file-system). **When** uses the redirected location to store configuration and data, and **whenever** works anyway, however this type of setup is discouraged because it leads to configuration files that are both hard to read and hard to find. Other installations performed using either _winget_, or the _Python Install Manager_, which in turn is becoming the preferred installation method for Python on Windows, or the standalone Python installer, are not affected by this problem. If a _Microsoft Store_ based setup is really needed, be careful to disable the setting that limits the path length, as it might cause issues because of the intrinsic length of the base path. +[^2]: the _PATH_ variable might need to be adjusted if this method is used, as suggested by the `pip` command, or the command `python -m pip` has to be used instead of simply typing `pipx`; the same yields for _Microsoft Store_ based Python installations. \ No newline at end of file From 52d04921f0adbb4e504b60f159a87361b1dd2928 Mon Sep 17 00:00:00 2001 From: Francesco Garosi Date: Sun, 3 May 2026 18:30:34 +0200 Subject: [PATCH 7/7] docs: fix typo in installation instructions --- support/docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/docs/install.md b/support/docs/install.md index 7c9198d..79e5b92 100644 --- a/support/docs/install.md +++ b/support/docs/install.md @@ -168,4 +168,4 @@ Both the installation of **When** using the **pipx** method, and the installatio [^1]: the reason is that, when Python is installed via the _Microsoft Store_, the _APPDATA_ directory is relocated within the interpreter in order to confine it in a sandbox: the way of handling file system redirections for packaged applications is [documented here](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes#file-system). **When** uses the redirected location to store configuration and data, and **whenever** works anyway, however this type of setup is discouraged because it leads to configuration files that are both hard to read and hard to find. Other installations performed using either _winget_, or the _Python Install Manager_, which in turn is becoming the preferred installation method for Python on Windows, or the standalone Python installer, are not affected by this problem. If a _Microsoft Store_ based setup is really needed, be careful to disable the setting that limits the path length, as it might cause issues because of the intrinsic length of the base path. -[^2]: the _PATH_ variable might need to be adjusted if this method is used, as suggested by the `pip` command, or the command `python -m pip` has to be used instead of simply typing `pipx`; the same yields for _Microsoft Store_ based Python installations. \ No newline at end of file +[^2]: the _PATH_ variable might need to be adjusted if this method is used, as suggested by the `pip` command, or the command `python -m pipx` has to be used instead of simply typing `pipx`; the same yields for _Microsoft Store_ based Python installations.