From 33d678d84e0cf56db7a8b79a3de842b51a9aabae Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Thu, 17 Oct 2024 01:25:51 +0100 Subject: [PATCH 1/3] feat:close application intent (#11) * feat:close application intent * feat:close application intent * Apply suggestions from coderabbit review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix:expand_options and use shlex --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- README.md | 1 + __init__.py | 98 ++++++++++++++++++++++++++------- locale/en-us/close.intent | 5 ++ requirements.txt | 4 +- translations/en-us/intents.json | 17 ++++-- 5 files changed, 100 insertions(+), 25 deletions(-) create mode 100644 locale/en-us/close.intent diff --git a/README.md b/README.md index 25144e8..6f3f332 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Scanned folders: ## Examples * "Open Volume Control" * "Launch Firefox" +* "Close Firefox" ## Category **Productivity** diff --git a/__init__.py b/__init__.py index 6486e9f..e32bdde 100644 --- a/__init__.py +++ b/__init__.py @@ -1,9 +1,16 @@ import os +import shlex +import subprocess +import time from os import listdir from os.path import expanduser, isdir, join +import psutil +from langcodes import closest_match +from ovos_utils.bracket_expansion import expand_options +from ovos_utils.lang import standardize_lang_tag from ovos_utils.log import LOG -from ovos_utils.parse import match_one +from ovos_utils.parse import match_one, fuzzy_match from ovos_workshop.skills.fallback import FallbackSkill from padacioso import IntentContainer @@ -31,14 +38,20 @@ def initialize(self): self.register_fallback(self.handle_fallback, 4) def register_fallback_intents(self): - # TODO close application intent + intents = ["close", "launch"] for lang in os.listdir(f"{self.root_dir}/locale"): - self.intent_matchers[lang] = IntentContainer() - launch = join(self.root_dir, "locale", self.lang, "launch.intent") - with open(launch) as f: - samples = [l for l in f.read().split("\n") - if not l.startswith("#") and l.strip()] - self.intent_matchers[lang].add_intent('launch', samples) + for intent_name in intents: + launch = join(self.root_dir, "locale", lang, f"{intent_name}.intent") + if not os.path.isfile(launch): + continue + lang = standardize_lang_tag(lang) + if lang not in self.intent_matchers: + self.intent_matchers[lang] = IntentContainer() + with open(launch) as f: + samples = [option for line in f.read().split("\n") + if not line.startswith("#") and line.strip() + for option in expand_options(line)] + self.intent_matchers[lang].add_intent(intent_name, samples) def get_app_aliases(self): apps = self.settings.get("user_commands") or {} @@ -55,8 +68,8 @@ def get_app_aliases(self): is_app = True if os.path.isdir(path): continue - with open(path) as f: - for l in f.read().split("\n"): + with open(path) as fi: + for l in fi.read().split("\n"): if "Name=" in l: name = l.split("Name=")[-1] names.append(norm(name)) @@ -91,26 +104,73 @@ def get_app_aliases(self): LOG.debug(f"found app {f} with aliases: {names}") return apps + def launch_app(self, app: str) -> bool: + applist = self.get_app_aliases() + cmd, score = match_one(app.title(), applist) + if score >= self.settings.get("thresh", 0.85): + LOG.info(f"Matched application: {app} (command: {cmd})") + try: + # Launch the application in a new process without blocking + subprocess.Popen(shlex.split(cmd)) + return True + except Exception as e: + LOG.error(f"Failed to launch {app}: {e}") + return False + + def close_app(self, app: str) -> bool: + """Close the application with the given name.""" + applist = self.get_app_aliases() + + cmd, _ = match_one(app.title(), applist) + cmd = cmd.split(" ")[0].split("/")[-1] + terminated = [] + + for proc in psutil.process_iter(['pid', 'name']): + score = fuzzy_match(cmd, proc.info['name']) + if score > 0.9: + LOG.debug(f"Matched '{app}' to {proc}") + try: + LOG.info(f"Terminating process: {proc.info['name']} (PID: {proc.info['pid']})") + proc.terminate() # or process.kill() to forcefully kill + terminated.append(proc.info['pid']) + + except (psutil.NoSuchProcess, psutil.AccessDenied): + LOG.error(f"Failed to terminate {proc}") + + if terminated: + LOG.debug(f"Terminated PIDs: {terminated}") + return True + return False + def handle_fallback(self, message): utterance = message.data.get("utterance", "") - if self.lang not in self.intent_matchers: + best_lang, score = closest_match(self.lang, list(self.intent_matchers.keys())) + + if score >= 10: + # unsupported lang return False - res = self.intent_matchers[self.lang].calc_intent(utterance) + best_lang = standardize_lang_tag(best_lang) + + res = self.intent_matchers[best_lang].calc_intent(utterance) + app = res.get('entities', {}).get("application") if app: - applist = self.get_app_aliases() - cmd, score = match_one(app.title(), applist) - if score >= self.settings.get("thresh", 0.85): - LOG.info(f"Executing command: {cmd}") - os.system(cmd) - return True + if res["name"] == "launch": + return self.launch_app(app) + elif res["name"] == "close": + return self.close_app(app) if __name__ == "__main__": + LOG.set_level("DEBUG") from ovos_utils.fakebus import FakeBus from ovos_bus_client.message import Message s = ApplicationLauncherSkill(skill_id="fake.test", bus=FakeBus()) - s.handle_fallback(Message("", {"utterance": "launch firefox"})) + s.handle_fallback(Message("", {"utterance": "abrir firefox", "lang": "pt-pt"})) + time.sleep(2) + # s.handle_fallback(Message("", {"utterance": "kill firefox"})) + time.sleep(2) + s.handle_fallback(Message("", {"utterance": "launch firefox", "lang": "en-UK"})) diff --git a/locale/en-us/close.intent b/locale/en-us/close.intent new file mode 100644 index 0000000..17edaef --- /dev/null +++ b/locale/en-us/close.intent @@ -0,0 +1,5 @@ +close {application} +terminate {application} +kill {application} +exit {application} +quit {application} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 743b627..8c6208e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ padacioso>=0.1.1 -ovos-workshop>=0.0.15,<2.0.0 \ No newline at end of file +ovos-workshop>=0.0.15,<2.0.0 +ovos-utils>=0.3.5 +psutil \ No newline at end of file diff --git a/translations/en-us/intents.json b/translations/en-us/intents.json index b3255cd..9617ab2 100644 --- a/translations/en-us/intents.json +++ b/translations/en-us/intents.json @@ -1,7 +1,14 @@ { - "launch.intent": [ - "launch {application}", - "open {application}", - "run {application}" - ] + "launch.intent": [ + "launch {application}", + "open {application}", + "run {application}" + ], + "close.intent": [ + "close {application}", + "terminate {application}", + "kill {application}", + "exit {application}", + "quit {application}" + ] } \ No newline at end of file From d298e5fd43f28a0756b635290b938fd1cc743471 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 17 Oct 2024 00:26:03 +0000 Subject: [PATCH 2/3] Increment Version to 0.3.0a1 --- version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.py b/version.py index a210c75..c75405b 100644 --- a/version.py +++ b/version.py @@ -1,6 +1,6 @@ # START_VERSION_BLOCK VERSION_MAJOR = 0 -VERSION_MINOR = 2 -VERSION_BUILD = 1 -VERSION_ALPHA = 0 +VERSION_MINOR = 3 +VERSION_BUILD = 0 +VERSION_ALPHA = 1 # END_VERSION_BLOCK From d853a18c174973d3314d89faf5bbd11725efec1f Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 17 Oct 2024 00:26:23 +0000 Subject: [PATCH 3/3] Update Changelog --- CHANGELOG.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0ac06..98030cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,16 @@ # Changelog -## [0.2.1a1](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/tree/0.2.1a1) (2024-10-15) +## [0.3.0a1](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/tree/0.3.0a1) (2024-10-17) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/compare/V0.1.0...0.2.1a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/compare/V0.2.1...0.3.0a1) **Merged pull requests:** -- fix:update\_requirements [\#9](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/pull/9) ([JarbasAl](https://github.com/JarbasAl)) -- Add Catalan translation [\#8](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/pull/8) ([gitlocalize-app[bot]](https://github.com/apps/gitlocalize-app)) +- feat:close application intent [\#11](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/pull/11) ([JarbasAl](https://github.com/JarbasAl)) + +## [V0.2.1](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/tree/V0.2.1) (2024-10-15) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-skill-application-launcher/compare/0.2.1...V0.2.1)