6

I am looking for a tray icon indicator that - upon click on the icon - shows me the list of my recently used files. This would be a great way to quickly access these files.

7
  • Can be done, but the list would be limited, mostly to files with a Gtk window. If no one posted an answer, I will, but it may take a while :) (need to be home long enough to write the code :) ). Jul 28, 2016 at 7:36
  • Wow, that would be great, Jacob! Can already anticipate the decent quality of your solution. :-) Would such a solution include the listing of, for example, recent audio files (do they have a Gtk window?)?
    – orschiro
    Jul 28, 2016 at 8:14
  • Hi Orschiro, posted the "test" version. Runs fine for the past few hours on my system, but please let me know. Jul 29, 2016 at 15:21
  • This is so beautiful, Jacob! Thank you a ton! :-)
    – orschiro
    Jul 29, 2016 at 15:33
  • 1
    Hi orschiro, you might have noticed, but this one will be developed further askubuntu.com/a/851148/72216. Including the option to only show recently used files, as a flexibel tool. The ppa is ppa:vlijm/placesfiles (see the linked answer). Nov 21, 2016 at 9:54

4 Answers 4

7

Have your recently used files in the panel

With the script below, you can have an arbitrary number of recently used items at hand in the panel, e.g. 6 items:

enter image description here

...or 20 items:

enter image description here

...depending on your settings.

How it works

The setup exists of two items:

  1. an icon, named (exactly) recent.png
  2. a script

Both need to be in one and the same folder. after that, simply run the script.

How to set up

Possibly, you need to install python3-gi:

sudo apt-get install python3-gi

Then:

  1. Copy the script below into an empty file, save it as recused.py

    #!/usr/bin/env python3
    import signal
    import gi
    gi.require_version('Gtk', '3.0')
    gi.require_version('AppIndicator3', '0.1')
    from gi.repository import Gtk, AppIndicator3, GObject
    import time
    from threading import Thread
    import os
    import subprocess
    
    # --- set the number of recently used files to appear below
    n = 20
    # ---
    
    home = os.environ["HOME"]
    recdata = os.path.join(home, ".local/share/recently-used.xbel")
    currpath = os.path.dirname(os.path.realpath(__file__))
    
    class Indicator():
        def __init__(self):
            self.app = 'show_recent'
            iconpath = os.path.join(currpath, "recent.png")
            self.indicator = AppIndicator3.Indicator.new(
                self.app, iconpath,
                AppIndicator3.IndicatorCategory.OTHER)
            self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)       
            self.indicator.set_menu(self.create_menu())
            # the thread:
            self.update = Thread(target=self.check_recent)
            # daemonize the thread to make the indicator stopable
            self.update.setDaemon(True)
            self.update.start()
    
        def get_files(self):
            # create the list of recently used files
            used = [l for l in open(recdata) if \
                    all([
                        '<bookmark href="file://' in l,
                        not "/tmp" in l,
                        "." in l,
                         ])]
            relevant = [l.split('="') for l in set(used)]
            relevant = [[it[1][7:-7], it[-2][:-10]] for it in relevant]
            relevant.sort(key=lambda x: x[1])
            return [item[0].replace("%20", " ") for item in relevant[::-1][:n]]
    
        def create_menu(self):
            # creates the (initial) menu
            self.menu = Gtk.Menu()
            # separator
            menu_sep = Gtk.SeparatorMenuItem()
            self.menu.append(menu_sep)
            # item_quit.show() 
            self.menu.show_all()
            return self.menu
    
        def open_file(self, *args):
            # opens the file with the default application
            index = self.menu.get_children().index(self.menu.get_active())
            selection = self.menu_items2[index]
            subprocess.Popen(["xdg-open", selection])
    
        def set_new(self):
            # update the list, appearing in the menu
            for i in self.menu.get_children():
                self.menu.remove(i)
            for file in self.menu_items2:
                sub = Gtk.MenuItem(file)
                self.menu.append(sub)
                sub.connect('activate', self.open_file)
            # separator
            menu_sep = Gtk.SeparatorMenuItem()
            self.menu.append(menu_sep)
            # quit
            item_quit = Gtk.MenuItem('Quit')
            item_quit.connect('activate', self.stop)
            self.menu.append(item_quit)
            self.menu.show_all()
    
        def check_recent(self):
            self.menu_items1 = []
            while True:
                time.sleep(3)
                self.menu_items2 = self.get_files()
                if self.menu_items2 != self.menu_items1:
                    GObject.idle_add(
                        self.set_new, 
                        priority=GObject.PRIORITY_DEFAULT
                        )
                self.menu_items1 = self.menu_items2
    
        def stop(self, source):
            Gtk.main_quit()
    
    Indicator()
    # this is where we call GObject.threads_init()
    GObject.threads_init()
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    Gtk.main()
    
  2. In the head section of the script, set the number of items to show:

    # --- set the number of recently used files to appear below
    n = 20
    # ---
    
  3. In one and the same folder save the icon below as (exactly) recent.png

    enter image description here

    (right-click on it -> save as)

  4. Test- run the script by the command:

    python3 /path/to/recused.py
    
  5. If all works fine, add to Startup Applications: Dash > Startup Applications > Add. Add the command:

    /bin/bash -c "sleep 15 && python3 /path/to/recused.py"
    

