Giter Club home page Giter Club logo

context_menu's Introduction

πŸ—‚οΈcontext_menu build passing readthedocs pip Downloads

logo

πŸ’» A Python library to create and deploy cross-platform native context menus. πŸ’»

Documentation available at: https://context-menu.readthedocs.io/en/latest/


example usage


Table of Contents

βš™ Features βš™

This library lets you edit the entries on the right click menu for Windows and Linux using pure Python. It also allows you to make cascading context menus!

context_menu was created as due to the lack of an intuitive and easy to use cross-platform context menu library. The library allows you to create your own context menu entries and control their behavior seamlessly in native Python. The library has the following features:

  • Written in pure python with no other dependencies
  • Extremely intuitive design inspired by Keras Tensorflow
  • Swift installation from Python's Package Manager (pip)
  • Painless context menu creation
  • Cascading context menu support
  • The ability to natively integrate python functions from a context entry call
  • Detailed documentation

πŸ™‹ What is the context menu? πŸ™‹

The context menu is the window that is displayed when you right click:

img.png

The context menu is different depending on what was right clicked. For example, right clicking a folder will give you different options than right clicking a file.

πŸ–₯️ What Operating Systems are supported? πŸ–₯️

Currently, the only operating systems supported are:

  • Windows 7
  • Windows 10
  • Windows 11
  • Linux (Using Nautilus)

🐍 What Python versions are supported? 🐍

All python versions 3.7 and above are supported.

πŸ’½ Installation πŸ’½

If you haven't installed Python, download and run an installer from the official website: https://www.python.org/downloads/

Once you have Python, the rest is super simple. Simply just run the following command in a terminal to install the package:

python -m pip install context_menu

or if you're on Linux:

python3 -m pip install context_menu

Note: If you're on Windows and it says the command isn't recognized, make sure to add Python to your path and run the command prompt as administrator

πŸ•ΉοΈ Quickstart πŸ•ΉοΈ

Let's say you want to make a basic context menu entry when you right click a file.

  1. If you haven't already Install the library via pip:
python -m pip install context_menu
  1. Create and compile the menu:

It's super easy! You can create entries in as little as 3 lines:

from context_menu import menus

fc = menus.FastCommand('Example Fast Command 1', type='FILES', command='echo Hello')
fc.compile()

example fast command

All you have to do is import the library and define the type of context entry you want. The options are:

  • A context menu (an entry that has more entries)
  • A fast command (a single context menu entry to kick a running script)
  • A context command which can be added to menus for more complex commands

You can also create much more complicated nested menus:

def foo2(filenames, params):
    print('foo2')
    print(filenames)
    input()


def foo3(filenames, params):
    print('foo3')
    print(filenames)
    input()


if __name__ == '__main__':
    from context_menu import menus

    cm = menus.ContextMenu('Foo menu', type='FILES')
    cm2 = menus.ContextMenu('Foo Menu 2')
    cm3 = menus.ContextMenu('Foo Menu 3')

    cm3.add_items([
        menus.ContextCommand('Foo One', command='echo hello > example.txt'),
    ])
    cm2.add_items([
        menus.ContextCommand('Foo Two', python=foo2),
        cm3,
    ])
    cm.add_items([
        cm2,
        menus.ContextCommand('Foo Three', python=foo3)
    ])

    cm.compile()

second Example

All context menus are permanent unless you remove them.

πŸ€– Advanced Usage πŸ€–

The ContextMenu Class

The ContextMenu object holds other context objects. It expects a name, the activation type if it is the root menu(the first menu), and an optional icon path. Only compile the root menu.

ContextMenu(name: str, type: str = None, icon_path: str = None)

Menus can be added to menus, creating cascading context menus. You can use the {MENU}.add_items{ITEMS} function to add context elements together, for example:

cm = menus.ContextMenu('Foo menu', type='DIRECTORY_BACKGROUND')
cm.add_items([
    menus.ContextMenu(...),
    menus.ContextCommand(...),
    menus.ContextCommand(...)
])
cm.compile()

You have to call {MENU}.compile() in order to create the menu.

The ContextCommand Class

The ContextCommand class creates the selectable part of the menu (you can click it). It requires a name, and either a Python function or a command (but NOT both) and has various other options

