Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
426 changes: 229 additions & 197 deletions WHATS_NEW.md

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions docs/source/Eng/doc/new_features/v203_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Open Files / URLs with the Default App
======================================

The framework could launch a literal executable (``start_exe`` / ``shell_process``),
but not the single most common "hand off to another app" RPA step: open
``report.pdf`` with whatever app is registered for it, ``print`` a document, or
open a URL in the default browser. ``shell_open`` adds that, routed per-OS to
``os.startfile`` / ``open`` / ``xdg-open`` / ``webbrowser``.

* :func:`plan_open` — pure planner: classify the target (URL vs file path),
validate it (URL scheme allow-list; ``realpath`` for files) and return the
dispatch descriptor,
* :func:`open_path` — run the plan through an injectable ``opener`` sink (the real
OS call by default).

Pure stdlib; the dispatch logic is unit-testable without launching anything via
the injectable ``opener``. Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import open_path, plan_open

open_path("report.pdf") # default PDF viewer
open_path("invoice.pdf", verb="print") # print it
open_path("https://example.com") # default browser

plan_open("https://example.com")
# {"kind": "url", "scheme": "https", "target": "...", "backend": "webbrowser",
# "verb": "open"}
plan_open("report.pdf")
# {"kind": "file", "target": "<realpath>", "backend": "startfile", ...}

A ``scheme://`` target (or ``mailto:`` / ``tel:``) is opened as a URL — only the
allow-listed schemes (``http`` / ``https`` / ``ftp`` / ``file`` / ``mailto`` /
``tel``) are accepted, anything else raises ``ValueError``. Everything else is a
file path (a Windows drive like ``C:\\…`` is correctly treated as a path, not a
scheme) and is ``realpath``-resolved. ``verb`` (``open`` / ``print`` / ``edit``)
applies to files on Windows.

Executor commands
-----------------

``AC_open_path`` (``target`` / ``verb`` → ``{opened}``) and ``AC_plan_open``
(``target`` / ``verb`` → the plan). They are exposed as the matching ``ac_*`` MCP
tools (``open_path`` side-effect-only, ``plan_open`` read-only) and as Script
Builder commands under **Shell**.
59 changes: 59 additions & 0 deletions docs/source/Eng/doc/new_features/v204_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
Idle Detection + Keep the Machine Awake
=======================================

Long unattended automation runs get derailed two ways: the screensaver / power
policy sleeps the box mid-run, or the run should hold while a human is actively
using the machine. The framework had neither signal. ``idle_keepawake`` adds
both, behind injectable seams so all logic is testable without touching the OS.

* :func:`idle_seconds` / :func:`is_idle` — seconds since the last user keyboard /
mouse input (``GetLastInputInfo`` on Windows), through an injectable ``probe``.
* :func:`plan_keep_awake` — pure planner describing which wake flags a request
maps to.
* :func:`keep_awake` — scoped context manager that keeps the machine awake for
the duration of a ``with`` block, restoring the prior state on exit.
* :func:`keep_awake_on` / :func:`allow_sleep` — a process-global on / off pair
for JSON action flows.

All three keep-awake entry points apply the plan through an injectable ``driver``
(``SetThreadExecutionState`` on Windows, ``caffeinate`` on macOS,
``systemd-inhibit`` on Linux by default). Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import (
idle_seconds, is_idle, keep_awake, keep_awake_on, allow_sleep,
)

idle_seconds() # e.g. 3.4 — seconds since last input
is_idle(300) # True once nobody has touched the machine for 5 min

# Scoped: keep awake only while a long step runs
with keep_awake():
run_long_batch()

# Flow-style: on at the start, off at the end
keep_awake_on(display=True, system=True)
try:
run_long_batch()
finally:
allow_sleep()

:func:`is_idle` is the gate for "only run when the user has stepped away";
:func:`keep_awake` / :func:`keep_awake_on` stop the display and system sleeping
so an overnight run is not interrupted. ``display=False`` keeps the system awake
but lets the screen blank (battery-friendly for headless boxes).

Executor commands
-----------------

``AC_idle_seconds`` (→ ``{idle_seconds}``), ``AC_is_idle`` (``threshold`` →
``{idle, idle_seconds}``), ``AC_plan_keep_awake`` (``display`` / ``system`` → the
plan), ``AC_keep_awake_on`` (``display`` / ``system`` → the active plan) and
``AC_allow_sleep`` (→ ``{released}``). They are exposed as the matching ``ac_*``
MCP tools (reads read-only, keep-awake on/off side-effect-only) and as Script
Builder commands under **Shell**. The :func:`keep_awake` context manager is the
Python-API surface for scoped use.
44 changes: 44 additions & 0 deletions docs/source/Eng/doc/new_features/v205_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Resolve the App Registered for a File Type
==========================================