Notes

  • This script was based on this previous answer, in case you are interested in an alternative, be it that that one was application-specific, and used a periodically updated .desktop file.
  • The script was tested on 16.04, but should work on earlier versions as well.
  • Due to a bug in the recently-used.xbelfile, sometimes a file is mentioned twice; once with, and once without extension. I solved that by filtering the latter out. The consequence is that files without extension won't appear in the list. If that is an issue, let me know, we can try to find another fix in that case.
  • If a file does not exist (anymore), the indicator simply (obviously) does not open the file. Most likely, I will polish the script a bit more to filter out those outdated entries from the recently-used.xbel file.

Explanation

Many applications, editing files and using a Gtk window, keep track of opened files in the file: ~/.local/share/recently-used.xbel. "Records" include the date & time, the application and the file that was opened.

The script reads the .xbel file, sorts the items by date/time, includes the first n- (depending on your settings) files in the menu of the indicator. The menu is updated (only if necessary) every 3 seconds as it is. Subsequently, when an item is selected, the file is opened with the command:

xdg-open <file>

Thus the selected file will be opened with the default application. It is very well possible to make sure the file is opened with the actual application it was last opened. That takes a somewhat more sophisticated parsing however. I will add that as an option to the planned ppa version on Launchpad.

The same goes for an option window to set a few options, like the number of files to show etc.

Note

The indicator is now merged with a few other things in this one.

18
  • 1
    Nice answer! Why not to turn this script into a full-featured app, adding a preferences option? Or just fork the xfce panel app to make it work in Gnome? Any of this is too advanced for me.
    – user308164
    Jul 29, 2016 at 16:26
  • @luchonacho thanks! Yeah, I think this one should move to launchpad, get a proper settings menu, a ppa etc. This is the first "holiday" version :) Thanks for the suggestion! Jul 29, 2016 at 16:31
  • Jacob, should I address a bug directly here? Umlaut characters (e.g. ü) are not properly interpreted. ü turns into %C3%9C.
    – orschiro
    Jul 31, 2016 at 19:16
  • Alright, take your time! Just wanted to make sure I won't forget it. :-)
    – orschiro
    Jul 31, 2016 at 19:28
  • @orschiro Hi orschiro, I just found out that even gedit and LibreOffice do not handle umlauts well. Even gedit's "recently used" breaks on them, since the files are not mentioned correctly in the xbel- file. Not much I can do about that. Aug 2, 2016 at 10:06
2

Although you do not mention which Ubuntu flavour you are using, there is a nice app for Xubuntu. More specifically, it is part of Xfce, so you might still be able to use it in Ubuntu if you use Xfce. The app is called Places.

To enable, right-click on the panel, and from the menu select Panel -> Add New Items..., and there choose Places. This shows a list of folders and Recent Documents you just opened. See below:

enter image description here

You can configure it nicely too:

enter image description here

Here is the project's page.


If you are not using Xfce, you can install it very easily, as explained in this answer. Simply do sudo apt-get install xfce-panel, and then run (see link). Notice that Xfce panel might not be compatible with all the other apps in your Unity (or other) panel. In my experience I have had no problem at all with it, so it might still be a good solution.

8
  • Very nice answer and sorry for forgetting to mention Ubuntu 16.04 Unity. Will I be able to install and run this Xfce places plugin on Unity?
    – orschiro
    Jul 28, 2016 at 8:36
  • That is something I wonder myself too. The package is here. Why don't you give it a try? See if sudo apt-get install xfce4-places-plugin works. I can't because it comes by default.
    – user308164
    Jul 28, 2016 at 8:54
  • I installed it but can't see a way to launch it.
    – orschiro
    Jul 28, 2016 at 9:02
  • Can you find Places from Add New item in the panel menu? It might not integrate easily with Unity then. You might need to switch to the entire xfce-panel. Try this answer. Then it should work!
    – user308164
    Jul 28, 2016 at 9:07
  • Bummer, I really like the Unity panel and don't want to loose it just for one places plugin. :-(
    – orschiro
    Jul 28, 2016 at 9:09