ContextCommand(name: str, command: str = None, python: 'function' = None, params: str = None, command_vars: list = None, icon_path: str = None)

Python functions can be passed to this method, regardless of their location. However, the function must accept only two parameters filenames, which is a list of paths*, and params, the parameters passed to the function. and if the function is in the same file as the menu, you have to surround it with if __name__ == '__main__':

# An example of a valid function
def valid_function(filenames, params):
    print('Im valid!')
    print(filenames)
    print(params)


# Examples of invalid functions
def invalid_function_1(filenames, param1, param2):
    print('Im invalid!')
    print(filenames)


def invalid_function_2(params):
    print('Im invalid!')
    print(params)

Any command passed (as a string) will be directly ran from the shell.

The FastCommand Class

The FastCommand class is an extension of the ContextMenu class and allows you to quickly create a single entry menu. It expects a name, type, command/function and an optional icon path.

FastCommand(
    name: str, type: str, command: str = None, python: 'function' = None, params: str = '', command_vars: list = None, icon_path: str = None)
def foo1(filenames, params):
    print(filenames)
    input()


if __name__ == '__main__':
    from context_menu import menus

    fc = menus.FastCommand('Example Fast Command 1', type='FILES', python=foo1)
    fc.compile()

The removeMenu method

You can remove a context menu entry easily as well. Simply call the 'menus.removeMenu()' method.

removeMenu(name: str, type: str)

For example, if I wanted to remove the menu 'Foo Menu' that activated on type 'FILES':

from context_menu import menus

menus.removeMenu('Foo Menu', 'FILES')

and boom! It's gone 😎

The params Command Parameter

In both the ContextCommand class and FastCommand class you can pass in a parameter, defined by the parameter=None variable. This value MUST be a string! This means instead of passing a list or numbers, pass it as a string separated by spaces or whatever to delimitate it.

fc = menus.FastCommand('Example Fast Command 1', type='FILES', python=foo1, params='a b c d e')
fc.compile()

For more information, see this.

Works on the FastCommand and ContextCommand class.

command_vars Command Parameter

If you decide to pass a shell command, you can access a list of special variables. For example, if I wanted to run a custom command with the file selected, I could use the following:

fc = menus.FastCommand('Weird Copy', type='FILES', command='touch ?x', command_vars=['FILENAME'])
fc.compile()

which would create a new file with the name of whatever I selected with an 'x' on the end. The ? variable is interpreted from left to right and replaced with the selected values (see this).

All of the preset values are as follows:

Name Function
FILENAME The path to the file selected
DIR/DIRECTORY The directory the script was ran in.
PYTHONLOC The location of the python interpreter.

Works on the FastCommand and ContextCommand class.

Opening on Files

Let's say you only want your context menu entry to open on a certain type of file, such as a .txt file. You can do this by adding a type variable to the ContextCommand or FastCommand class.

fc = menus.FastCommand('Weird Copy', type='.txt', command='touch ?x',
                       command_vars=['FILENAME'])  # opens only on .txt files
fc.compile()

Now you'll only see the "Weird Copy" menu entry when you right click a .txt file.

Activation Types

There are different locations where a context menu can fire. For example, if you right click on a folder you'll get different options than if you right click on a file. The type variable controls this behavior in the library, and you can reference this table to determine the type:

Name Location Action
FILES HKEY_CURRENT_USER\Software\Classes\*\shell\ Opens on a file
DIRECTORY HKEY_CURRENT_USER\Software\Classes\Directory\shell Opens on a directory
DIRECTORY_BACKGROUND HKEY_CURRENT_USER\Software\Classes\Directory\Background\shell Opens on the background of the Directory
DRIVE HKEY_CURRENT_USER\Software\Classes\Drive\shell Opens on the drives(think USBs)
DESKTOP Software\Classes\DesktopBackground\shell Opens on the background of the desktop

I strongly recommend checking out the examples folder for more complicated examples and usage.

You can check out the official documentation here.


🏁 Goals 🏁

This project tackles some pretty big issues, and there's definetly some goals that I'd like to accomplish. The current roadmap is as follows:

  • Support for other Linux distributions
  • Better approach to the Linux GNOME integration
  • Mac support
  • Bypass 16 entry limit on windows

If by all means you want to help reach these milestones, see contribution below.

πŸ™Œ Contribution πŸ™Œ