:func:`open_path` (``shell_open``) opens a file with whatever app is registered
for it; ``file_assoc`` answers the inverse, read-only question — *which* app is
that? Given ``report.pdf`` (or a bare ``.pdf`` / ``pdf``) it returns the
registered executable, the friendly app name, the open command line and the MIME
content type, via the Windows ``AssocQueryStringW`` shell API.

* :func:`normalize_ext` — pure helper turning a path / ``.ext`` / bare ``ext``
into a lowercased ``.ext``,
* :func:`file_association` — run the lookup through an injectable ``resolver``
seam (the real shell API by default).

The assembly logic is unit-testable without Windows via the injectable
``resolver``. Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import file_association, normalize_ext

normalize_ext("report.PDF") # ".pdf"
normalize_ext("archive.tar.gz") # ".gz"

file_association("report.pdf")
# {"ext": ".pdf", "command": "...AcroRd32.exe \"%1\"",
# "exe": "...AcroRd32.exe", "friendly": "Adobe Acrobat",
# "content_type": "application/pdf"}

The app fields are ``None`` when nothing is registered for the type. This is the
natural companion to :func:`open_path`: ``file_association`` tells you *what*
would open a file (assert "PDFs open in Acrobat, not the browser"), and
``open_path`` actually opens it. The live lookup uses the Windows shell API; on
other platforms pass your own ``resolver``.

Executor commands
-----------------

``AC_normalize_ext`` (``target`` → ``{ext}``, pure) and ``AC_file_association``
(``target`` → the association dict). They are exposed as the matching ``ac_*``
MCP tools (both read-only) and as Script Builder commands under **Shell**.
43 changes: 43 additions & 0 deletions docs/source/Zh/doc/new_features/v203_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
以預設程式開啟檔案 / URL
========================

框架原本能啟動字面執行檔(``start_exe`` / ``shell_process``),卻無法做最常見的「交接給另一個應用程式」
RPA 步驟:用註冊的應用程式開啟 ``report.pdf``、``print`` 一份文件,或在預設瀏覽器開啟 URL。
``shell_open`` 補上這點,依作業系統路由到 ``os.startfile`` / ``open`` / ``xdg-open`` /
``webbrowser``。

* :func:`plan_open` ——純 planner:分類目標(URL 或檔案路徑)、驗證(URL scheme 白名單;檔案用
``realpath``)並回傳分派描述子,
* :func:`open_path` ——透過可注入的 ``opener`` 接縫執行計畫(預設為真正的 OS 呼叫)。

純標準庫;透過可注入的 ``opener``,分派邏輯可在不真正開啟任何東西的情況下單元測試。不匯入
``PySide6``。

無頭 API
--------

.. code-block:: python

from je_auto_control import open_path, plan_open

open_path("report.pdf") # 預設 PDF 檢視器
open_path("invoice.pdf", verb="print") # 列印
open_path("https://example.com") # 預設瀏覽器

plan_open("https://example.com")
# {"kind": "url", "scheme": "https", "target": "...", "backend": "webbrowser",
# "verb": "open"}
plan_open("report.pdf")
# {"kind": "file", "target": "<realpath>", "backend": "startfile", ...}

``scheme://`` 目標(或 ``mailto:`` / ``tel:``)會以 URL 開啟——只接受白名單 scheme
(``http`` / ``https`` / ``ftp`` / ``file`` / ``mailto`` / ``tel``),其他則拋出 ``ValueError``。
其餘皆視為檔案路徑(Windows 磁碟代號如 ``C:\\…`` 會正確視為路徑而非 scheme)並以 ``realpath``
解析。``verb``(``open`` / ``print`` / ``edit``)在 Windows 上套用於檔案。

執行器指令
----------

``AC_open_path``(``target`` / ``verb`` → ``{opened}``)與 ``AC_plan_open``(``target`` /
``verb`` → 計畫)。皆以對應的 ``ac_*`` MCP 工具(``open_path`` 為僅副作用、``plan_open`` 為唯讀)
及 Script Builder 指令(位於 **Shell** 分類下)形式提供。
53 changes: 53 additions & 0 deletions docs/source/Zh/doc/new_features/v204_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
閒置偵測 + 保持機器清醒
=======================