1

Not a tray icon but a quicklist script for the unity launcher (works with Umlauts), and you can pin files as well: Ubuntu-Recentquicklists

URQ screenshot

[detailed install instructions here]

Quick instructions:

  • download .zip
  • extract
  • make install.sh and ubuntu-recentquicklists.py executable
  • run install.sh script

Features:

  • automatically adds recent files
  • manually add non-recent files / pin recent files
  • specify how many items per list you want and
  • how old the items may be
  • resolve symbolic links
  • display the full path (or just the filename, as in the screenshot)
2
  • This looks very promising. Thanks for sharing! Can you explain in your answer what the pinning feature does?
    – orschiro
    Sep 26, 2016 at 9:13
  • 1
    Recent files go away if not accessed for a while, but if you e.g. have an important .xls file you want to stay, you can enter pinning mode (click on filepinning), click on the stuff you want to stay (until you unpin it), and exit pinning mode (click on filepinning once again). This writes the pinned files to the config-file as well, so rebooting etc. doesn't remove them
    – hirsch
    Sep 26, 2016 at 9:46
1

Update Feb/24/2017: the indicator now has option to pin web links.

Introduction

Files Indicator presented below is simple indicator for accessing user's files and folders. It allows checking recently used files, bookmarking files and directories.

enter image description here

Update: indicator now also supports launching .desktop files that have been pinned. For instance if you have firefox.desktop pinned, it will launch firefox. Thus, the indicator can be used as quick launcher for programs. The feature is on the way into PPA at the time of writing (Nov 19, 7:53 pm GMT, should take about 24 hours to process), but is already on github as well as here, in updated source code.

enter image description here

Obtaining the indicator

The indicator is available from my personal PPA as well as GitHub. Use the following steps to obtain it:

sudo add-apt-repository ppa:1047481448-2/sergkolo
sudo apt-get update
sudo apt-get install files-indicator

Source code

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
# Author: Serg Kolo , contact: 1047481448@qq.com
# Date: November 19 , 2016
# Purpose: appindicator for accessing files and folders
# Tested on: Ubuntu 16.04 LTS
#
#
# Licensed under The MIT License (MIT).
# See included LICENSE file or the notice below.
#
# Copyright © 2016 Sergiy Kolodyazhnyy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import gi
gi.require_version('AppIndicator3', '0.1')
gi.require_version('Notify', '0.7')
from gi.repository import GLib as glib
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Gtk as gtk
from gi.repository import Gio
from gi.repository import Notify
from collections import OrderedDict
# from collections import OrderedDict
import urllib.parse
import subprocess
import copy
import shutil
import dbus
import math
import json
import os

