From fb8bf62faee231a6fa0d69a4447db6be0f9e7944 Mon Sep 17 00:00:00 2001 From: HumblePresent <60856003+HumblePresent@users.noreply.github.com> Date: Sat, 24 Apr 2021 04:37:06 -0500 Subject: [PATCH] Old playerctl cli backend added as option (#49) * Old playerctl cli backend added as option * Move files into directory and only require playerctl lib on enable * Added wrapper functions to select backend based on args * Switched 'player_stopped' signal to 'no_players' in CLI backend * Added support for setting interval directly in enable() for CLI backend * Update docs for both backends --- docs/signals/pctl.md | 20 ++- docs/theme.md | 1 + signal/playerctl/init.lua | 19 +++ signal/playerctl/playerctl_cli.lua | 133 ++++++++++++++++++ .../playerctl_lib.lua} | 5 +- theme-var-template.lua | 1 + 6 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 signal/playerctl/init.lua create mode 100644 signal/playerctl/playerctl_cli.lua rename signal/{playerctl.lua => playerctl/playerctl_lib.lua} (98%) diff --git a/docs/signals/pctl.md b/docs/signals/pctl.md index 9d96575..f325f80 100644 --- a/docs/signals/pctl.md +++ b/docs/signals/pctl.md @@ -99,7 +99,17 @@ end) ``` ### Theme Variables and Configuration -By default, this module will output signals from the most recently active player. If you wish to customize the behavior furthur, the following configuration options are available. +By default, this module will output signals from the most recently active player. If you wish to customize the behavior furthur, the following configuration options are available depending on the selected backend. Here is a summary of the two backends and which configuration options they support. + +| Option | playerctl_cli | playerctl_lib | +| ------------------- | ------------------ | ------------------ | +| backend | :heavy_check_mark: | :heavy_check_mark: | +| ignore | | :heavy_check_mark: | +| player | | :heavy_check_mark: | +| update_on_activity | | :heavy_check_mark: | +| interval | :heavy_check_mark: | :heavy_check_mark: | + +- `backend`: This is a string containing the name of the backend that will be used to produce the playerctl signals, either `playerctl_cli` or `playertl_lib`. `playerctl_cli` is used by default because it is supported on most if not all systems. That said, if the playerctl package for your distribution supports the `playerctl_lib` backend, it is recommended, because it supports all the configuration options as seen in the table above and uses less system resources. If you are not sure if your package supports the `playerctl_lib` backend you can simply try it and will receive an error message from Awesome upon calling `bling.signal.playerctl.enable()` if it is not supported. See the examples below for how to set configuration options. - `ignore`: This option is either a string with a single name or a table of strings containing names of players that will be ignored by this module. It is empty by default. @@ -111,6 +121,7 @@ By default, this module will output signals from the most recently active player These options can be set through a call to `bling.signal.playerctl.enable()` or these theme variables: ```lua +theme.playerctl_backend = "playerctl_cli" theme.playerctl_ignore = {} theme.playerctl_player = {} theme.playerctl_update_on_activity = true @@ -121,18 +132,25 @@ theme.playerctl_position_update_interval = 1 ```lua -- Prioritize ncspot over all other players and ignore firefox players (e.g. YouTube and Twitch tabs) completely bling.signal.playerctl.enable { + backend = "playerctl_lib", ignore = "firefox", player = {"ncspot", "%any"} } -- OR in your theme file: -- Same config as above but with theme variables +theme.playerctl_backend = "playerctl_lib" theme.playerctl_ignore = "firefox" theme.playerctl_player = {"ncspot", "%any"} -- Prioritize vlc over all other players and deprioritize spotify +theme.playerctl_backend = "playerctl_lib" theme.playerctl_player = {"vlc", "%any", "spotify"} -- Disable priority of most recently active players +theme.playerctl_backend = "playerctl_lib" theme.playerctl_update_on_activity = false + +-- Only emit the position signal every 2 seconds +theme.playerctl_position_update_interval = 2 ``` diff --git a/docs/theme.md b/docs/theme.md index 811585f..4936adf 100644 --- a/docs/theme.md +++ b/docs/theme.md @@ -15,6 +15,7 @@ theme.flash_focus_start_opacity = 0.6 -- the starting opacity theme.flash_focus_step = 0.01 -- the step of animation -- playerctl signal +theme.playerctl_backend = "playerctl_cli" -- backend to use theme.playerctl_ignore = {} -- list of players to be ignored theme.playerctl_player = {} -- list of players to be used in priority order theme.playerctl_update_on_activity = true -- whether to prioritize the most recently active players or not diff --git a/signal/playerctl/init.lua b/signal/playerctl/init.lua new file mode 100644 index 0000000..005724c --- /dev/null +++ b/signal/playerctl/init.lua @@ -0,0 +1,19 @@ +local beautiful = require("beautiful") + +-- Use CLI backend as default as it is supported on most if not all systems +local backend_config = beautiful.playerctl_backend or "playerctl_cli" +local backends = { + playerctl_cli = require(... .. ".playerctl_cli"), + playerctl_lib = require(... .. ".playerctl_lib") +} + +local function enable_wrapper(args) + backend_config = (args and args.backend) or backend_config + backends[backend_config].enable(args) +end + +local function disable_wrapper() + backends[backend_config].disable() +end + +return {enable = enable_wrapper, disable = disable_wrapper} diff --git a/signal/playerctl/playerctl_cli.lua b/signal/playerctl/playerctl_cli.lua new file mode 100644 index 0000000..82ba3b6 --- /dev/null +++ b/signal/playerctl/playerctl_cli.lua @@ -0,0 +1,133 @@ +-- +-- Provides: +-- bling::playerctl::status +-- playing (boolean) +-- bling::playerctl::title_artist_album +-- title (string) +-- artist (string) +-- album_path (string) +-- bling::playerctl::position +-- interval_sec (number) +-- length_sec (number) +-- bling::playerctl::no_players +-- +local awful = require("awful") +local beautiful = require("beautiful") + +local interval = beautiful.playerctl_position_update_interval or 1 + +local function emit_player_status() + local status_cmd = "playerctl status -F" + + -- Follow status + awful.spawn.easy_async({ + "pkill", "--full", "--uid", os.getenv("USER"), "^playerctl status" + }, function() + awful.spawn.with_line_callback(status_cmd, { + stdout = function(line) + local playing = false + if line:find("Playing") then + playing = true + else + playing = false + end + awesome.emit_signal("bling::playerctl::status", playing) + end + }) + collectgarbage("collect") + end) +end + +local function emit_player_info() + local art_script = [[ +sh -c ' + +tmp_dir="$XDG_CACHE_HOME/awesome/" + +if [ -z ${XDG_CACHE_HOME} ]; then + tmp_dir="$HOME/.cache/awesome/" +fi + +tmp_cover_path=${tmp_dir}"cover.png" + +if [ ! -d $tmp_dir ]; then + mkdir -p $tmp_dir +fi + +link="$(playerctl metadata mpris:artUrl)" + +curl -s "$link" --output $tmp_cover_path + +echo "$tmp_cover_path" +']] + + -- Command that lists artist and title in a format to find and follow + local song_follow_cmd = + "playerctl metadata --format 'artist_{{artist}}title_{{title}}' -F" + + -- Progress Cmds + local prog_cmd = "playerctl position" + local length_cmd = "playerctl metadata mpris:length" + + awful.widget.watch(prog_cmd, interval, function(_, interval) + awful.spawn.easy_async_with_shell(length_cmd, function(length) + local length_sec = tonumber(length) -- in microseconds + local interval_sec = tonumber(interval) -- in seconds + if length_sec and interval_sec then + if interval_sec >= 0 and length_sec > 0 then + awesome.emit_signal("bling::playerctl::position", + interval_sec, length_sec / 1000000) + end + end + end) + collectgarbage("collect") + end) + + -- Follow title + awful.spawn.easy_async({ + "pkill", "--full", "--uid", os.getenv("USER"), "^playerctl metadata" + }, function() + awful.spawn.with_line_callback(song_follow_cmd, { + stdout = function(line) + local album_path = "" + awful.spawn.easy_async_with_shell(art_script, function(out) + -- Get album path + album_path = out:gsub('%\n', '') + -- Get title and artist + local artist = line:match('artist_(.*)title_') + local title = line:match('title_(.*)') + -- If the title is nil or empty then the players stopped + if title and title ~= "" then + awesome.emit_signal( + "bling::playerctl::title_artist_album", title, + artist, album_path) + else + awesome.emit_signal("bling::playerctl::no_players") + end + end) + collectgarbage("collect") + end + }) + collectgarbage("collect") + end) +end + +-- Emit info +-- emit_player_status() +-- emit_player_info() + +local enable = function(args) + interval = (args and args.interval) or interval + emit_player_status() + emit_player_info() +end + +local disable = function() + awful.spawn.with_shell("pkill --full --uid " .. os.getenv("USER") .. + " '^playerctl status -F'") + + awful.spawn.with_shell("pkill --full --uid " .. os.getenv("USER") .. + " '^playerctl metadata --format'") +end + +return {enable = enable, disable = disable} diff --git a/signal/playerctl.lua b/signal/playerctl/playerctl_lib.lua similarity index 98% rename from signal/playerctl.lua rename to signal/playerctl/playerctl_lib.lua index 5e3e95f..d4d7c14 100644 --- a/signal/playerctl.lua +++ b/signal/playerctl/playerctl_lib.lua @@ -19,7 +19,7 @@ local gears = require("gears") local awful = require("awful") local beautiful = require("beautiful") -local Playerctl = require("lgi").Playerctl +local Playerctl = nil local manager = nil local position_timer = nil @@ -270,6 +270,9 @@ local function playerctl_enable(args) args.interval = args.interval or beautiful.playerctl_position_update_interval parse_args(args) + -- Grab playerctl library + Playerctl = require("lgi").Playerctl + -- Ensure main event loop has started before starting player manager gears.timer.delayed_call(start_manager) end diff --git a/theme-var-template.lua b/theme-var-template.lua index bbd4bc4..c08bc3e 100644 --- a/theme-var-template.lua +++ b/theme-var-template.lua @@ -15,6 +15,7 @@ theme.flash_focus_start_opacity = 0.6 -- the starting opacity theme.flash_focus_step = 0.01 -- the step of animation -- playerctl signal +theme.playerctl_backend = "playerctl_cli" -- backend to use theme.playerctl_ignore = {} -- list of players to be ignored theme.playerctl_player = {} -- list of players to be used in priority order theme.playerctl_update_on_activity = true -- whether to prioritize the most recently active players or not