1
0
mirror of https://github.com/jiriks74/presence.nvim synced 2024-11-23 20:37:50 +01:00

Add WSL 1 support

* Update README & remove unnecessary relay script
* Remove awk usage with explicit output parsers
* Ensure Presence is canceled on leave event
* Use release information to determine WSL 1 use
* Support P2P state sharing in WSL with ss
    * Use ss if netstat is not available
    * Use jobstart instead of io.popen to get WSL nvim sockets
* Detect OS via uname
* Add support for WSL
This commit is contained in:
Andrew Kwon 2021-07-09 13:14:38 -07:00
parent 774994a5b9
commit 080d24394b
2 changed files with 191 additions and 68 deletions

@ -7,11 +7,12 @@
<img src="https://gist.githubusercontent.com/andweeb/df3216345530234289b87cf5080c2c60/raw/4b07351547ae9a6bfdcbc1f915889b90a5349242/presence-demo.gif" alt="demo.gif"> <img src="https://gist.githubusercontent.com/andweeb/df3216345530234289b87cf5080c2c60/raw/4b07351547ae9a6bfdcbc1f915889b90a5349242/presence-demo.gif" alt="demo.gif">
## Features ## Features
* Simple and unobtrusive * Light and unobtrusive
* Support for macOS, Linux, and Windows[\*](#notes)
* No Python/Node providers (or CoC) required * No Python/Node providers (or CoC) required
* Cross-platform support: macOS, nixOS, Linux, Windows[\*](https://github.com/andweeb/presence.nvim/projects/1#card-60537963), WSL[\*](https://github.com/andweeb/presence.nvim/wiki/Rich-Presence-in-WSL)
* Startup time is fast(er than other Rich Presence plugins, by [kind of a lot](https://github.com/andweeb/presence.nvim/wiki/Plugin-Comparisons)) * Startup time is fast(er than other Rich Presence plugins, by [kind of a lot](https://github.com/andweeb/presence.nvim/wiki/Plugin-Comparisons))
* Written in Lua and configurable in Lua (but also configurable in VimL if you want) * Written in Lua and [highly configurable](#configuration) in Lua (but also configurable in VimL if you want)
* Manages Rich Presence across multiple Neovim instances in various environments (tmux panes/windows, ssh sessions, terminal tabs/windows, etc.)
## Installation ## Installation
Use your favorite plugin manager Use your favorite plugin manager
@ -19,14 +20,14 @@ Use your favorite plugin manager
* [packer.nvim](https://github.com/wbthomason/packer.nvim): `use 'andweeb/presence.nvim'` * [packer.nvim](https://github.com/wbthomason/packer.nvim): `use 'andweeb/presence.nvim'`
#### Notes #### Notes
* Requires [Neovim nightly](https://github.com/neovim/neovim/releases/tag/nightly) (0.5) * Requires [Neovim 0.5](https://github.com/neovim/neovim/releases/tag/v0.5.0) or higher
* Windows is [partially supported](https://github.com/andweeb/presence.nvim/projects/1#card-60537963), WSL is [not yet supported](https://github.com/andweeb/presence.nvim/projects/1#card-60537961) * Rich Presence should work automatically after installation (unless you're using WSL, in which case [see here](https://github.com/andweeb/presence.nvim/wiki/Rich-Presence-in-WSL))
## Configuration ## Configuration
Rich Presence works right out of the box after installation, so configuration is **optional**! For those that do want to override default behaviors, however, configuration options are available in either Lua or VimL. Configuration is not necessary for Rich Presence to work. But for those that want to override the default configs, the following options are available to configure in either Lua or VimL.
### Lua ### Lua
Require the plugin and call `setup` with a config table with any of the following keys: Require the plugin and call `setup` with a config table with one or more of the following keys:
```lua ```lua
-- The setup config table shows all available config options with their default values: -- The setup config table shows all available config options with their default values:
@ -75,13 +76,14 @@ let g:presence_line_number_text = "Line %s out of %s"
## Troubleshooting ## Troubleshooting
* Ensure that Discord is running * Ensure that Discord is running
* Ensure that your Neovim version is on 0.5 * Ensure that your Neovim version is 0.5 or higher
* Ensure Game Activity is enabled in your Discord settings * Ensure Game Activity is enabled in your Discord settings
* Enable logging and inspect the logs after opening a buffer * Enable logging and inspect the logs after opening a buffer
* Set the [`log_level`](#lua) setup option or [`g:presence_log_level`](#viml) to `"debug"` * Set the [`log_level`](#lua) setup option or [`g:presence_log_level`](#viml) to `"debug"`
* Load a file and inspect the logs with `:messages` * Load a file and inspect the logs with `:messages`
* If there is a `Failed to get Discord IPC socket` error, your particular OS may not yet be supported * If there is a `Failed to determine Discord IPC socket` error, your particular OS may not yet be supported
* If you don't see an existing [issue](https://github.com/andweeb/presence.nvim/issues) or [card](https://github.com/andweeb/presence.nvim/projects/1#column-14183588) for your OS, create a prefixed [issue](https://github.com/andweeb/presence.nvim/issues/new) (e.g. `[Void Linux]`) * If you don't see an existing [issue](https://github.com/andweeb/presence.nvim/issues) or [card](https://github.com/andweeb/presence.nvim/projects/1#column-14183588) for your OS, create a prefixed [issue](https://github.com/andweeb/presence.nvim/issues/new) (e.g. `[Void Linux]`)
* Still not working and need help? Create a new [issue](https://github.com/andweeb/presence.nvim/issues)!
## Development ## Development
* Clone the repo: `git clone https://github.com/andweeb/presence.nvim.git` * Clone the repo: `git clone https://github.com/andweeb/presence.nvim.git`
@ -90,6 +92,6 @@ let g:presence_line_number_text = "Line %s out of %s"
* Ensure that there are no [luacheck](https://github.com/mpeterv/luacheck/) errors: `luacheck lua` * Ensure that there are no [luacheck](https://github.com/mpeterv/luacheck/) errors: `luacheck lua`
## Contributing ## Contributing
Pull requests are very welcome, feel free to open an issue to work on any of the open [todo items](https://github.com/andweeb/presence.nvim/projects/1?add_cards_query=is%3Aopen)! Pull requests are very welcome, feel free to open an issue to work on any of the open [todo items](https://github.com/andweeb/presence.nvim/projects/1?add_cards_query=is%3Aopen) or message [droob#1322](https://discordapp.com/users/241953146232897550) on Discord!
Asset additions and changes are also welcome! Supported file types can be found in [`file_assets.lua`](lua/presence/file_assets.lua) and their referenced asset files can be found [in this folder](https://www.dropbox.com/sh/j8913f0gav3toeh/AADxjn0NuTprGFtv3Il1Pqz-a?dl=0). Asset additions and changes are also welcome! Supported file types can be found in [`file_assets.lua`](lua/presence/file_assets.lua) and their referenced asset files can be found [in this folder](https://www.dropbox.com/sh/j8913f0gav3toeh/AADxjn0NuTprGFtv3Il1Pqz-a?dl=0).

@ -60,21 +60,13 @@ Presence.socket = vim.v.servername
Presence.workspace = nil Presence.workspace = nil
Presence.workspaces = {} Presence.workspaces = {}
-- Get the operating system name (eh should be good enough)
-- http://www.lua.org/manual/5.3/manual.html#pdf-package.config
local separator = package.config:sub(1,1)
Presence.os = {
name = separator == [[\]] and "windows" or "unix",
path_separator = separator,
}
local log = require("lib.log") local log = require("lib.log")
local msgpack = require("deps.msgpack") local msgpack = require("deps.msgpack")
local serpent = require("deps.serpent") local serpent = require("deps.serpent")
local Discord = require("presence.discord")
local file_assets = require("presence.file_assets") local file_assets = require("presence.file_assets")
local file_explorers = require("presence.file_explorers") local file_explorers = require("presence.file_explorers")
local plugin_managers = require("presence.plugin_managers") local plugin_managers = require("presence.plugin_managers")
local Discord = require("presence.discord")
function Presence:setup(options) function Presence:setup(options)
options = options or {} options = options or {}
@ -83,7 +75,30 @@ function Presence:setup(options)
-- Initialize logger -- Initialize logger
self:set_option("log_level", nil, false) self:set_option("log_level", nil, false)
self.log = log:init({ level = options.log_level }) self.log = log:init({ level = options.log_level })
self.log:debug("Setting up plugin...")
-- Get operating system information including path separator
-- http://www.lua.org/manual/5.3/manual.html#pdf-package.config
local uname = vim.loop.os_uname()
local separator = package.config:sub(1,1)
local wsl_distro_name = os.getenv("WSL_DISTRO_NAME")
local os_name = self.get_os_name(uname)
self.os = {
name = os_name,
is_wsl = uname.release:find("Microsoft") ~= nil,
path_separator = separator,
}
-- Print setup message with OS information
local setup_message_fmt = "Setting up plugin for %s"
if self.os.name then
local setup_message = self.os.is_wsl
and string.format(setup_message_fmt.." in WSL (%s)", self.os.name, vim.inspect(wsl_distro_name))
or string.format(setup_message_fmt, self.os.name)
self.log:debug(setup_message)
else
self.log:error(string.format("Unable to detect operating system: %s"))
self.log:debug(vim.inspect(vim.loop.os_uname()))
end
-- Use the default or user-defined client id if provided -- Use the default or user-defined client id if provided
if options.client_id then if options.client_id then
@ -106,19 +121,23 @@ function Presence:setup(options)
self:set_option("workspace_text", "Working on %s") self:set_option("workspace_text", "Working on %s")
self:set_option("line_number_text", "Line %s out of %s") self:set_option("line_number_text", "Line %s out of %s")
local discord_socket = self:get_discord_socket() -- Get and check discord socket path
if not discord_socket then local discord_socket_path = self:get_discord_socket_path()
self.log:error("Failed to get Discord IPC socket") if discord_socket_path then
self.log:debug(string.format("Using Discord IPC socket path: %s", discord_socket_path))
self:check_discord_socket(discord_socket_path)
else
self.log:error("Failed to determine Discord IPC socket path")
end end
-- Initialize discord RPC client -- Initialize discord RPC client
self.discord = Discord:init({ self.discord = Discord:init({
logger = self.log, logger = self.log,
client_id = options.client_id, client_id = options.client_id,
ipc_socket = discord_socket, ipc_socket = discord_socket_path,
}) })
-- Seed instance id using unique socket address -- Seed instance id using unique socket path
local seed_nums = {} local seed_nums = {}
self.socket:gsub(".", function(c) table.insert(seed_nums, c:byte()) end) self.socket:gsub(".", function(c) table.insert(seed_nums, c:byte()) end)
self.id = self.discord.generate_uuid(tonumber(table.concat(seed_nums)) / os.clock()) self.id = self.discord.generate_uuid(tonumber(table.concat(seed_nums)) / os.clock())
@ -141,6 +160,19 @@ function Presence:setup(options)
return self return self
end end
-- Normalize the OS name from uname
function Presence.get_os_name(uname)
if uname.sysname:find("Windows") then
return "windows"
elseif uname.sysname:find("Darwin") then
return "macos"
elseif uname.sysname:find("Linux") then
return "linux"
end
return "unknown"
end
-- To ensure consistent option values, coalesce true and false values to 1 and 0 -- To ensure consistent option values, coalesce true and false values to 1 and 0
function Presence.coalesce_option(value) function Presence.coalesce_option(value)
if type(value) == "boolean" then if type(value) == "boolean" then
@ -181,6 +213,28 @@ function Presence:check_dup_options(option)
end end
end end
-- Check the Discord socket at the given path
function Presence:check_discord_socket(path)
self.log:debug(string.format("Checking Discord IPC socket at %s...", path))
-- Asynchronously check socket path via stat
vim.loop.fs_stat(path, function(err, stats)
if err then
local err_msg = "Failed to get socket information"
self.log:error(string.format("%s: %s", err_msg, err))
return
end
if stats.type ~= "socket" then
local warning_msg = "Found unexpected Discord IPC socket type"
self.log:warn(string.format("%s: %s", warning_msg, err))
return
end
self.log:debug(string.format("Checked Discord IPC socket, looks good!"))
end)
end
-- Send a nil activity to unset the presence -- Send a nil activity to unset the presence
function Presence:cancel() function Presence:cancel()
self.log:debug("Canceling Discord presence...") self.log:debug("Canceling Discord presence...")
@ -289,31 +343,44 @@ function Presence:authorize(on_done)
end) end)
end end
-- Find the the IPC path in temp runtime directories -- Find the Discord socket from temp runtime directories
function Presence:get_discord_socket() function Presence:get_discord_socket_path()
local sock_name = "discord-ipc-0" local sock_name = "discord-ipc-0"
local sock_path = nil
if self.os.name == "windows" then if self.os.is_wsl then
return [[\\.\pipe\]]..sock_name -- Use socket created by relay for WSL
end sock_path = "/var/run/"..sock_name
elseif self.os.name == "windows" then
-- Use named pipe in NPFS for Windows
sock_path = [[\\.\pipe\]]..sock_name
elseif self.os.name == "macos" then
-- Use $TMPDIR for macOS
local path = os.getenv("TMPDIR")
sock_path = path:match("/$")
and path..sock_name
or path.."/"..sock_name
elseif self.os.name == "linux" then
-- Check various temp directory environment variables
local env_vars = {
"XDG_RUNTIME_DIR",
"TEMP",
"TMP",
"TMPDIR",
}
local env_vars = { for i = 1, #env_vars do
"XDG_RUNTIME_DIR", local var = env_vars[i]
"TEMP", local path = os.getenv(var)
"TMP", if path then
"TMPDIR", self.log:debug(string.format("Using runtime path: %s", path))
} sock_path = path:match("/$") and path..sock_name or path.."/"..sock_name
break
for i = 1, #env_vars do end
local var = env_vars[i]
local path = vim.loop.os_getenv(var)
if path then
self.log:debug(string.format("Using runtime path: %s", path))
return path:match("/$") and path..sock_name or path.."/"..sock_name
end end
end end
return nil return sock_path
end end
-- Gets the file path of the current vim buffer -- Gets the file path of the current vim buffer
@ -399,34 +466,85 @@ function Presence:get_status_text(filename)
end end
end end
-- Get all active local nvim unix domain socket addresses -- Get all local nvim socket paths
function Presence:get_nvim_socket_addrs(on_done) function Presence:get_nvim_socket_paths(on_done)
self.log:debug("Getting nvim socket addresses...") self.log:debug("Getting nvim socket paths...")
local sockets = {}
local parser = {}
local cmd
-- TODO: Find a better way to get paths of remote Neovim sockets lol if self.os.is_wsl then
local commands = { -- TODO: There needs to be a better way of doing this... no support for ss/netstat?
unix = table.concat({ -- (See https://github.com/microsoft/WSL/issues/2249)
"netstat -u", local cmd_fmt = "for file in %s/nvim*; do echo $file/0; done"
[[grep --color=never "nvim.*/0"]], local shell_cmd = string.format(cmd_fmt, vim.loop.os_tmpdir() or "/tmp")
[[awk -F "[ :]+" '{print $9}']],
"sort", cmd = {
"uniq", "sh",
}, "|"), "-c",
windows = { shell_cmd,
}
elseif self.os.name == "windows" then
cmd = {
"powershell.exe", "powershell.exe",
"-Command", "-Command",
[[(Get-ChildItem \\.\pipe\).FullName | findstr 'nvim']], [[(Get-ChildItem \\.\pipe\).FullName | findstr 'nvim']],
}, }
} elseif self.os.name == "macos" then
local cmd = commands[self.os.name] if vim.fn.executable("netstat") == 0 then
self.log:warn("Unable to get nvim socket paths: `netstat` command unavailable")
return
end
-- Define macOS BSD netstat output parser
function parser.parse(data)
return data:match("%s(/.+)")
end
cmd = table.concat({
"netstat -u",
[[grep --color=never "nvim.*/0"]],
}, "|")
elseif self.os.name == "linux" then
if vim.fn.executable("netstat") == 1 then
-- Use `netstat` if available
cmd = table.concat({
"netstat -u",
[[grep --color=never "nvim.*/0"]],
}, "|")
-- Define netstat output parser
function parser.parse(data)
return data:match("%s(/.+)")
end
elseif vim.fn.executable("ss") == 1 then
-- Use `ss` if available
cmd = table.concat({
"ss -lx",
[[grep "nvim.*/0"]],
}, "|")
-- Define ss output parser
function parser.parse(data)
return data:match("%s(/.-)%s")
end
else
local warning_msg = "Unable to get nvim socket paths: `netstat` and `ss` commands unavailable"
self.log:warn(warning_msg)
return
end
else
local warning_fmt = "Unable to get nvim socket paths: Unexpected OS: %s"
self.log:warn(string.format(warning_fmt, self.os.name))
return
end
local sockets = {}
local function handle_data(_, data) local function handle_data(_, data)
if not data then return end if not data then return end
for i = 1, #data do for i = 1, #data do
local socket = vim.trim(data[i]) local socket = parser.parse and parser.parse(vim.trim(data[i])) or vim.trim(data[i])
if socket ~= "" and socket ~= self.socket then if socket and socket ~= "" and socket ~= self.socket then
table.insert(sockets, socket) table.insert(sockets, socket)
end end
end end
@ -436,15 +554,17 @@ function Presence:get_nvim_socket_addrs(on_done)
if not data then return end if not data then return end
if data[1] ~= "" then if data[1] ~= "" then
self.log:error(data[1]) self.log:error(string.format("Unable to get nvim socket paths: %s", data[1]))
end end
end end
local function handle_exit() local function handle_exit()
self.log:debug(string.format("Got nvim socket addresses: %s", vim.inspect(sockets))) self.log:debug(string.format("Got nvim socket paths: %s", vim.inspect(sockets)))
on_done(sockets) on_done(sockets)
end end
local cmd_str = type(cmd) == "table" and table.concat(cmd, ", ") or cmd
self.log:debug(string.format("Executing command: `%s`", cmd_str))
vim.fn.jobstart(cmd, { vim.fn.jobstart(cmd, {
on_stdout = handle_data, on_stdout = handle_data,
on_stderr = handle_error, on_stderr = handle_error,
@ -766,9 +886,9 @@ function Presence:register_and_sync_peer(id, socket)
end end
-- Register self to any remote Neovim instances -- Register self to any remote Neovim instances
-- Simply emits to all nvim socket addresses as we have not yet been synced with peer list -- Simply emits to all nvim sockets as we have not yet been synced with peer list
function Presence:register_self() function Presence:register_self()
self:get_nvim_socket_addrs(function(sockets) self:get_nvim_socket_paths(function(sockets)
if #sockets == 0 then if #sockets == 0 then
self.log:debug("No other remote nvim instances") self.log:debug("No other remote nvim instances")
return return
@ -895,6 +1015,7 @@ end
function Presence:handle_vim_leave_pre() function Presence:handle_vim_leave_pre()
self.log:debug("Handling VimLeavePre event...") self.log:debug("Handling VimLeavePre event...")
self:unregister_self() self:unregister_self()
self:cancel()
end end
-- WinEnter events force-update the current buffer presence unless it's a quickfix window -- WinEnter events force-update the current buffer presence unless it's a quickfix window
@ -947,7 +1068,7 @@ function Presence:handle_buf_enter()
self:update() self:update()
end end
-- WinLeave events cancel the current buffer presence -- BufAdd events force-update the presence for the current buffer unless it's a quickfix window
function Presence:handle_buf_add() function Presence:handle_buf_add()
self.log:debug("Handling BufAdd event...") self.log:debug("Handling BufAdd event...")