class FilesIndicator(object):

    def __init__(self):
        self.app = appindicator.Indicator.new(
            'files-indicator', "document-open-recent",
            appindicator.IndicatorCategory.HARDWARE
        )
        self.user_home = os.path.expanduser('~')
        filename = '.pinned_files.json'
        self.pinned_list = os.path.join(self.user_home,filename)

        self.config = os.path.join(self.user_home,'.files_indicator.json')
        self.max_items = 15
        self.name_length = 20
        self.read_config()

        self.app.set_status(appindicator.IndicatorStatus.ACTIVE)

        self.cached_files = self.get_recent_files()
        self.make_menu()
        self.update()

    def read_config(self,*args):
        config = {}
        try:
            with open(self.config) as f:
                 config = json.load(f)

        except FileNotFoundError:
            print('>>> ',self.config,' not found.Creating one')
            f = open(self.config,'w')
            config = {'max_items':self.max_items,
                      'name_length':self.name_length
            }
            json.dump(config,f,indent=4)
            f.close()
        except json.JSONDecodeError:
            print(">>> Can't read ",self.pinned_list,',may be corrupt')
            return None
        else:
            self.max_items = config['max_items']
            self.name_length = config['name_length']

    def add_menu_item(self, menu_obj, item_type, image, label, action, args):
        """ dynamic function that can add menu items depending on
            the item type and other arguments"""
        menu_item, icon = None, None
        if item_type is gtk.ImageMenuItem and label:
            menu_item = gtk.ImageMenuItem.new_with_label(label)
            menu_item.set_always_show_image(True)
            if '/' in image:
                icon = gtk.Image.new_from_file(image)
            else:
                icon = gtk.Image.new_from_icon_name(image, 48)
            menu_item.set_image(icon)
        elif item_type is gtk.ImageMenuItem and not label:
            menu_item = gtk.ImageMenuItem()
            menu_item.set_always_show_image(True)
            if '/' in image:
                icon = gtk.Image.new_from_file(image)
            else:
                icon = gtk.Image.new_from_icon_name(image, 16)
            menu_item.set_image(icon)
        elif item_type is gtk.MenuItem:
            menu_item = gtk.MenuItem(label)
        elif item_type is gtk.SeparatorMenuItem:
            menu_item = gtk.SeparatorMenuItem()
        if action:
            menu_item.connect('activate', action, *args)

        menu_obj.append(menu_item)
        menu_item.show()

    def get_user_dirs(self,*args):
        user_dirs = []
        for index,val in glib.UserDirectory.__enum_values__.items():
            if index == 8: continue
            dir = glib.get_user_special_dir(index)
            if dir: user_dirs.append(dir)
        return user_dirs

    def get_file_icon(self,*args):
        if args[-1].endswith('.desktop'):
            desk_file = Gio.DesktopAppInfo.new_from_filename(args[-1])
            icon = desk_file.get_icon()
            if type(icon) == Gio.ThemedIcon:
                themed_name = icon.get_names()[0]
                theme = gtk.IconTheme.get_default()
                name = theme.lookup_icon(themed_name, 48, 0).get_filename()
            if type(icon) == Gio.FileIcon:
                name = icon.get_file().get_uri()

            icon_url= urllib.parse.unquote(name).replace('file://','') 
            return icon_url

        file = Gio.File.new_for_path(args[-1])
        file_info = file.query_info("standard::*",0)
        icon_string = file_info.get_icon().to_string()
        if 'folder-' in icon_string:
            return icon_string.split()[-2]
        return icon_string.split()[-1]

    def get_recent_files(self,*args):
        manager = gtk.RecentManager.get_default()
        try:
            files = OrderedDict()
            for index,item in enumerate(manager.get_items(),1):
                    uri = item.get_uri()
                    uri_decoded = urllib.parse.unquote(uri)
                    filepath = uri_decoded.replace('file://','')
                    if not os.path.exists(filepath): continue
                    basename = os.path.basename(uri_decoded)
                    files[basename] = filepath
                    if index == self.max_items:
                        break
        except Exception as e:
            print(e)
            return None
        finally:  return files

    def callback(self,*args):
        self.update()

    def update(self,*args):
        current_files = self.get_recent_files()
        if current_files != self.cached_files:
             self.make_menu()
             self.cached_files = current_files
        glib.timeout_add_seconds(3,self.callback)

    def add_submenu(self,top_menu,label):
        menuitem = gtk.MenuItem(label)
        submenu = gtk.Menu()
        menuitem.set_submenu(submenu)
        top_menu.append(menuitem)
        menuitem.show()
        return submenu

    def make_menu(self):
        if hasattr(self, 'app_menu'):
            for item in self.app_menu.get_children():
                    self.app_menu.remove(item)
        else:
            self.app_menu = gtk.Menu()
        recent = self.add_submenu(self.app_menu,'Recent Files')
        recent_dict = self.get_recent_files()

        content = [recent,gtk.ImageMenuItem,'gtk-add',
                   'Add to Recent Files',self.add_recent,[None]
        ]      
        self.add_menu_item(*content) 

        content = [recent,gtk.ImageMenuItem,'user-trash',
                   'Clear recent files list',self.clear_recent,[None]
        ]      
        self.add_menu_item(*content)

        content = [recent,gtk.SeparatorMenuItem,
                   None,None,
                   None,[None]
        ]
        self.add_menu_item(*content)
        self.add_menu_item(*content) 
        if not recent_dict:
            content = [recent,gtk.MenuItem,None,
                       'No items',None,None
            ]
            self.add_menu_item(*content)
            last = None
            for i in recent.get_children():
                last = i
            last.set_sensitive(False)
        else:
            for name,data in recent_dict.items():
                icon = self.get_file_icon(data)
                content = [recent, gtk.ImageMenuItem,
                           icon, name[:self.name_length],
                           self.open_item, [data]
                ]
                self.add_menu_item(*content)

        # Pinned files
        bookmarks = self.add_submenu(self.app_menu,'Pinned Files')
        content = [bookmarks,gtk.ImageMenuItem,
                   'bookmark_add','Pin a file',
                   self.pin_file,[bookmarks,None]
        ]
        self.add_menu_item(*content)

        content = [bookmarks,gtk.ImageMenuItem,
                   'remove','Remove item',
                   self.remove_pinned,['files']
        ]
        self.add_menu_item(*content)

        content = [bookmarks,gtk.ImageMenuItem,
                   'user-trash','Remove All',
                   self.remove_all_pinned,[None]
        ]
        self.add_menu_item(*content)
        content = [bookmarks,gtk.SeparatorMenuItem,
                   None,None,
                   None,[None]
        ]
        self.add_menu_item(*content)
        self.add_menu_item(*content) 

        pinned_files = self.get_pinned()

        if (pinned_files and 
            'files' in pinned_files.keys() and
            pinned_files['files']):
            for filepath in pinned_files['files']:
                icon = self.get_file_icon(filepath) 
                content = [bookmarks,gtk.ImageMenuItem,
                           icon,os.path.basename(filepath),
                           self.open_item,[filepath]
                ]
                self.add_menu_item(*content)
        else:
            content = [bookmarks,gtk.MenuItem,None,
                       'No items',None,None
            ]
            self.add_menu_item(*content)
            last = None
            for i in bookmarks.get_children():
                last = i
            last.set_sensitive(False)

        places = self.add_submenu(self.app_menu,'Places')
        content = [places,gtk.ImageMenuItem,'add',
                   'Pin Directory',self.pin_dir,[None]
        ]

        self.add_menu_item(*content)

        content = [places,gtk.ImageMenuItem,
                   'remove','Remove Pinned',
                   self.remove_pinned,['dirs']
        ]
        self.add_menu_item(*content)

        content = [places,gtk.SeparatorMenuItem,
                   None,None,
                   None,[None]
        ]
        self.add_menu_item(*content)

        content = [places,gtk.MenuItem,None,
                   'Standard Dirs',None,None
        ]
        self.add_menu_item(*content)
        last = None
        for i in places.get_children():
            last = i
        last.set_sensitive(False)
        for dir in self.get_user_dirs():
            icon = self.get_file_icon(dir)
            content = [places,gtk.ImageMenuItem,icon,
                       os.path.basename(dir),self.open_item,[dir]

            ]
            self.add_menu_item(*content)

        content = [places,gtk.SeparatorMenuItem,
                   None,None,
                   None,[None]
        ]
        self.add_menu_item(*content)

        content = [places,gtk.MenuItem,None,
                   'Pinned Dirs',None,None
        ]
        self.add_menu_item(*content)
        last = None
        for i in places.get_children():
            last = i
        last.set_sensitive(False)

        if (pinned_files and 
           'dirs' in pinned_files.keys() and
           pinned_files['dirs']):
            for dir in pinned_files['dirs']:
                icon = self.get_file_icon(dir)
                print(icon)
                content = [places,gtk.ImageMenuItem,icon,
                           os.path.basename(dir),self.open_item,[dir]

                ]
                self.add_menu_item(*content)
        else:
            content = [places,gtk.MenuItem,None,
                       'No items',None,None
            ]
            self.add_menu_item(*content)
            last = None
            for i in places.get_children():
                last = i
            last.set_sensitive(False)

        content = [self.app_menu,gtk.SeparatorMenuItem,
                   None,None,
                   None,[None]
        ]
        self.add_menu_item(*content)
        content = [self.app_menu,gtk.ImageMenuItem,'exit',
                   'quit',self.quit,[None]
        ]
        self.add_menu_item(*content)
        self.app.set_menu(self.app_menu)

    def check_directory(self,*args):
        current_set = set(os.listdir(args[-1]))
        return current_set - self.cached_set


    def get_pinned(self,*args):
        try:
            with open(self.pinned_list) as f:
                 return json.load(f,object_pairs_hook=OrderedDict)

        except FileNotFoundError:
            print('>>> ',self.pinned_list,' not found')
            return None
        except json.JSONDecodeError:
            print(">>> Can't read ",self.pinned_list,',may be corrupt')
            return None

    def pin_dir(self,*args):
        # TODO
        current_list = self.get_pinned()
        if not current_list:
                current_list = OrderedDict()
                current_list['dirs'] = []
                f = open(self.pinned_list,'w')
                f.write("")
                f.close()

        if not args[-1]:
                cmd = "zenity --file-selection --directory --separator || --multiple"
                dirs = self.run_cmd(cmd.split())
        else:
                dirs = args[-1]

        dir_list = []
        if not dirs: return None
        dir_list = dirs.decode().strip().split("||")
        if not 'dirs' in current_list.keys():
             current_list['dirs'] = []
        for f in dir_list:
                #icon = self.get_file_icon(f)
                current_list['dirs'].append(f)

        with open(self.pinned_list,'w') as f:
                json.dump(current_list,f,indent=4)
        self.make_menu()

    def pin_file(self,*args):
        current_list = self.get_pinned()
        if not current_list:
                current_list = OrderedDict()
                current_list['files'] = []
                f = open(self.pinned_list,'w')
                f.write("")
                f.close()
        if not args[-1]:
                cmd = "zenity --file-selection --separator || --multiple "
                files = self.run_cmd(cmd.split())
        else:
                files = args[-1]

        file_list = []
        if not files: return None
        file_list = files.decode().strip().split("||")
        if not 'files' in current_list.keys():
            current_list['files'] = []
        for f in file_list:
                #icon = self.get_file_icon(f)
                current_list['files'].append(f)

        with open(self.pinned_list,'w') as f:
                json.dump(current_list,f,indent=4)
        self.make_menu()

    def remove_all_pinned(self,*args):
        try:
            #os.unlink(self.pinned_list)

            with open(self.pinned_list) as f:
                pinned = json.load(f)
            pinned.pop('files')       
            with open(self.pinned_list,'w') as f:
                    json.dump(pinned,f,indent=4)
        except:
            pass
        finally:
            self.make_menu()

    def remove_pinned(self,*args):
        key = args[-1]
        pinned = self.get_pinned() 
        if not pinned: return
        cmd_str = "zenity --forms --add-combo Remove --combo-values"
        vals = "|".join(pinned[key])
        cmd = cmd_str.split() + [vals]
        item = self.run_cmd(cmd)
        if item: 
            path = item.decode().strip()
            index = pinned[key].index(path)
            pinned[key].pop(index)
            with open(self.pinned_list,'w') as f:
                json.dump(pinned,f,indent=4)
            self.make_menu()

    def add_recent(self,*args):
        cmd = "zenity --file-selection --separator || --multiple "
        files = self.run_cmd(cmd.split())
        file_list = []
        if not files: return
        file_list = files.decode().strip().split("||")
        items = ['file://' + f for f in file_list]
        for f in items: gtk.RecentManager().get_default().add_item(f)

    def clear_recent(self,*args):
        try:
            gtk.RecentManager.get_default().purge_items()
            self.make_menu()
        except:
            pass

    def open_item(self,*args):
        #self.run_cmd(['xdg-open',args[-1]]) 
        if args[-1].endswith('.desktop'):
            desk_file = Gio.DesktopAppInfo.new_from_filename(args[-1])
            return desk_file.launch_uris()
        return subprocess.Popen(['xdg-open',args[-1]])

    def quit(self,*args):
        gtk.main_quit()

    def run_cmd(self, cmdlist):
        """ utility: reusable function for running external commands """
        #new_env = dict(os.environ)
        #new_env['LC_ALL'] = 'C'
        try:
            stdout = subprocess.check_output(cmdlist) #env=new_env)
        except subprocess.CalledProcessError:
            pass
        else:
            if stdout:
                return stdout

    def run(self):
        """ Launches the indicator """
        try:
            gtk.main()
        except KeyboardInterrupt:
            pass

    def quit(self, *args):
        """ closes indicator """
        gtk.main_quit()


def main():
    """ defines program entry point """
    indicator = FilesIndicator()
    indicator.run()

if __name__ == '__main__':
  try:
    main()
  except  KeyboardInterrupt:
    gtk.main_quit()

Configurations

The indicator is configured via two json files stored in user's home directory.

~/.files_indicator.json controls user interface , the length of menu entries and maximum numbers in the Recent Files Menu.

{
    "name_length": 30,
    "max_items": 10
}

The ~/.pinned_files.json controls the lists of pinned files and folders. Each item is a list/array.

{
    "dirs": [
        "/home/xieerqi/\u56fe\u7247/Wallpapers"
    ],
    "files": [
        "/home/xieerqi/work_in_progress/videonauth_code.py",
        "/home/xieerqi/work_in_progress/spin_button.py"
    ]
}
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.