I really want to add support for MacOS, but I don't have the experience required to implement it.

Contributing is super simple! Create an additional branch and make a pull request with your changes. If the changes past the automated tests, it will be manually reviewed and merged accordingly.

Any and all help is appreciated, and if you have any questions, feel free to contact me directly.

πŸ““ Important notes πŸ““

  • Almost all the errors I've encountered in testing were when the code and the functions were in the same file. You should make a separate file for the code or surround it with if __name__ == '__main__':.
  • On windows, there's currently a 16 entry limit on the context menu.

πŸ’» Freshen - A context_menu project! πŸ’»

Feel free to check out a file sorter program I made that directly implements this library.

Readme Card

πŸ’™ Support πŸ’™

All my work is and always will be free and open source. If you'd like to support me, please consider leaving a ⭐ star ⭐, as it motivates me and the community to keep working on this project.

Thanks for reading!

context_menu's People

Contributors

m-haisham avatar nauja avatar rma6 avatar saalaus avatar saleguas avatar ulnasensei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

context_menu's Issues

Can't Specify File Type With Fast Command

This does not work whatsoever on Windows 10: straight from the examples:

fc = menus.FastCommand('Weird Copy', type='.txt', command='touch ?x',
                       command_vars=['FILENAME'])  # opens only on .txt files
fc.compile()

It doesn't add a context menu item at all.

Context Menu visibility

Is there any option where context menu would be only visible for a particular path or drive in linux?

Thank you.

'NoneType' object has no attribute 'upper'

In RegistryMenu, the type parameter can't be None, because there is type.upper():

class RegistryMenu:
    '''
    Class to convert the general menu from menus.py to a Windows-specific menu.
    '''
    def __init__(self, name: str, sub_items: list, type: str):
        '''
        Handled automatically by menus.py, but requires a name, all the sub items, and a type
        '''
        self.name = name
        self.sub_items = sub_items
        self.type = type.upper()
        self.path = context_registry_format(type)

However in ContextMenu, and in provided examples, it looks like the parameter type should be optional:

class ContextMenu:
    '''
    The general menu class. This class generalizes the menus and eventually passes the correct values to the platform-specifically menus.
    '''

    def __init__(self, name: str, type: str = None):
        '''
        Only specify type if it's the root menu.
        '''

        self.name = name
        self.sub_items = []
        self.type = type

It seems there is the same issue for NautilusMenu where type is passed to CodeBuilder where there is type.upper().

Is it that the correct fix would be to make the parameter type mandatory in ContextMenu ?

Documentation does not provide a directory example

It would be great to have examples for the directory and directory-background menus. For the directory menu, one would assume that the directory parameter would be a single string -- not a list. This can be inferred from the fact that parameters past in are not a list but a string delineated by spaces.

