1"""
2Control Audacious or VLC media player.
3
4Provides an icon to control simple functions of audio/video players:
5    - start (left click)
6    - stop  (left click)
7    - pause (middle click)
8
9Configuration parameters:
10    cache_timeout: how often to update in seconds (default 10)
11    debug: enable verbose logging (bool) (default False)
12    format: format of the output (default "{icon}")
13    pause_icon: (default '❚❚')
14    play_icon: (default '▶')
15    stop_icon: (default '◼')
16    supported_players: supported players (str) (comma separated list)
17        (default 'audacious,vlc')
18    volume_tick: percentage volume change on mouse wheel (int) (positive number
19        or None to disable it) (default 1)
20
21Format placeholders:
22    {icon} an icon to control music/video players
23
24@author Federico Ceratto <federico.ceratto@gmail.com>, rixx
25@license BSD
26
27SAMPLE OUTPUT
28{'full_text': u'\u25b6'}
29
30stop
31{'full_text': u'\u25fc'}
32
33pause
34{'full_text': u'\u275a\u275a'}
35"""
36# Any contributor to this module should add his/her name to the @author
37# line, comma separated.
38
39from pathlib import Path
40
41try:
42    import dbus
43
44    dbus_available = True
45except:  # noqa e722 // (ImportError, ModuleNotFoundError):  # (py2, assumed py3)
46    dbus_available = False
47
48
49class Py3status:
50    """
51    """
52
53    # available configuration parameters
54    cache_timeout = 10
55    debug = False
56    format = "{icon}"
57    pause_icon = "❚❚"
58    play_icon = "▶"
59    stop_icon = "◼"
60    supported_players = "audacious,vlc"
61    volume_tick = 1
62
63    def post_config_hook(self):
64        self.status = "stop"
65        self.icon = self.play_icon
66
67    def on_click(self, event):
68        """
69        """
70        buttons = (None, "left", "middle", "right", "up", "down")
71        try:
72            button = buttons[event["button"]]
73        except IndexError:
74            return
75
76        if button in ("up", "down"):
77            if self.volume_tick is None:
78                return
79
80            self._change_volume(button == "up")
81            return
82
83        if self.status == "play":
84            if button == "left":
85                self._stop()
86
87            elif button == "middle":
88                self._pause()
89
90        elif self.status == "stop":
91            if button == "left":
92                self._play()
93
94        elif self.status == "pause":
95            if button in ("left", "right"):
96                self._play()
97
98    def _run(self, command):
99        if self.debug:
100            self.py3.log(f"running {command}")
101        self.py3.command_run(command)
102
103    def _play(self):
104        self.status = "play"
105        self.icon = self.stop_icon
106        player_name = self._detect_running_player()
107        if player_name == "audacious":
108            self._run(["audacious", "-p"])
109        elif player_name == "vlc":
110            player = self._get_vlc()
111            if player:
112                player.Play()
113
114    def _stop(self):
115        self.status = "stop"
116        self.icon = self.play_icon
117        player_name = self._detect_running_player()
118        if player_name == "audacious":
119            self._run(["audacious", "-s"])
120        elif player_name == "vlc":
121            player = self._get_vlc()
122            if player:
123                player.Stop()
124
125    def _pause(self):
126        self.status = "pause"
127        self.icon = self.pause_icon
128        player_name = self._detect_running_player()
129        if player_name == "audacious":
130            self._run(["audacious", "-u"])
131        elif player_name == "vlc":
132            player = self._get_vlc()
133            if player:
134                player.Pause()
135
136    def _change_volume(self, increase):
137        """Change volume using amixer
138        """
139        sign = "+" if increase else "-"
140        delta = f"{self.volume_tick}%{sign}"
141        self._run(["amixer", "-q", "sset", "Master", delta])
142
143    def _detect_running_player(self):
144        """Detect running player process, if any
145        """
146        supported_players = self.supported_players.split(",")
147        running_players = []
148        for pid in Path("/proc").iterdir():
149            if not pid.name.isdigit():
150                continue
151            try:
152                player_name = (pid / "comm").read_bytes().decode().rstrip()
153            except:  # noqa e722
154                # (IOError, FileNotFoundError):  # (assumed py2, assumed py3)
155                continue
156
157            if player_name in supported_players:
158                running_players.append(player_name)
159
160        # Pick which player to use based on the order in self.supported_players
161        for player_name in supported_players:
162            if player_name in running_players:
163                if self.debug:
164                    self.py3.log(f"found player: {player_name}")
165
166                # those players need the dbus module
167                if player_name == "vlc" and not dbus_available:
168                    self.py3.log(f"{player_name} requires the dbus python module")
169                    return None
170
171                return player_name
172
173        return None
174
175    def _get_vlc(self):
176        mpris = "org.mpris.MediaPlayer2"
177        mpris_slash = "/" + mpris.replace(".", "/")
178        bus = dbus.SessionBus()
179        proxy = bus.get_object(mpris + ".vlc", mpris_slash)
180        return dbus.Interface(proxy, dbus_interface=mpris + ".Player")
181
182    def player_control(self):
183        return dict(
184            full_text=self.py3.safe_format(self.format, {"icon": self.icon}),
185            cached_until=self.py3.time_in(self.cache_timeout),
186        )
187
188
189if __name__ == "__main__":
190    """
191    Run module in test mode.
192    """
193    from py3status.module_test import module_test
194
195    module_test(Py3status)
196