長時間無人值守的自動化執行常因兩種情況中斷:螢幕保護 / 電源原則在執行中途讓機器睡眠,或是當有人正在
使用機器時執行應該暫停。框架原本兩種訊號都沒有。``idle_keepawake`` 補上這兩者,並以可注入接縫實作,
所有邏輯都能在不碰作業系統的情況下測試。

* :func:`idle_seconds` / :func:`is_idle` ——距離使用者上次鍵盤 / 滑鼠輸入的秒數(Windows 上用
``GetLastInputInfo``),透過可注入的 ``probe`` 取得。
* :func:`plan_keep_awake` ——純 planner,描述請求對應到哪些清醒旗標。
* :func:`keep_awake` ——具範圍的 context manager,在 ``with`` 區塊期間保持機器清醒,離開時還原先前狀態。
* :func:`keep_awake_on` / :func:`allow_sleep` ——供 JSON 動作流程使用的行程全域開 / 關配對。

三個 keep-awake 入口皆透過可注入的 ``driver`` 套用計畫(預設 Windows 用
``SetThreadExecutionState``、macOS 用 ``caffeinate``、Linux 用 ``systemd-inhibit``)。不匯入
``PySide6``。

無頭 API
--------

.. code-block:: python

from je_auto_control import (
idle_seconds, is_idle, keep_awake, keep_awake_on, allow_sleep,
)

idle_seconds() # 例如 3.4 ——距離上次輸入的秒數
is_idle(300) # 沒人碰機器滿 5 分鐘後回傳 True

# 具範圍:只在長步驟執行時保持清醒
with keep_awake():
run_long_batch()

# 流程式:開始時開、結束時關
keep_awake_on(display=True, system=True)
try:
run_long_batch()
finally:
allow_sleep()

:func:`is_idle` 是「只在使用者離開時才執行」的判斷閘;:func:`keep_awake` /
:func:`keep_awake_on` 阻止螢幕與系統睡眠,讓整夜執行不被打斷。``display=False`` 會保持系統清醒但允許
螢幕變黑(對無頭機器較省電)。

執行器指令
----------

``AC_idle_seconds``(→ ``{idle_seconds}``)、``AC_is_idle``(``threshold`` →
``{idle, idle_seconds}``)、``AC_plan_keep_awake``(``display`` / ``system`` → 計畫)、
``AC_keep_awake_on``(``display`` / ``system`` → 生效中的計畫)與 ``AC_allow_sleep``
(→ ``{released}``)。皆以對應的 ``ac_*`` MCP 工具(讀取為唯讀、keep-awake 開 / 關為僅副作用)
及 Script Builder 指令(位於 **Shell** 分類下)形式提供。:func:`keep_awake` context manager
則是具範圍使用的 Python API 介面。
37 changes: 37 additions & 0 deletions docs/source/Zh/doc/new_features/v205_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
解析檔案類型已註冊的應用程式
============================

:func:`open_path`(``shell_open``)用註冊的應用程式開啟檔案;``file_assoc`` 回答相反的唯讀問題——
那個應用程式是「哪一個」?給定 ``report.pdf``(或裸的 ``.pdf`` / ``pdf``),它會透過 Windows
``AssocQueryStringW`` shell API 回傳已註冊的執行檔、友善應用程式名稱、開啟命令列與 MIME 內容類型。

* :func:`normalize_ext` ——純輔助函式,把路徑 / ``.ext`` / 裸 ``ext`` 轉成小寫的 ``.ext``,
* :func:`file_association` ——透過可注入的 ``resolver`` 接縫執行查詢(預設為真正的 shell API)。

組裝邏輯可透過可注入的 ``resolver`` 在非 Windows 上單元測試。不匯入 ``PySide6``。

無頭 API
--------

.. code-block:: python

from je_auto_control import file_association, normalize_ext

normalize_ext("report.PDF") # ".pdf"
normalize_ext("archive.tar.gz") # ".gz"

file_association("report.pdf")
# {"ext": ".pdf", "command": "...AcroRd32.exe \"%1\"",
# "exe": "...AcroRd32.exe", "friendly": "Adobe Acrobat",
# "content_type": "application/pdf"}

當該類型未註冊任何應用程式時,應用程式欄位為 ``None``。這是 :func:`open_path` 的自然搭檔:
``file_association`` 告訴你「什麼」會開啟檔案(可斷言「PDF 用 Acrobat 開,不是瀏覽器」),而
``open_path`` 實際開啟它。即時查詢使用 Windows shell API;其他平台請傳入自己的 ``resolver``。

執行器指令
----------