Ended up opening an error file with the generated exception to figure out why the code was breaking. :(

I am happy to supply my working code as an example.

Window's submenu size limit

The current, hard limit for how many items can be in a submenu is 16.
Could it be possible to change this somehow?

Display only on shift-click

Windows

  • adds an empty string value named Extended for key created

More on stackoverflow post

Linux

Not aware of the existance of the feature

New Windows 11 Menu

Windows 11 have currently 2 context menus and it would be nice if this library would support the new context menu

Issues with displaying menu on Ubuntu 20.04

Hello I'm having an issue getting the context menu to be displayed in my environment.
my setup is as follows:
python3 -m pip install context_menu which successfully installs.
attempting to use the quick start given code:

fc = menus.FastCommand('Example Fast Command 1', type='FILES', command='echo Hello')
fc.compile()

Running my script python3 sample.py
running the nautilus command nautilus -q
and my file explorer closes and i reopen it and head over to a text file, I've tried other files but I'm attempting to mimic the video on the read-me to hopefully solve the issue. but I'm still having no luck, with the context menu being displayed.

ive tried to reinstall Python3, context_menu, nautilus, along with trying both of the given example scripts.

versions of stuff I'm using:

nautilus: GNOME nautilus 3.36.3
System: Ubuntu 20.04.4 LTS 64bit
GNOME: 3.36.8
Windowing system: X11
python 2.7.18 && 3.8

I'm 100% doing something incorrect on my end and not an expert when it comes to Ubuntu so apologies in advance and thank you for any clarity on my issue.

[Suggestion] The ability to add custom images to the context menu

See title, for reference in how this could be done see here
Thanks for the awesome library by the way!

PD: I've noticed a limitation with the way the shell context works on Windows, since if the path to the selected destination is sufficiently long, it will just ignore the rest of the command, I don't know if this is my own wrongdoing or a limitation with the way Windows runs commands.

Example not working

Hi, i am trying to add the context menu functionality to a simple script i wrote but i can't get the example script to generate the menu. When i try to run it it doesn't get me any errors.

[QUESTION] Running on VM - Ubuntu

Hello, I am trying create fast command but it seems not working. Did someone tried create context menus on VM?

My configuration:
HOST: Windows 7 64bit
GUEST: Ubuntu 20.04 64bit
Running on Virtual Box 6.1

Thank's for any help.

Examples not working on Windows 11

Hi,

I had a hard time with the provided examples on Windows 11, because in the Quickstart you say:

It's super easy! You can create entries in as little as 3 lines:

from context_menu import menus

fc = menus.FastCommand('Example Fast Command 1', type='FILES', command='echo Hello')
fc.compile()

With a screenshot of the menu on Windows.

However I don't know if that's a difference with older Windows, or because you wanted the examples to look crossplatform, or concises, but for me the FastCommand with echo hello doesn't work. In fact it has to be cmd /c echo hello.

Actually I test it with pause so that if there's a command prompt it doesn't disappear right away. With pause, Windows opens me a menu to choose how to open the file because the FastCommand doesn't work. With cmd /c pause, the FastCommand works correctly and the command prompt opens.

Not saying that you should change the examples to include cmd /c, but right now the README makes it look like echo hello should work, and I spent many time trying to find why it didn't work for me πŸ˜… . So the README should mention somewhere that if you want to do echo hello, the command actually has to be cmd /c echo hello on Windows

Command_vars dont work correctly with spaces in filename

The script does not escape file paths with quotes, so it does not work properly if there are problems in the file path

cm = menus.ContextMenu("Converter", type=".mp4")
cm.add_items(
    [menus.ContextCommand("to mp3", "C:/ProgramData/chocolatey/bin/ffmpeg.exe -i ? test.mp3", command_vars=['FILENAME'])]
)
cm.compile()

For file path: "C:\Users\saala\Videos\Base Profile\test123.mp4"

Execute script: C:/ProgramData/chocolatey/bin/ffmpeg.exe -i C:\Users\saala\Videos\Base Profile\test123.mp4 test.mp3
^ this dont work correctly

Flexibility of the target function's signature?

Hello,
I just discovered your library and I already find it very very practical. I have a program which can already load files through a pop-up (PyQt5's QFileDialog) and would like to add the option to load them via context menu too.

My question is: can the function's signature be more flexible?
As of now, it seems that only f(filenames, params) and f(filenames=None, params=None) are supported. f(**kwargs) would be very handy as we could still recover the filenames and params while allowing extra parameters, and f(self, ...) would be amazing too for when your target function is actually a method.

I have prepared test cases for you.
The code below will add four context menu actions for files. Only actions 1 and 2 actually work, while 3 and 4 (the kwargs and class variants) don't. Uncomment the code in the context manager and run the code again to remove those context menu actions.

Thank you in advance!

from contextlib import contextmanager

@contextmanager
def add_context_menu_action(action_name, function):
    """Add a context menu action only for the duration of the encompassed scope.

       args:
       -----
       :action_name: (str) the label of the action button.
       :function: the function to call on action button click. Because of the
                  context-menu library requirements, this function must have
                  a `function(filenames, params)` signature. No *args or **kwargs.

       See also:
       -----
       https://github.com/saleguas/context_menu
       https://pypi.org/project/context-menu/"""

    # Creates a direct context menu entry.
    kwargs = dict(name=action_name, type="FILES")
    fc = menus.FastCommand(**kwargs, python=function)
    fc.compile()
#    yield
#    try:
#        menus.removeMenu(**kwargs)
#    except FileNotFoundError: # Prevents an error if the function could not be located.
#        pass



def _TEST1(filenames, params):

    with open("ZE_TEST 1.txt", "w") as file:
        file.write(f"filenames: {filenames}\n")
        file.write(f"params: {params}\n")
        file.write("*"*15)



def _TEST2(filenames=None, params=None):

    with open("ZE_TEST 2.txt", "w") as file:
        file.write(f"filenames: {filenames}\n")
        file.write(f"params: {params}\n")
        file.write("*"*15)



def _TEST3(**kwargs):

    filenames, params = (kwargs[key] for key in ("filenames", "params"))
    with open("ZE_TEST 3.txt", "w") as file:
        file.write(f"filenames: {filenames}\n")
        file.write(f"params: {params}\n")
        file.write("*"*15)



class MethodTester:

    def __init__(self):
        pass

    def _TEST4(self, filenames, params):
        with open("ZE_TEST 4.txt", "w") as file:
            file.write(f"filenames: {filenames}\n")
            file.write(f"params: {params}\n")
            file.write("*"*15)



if __name__ == "__main__":

    x = MethodTester()
    for n in (1, 2, 3, 4):
        name = f"ZE_TEST FastCommand {n}"
        function = {1: _TEST1,
                    2: _TEST2,
                    3: _TEST3,
                    4: x._TEST4}[n]
        with add_context_menu_action(name, function):
            pass

Don't work on Windows with Python3.9

Hi, I tested this library on Windows 11 with Python 3.9 and couldn't get it to work at all.

There is no error, however the calls to winreg.CreateKey don't create anything.
It's only when I found this thread https://stackoverflow.com/questions/65929777/python-winreg-unable-to-write-to-hkey-current-user that I tried the exact same code with Python 3.8, and it worked perfectly fine.

I see you had users mentioning this problem in the issue #13 that have been closed, and while it may not be something to fix in your library, but a bug in Python, I'm opening this issue for further reference

Support for passing filename to command

So, I absolutely love this project, but I have one problem: While I can pass the filename[s] to a Python function, I can't pass them to the command option. For normal Python code this isn't a big problem; however, compiling code with PyInstaller makes the python option unusable.

With that in mind, I would like it if you could add the ability to do something like this:

foo = menus.FastCommand('Foobar', type='FILES', command="foo ? ?", command_vars = ["FILENAME", "DIR"]

where command_vars is an iterable specifying the context menu-related variables (e.g. the filename or the current working directory) with which to replace the ?s in command.

[v0.1.20] Script added key to HKEY_CLASSES_ROOT

If i'm not wrong as of v0.1.20 registry keys should be added to HKEY_CURRENT_USER\Software\Classes. However my code (shown below) added the key to HKEY_CLASSES_ROOT.

def command(*args, **kwargs):
    print(args)
    print(kwargs)
    input()


if __name__ == '__main__':
    from context_menu import menus

    menus.FastCommand('test', type='FILES', python=command).compile()

As a side-effect, remove menu says that key doesnt exist

macOS support

It would be great to have macOS support without having to use Automator or Service Station. Unfortunately I have no clue on how to help but I'll do some research!

Unable to parse file with spaces in name (Windows 11)

I'm testing this script and so far, it seems a nice way to play with custom context menus. However, I'm having trouble getting files to open if they have spaces in the name.

Example with notepad (Foo Two):

if __name__ == '__main__':
    from context_menu import menus

    cm = menus.ContextMenu('Foo menu', type='FILES')
    cm.add_items([
        menus.ContextCommand('Foo One', command='cmd /K echo ?', command_vars=['FILENAME']),
        menus.ContextCommand('Foo Two', command='notepad ?', command_vars=['FILENAME']),
        menus.ContextCommand('Foo Three', python=foo3)
        ])
    cm.compile()

If the file is called d:\1.jpg everything works as expected (It should not looking below), if however, the file is d:\cute kittens.jpg it fails with a file not found error:
image

If I use Foo One I get an echo but it shows a trailing " at the end of the filename:
image
image

File types with Windows default programs do not show context menus

Due to how Windows handles context menu preference, if a file type has a default program set, context menus from this package will not show properly.

The issue is that defining individual file extensions starting from the Software\\Classes\\{EXTENSION} registry path allows them to be overridden. Instead, if the file extensions are defined at the registry path Software\\Classes\\SystemFileAssociations\\, Windows will put them in the menu at a lower priority than the default program.

See the Windows Documentation or this Stack Overflow question.

If I can get some time this weekend, I'll try to put up a PR with a fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.