diff --git a/.github/workflows/validate-plugins.py b/.github/workflows/validate-plugins.py index 05acc21..f1f46f4 100644 --- a/.github/workflows/validate-plugins.py +++ b/.github/workflows/validate-plugins.py @@ -111,6 +111,7 @@ ENTRY_TYPES = ("widget", "panel", "shortcut", "desktop_widget", "launcher_provid SETTING_OWNER_TYPES = ("widget", "panel", "desktop_widget", "launcher_provider") SETTING_TYPES = {"string", "string_list", "bool", "glyph", "select", "folder", "file", "int", "color"} PANEL_PLACEMENTS = {"attached", "floating"} +PANEL_KEYBOARD_FOCUS = {"on_demand", "exclusive", "none"} PANEL_POSITIONS = { "auto", "center", @@ -132,7 +133,19 @@ ROOT_FIELDS = set(ROOT_STRING_FIELDS) | set(ROOT_ARRAY_FIELDS) | set(ENTRY_TYPES BASE_ENTRY_FIELDS = {"id", "entry"} ENTRY_FIELDS = { "widget": BASE_ENTRY_FIELDS | {"setting"}, - "panel": BASE_ENTRY_FIELDS | {"setting", "width", "height", "placement", "position", "open_near_click"}, + "panel": BASE_ENTRY_FIELDS + | { + "setting", + "width", + "height", + "placement", + "position", + "open_near_click", + "dismiss_on_outside_click", + "keyboard_focus", + "persistent", + "capture_keys", + }, "desktop_widget": BASE_ENTRY_FIELDS | {"setting"}, "service": BASE_ENTRY_FIELDS, "shortcut": BASE_ENTRY_FIELDS, @@ -686,7 +699,13 @@ class Validator: if not is_non_empty_string(category.get("glyph")): self.add_context_error(manifest_path, category_context, "glyph must be a non-empty string") - def validate_panel_fields(self, manifest_path: Path, context: str, entry: dict[str, Any]) -> None: + def validate_panel_fields( + self, + manifest_path: Path, + context: str, + entry: dict[str, Any], + plugin_api: Any, + ) -> None: # Mirrors the shell parser: a positive number (logical px) or the # literal string "fill" (span the output's available extent; requires # floating placement). @@ -726,6 +745,39 @@ class Validator: if "open_near_click" in entry and not isinstance(entry["open_near_click"], bool): self.add_context_error(manifest_path, context, "open_near_click must be a bool") + if "dismiss_on_outside_click" in entry: + if not is_int(plugin_api) or plugin_api < 8: + self.add_context_error( + manifest_path, + context, + "dismiss_on_outside_click requires plugin_api >= 8", + ) + elif not isinstance(entry["dismiss_on_outside_click"], bool): + self.add_context_error(manifest_path, context, "dismiss_on_outside_click must be a bool") + + if "keyboard_focus" in entry: + if not is_int(plugin_api) or plugin_api < 10: + self.add_context_error(manifest_path, context, "keyboard_focus requires plugin_api >= 10") + elif entry["keyboard_focus"] not in PANEL_KEYBOARD_FOCUS: + valid = ", ".join(sorted(PANEL_KEYBOARD_FOCUS)) + self.add_context_error(manifest_path, context, f"keyboard_focus must be one of: {valid}") + + if "persistent" in entry: + if not is_int(plugin_api) or plugin_api < 11: + self.add_context_error(manifest_path, context, "persistent requires plugin_api >= 11") + elif not isinstance(entry["persistent"], bool): + self.add_context_error(manifest_path, context, "persistent must be a bool") + + if "capture_keys" in entry: + if not is_int(plugin_api) or plugin_api < 13: + self.add_context_error(manifest_path, context, "capture_keys requires plugin_api >= 13") + elif not isinstance(entry["capture_keys"], list) or not all( + is_non_empty_string(chord) for chord in entry["capture_keys"] + ): + self.add_context_error( + manifest_path, context, "capture_keys must be an array of key chord strings" + ) + def validate_entries( self, manifest_path: Path, @@ -771,7 +823,7 @@ class Validator: self.validate_launcher_fields(manifest_path, context, entry) if entry_type == "panel": - self.validate_panel_fields(manifest_path, context, entry) + self.validate_panel_fields(manifest_path, context, entry, manifest.get("plugin_api")) if entry_type in SETTING_OWNER_TYPES and "setting" in entry: self.validate_settings(