``AC_normalize_ext``(``target`` → ``{ext}``,純)與 ``AC_file_association``
(``target`` → 關聯 dict)。皆以對應的 ``ac_*`` MCP 工具(皆唯讀)及 Script Builder 指令
(位於 **Shell** 分類下)形式提供。
13 changes: 13 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@
)
# Reactive UIA event waits (focus-changed)
from je_auto_control.utils.ax_events import wait_for_focus_change
# Open a file with its default app / a URL in the default browser
from je_auto_control.utils.shell_open import open_path, plan_open
# Detect user-idle time and keep the machine awake during unattended runs
from je_auto_control.utils.idle_keepawake import (
allow_sleep, idle_seconds, is_idle, keep_awake, keep_awake_on,
plan_keep_awake,
)
# Resolve which application is registered to open a given file type
from je_auto_control.utils.file_assoc import file_association, normalize_ext
# Rich clipboard formats — RTF + CSV/TSV codecs and Windows get / set
from je_auto_control.utils.clipboard_rich_formats import (
build_rtf, csv_to_rows, get_clipboard_csv, get_clipboard_rtf, rows_to_csv,
Expand Down Expand Up @@ -1700,6 +1709,10 @@ def start_autocontrol_gui(*args, **kwargs):
"legacy_info", "legacy_default_action",
"get_selection", "list_views", "set_view",
"wait_for_focus_change",
"plan_open", "open_path",
"idle_seconds", "is_idle", "plan_keep_awake",
"keep_awake", "keep_awake_on", "allow_sleep",
"normalize_ext", "file_association",
"build_rtf", "rtf_to_text", "rows_to_csv", "csv_to_rows",
"set_clipboard_rtf", "get_clipboard_rtf",
"set_clipboard_csv", "get_clipboard_csv",
Expand Down
68 changes: 68 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4250,6 +4250,74 @@ def _add_work_queue_specs(specs: List[CommandSpec]) -> None:
"AC_shell_command", "Shell", "Shell Command",
fields=(FieldSpec("shell_command", FieldType.STRING),),
))
specs.append(CommandSpec(
"AC_open_path", "Shell", "Open File / URL (default app)",
fields=(
FieldSpec("target", FieldType.STRING,
placeholder="report.pdf or https://example.com"),
FieldSpec("verb", FieldType.STRING, optional=True, default="open",
placeholder="open / print / edit"),
),
description="Open a file with its default app, or a URL in the browser.",
))
specs.append(CommandSpec(
"AC_plan_open", "Shell", "Plan Open (classify)",
fields=(
FieldSpec("target", FieldType.STRING),
FieldSpec("verb", FieldType.STRING, optional=True, default="open"),
),
description="Classify how a file/URL would be opened (pure, no launch).",
))
specs.append(CommandSpec(
"AC_idle_seconds", "Shell", "Idle Seconds",
fields=(),
description="Seconds since the last user keyboard / mouse input.",
))
specs.append(CommandSpec(
"AC_is_idle", "Shell", "Is User Idle",
fields=(
FieldSpec("threshold", FieldType.FLOAT, default=300.0,
placeholder="idle seconds threshold"),
),
description="True if the user has been idle for >= threshold seconds.",
))
specs.append(CommandSpec(
"AC_plan_keep_awake", "Shell", "Plan Keep Awake",
fields=(
FieldSpec("display", FieldType.BOOL, optional=True, default=True),
FieldSpec("system", FieldType.BOOL, optional=True, default=True),
),
description="Describe a keep-awake request (pure, no OS call).",
))
specs.append(CommandSpec(
"AC_keep_awake_on", "Shell", "Keep Machine Awake",
fields=(
FieldSpec("display", FieldType.BOOL, optional=True, default=True),
FieldSpec("system", FieldType.BOOL, optional=True, default=True),
),
description="Keep the machine awake until Allow Sleep is run.",
))
specs.append(CommandSpec(
"AC_allow_sleep", "Shell", "Allow Machine to Sleep",
fields=(),
description="Release a previously-started keep-awake.",
))
specs.append(CommandSpec(
"AC_normalize_ext", "Shell", "Normalize Extension",
fields=(
FieldSpec("target", FieldType.STRING,
placeholder="report.pdf or .pdf or pdf"),
),
description="Lowercased file extension (with dot) of a path / ext.",
))
specs.append(CommandSpec(
"AC_file_association", "Shell", "File Association (default app)",
fields=(
FieldSpec("target", FieldType.STRING,
placeholder="report.pdf or .pdf"),
),
description="Which app is registered to open a file type (Windows).",
))
specs.append(CommandSpec(
"AC_take_golden", "Report", "Capture Golden Image",
fields=(FieldSpec("path", FieldType.FILE_PATH),),
Expand Down
Loading
Loading