ovos-phal-plugin-mac 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ovos_phal_plugin_mac-0.1.0/PKG-INFO +79 -0
- ovos_phal_plugin_mac-0.1.0/README.md +59 -0
- ovos_phal_plugin_mac-0.1.0/ovos_phal_plugin_mac.egg-info/PKG-INFO +79 -0
- ovos_phal_plugin_mac-0.1.0/ovos_phal_plugin_mac.egg-info/SOURCES.txt +13 -0
- ovos_phal_plugin_mac-0.1.0/ovos_phal_plugin_mac.egg-info/dependency_links.txt +1 -0
- ovos_phal_plugin_mac-0.1.0/ovos_phal_plugin_mac.egg-info/entry_points.txt +2 -0
- ovos_phal_plugin_mac-0.1.0/ovos_phal_plugin_mac.egg-info/requires.txt +9 -0
- ovos_phal_plugin_mac-0.1.0/ovos_phal_plugin_mac.egg-info/top_level.txt +1 -0
- ovos_phal_plugin_mac-0.1.0/phal_plugin_mac/__init__.py +239 -0
- ovos_phal_plugin_mac-0.1.0/phal_plugin_mac/version.py +1 -0
- ovos_phal_plugin_mac-0.1.0/pyproject.toml +47 -0
- ovos_phal_plugin_mac-0.1.0/requirements/requirements-dev.txt +2 -0
- ovos_phal_plugin_mac-0.1.0/requirements/requirements.txt +5 -0
- ovos_phal_plugin_mac-0.1.0/setup.cfg +4 -0
- ovos_phal_plugin_mac-0.1.0/test/test_plugin.py +330 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ovos-phal-plugin-mac
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: # PHAL-plugin-mac
|
|
5
|
+
Author-email: Mike Gray <mike@oscillatelabs.net>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/OscillateLabsLLC/ovos-phal-plugin-mac
|
|
8
|
+
Project-URL: Repository, https://github.com/OscillateLabsLLC/ovos-phal-plugin-mac
|
|
9
|
+
Keywords: ovos,plugin,voice,assistant
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: ovos-utils
|
|
13
|
+
Requires-Dist: ovos-bus-client
|
|
14
|
+
Requires-Dist: ovos-workshop
|
|
15
|
+
Requires-Dist: ovos-plugin-manager
|
|
16
|
+
Requires-Dist: osascript==2020.12.3
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest; extra == "test"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
20
|
+
|
|
21
|
+
# PHAL-plugin-mac
|
|
22
|
+
|
|
23
|
+
Provides system specific commands to OVOS for Mac OS. Creates fake ducking for OCP/ovos-media, barge-in volume adjustment, GUI button compatability, and allows for management of OVOS services.
|
|
24
|
+
|
|
25
|
+
Tested on Mac OS Sonoma 14.6.1, but should be valid for all currently supported Mac OS versions as of August 2024.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
`pip install PHAL-plugin-mac`
|
|
30
|
+
|
|
31
|
+
Requires associated skill for volume-based voice commands:
|
|
32
|
+
|
|
33
|
+
- skill-ovos-volume
|
|
34
|
+
|
|
35
|
+
## Config
|
|
36
|
+
|
|
37
|
+
This plugin is not an Admin plugin, but in order for most of the system level commands to work, the user must be in the sudoers file. This can be done by running the following command in the terminal:
|
|
38
|
+
|
|
39
|
+
`sudo vim /private/etc/sudoers.d/<username>`
|
|
40
|
+
Replace <username> with the username of the user running the OVOS instance.
|
|
41
|
+
|
|
42
|
+
Then add the following lines to the file:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
<username> ALL=(ALL) NOPASSWD: /usr/sbin/systemsetup
|
|
46
|
+
<username> ALL=(ALL) NOPASSWD: /usr/sbin/shutdown
|
|
47
|
+
<username> ALL=(ALL) NOPASSWD: /usr/bin/sntp
|
|
48
|
+
<username> ALL=(ALL) NOPASSWD: /usr/bin/defaults
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Be sure to replace `<username>` with the username of the user running the OVOS instance.
|
|
52
|
+
|
|
53
|
+
**NOTE:** Do this at your own risk. This is a security risk and should only be done if you understand the implications.
|
|
54
|
+
|
|
55
|
+
## Handle bus events to interact with the OS
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# System
|
|
59
|
+
self.bus.on("system.ntp.sync", self.handle_ntp_sync_request)
|
|
60
|
+
self.bus.on("system.ssh.status", self.handle_ssh_status)
|
|
61
|
+
self.bus.on("system.ssh.enable", self.handle_ssh_enable_request)
|
|
62
|
+
self.bus.on("system.ssh.disable", self.handle_ssh_disable_request)
|
|
63
|
+
self.bus.on("system.reboot", self.handle_reboot_request)
|
|
64
|
+
self.bus.on("system.shutdown", self.handle_shutdown_request)
|
|
65
|
+
self.bus.on("system.configure.language", self.handle_configure_language_request)
|
|
66
|
+
self.bus.on("system.mycroft.service.restart", self.handle_mycroft_restart_request)
|
|
67
|
+
# Volume
|
|
68
|
+
self.bus.on("mycroft.volume.get", self.handle_volume_set)
|
|
69
|
+
self.bus.on("mycroft.volume.set", self.handle_volume_set)
|
|
70
|
+
self.bus.on("mycroft.volume.decrease", self.handle_volume_decrease)
|
|
71
|
+
self.bus.on("mycroft.volume.increase", self.handle_volume_increase)
|
|
72
|
+
self.bus.on("mycroft.volume.mute", self.handle_volume_mute)
|
|
73
|
+
self.bus.on("mycroft.volume.unmute", self.handle_volume_unmute)
|
|
74
|
+
self.bus.on("mycroft.volume.mute.toggle", self.handle_volume_mute_toggle)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Credits
|
|
78
|
+
|
|
79
|
+
Oscillate Labs (@mikejgray)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# PHAL-plugin-mac
|
|
2
|
+
|
|
3
|
+
Provides system specific commands to OVOS for Mac OS. Creates fake ducking for OCP/ovos-media, barge-in volume adjustment, GUI button compatability, and allows for management of OVOS services.
|
|
4
|
+
|
|
5
|
+
Tested on Mac OS Sonoma 14.6.1, but should be valid for all currently supported Mac OS versions as of August 2024.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
`pip install PHAL-plugin-mac`
|
|
10
|
+
|
|
11
|
+
Requires associated skill for volume-based voice commands:
|
|
12
|
+
|
|
13
|
+
- skill-ovos-volume
|
|
14
|
+
|
|
15
|
+
## Config
|
|
16
|
+
|
|
17
|
+
This plugin is not an Admin plugin, but in order for most of the system level commands to work, the user must be in the sudoers file. This can be done by running the following command in the terminal:
|
|
18
|
+
|
|
19
|
+
`sudo vim /private/etc/sudoers.d/<username>`
|
|
20
|
+
Replace <username> with the username of the user running the OVOS instance.
|
|
21
|
+
|
|
22
|
+
Then add the following lines to the file:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
<username> ALL=(ALL) NOPASSWD: /usr/sbin/systemsetup
|
|
26
|
+
<username> ALL=(ALL) NOPASSWD: /usr/sbin/shutdown
|
|
27
|
+
<username> ALL=(ALL) NOPASSWD: /usr/bin/sntp
|
|
28
|
+
<username> ALL=(ALL) NOPASSWD: /usr/bin/defaults
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Be sure to replace `<username>` with the username of the user running the OVOS instance.
|
|
32
|
+
|
|
33
|
+
**NOTE:** Do this at your own risk. This is a security risk and should only be done if you understand the implications.
|
|
34
|
+
|
|
35
|
+
## Handle bus events to interact with the OS
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# System
|
|
39
|
+
self.bus.on("system.ntp.sync", self.handle_ntp_sync_request)
|
|
40
|
+
self.bus.on("system.ssh.status", self.handle_ssh_status)
|
|
41
|
+
self.bus.on("system.ssh.enable", self.handle_ssh_enable_request)
|
|
42
|
+
self.bus.on("system.ssh.disable", self.handle_ssh_disable_request)
|
|
43
|
+
self.bus.on("system.reboot", self.handle_reboot_request)
|
|
44
|
+
self.bus.on("system.shutdown", self.handle_shutdown_request)
|
|
45
|
+
self.bus.on("system.configure.language", self.handle_configure_language_request)
|
|
46
|
+
self.bus.on("system.mycroft.service.restart", self.handle_mycroft_restart_request)
|
|
47
|
+
# Volume
|
|
48
|
+
self.bus.on("mycroft.volume.get", self.handle_volume_set)
|
|
49
|
+
self.bus.on("mycroft.volume.set", self.handle_volume_set)
|
|
50
|
+
self.bus.on("mycroft.volume.decrease", self.handle_volume_decrease)
|
|
51
|
+
self.bus.on("mycroft.volume.increase", self.handle_volume_increase)
|
|
52
|
+
self.bus.on("mycroft.volume.mute", self.handle_volume_mute)
|
|
53
|
+
self.bus.on("mycroft.volume.unmute", self.handle_volume_unmute)
|
|
54
|
+
self.bus.on("mycroft.volume.mute.toggle", self.handle_volume_mute_toggle)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Credits
|
|
58
|
+
|
|
59
|
+
Oscillate Labs (@mikejgray)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ovos-phal-plugin-mac
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: # PHAL-plugin-mac
|
|
5
|
+
Author-email: Mike Gray <mike@oscillatelabs.net>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/OscillateLabsLLC/ovos-phal-plugin-mac
|
|
8
|
+
Project-URL: Repository, https://github.com/OscillateLabsLLC/ovos-phal-plugin-mac
|
|
9
|
+
Keywords: ovos,plugin,voice,assistant
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: ovos-utils
|
|
13
|
+
Requires-Dist: ovos-bus-client
|
|
14
|
+
Requires-Dist: ovos-workshop
|
|
15
|
+
Requires-Dist: ovos-plugin-manager
|
|
16
|
+
Requires-Dist: osascript==2020.12.3
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest; extra == "test"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
20
|
+
|
|
21
|
+
# PHAL-plugin-mac
|
|
22
|
+
|
|
23
|
+
Provides system specific commands to OVOS for Mac OS. Creates fake ducking for OCP/ovos-media, barge-in volume adjustment, GUI button compatability, and allows for management of OVOS services.
|
|
24
|
+
|
|
25
|
+
Tested on Mac OS Sonoma 14.6.1, but should be valid for all currently supported Mac OS versions as of August 2024.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
`pip install PHAL-plugin-mac`
|
|
30
|
+
|
|
31
|
+
Requires associated skill for volume-based voice commands:
|
|
32
|
+
|
|
33
|
+
- skill-ovos-volume
|
|
34
|
+
|
|
35
|
+
## Config
|
|
36
|
+
|
|
37
|
+
This plugin is not an Admin plugin, but in order for most of the system level commands to work, the user must be in the sudoers file. This can be done by running the following command in the terminal:
|
|
38
|
+
|
|
39
|
+
`sudo vim /private/etc/sudoers.d/<username>`
|
|
40
|
+
Replace <username> with the username of the user running the OVOS instance.
|
|
41
|
+
|
|
42
|
+
Then add the following lines to the file:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
<username> ALL=(ALL) NOPASSWD: /usr/sbin/systemsetup
|
|
46
|
+
<username> ALL=(ALL) NOPASSWD: /usr/sbin/shutdown
|
|
47
|
+
<username> ALL=(ALL) NOPASSWD: /usr/bin/sntp
|
|
48
|
+
<username> ALL=(ALL) NOPASSWD: /usr/bin/defaults
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Be sure to replace `<username>` with the username of the user running the OVOS instance.
|
|
52
|
+
|
|
53
|
+
**NOTE:** Do this at your own risk. This is a security risk and should only be done if you understand the implications.
|
|
54
|
+
|
|
55
|
+
## Handle bus events to interact with the OS
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# System
|
|
59
|
+
self.bus.on("system.ntp.sync", self.handle_ntp_sync_request)
|
|
60
|
+
self.bus.on("system.ssh.status", self.handle_ssh_status)
|
|
61
|
+
self.bus.on("system.ssh.enable", self.handle_ssh_enable_request)
|
|
62
|
+
self.bus.on("system.ssh.disable", self.handle_ssh_disable_request)
|
|
63
|
+
self.bus.on("system.reboot", self.handle_reboot_request)
|
|
64
|
+
self.bus.on("system.shutdown", self.handle_shutdown_request)
|
|
65
|
+
self.bus.on("system.configure.language", self.handle_configure_language_request)
|
|
66
|
+
self.bus.on("system.mycroft.service.restart", self.handle_mycroft_restart_request)
|
|
67
|
+
# Volume
|
|
68
|
+
self.bus.on("mycroft.volume.get", self.handle_volume_set)
|
|
69
|
+
self.bus.on("mycroft.volume.set", self.handle_volume_set)
|
|
70
|
+
self.bus.on("mycroft.volume.decrease", self.handle_volume_decrease)
|
|
71
|
+
self.bus.on("mycroft.volume.increase", self.handle_volume_increase)
|
|
72
|
+
self.bus.on("mycroft.volume.mute", self.handle_volume_mute)
|
|
73
|
+
self.bus.on("mycroft.volume.unmute", self.handle_volume_unmute)
|
|
74
|
+
self.bus.on("mycroft.volume.mute.toggle", self.handle_volume_mute_toggle)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Credits
|
|
78
|
+
|
|
79
|
+
Oscillate Labs (@mikejgray)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
ovos_phal_plugin_mac.egg-info/PKG-INFO
|
|
4
|
+
ovos_phal_plugin_mac.egg-info/SOURCES.txt
|
|
5
|
+
ovos_phal_plugin_mac.egg-info/dependency_links.txt
|
|
6
|
+
ovos_phal_plugin_mac.egg-info/entry_points.txt
|
|
7
|
+
ovos_phal_plugin_mac.egg-info/requires.txt
|
|
8
|
+
ovos_phal_plugin_mac.egg-info/top_level.txt
|
|
9
|
+
phal_plugin_mac/__init__.py
|
|
10
|
+
phal_plugin_mac/version.py
|
|
11
|
+
requirements/requirements-dev.txt
|
|
12
|
+
requirements/requirements.txt
|
|
13
|
+
test/test_plugin.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ovos_phal_plugin_mac
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""macOS PHAL plugin for OVOS."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import osascript
|
|
6
|
+
from ovos_bus_client import Message
|
|
7
|
+
from ovos_plugin_manager.phal import PHALPlugin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MacOSPlugin(PHALPlugin):
|
|
11
|
+
"""macOS PHAL plugin for OVOS."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, bus=None, config=None, *args, **kwargs):
|
|
14
|
+
super().__init__(bus=bus, config=config, name="ovos-PHAL-plugin-mac", *args, **kwargs)
|
|
15
|
+
# System events
|
|
16
|
+
self.bus.on("system.ntp.sync", self.handle_ntp_sync_request)
|
|
17
|
+
self.bus.on("system.ssh.status", self.handle_ssh_status)
|
|
18
|
+
self.bus.on("system.ssh.enable", self.handle_ssh_enable_request)
|
|
19
|
+
self.bus.on("system.ssh.disable", self.handle_ssh_disable_request)
|
|
20
|
+
self.bus.on("system.reboot", self.handle_reboot_request)
|
|
21
|
+
self.bus.on("system.shutdown", self.handle_shutdown_request)
|
|
22
|
+
self.bus.on("system.configure.language", self.handle_configure_language_request)
|
|
23
|
+
self.bus.on("system.mycroft.service.restart", self.handle_mycroft_restart_request)
|
|
24
|
+
# Volume events
|
|
25
|
+
self.bus.on("mycroft.volume.get", self.handle_volume_get)
|
|
26
|
+
self.bus.on("mycroft.volume.set", self.handle_volume_set)
|
|
27
|
+
self.bus.on("mycroft.volume.decrease", self.handle_volume_decrease)
|
|
28
|
+
self.bus.on("mycroft.volume.increase", self.handle_volume_increase)
|
|
29
|
+
self.bus.on("mycroft.volume.mute", self.handle_volume_mute)
|
|
30
|
+
self.bus.on("mycroft.volume.unmute", self.handle_volume_unmute)
|
|
31
|
+
self.bus.on("mycroft.volume.mute.toggle", self.handle_volume_mute_toggle)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def allow_reboot(self):
|
|
35
|
+
"""Check if reboot is allowed."""
|
|
36
|
+
return self.config.get("allow_reboot", True)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def allow_shutdown(self):
|
|
40
|
+
"""Check if shutdown is allowed."""
|
|
41
|
+
return self.config.get("allow_shutdown", True)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def volume_change_interval(self):
|
|
45
|
+
"""Get the volume change interval percentage. Defaults to 10."""
|
|
46
|
+
return self.config.get("volume_change_interval", 10)
|
|
47
|
+
|
|
48
|
+
def _run_command(self, command, check=True):
|
|
49
|
+
"""Private method to run shell commands."""
|
|
50
|
+
try:
|
|
51
|
+
return subprocess.run(command, check=check, capture_output=True, text=True)
|
|
52
|
+
except Exception as err:
|
|
53
|
+
self.log.exception("Error running command: %s", err)
|
|
54
|
+
|
|
55
|
+
def _run_applescript(self, script):
|
|
56
|
+
"""Private method to run AppleScript."""
|
|
57
|
+
return_code, out, err = osascript.run(script)
|
|
58
|
+
self.log.debug("Return code for %s was %s", script, return_code)
|
|
59
|
+
if return_code and return_code > 0:
|
|
60
|
+
self.log.error("Error code %s running AppleScript: %s", return_code, err)
|
|
61
|
+
return
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
def _set_volume(self, volume):
|
|
65
|
+
"""Set the system volume (0-100)."""
|
|
66
|
+
script = f"set volume output volume {volume}"
|
|
67
|
+
self._run_applescript(script)
|
|
68
|
+
|
|
69
|
+
def _get_volume(self):
|
|
70
|
+
"""Get the current system volume (0-100)."""
|
|
71
|
+
script = "output volume of (get volume settings)"
|
|
72
|
+
result = self._run_applescript(script)
|
|
73
|
+
self.log.debug("Current volume: %s", result)
|
|
74
|
+
return int(result) if result else None
|
|
75
|
+
|
|
76
|
+
def _is_muted(self):
|
|
77
|
+
"""Check if the system is muted."""
|
|
78
|
+
script = "output muted of (get volume settings)"
|
|
79
|
+
result = self._run_applescript(script)
|
|
80
|
+
if not result:
|
|
81
|
+
return False
|
|
82
|
+
return "true" in result.lower()
|
|
83
|
+
|
|
84
|
+
def _set_mute(self, mute):
|
|
85
|
+
"""Set the system mute state."""
|
|
86
|
+
script = "set volume with output muted"
|
|
87
|
+
if not mute:
|
|
88
|
+
script = "set volume without output muted"
|
|
89
|
+
self._run_applescript(script)
|
|
90
|
+
|
|
91
|
+
def handle_volume_get(self, message: Message):
|
|
92
|
+
"""Handle the volume get request."""
|
|
93
|
+
volume = self._get_volume()
|
|
94
|
+
if volume:
|
|
95
|
+
self.bus.emit(message.reply("mycroft.volume.get.response", {"percent": volume}))
|
|
96
|
+
else:
|
|
97
|
+
self.log.error("Error getting Mac volume")
|
|
98
|
+
|
|
99
|
+
def handle_volume_set(self, message: Message):
|
|
100
|
+
"""Handle the volume set request."""
|
|
101
|
+
volume = message.data.get("percent", 50)
|
|
102
|
+
volume = max(0, min(100, volume)) # Ensure volume is between 0 and 100
|
|
103
|
+
self._set_volume(volume)
|
|
104
|
+
self.bus.emit(message.forward("mycroft.volume.set.confirm", {"percent": volume}))
|
|
105
|
+
|
|
106
|
+
def handle_volume_decrease(self, message: Message):
|
|
107
|
+
"""Handle the volume decrease request."""
|
|
108
|
+
current_volume = self._get_volume()
|
|
109
|
+
new_volume = max(
|
|
110
|
+
0, current_volume - self.volume_change_interval
|
|
111
|
+
) # Decrease by volume_change_interval, but not below 0
|
|
112
|
+
self._set_volume(new_volume)
|
|
113
|
+
self.bus.emit(message.forward("mycroft.volume.set.confirm", {"percent": new_volume}))
|
|
114
|
+
|
|
115
|
+
def handle_volume_increase(self, message: Message):
|
|
116
|
+
"""Handle the volume increase request."""
|
|
117
|
+
current_volume = self._get_volume()
|
|
118
|
+
new_volume = min(
|
|
119
|
+
100, current_volume + self.volume_change_interval
|
|
120
|
+
) # Increase by volume_change_interval, but not above 100
|
|
121
|
+
self._set_volume(new_volume)
|
|
122
|
+
self.bus.emit(message.forward("mycroft.volume.set.confirm", {"percent": new_volume}))
|
|
123
|
+
|
|
124
|
+
def handle_volume_mute(self, message: Message):
|
|
125
|
+
"""Handle the volume mute request."""
|
|
126
|
+
self._set_mute(True)
|
|
127
|
+
self.bus.emit(message.forward("mycroft.volume.mute.confirm", {"muted": True}))
|
|
128
|
+
|
|
129
|
+
def handle_volume_unmute(self, message: Message):
|
|
130
|
+
"""Handle the volume unmute request."""
|
|
131
|
+
self._set_mute(False)
|
|
132
|
+
self.bus.emit(message.forward("mycroft.volume.mute.confirm", {"muted": False}))
|
|
133
|
+
|
|
134
|
+
def handle_volume_mute_toggle(self, message: Message):
|
|
135
|
+
"""Handle the volume mute toggle request."""
|
|
136
|
+
current_mute = self._is_muted()
|
|
137
|
+
self._set_mute(not current_mute)
|
|
138
|
+
self.bus.emit(message.forward("mycroft.volume.mute.confirm", {"muted": not current_mute}))
|
|
139
|
+
|
|
140
|
+
def _get_ntp_server(self):
|
|
141
|
+
"""Private method to get the configured NTP server."""
|
|
142
|
+
try:
|
|
143
|
+
result = self._run_command(["systemsetup", "-getnetworktimeserver"])
|
|
144
|
+
return result.stdout.strip().split(": ")[-1]
|
|
145
|
+
except Exception as err:
|
|
146
|
+
self.log.exception("Error getting NTP server: %s", err)
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
def handle_ntp_sync_request(self, message: Message):
|
|
150
|
+
"""Handle the NTP sync request."""
|
|
151
|
+
try:
|
|
152
|
+
ntp_server = self._get_ntp_server()
|
|
153
|
+
self._run_command(["sntp", "-sS", ntp_server])
|
|
154
|
+
self.bus.emit(message.forward("system.ntp.sync.complete"))
|
|
155
|
+
except subprocess.CalledProcessError:
|
|
156
|
+
self.bus.emit(message.forward("system.ntp.sync.failed"))
|
|
157
|
+
|
|
158
|
+
def handle_ssh_status(self, message: Message):
|
|
159
|
+
"""Handle the SSH status request."""
|
|
160
|
+
status = self._run_command(["systemsetup", "-getremotelogin"])
|
|
161
|
+
is_enabled = "On" in status.stdout
|
|
162
|
+
self.bus.emit(message.forward("system.ssh.status.response", {"enabled": is_enabled}))
|
|
163
|
+
|
|
164
|
+
def handle_ssh_enable_request(self, message: Message):
|
|
165
|
+
"""Handle the SSH enable request."""
|
|
166
|
+
try:
|
|
167
|
+
script = """
|
|
168
|
+
tell application "System Events"
|
|
169
|
+
activate
|
|
170
|
+
display dialog "OVOS needs Full Disk Access to enable Remote Login. Please grant permission in System Preferences." buttons {"OK"} default button "OK"
|
|
171
|
+
end tell
|
|
172
|
+
"""
|
|
173
|
+
self._run_applescript(script)
|
|
174
|
+
self._run_command(["systemsetup", "-setremotelogin", "on"])
|
|
175
|
+
self.bus.emit(message.forward("system.ssh.enabled"))
|
|
176
|
+
except subprocess.CalledProcessError:
|
|
177
|
+
self.bus.emit(message.forward("system.ssh.enable.failed"))
|
|
178
|
+
|
|
179
|
+
def handle_ssh_disable_request(self, message: Message):
|
|
180
|
+
"""Handle the SSH disable request."""
|
|
181
|
+
try:
|
|
182
|
+
script = """
|
|
183
|
+
tell application "System Events"
|
|
184
|
+
activate
|
|
185
|
+
display dialog "OVOS needs Full Disk Access to disable Remote Login. Please grant permission in System Preferences." buttons {"OK"} default button "OK"
|
|
186
|
+
end tell
|
|
187
|
+
"""
|
|
188
|
+
self._run_applescript(script)
|
|
189
|
+
self._run_command(["systemsetup", "-setremotelogin", "off"])
|
|
190
|
+
self.bus.emit(message.forward("system.ssh.disabled"))
|
|
191
|
+
except subprocess.CalledProcessError:
|
|
192
|
+
self.bus.emit(message.forward("system.ssh.disable.failed"))
|
|
193
|
+
|
|
194
|
+
def handle_reboot_request(self, message: Message):
|
|
195
|
+
"""Handle the reboot request."""
|
|
196
|
+
if self.allow_reboot is False:
|
|
197
|
+
self.bus.emit(message.forward("system.reboot.failed"))
|
|
198
|
+
try:
|
|
199
|
+
self._run_command(["shutdown", "-r", "now"])
|
|
200
|
+
except subprocess.CalledProcessError:
|
|
201
|
+
self.bus.emit(message.forward("system.reboot.failed"))
|
|
202
|
+
|
|
203
|
+
def handle_shutdown_request(self, message: Message):
|
|
204
|
+
"""Handle the shutdown request."""
|
|
205
|
+
if self.allow_shutdown is False:
|
|
206
|
+
self.bus.emit(message.forward("system.shutdown.failed"))
|
|
207
|
+
try:
|
|
208
|
+
self._run_command(["shutdown", "-h", "now"])
|
|
209
|
+
except subprocess.CalledProcessError:
|
|
210
|
+
self.bus.emit(message.forward("system.shutdown.failed"))
|
|
211
|
+
|
|
212
|
+
def handle_configure_language_request(self, message: Message):
|
|
213
|
+
"""Handle the configure language request."""
|
|
214
|
+
lang = message.data.get("lang")
|
|
215
|
+
if lang:
|
|
216
|
+
try:
|
|
217
|
+
self._run_command(["defaults", "write", "NSGlobalDomain", "AppleLanguages", f'("{lang}")'])
|
|
218
|
+
self.bus.emit(message.forward("system.language.configured", {"lang": lang}))
|
|
219
|
+
except subprocess.CalledProcessError:
|
|
220
|
+
self.bus.emit(message.forward("system.language.configure.failed"))
|
|
221
|
+
else:
|
|
222
|
+
self.bus.emit(message.forward("system.language.configure.failed", {"error": "Language not specified"}))
|
|
223
|
+
|
|
224
|
+
def handle_mycroft_restart_request(self, message: Message):
|
|
225
|
+
"""Handle the Mycroft restart request."""
|
|
226
|
+
try:
|
|
227
|
+
self._run_command(["launchctl", "stop", "com.ovos.service"])
|
|
228
|
+
self._run_command(["launchctl", "start", "com.ovos.service"])
|
|
229
|
+
self.bus.emit(message.forward("system.mycroft.service.restarted"))
|
|
230
|
+
except subprocess.CalledProcessError as err:
|
|
231
|
+
self.log.exception("OVOS service request restart failed", err)
|
|
232
|
+
self.bus.emit(message.forward("system.mycroft.service.restart.failed"))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
from ovos_utils.fakebus import FakeBus
|
|
237
|
+
|
|
238
|
+
plugin = MacOSPlugin(bus=FakeBus())
|
|
239
|
+
print("BREAK")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0" # This gets updated automatically by release-please
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ovos-phal-plugin-mac"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
authors = [{ name = "Mike Gray", email = "mike@oscillatelabs.net" }]
|
|
9
|
+
license = { text = "Apache-2.0" }
|
|
10
|
+
keywords = ["ovos", "plugin", "voice", "assistant"]
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dynamic = ["version", "dependencies", "optional-dependencies", "description"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/OscillateLabsLLC/ovos-phal-plugin-mac"
|
|
16
|
+
Repository = "https://github.com/OscillateLabsLLC/ovos-phal-plugin-mac"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
package-dir = { "ovos_phal_plugin_mac" = "phal_plugin_mac" }
|
|
20
|
+
packages = ["ovos_phal_plugin_mac"]
|
|
21
|
+
include-package-data = true
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.dynamic]
|
|
24
|
+
dependencies = { file = ["requirements/requirements.txt"] }
|
|
25
|
+
optional-dependencies = { test = { file = [
|
|
26
|
+
"requirements/requirements-dev.txt",
|
|
27
|
+
] } }
|
|
28
|
+
description = { file = "README.md" }
|
|
29
|
+
version = { attr = "phal_plugin_mac.version.__version__" }
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.package-data]
|
|
32
|
+
ovos_phal_plugin_mac = [
|
|
33
|
+
"*.json",
|
|
34
|
+
"locale/*",
|
|
35
|
+
"intents/*",
|
|
36
|
+
"dialog/*",
|
|
37
|
+
"vocab/*",
|
|
38
|
+
"regex/*",
|
|
39
|
+
"ui/*",
|
|
40
|
+
"requirements/*",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.entry-points."ovos.plugin.phal"]
|
|
44
|
+
ovos-phal-plugin-mac = "ovos_phal_plugin_mac:MacOSPlugin"
|
|
45
|
+
|
|
46
|
+
[tool.uv]
|
|
47
|
+
package = true
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# pylint: disable=missing-docstring,redefined-outer-name,protected-access,unnecessary-lambda
|
|
2
|
+
import subprocess
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from ovos_bus_client import Message
|
|
7
|
+
from ovos_plugin_manager.phal import find_phal_plugins
|
|
8
|
+
from ovos_utils.fakebus import FakeBus
|
|
9
|
+
|
|
10
|
+
from phal_plugin_mac import MacOSPlugin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def bus():
|
|
15
|
+
return FakeBus()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def plugin(bus):
|
|
20
|
+
config = {"allow_reboot": True, "allow_shutdown": True, "volume_change_interval": 10}
|
|
21
|
+
return MacOSPlugin(bus=bus, config=config)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def message():
|
|
26
|
+
return Message("test.message")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_find_phal_plugins():
|
|
30
|
+
plugins = find_phal_plugins()
|
|
31
|
+
assert "ovos-phal-plugin-mac" in plugins
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_init(plugin):
|
|
35
|
+
assert plugin.allow_reboot is True
|
|
36
|
+
assert plugin.allow_shutdown is True
|
|
37
|
+
assert plugin.volume_change_interval == 10
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_run_command_error(plugin):
|
|
41
|
+
cmd_results = plugin._run_command(["rm", "-r", "/ae5ih7srjo8g4ege5"])
|
|
42
|
+
assert not isinstance(cmd_results, subprocess.CompletedProcess)
|
|
43
|
+
assert cmd_results is None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@patch("subprocess.run")
|
|
47
|
+
def test_get_ntp_server(mock_run, plugin):
|
|
48
|
+
mock_run.return_value = MagicMock(stdout="Network Time Server: test.ntp.org\n")
|
|
49
|
+
assert plugin._get_ntp_server() == "test.ntp.org"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@patch("subprocess.run")
|
|
53
|
+
def test_get_ntp_server_error(mock_run, plugin):
|
|
54
|
+
mock_run.side_effect = subprocess.CalledProcessError(1, "test")
|
|
55
|
+
assert plugin._get_ntp_server() is None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@patch("phal_plugin_mac.MacOSPlugin._run_applescript")
|
|
59
|
+
def test_handle_ssh_enable_request(mock_run_applescript, plugin, message, bus):
|
|
60
|
+
received_messages = []
|
|
61
|
+
bus.on("system.ssh.enabled", lambda m: received_messages.append(m))
|
|
62
|
+
|
|
63
|
+
plugin.handle_ssh_enable_request(message)
|
|
64
|
+
|
|
65
|
+
mock_run_applescript.assert_called()
|
|
66
|
+
assert len(received_messages) == 1
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@patch("phal_plugin_mac.MacOSPlugin._run_applescript")
|
|
70
|
+
def test_handle_ssh_disable_request(mock_run_applescript, plugin, message, bus):
|
|
71
|
+
received_messages = []
|
|
72
|
+
bus.on("system.ssh.disabled", lambda m: received_messages.append(m))
|
|
73
|
+
|
|
74
|
+
plugin.handle_ssh_disable_request(message)
|
|
75
|
+
|
|
76
|
+
mock_run_applescript.assert_called()
|
|
77
|
+
assert len(received_messages) == 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_handle_reboot_request_not_allowed(plugin, message, bus):
|
|
81
|
+
plugin.config["allow_reboot"] = False
|
|
82
|
+
received_messages = []
|
|
83
|
+
bus.on("system.reboot.failed", lambda m: received_messages.append(m))
|
|
84
|
+
|
|
85
|
+
plugin.handle_reboot_request(message)
|
|
86
|
+
|
|
87
|
+
assert len(received_messages) == 1
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_handle_shutdown_request_not_allowed(plugin, message, bus):
|
|
91
|
+
plugin.config["allow_shutdown"] = False
|
|
92
|
+
received_messages = []
|
|
93
|
+
bus.on("system.shutdown.failed", lambda m: received_messages.append(m))
|
|
94
|
+
|
|
95
|
+
plugin.handle_shutdown_request(message)
|
|
96
|
+
|
|
97
|
+
assert len(received_messages) == 1
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@patch.object(MacOSPlugin, "_run_command")
|
|
101
|
+
def test_handle_configure_language_request_error(mock_run_command, plugin, message, bus):
|
|
102
|
+
mock_run_command.side_effect = subprocess.CalledProcessError(1, "language config failure")
|
|
103
|
+
received_messages = []
|
|
104
|
+
bus.on("system.language.configure.failed", lambda m: received_messages.append(m))
|
|
105
|
+
|
|
106
|
+
message.data["lang"] = "en-US"
|
|
107
|
+
plugin.handle_configure_language_request(message)
|
|
108
|
+
|
|
109
|
+
assert len(received_messages) == 1
|
|
110
|
+
# Optionally, check the content of the received message
|
|
111
|
+
# assert received_messages[0].data == {"error": "Command execution failed"}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@patch.object(MacOSPlugin, "_run_command")
|
|
115
|
+
def test_handle_mycroft_restart_request_error(mock_run_command, plugin, message, bus):
|
|
116
|
+
mock_run_command.side_effect = subprocess.CalledProcessError(1, "mycroft restart request error")
|
|
117
|
+
received_messages = []
|
|
118
|
+
bus.on("system.mycroft.service.restart.failed", lambda m: received_messages.append(m))
|
|
119
|
+
|
|
120
|
+
plugin.handle_mycroft_restart_request(message)
|
|
121
|
+
assert len(received_messages) == 1
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@patch.object(MacOSPlugin, "_run_command")
|
|
125
|
+
def test_handle_ntp_sync_request_error(mock_run_command, plugin, message, bus):
|
|
126
|
+
mock_run_command.side_effect = subprocess.CalledProcessError(1, "ntp sync request error")
|
|
127
|
+
received_messages = []
|
|
128
|
+
bus.on("system.ntp.sync.failed", lambda m: received_messages.append(m))
|
|
129
|
+
|
|
130
|
+
plugin.handle_ntp_sync_request(message)
|
|
131
|
+
|
|
132
|
+
assert len(received_messages) == 1
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@patch.object(MacOSPlugin, "_run_command")
|
|
136
|
+
def test_handle_ssh_enable_request_error(mock_run_command, plugin, message, bus):
|
|
137
|
+
mock_run_command.side_effect = subprocess.CalledProcessError(1, "ssh enable request error")
|
|
138
|
+
received_messages = []
|
|
139
|
+
bus.on("system.ssh.enable.failed", lambda m: received_messages.append(m))
|
|
140
|
+
|
|
141
|
+
plugin.handle_ssh_enable_request(message)
|
|
142
|
+
|
|
143
|
+
assert len(received_messages) == 1
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@patch.object(MacOSPlugin, "_run_command")
|
|
147
|
+
def test_handle_ssh_disable_request_error(mock_run_command, plugin, message, bus):
|
|
148
|
+
mock_run_command.side_effect = subprocess.CalledProcessError(1, "ssh disable request error")
|
|
149
|
+
received_messages = []
|
|
150
|
+
bus.on("system.ssh.disable.failed", lambda m: received_messages.append(m))
|
|
151
|
+
|
|
152
|
+
plugin.handle_ssh_disable_request(message)
|
|
153
|
+
|
|
154
|
+
assert len(received_messages) == 1
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@patch("subprocess.run")
|
|
158
|
+
def test_run_command(mock_run, plugin):
|
|
159
|
+
mock_run.return_value = MagicMock(stdout="test output")
|
|
160
|
+
result = plugin._run_command(["test", "command"])
|
|
161
|
+
mock_run.assert_called_once_with(["test", "command"], check=True, capture_output=True, text=True)
|
|
162
|
+
assert result.stdout == "test output"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@patch("phal_plugin_mac.MacOSPlugin._run_applescript")
|
|
166
|
+
def test_get_volume(mock_run_applescript, plugin):
|
|
167
|
+
mock_run_applescript.return_value = 50
|
|
168
|
+
volume = plugin._get_volume()
|
|
169
|
+
mock_run_applescript.assert_called_once_with("output volume of (get volume settings)")
|
|
170
|
+
assert volume == 50
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@patch("phal_plugin_mac.MacOSPlugin._run_applescript")
|
|
174
|
+
def test_set_volume(mock_run_applescript, plugin):
|
|
175
|
+
plugin._set_volume(75)
|
|
176
|
+
mock_run_applescript.assert_called_once_with("set volume output volume 75")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@patch("phal_plugin_mac.MacOSPlugin._run_applescript")
|
|
180
|
+
def test_is_muted(mock_run_applescript, plugin):
|
|
181
|
+
mock_run_applescript.return_value = "true"
|
|
182
|
+
assert plugin._is_muted() is True
|
|
183
|
+
mock_run_applescript.assert_called_once_with("output muted of (get volume settings)")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@patch("phal_plugin_mac.MacOSPlugin._set_volume")
|
|
187
|
+
def test_handle_volume_set(mock_set_volume, plugin, message, bus):
|
|
188
|
+
received_messages = []
|
|
189
|
+
bus.on("mycroft.volume.set.confirm", lambda m: received_messages.append(m))
|
|
190
|
+
|
|
191
|
+
message.data["percent"] = 60
|
|
192
|
+
plugin.handle_volume_set(message)
|
|
193
|
+
|
|
194
|
+
mock_set_volume.assert_called_once_with(60)
|
|
195
|
+
assert len(received_messages) == 1
|
|
196
|
+
assert received_messages[0].data["percent"] == 60
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@patch("phal_plugin_mac.MacOSPlugin._get_volume")
|
|
200
|
+
@patch("phal_plugin_mac.MacOSPlugin._set_volume")
|
|
201
|
+
def test_handle_volume_decrease(mock_set_volume, mock_get_volume, plugin, message, bus):
|
|
202
|
+
received_messages = []
|
|
203
|
+
bus.on("mycroft.volume.set.confirm", lambda m: received_messages.append(m))
|
|
204
|
+
|
|
205
|
+
mock_get_volume.return_value = 50
|
|
206
|
+
plugin.handle_volume_decrease(message)
|
|
207
|
+
|
|
208
|
+
mock_set_volume.assert_called_once_with(40)
|
|
209
|
+
assert len(received_messages) == 1
|
|
210
|
+
assert received_messages[0].data["percent"] == 40
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@patch("phal_plugin_mac.MacOSPlugin._get_volume")
|
|
214
|
+
@patch("phal_plugin_mac.MacOSPlugin._set_volume")
|
|
215
|
+
def test_handle_volume_increase(mock_set_volume, mock_get_volume, plugin, message, bus):
|
|
216
|
+
received_messages = []
|
|
217
|
+
bus.on("mycroft.volume.set.confirm", lambda m: received_messages.append(m))
|
|
218
|
+
|
|
219
|
+
mock_get_volume.return_value = 50
|
|
220
|
+
plugin.handle_volume_increase(message)
|
|
221
|
+
|
|
222
|
+
mock_set_volume.assert_called_once_with(60)
|
|
223
|
+
assert len(received_messages) == 1
|
|
224
|
+
assert received_messages[0].data["percent"] == 60
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@patch("phal_plugin_mac.MacOSPlugin._set_mute")
|
|
228
|
+
def test_handle_volume_mute(mock_set_mute, plugin, message, bus):
|
|
229
|
+
received_messages = []
|
|
230
|
+
bus.on("mycroft.volume.mute.confirm", lambda m: received_messages.append(m))
|
|
231
|
+
|
|
232
|
+
plugin.handle_volume_mute(message)
|
|
233
|
+
|
|
234
|
+
mock_set_mute.assert_called_once_with(True)
|
|
235
|
+
assert len(received_messages) == 1
|
|
236
|
+
assert received_messages[0].data["muted"] is True
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@patch("phal_plugin_mac.MacOSPlugin._set_mute")
|
|
240
|
+
def test_handle_volume_unmute(mock_set_mute, plugin, message, bus):
|
|
241
|
+
received_messages = []
|
|
242
|
+
bus.on("mycroft.volume.mute.confirm", lambda m: received_messages.append(m))
|
|
243
|
+
|
|
244
|
+
plugin.handle_volume_unmute(message)
|
|
245
|
+
|
|
246
|
+
mock_set_mute.assert_called_once_with(False)
|
|
247
|
+
assert len(received_messages) == 1
|
|
248
|
+
assert not received_messages[0].data["muted"]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@patch("phal_plugin_mac.MacOSPlugin._is_muted")
|
|
252
|
+
@patch("phal_plugin_mac.MacOSPlugin._set_mute")
|
|
253
|
+
def test_handle_volume_mute_toggle(mock_set_mute, mock_is_muted, plugin, message, bus):
|
|
254
|
+
received_messages = []
|
|
255
|
+
bus.on("mycroft.volume.mute.confirm", lambda m: received_messages.append(m))
|
|
256
|
+
|
|
257
|
+
mock_is_muted.return_value = True
|
|
258
|
+
plugin.handle_volume_mute_toggle(message)
|
|
259
|
+
|
|
260
|
+
mock_set_mute.assert_called_once_with(False)
|
|
261
|
+
assert len(received_messages) == 1
|
|
262
|
+
assert not received_messages[0].data["muted"]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@patch("subprocess.run")
|
|
266
|
+
def test_handle_ntp_sync_request(mock_run, plugin, message, bus):
|
|
267
|
+
received_messages = []
|
|
268
|
+
bus.on("system.ntp.sync.complete", lambda m: received_messages.append(m))
|
|
269
|
+
|
|
270
|
+
mock_run.return_value = MagicMock(stdout="Network Time Server: time.apple.com\n")
|
|
271
|
+
plugin.handle_ntp_sync_request(message)
|
|
272
|
+
|
|
273
|
+
mock_run.assert_any_call(["systemsetup", "-getnetworktimeserver"], check=True, capture_output=True, text=True)
|
|
274
|
+
mock_run.assert_any_call(["sntp", "-sS", "time.apple.com"], check=True, capture_output=True, text=True)
|
|
275
|
+
assert len(received_messages) == 1
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@patch("subprocess.run")
|
|
279
|
+
def test_handle_ssh_status(mock_run, plugin, message, bus):
|
|
280
|
+
received_messages = []
|
|
281
|
+
bus.on("system.ssh.status.response", lambda m: received_messages.append(m))
|
|
282
|
+
|
|
283
|
+
mock_run.return_value = MagicMock(stdout="Remote Login: On\n")
|
|
284
|
+
plugin.handle_ssh_status(message)
|
|
285
|
+
|
|
286
|
+
mock_run.assert_called_once_with(["systemsetup", "-getremotelogin"], check=True, capture_output=True, text=True)
|
|
287
|
+
assert len(received_messages) == 1
|
|
288
|
+
assert received_messages[0].data["enabled"] is True
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@patch("subprocess.run")
|
|
292
|
+
def test_handle_reboot_request(mock_run, plugin, message):
|
|
293
|
+
plugin.handle_reboot_request(message)
|
|
294
|
+
mock_run.assert_called_once_with(["shutdown", "-r", "now"], check=True, capture_output=True, text=True)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@patch("subprocess.run")
|
|
298
|
+
def test_handle_shutdown_request(mock_run, plugin, message):
|
|
299
|
+
plugin.handle_shutdown_request(message)
|
|
300
|
+
mock_run.assert_called_once_with(["shutdown", "-h", "now"], check=True, capture_output=True, text=True)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@patch("subprocess.run")
|
|
304
|
+
def test_handle_configure_language_request(mock_run, plugin, message, bus):
|
|
305
|
+
received_messages = []
|
|
306
|
+
bus.on("system.language.configured", lambda m: received_messages.append(m))
|
|
307
|
+
|
|
308
|
+
message.data["lang"] = "en-US"
|
|
309
|
+
plugin.handle_configure_language_request(message)
|
|
310
|
+
|
|
311
|
+
mock_run.assert_called_once_with(
|
|
312
|
+
["defaults", "write", "NSGlobalDomain", "AppleLanguages", '("en-US")'],
|
|
313
|
+
check=True,
|
|
314
|
+
capture_output=True,
|
|
315
|
+
text=True,
|
|
316
|
+
)
|
|
317
|
+
assert len(received_messages) == 1
|
|
318
|
+
assert received_messages[0].data["lang"] == "en-US"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@patch("subprocess.run")
|
|
322
|
+
def test_handle_mycroft_restart_request(mock_run, plugin, message, bus):
|
|
323
|
+
received_messages = []
|
|
324
|
+
bus.on("system.mycroft.service.restarted", lambda m: received_messages.append(m))
|
|
325
|
+
|
|
326
|
+
plugin.handle_mycroft_restart_request(message)
|
|
327
|
+
|
|
328
|
+
mock_run.assert_any_call(["launchctl", "stop", "com.ovos.service"], check=True, capture_output=True, text=True)
|
|
329
|
+
mock_run.assert_any_call(["launchctl", "start", "com.ovos.service"], check=True, capture_output=True, text=True)
|
|
330
|
+
assert len(received_messages) == 1
|