Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://gitlab.com/carmanaught/mpvcontextmenu

Context Menu for mpv
https://gitlab.com/carmanaught/mpvcontextmenu

Last synced: 3 days ago
JSON representation

Context Menu for mpv

Lists

README

        

# Context Menu for mpv

This is a context menu forked and fairly extensively modified from [this one](https://gist.github.com/avih/bee746200b5712220b8bd2f230e535de) (credit to avih).

**The current version of `mvpcontextmenu` uses JavaScript-based scripts rather than LUA, with data passed to the scripts via JSON. For the LUA version, use the [lua](../../tree/lua) branch. This JavaScript version also requires `tcllib` (see [Requirements](#requirements) below).**

**If using a pre-JavaScript version of `mpvcontextmenu` please note that this version has quite extensive changes and uses JavaScript objects leading to large changes in how the menu structure is built.**

For mpv versions before 0.33, use the [pre-0.33](../../tree/pre-0.33) branch as the script directory changes make this script incompatible with earlier versions of mpv.

The correct posititioning of the menu under Wayland is not expected to work correctly.

### Table of Contents

* [Requirements](#requirements)
* [Flatpak-specific requirements](#flatpak-specific-requirements)
* [Tcl/Tk](#tcltk)
* [GTK](#gtk)
* [Dialogs](#dialogs)
* [Fonts](#fonts)
* [Tk](#tk-1)
* [GTK](#gtk-1)
* [Scripts](#scripts)
* [Usage](#usage)
* [Configuration](#configuration)
* [Customization](#customization)
* [Menu Layout](#menu-layout)
* [Function call/in-line function](#function-callin-line-function)
* [Notes](#notes)
* [Disclaimer](#disclaimer)
* [Credits](#credits)

This is an example of what the Tk (left) and GTK (right) menus look like in use (showing the audio Channel Layout sub-menu):

![Menus: Tk (left), GTK (right)](./screenshots/tk-gtk-menus.png?raw=true)

A lot of the code from the original menu has been rewritten. In particular, the Tk menu adds sub-menu's using the Tcl menu 'cascade' command, retaining the possibility to rebuild the menu along with sub-menu's through the use of the Tcl 'postcascade' command. **Note**: The 'postcascade' will not work on Windows.

The menu layout is based on the right-click menu for Bomi, which is what I was using before switching to mpv. If you were a Bomi user be aware that not all the menu items for Bomi are implemented (particularly those around video settings) and there is no current plan to implement them at this point.

Some of the menu items reference commands that use the functions/bindings in `gui-dialogs.js` to show dialogs. These are based on the [KDialog-open-files](https://gist.github.com/ntasos/d1d846abd7d25e4e83a78d22ee067a22) script (credit to ntasos) and include both `zenity` and `kdialog` options.

The menu layout definitions and the menu engine have been separated. Part of this is to allow for the use of other menu builders, with the logic and interactions with builders handled by the menu engine script. This has the advantage that the menu engine script doesn't care about the menu definition file and actually allows multiple menu definition files to be used if desired, ensuring they are configured correctly (see [Customization](#customization) below).

While some of this may work on Windows or macOS, most of this is only tested on Linux. For macOS, some of the requirements may be available via [Homebrew](https://brew.sh/) (including `tcllib` it seems), though I can't provide any guarantees for things working.

## Requirements

For the menu's to work, this currently requires the following:

* **tcl**, **tk** for the Tk based menu.
* **tcllib** is needed to enable parsing JSON in the `tcl` script.
* **gjs** (and it's dependancies, likely including **gtk3**) for the GTK based menu.
* **kdialog** or **zenity** for the dialogs to open files/folders/URLs.
* **xdotool** for use in the `gui-dialogs.js` script.

Place the `mpvcontextmenu` folder in your `~/.config/mpv/scripts/` or `~/.mpv/scripts/` folder. The `input.conf` is not strictly necessary, but the key-bindings shown in the menu are based on those in the `input.conf` . **Note:** the key-bindings are not automatically detected and have been manually added as text to the menu, so you'll need to change/remove them if they don't match your own.

### Flatpak-specific requirements

The script can run from within a flatpak by running the commands from the host system, however the setup will essentially make the flatpak unsandboxed. To do so, you need to be able to run the following command:

* **flatpak-spawn**, which may be in a **`flatpak-xdg-utils`** package.

You will need to override the flatpak configuration either by running the following command in the terminal (assuming use of the `io.mpv.Mpv` flatpak from flathub):

```shell
flatpak override --user --talk-name=org.freedesktop.Flatpak io.mpv.Mpv
```

Or if using `Flatseal` add `org.freedesktop.Flatpak` to the **Talks** under **Session Bus**.

You will still need the other applications in the requirements installed on the host as the script will run `flatpak-spawn --host` to run the scripts.

The scripts also need to go into the flatpak `.var` folder in the home drive, which should be `~/.var/app/io.mpv.Mpv/config/mpv/scripts`.

Any menu commands that may trigger additional scripts, which in turn may run subprocesses which sit outside the flatpak need to be handled manually. This could be done by looking at how the flatpak-check helper script has been used to check if `mpv` is running in a flatpak, then add additional arguments to the subprocess `args` list.

### Tcl/Tk

You will need to install Tcl and Tk (for the Tk menu) and ensure that the `interpreter["tk"]` variable in `menu-engine.js` is set properly. This can be set to `wish` or `tclsh` (set to `wish` by default, though `tclsh` may work smoother) and should either be accessible via the PATH environment variable or the full path should be specified.

You will also need `tcllib` as this is used to parse JSON in the script. Under Linux, check [Repology](https://repology.org/project/tcllib/) to find out if it's packaged for your distribution (as an example, for Arch Linux the package is in the [AUR](https://aur.archlinux.org/packages/tcllib)).

For Windows, download your preferred Tcl package (check the [Tcl software page](https://www.tcl.tk/software/tcltk/) or perhaps [ActiveTcl](https://www.activestate.com/activetcl) or [Tcl3D](http://www.tcl3d.org/html/appTclkits.html)) and install/extract as needed. It appears that `tcllib` is included with ActiveTcl [based on this page](https://wiki.tcl-lang.org/page/Tcllib+Location) while Tcl3D references it [on their BAWT downloads page](https://www.tcl3d.org/bawt/download.html) (not sure how to install however). Ensure that the relevant wish.exe or tclsh.exe is either in the PATH, the full path has been specified, or put the necessary executable into the the mpv.exe directory. You could also use tclsh/wish from git/msys2 (mingw).

### GTK

The GTK menu uses `gjs` for the interpreter and will need that installed along with whatever dependencies that requires. The `interpreter["gtk"]` variable in `menu-engine.js` should be set properly as needed, depending on whether it's accessible from the PATH environment variable, etc.

As noted elsewhere, the GTK menu cannot use the "repost" functionality that is available to the Tcl/Tk menu.

This hasn't been tested on Windows as I'm unsure if there is a workable port/version of gjs for Windows. For macOS, it looks like it's available in Homebrew ([Gjs](https://formulae.brew.sh/formula/gjs), though it doesn't specify dependencies, so you may need [Gtk+3](https://formulae.brew.sh/formula/gtk+3) from there as well.

### Dialogs

For the dialogs to work, either `kdialog` or `zenity` need to be installed and accessible via the PATH or the path set up manually in `gui-dialogs.js`. By default, neither the `kdialog` or `zenity` dialogs are enabled and either the script file should be modified or a `gui-dialogs.conf` file should be created (read configuration section below regarding the right directory for this).

If you do not wish to use any of the included dialogs you can also remove the entries in the menu items referencing them.

For `zenity`, apparently there is a port of [Zenity for windows](https://github.com/kvaps/zenity-windows), but this has not been tested. For macOS, apparently this is available via Homebrew ([Zenity](https://formulae.brew.sh/formula/zenity)). As far as I'm aware, `kdialog` is not readily available on windows or macOS.

### Fonts

The menu defaults to using the Source Code Pro font, which can be [found here](https://github.com/adobe-fonts/source-code-pro) (or check your repositories). The font is set by default in the `menu-engine.js` and can be overridden via an associated configuration file. The font for the `menu-builder-tk.tcl` or `menu-builder-gtk.js` files will use the arguments provided from the `menu-engine`. A mono-spaced font must be used for the menu items to appear correctly.

As the font is set from the `menu-engine.js` it's important to ensure that the font name will be valid for the menu type being used (the default is valid for both Tk and GTK).

#### Tk

To find out a valid font name for the Tk menu, run `wish` from a terminal, then from the wish prompt, enter `puts [font families]`.

```shell
user@hostname:~$ wish
% puts [font families]
```

This should output a list of available fonts enclosed by curly braces, which can be used to copy the name of the desired font. To exit, type `exit`.

% exit

Set the font to be used in a custom `menu-engine.conf`.

It is also possible to change the fallback font in `menu-builder-tk.tcl`, changing the line with `{Source Code Pro}` below in the Tcl file to whichever font is preferred and adjusting size as desired.

```
font create defFont -family {Source Code Pro} -size 9
```

#### GTK

The GTK menu uses CSS to specify the font, so you should be able to use the font name as it appears to most other applications. The font will default to the one specified in the `menu-engine.js` or the `menu-engine.conf`, but you can change the fallback font by searching for `Source Code Pro` in the code and changing the `fontFace` variable that appears as follows:

```javascript
fontFace = "Source Code Pro"
```

Likewise, the font size can be changed around the same area by modifying the `fontSize` that likewise appears as follows.

```javascript
fontSize = 9
```

### Scripts

Additionally the context menu uses some mpv scripts that other people have written, called via script-binding/script-message. These are currently:

- [subit](https://github.com/wiiaboo/mpv-scripts/blob/master/subit.lua) for the "Find Subtitle (Subit)" item under "Tools"
- [playlistmanager](https://github.com/donmaiq/Mpv-Playlistmanager) for the "Show" item under "Tools > Playlist"
- ~~[stats](https://github.com/Argon-/mpv-stats/) for the "Playback Information" item under "Tools"~~ (**Note**: As of mpv 0.28.0 this is not needed as it is [part of mpv](https://github.com/mpv-player/mpv/blob/master/player/lua/stats.lua)).

These will need to be either downloaded and set-up for these menu items to work or the commands to use them should be removed if not.

## Usage

There is no default binding for the context menu and the choice of menu needs to be specified via a binding in `input.conf` via ` script_message ` replacing the `` with the desired key-binding. The `` options are:

* `mpv_context_menu_tk` for the Tk menu,
* `mpv_context_menu_gtk` for the GTK menu.

To get the Tk menu to work with the right-click button of the mouse, for instance, you would use:

MOUSE_BTN2 script_message mpv_context_menu_tk

The default bindings for the dialogs in `gui-dialogs.js` are:

| Key | Action |
| ------------------------------------------------:|:------------------------------------------- |
| Ctrl + F | Open files, replacing the playlist |
| Ctrl + G | Open a folder, replacing the playlist |
| Ctrl + Shift +F | Add/append files to the playlist |
| Ctrl + Shift +G | Add/append a folder to the playlist |
| Shift +F | Open a subtitle file (for the playing file) |

## Configuration

The scripts for `gui-dialogs.js` and `menu-engine.js` will read options stored in respective config files (in a `lua-settings` directory for mpv versions below 0.29.X and a `script-opts` directory for mpv versions from 0.29.X on) in your mpv config directory (mentioned above). The files should be named the same as the JavaScript files but with `.conf` instead of `.js` on the end. Check the near the top of each of the files to see which settings can be changed.

For mpv versions from 0.33.X onwards, the support for the script directory means that there is now a `main.js` file in the `mpvcontextmenu` script directory, however any config for `main.js` should be done in `mpvcontextmenu.conf` as the script directory name becomes the script name.

As an example of configuration, if you want to specify the use of `zenity` dialogs and change the key-bindings for the dialogs, you could create `gui-dialogs.conf` and specify the `dialogPref` and the shortcuts as such:

```ini
# Dialog preference
dialogPref=zenity
# Open files and open folder
addFiles=Ctrl+f
addFolder=Ctrl+g
```

Keep in mind that the shortcut style should match the `input.conf` format that mpv uses (allowing for case sensitivity) and that there should be no space between the `=` and the value after the `=` should not use quote marks.

For the menu engine, the font face can be set universally through a `menu-engine.conf`:

```ini
# Font face
fontFace=Source Code Pro
# Font size
fontSize=9
```

The font face name should be configured to work with the menu type intended to be used.

For the context menu itself, the options listed in the top of the file specify the unit for the values used. It's important to specify the options in a conf file keeping the same unit values in mind (e.g. Audio Sync is set up for milliseconds but Seek is set up for seconds). An example for `mpvcontextmenu.conf` might be:

```ini
# Play > Seek - Seconds
seekSmall=10
seekMedium=60
seekLarge=600
# Audio > Volume - Percentage
audVol=1
```

Note that the changes to the values here only affect the menu, not the values for shortcuts in the `input.conf` which has to be edited separately.

## Customization

For those wishing to change the menu items or add/remove menu items, the following should help, though looking at the code directly will be best. It's best if you know some JavaScript to make changes here.

**Important:** There is a `rebuildWatch` object in the `main.js` file that has a list of properties to watch for and the menu to rebuild if those properties change. If you're adding any new menu items or removing existing menu items, you may want to check whether the properties need to be watched.

The [Menu Layout](menu-layout) below goes into the menu structure more, but be aware that anywhere you use a function to return the state of a menu item will require that the associated property is changed, e.g. if a function used to return the stat of the `ontop` property and the menu item item is the main menu, then that is the menu to specify for rebuild:

```javascript
"ontop": "context_menu"
```

**Note:** The value "context_menu" is hard-coded in the `menu-engine` script as the main/general menu.

For menus under the main menu (in `main.js`) that use functions to rebuild, any menu items that are built via the function which should reflect changes requires that the submenu is listed as a menu to rebuild, e.g. if a sub-menu `audtrack_menu` uses a function to rebuild and an audio track menu item property is to be watched, then `audtrack_menu` is the menu to specify next to the property:

```javascript
"aid": "audtrack_menu"
```

### Menu Layout

The menu layouts use JavaScript objects. The layout for the menus are sets of objects nested inside an over-arching object (currently called menuList).

When the pseudo-gui is in use and there is no file playing, the layout uses a select number of relevant options from the "while playing" menu allowing for a slightly different menu.

For files that are playing, the layout is wrapped inside a function that is triggered by the "file-loaded" event in mpv (registered with `mp.register_event`). This is important as some of the mpv property values like track-list items and chapter information, etc. are not available until the file has been loaded.

Much of the menu construction is done by calling creating new objects via a `menuItem` function. The function is used to return an object with the relevant object properties set.

For both, menus, the layout is inside a object:

```lua
menuList = {
-- Menu items go here
}
```

Each menu item is itself an object.

A separator is comprised of a single item and uses the variable SEP to indicate this like so:

```javascript
1: new menuItem(SEP),
```

Keep in mind that for the JavaScript-based object menu structure, numerical values are used for the properties (the `1:` in the code above) where it necessary to iterate through menu items that are part of menus.

A full menu item object can be comprised of up to seven (7) items. The layout for creationg of a new menu item looks something like this (for all seven items):

```javascript
new menuItem(Item Type, Label, Accelerator, Command, Item State, Item Disable, Menu Rebuild),
```

Using the above as a guide, the below table provides some information for what can/should be entered for each item.

| Value | Purpose |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| Item Type | The item type specifies whether this is a a cascade (sub-menu), command, checkbox, radio item, etc. |
| Label | The label for the menu item and is what will be seen when using the menu. |
| Accelerator | The shortcut for the menu item and will show to the right of the label when using the menu. |
| Command | The command you want to be executed when an item is clicked on. |
| Item State | For checkboxes and radio items, this is the selected/unselected state. |
| Item Disable | This is set to true or false depending on whether the menu item should be clickable/usable. |
| Menu Rebuild | Set this to true if the menu should be rebuilt and re-cascaded so that it appears in the same place with the same sub-menus open. |

For **Item Type**, this should be one of the preset variable names, CASCADE, COMMAND, CHECK and RADIO respectively.

The **Label** and **Accelerator** items become the actual text that shows for each menu item.

**Command** is either the a command to be sent directly to mpv (via `mp.command`) or a function call that will do something (better detailed further below).

The **Item State** should normally be `true` or `false` values, though there is a special case for the A/B Repeat where the receiving script will handle "a", "b" or "off" values and change the appearance of the "checkbox" based on the value.

The best way to handle this for checkboxes or radio items is to wrap a function that will return a true/false for the respective menu item in a function call (detailed below).

The **Item Disable** item is used to enable/disable menu items and can also disable cascades from functioning. This can be useful if a menu item should be disabled when certain functionality is not available with a given file. Set this to `true` to disable the item and `false` to leave it enabled.

**Menu Rebuild** is used to toggle/use a menu item, but keep the menu open to the same sub-menu. This makes it possible to have the mouse over a menu item and click the item, causing the menu to close and reopen (may be a flicker, could be more obvious) as the menu is essentially closed and reopened to where it was. This currently only works with the Tk based menu.

For the **Item Label**, **Accelerator** and **Command** items, you can use string concatenation `("Text " + var + " Text")` as the value is evaluated at much the same time as in-line functions returning values (detailed below).

If not creating a separator (noted above), at least six items should be entered and empty quotes used when not specifying a value. A seventh can be used when using the **Menu Rebuild** functionality. There is code to check the table sizes for menu items in `main.js` which will output an OSD message if the check fails.

An example item with these in mind, could be like so:

```javascript
?: new menuItem(COMMAND, "Toggle Fullscreen", "F", "cycle fullscreen", "", false, true),
```

With all that in mind, a basic menu might look something like this (it's important to remember your commas!):

```javascript
menuList = {
context_menu: {
1: new menuItem(CASCADE, "Play", "play_menu", "", "", false),
},

play_menu: {
1: new menuItem(COMMAND, "Play/Pause", "Space", "cycle pause", "", false, true),
2: new menuItem(COMMAND, "Stop", "Ctrl+Space", "stop", "", false),
3: new menuItem(SEP),
4: new menuItem(COMMAND, "Previous", "<", "playlist-prev", "", false, true),
5: new menuItem(COMMAND, "Next", ">", "playlist-next", "", false, true),
}
```

The **CASCADE** item is special in that the third value in the table for a cascade menu item is the name of the table that should cascade from that menu item.

In the above example, the cascade will have the `play_menu` table as a sub-menu coming off of it. There is a maximum of 10 total menu levels, including the base, making for up to 9 sub-menu levels deep. Any more than that will throw an error, which also prevents infinite recursion in the menu building function.

### Function call/in-line function

Apart from the separator, the menu items can make use of a function call:

```javascript
function() { return somefunction(); }
```

Apart from the **Command** item, this function call is evaluated each time the menu is built (each time the script-message shortcut binding is activated). This allows you to dynamically build labels or set values at the time of creating the menu.

In the instance of the **Command** item, if there is a function call, this will be evaluated as a function only upon being clicked and will **not** send a command unless you do so within your function. If there is not a function call, the command provided will be sent to `mpv`.

There is also a difference between a function call like the above and an in-line function returning a value as part of building a command to send to mpv. For instance, if we had a menu item like this:

```javascript
?: new menuItem(COMMAND, getLabel(), "Shift+L", "", "", getState())
```

When the JavaScript code is being executed (either on initially being loaded or after a file is loaded), the `getLabel()` and `getState()` functions would be run and should return a label string and a true/false value respectively.

## Notes

One of the files included is the `langcodes.js` which holds two tables filled with associative arrays using the 2 or 3 character long language name representations (ISO 639-1 and ISO 639-2) as property accessors to get full length language names, though these are only in English at this point.

## Disclaimer

I have tried to test this on a variety of media files and have attempted to deal with any bugs that have arisen, but I can't guarantee that this is bug-free. There may be use-cases I haven't considered or some functions may throw errors from unexpected values/input.

I also have only tested this on Linux and any information about Windows or macOS is based either on the original authors code comments or some searches I've done. I may look at trying to test other OS's, but can't guarantee anything.

## Credits

Thanks go out to the following people:

* avih for the [original Tcl/Tk context menu](https://gist.github.com/avih/bee746200b5712220b8bd2f230e535de) upon which this menu has been built
* ntasos for some code and ideas from the [KDialog-open-files](https://gist.github.com/ntasos/d1d846abd7d25e4e83a78d22ee067a22) script