是否有指示器可以快速访问最近使用过的文件?

我正在寻找一个托盘图标指示器 – 单击图标 – 显示我最近使用的文件列表。 这将是快速访问这些文件的好方法。

将最近使用的文件放在面板中

使用下面的脚本,您可以在面板中拥有任意数量的最近使用的项目,例如6项:

在此处输入图像描述

……或20件物品:

在此处输入图像描述

…取决于您的设置。

这个怎么运作

设置存在两个项目:

  1. 一个名为(完全) recent.png的图标
  2. 一个脚本

两者都需要在同一个文件夹中。 之后,只需运行脚本即可。

如何设置

可能你需要安装python3-gi

 sudo apt-get install python3-gi 

然后:

  1. 将下面的脚本复制到一个空文件中,将其另存为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([ ' 
  2. 在脚本的head部分中,设置要显示的项目数:

     # --- set the number of recently used files to appear below n = 20 # --- 
  3. 同一个文件夹中将下面的图标保存为(完全) recent.png

    在此处输入图像描述

    (右键单击它 - >另存为)

  4. 通过以下命令测试脚本:

     python3 /path/to/recused.py 
  5. 如果一切正常,请添加到启动应用程序:Dash>启动应用程序>添加。 添加命令:

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

笔记

  • 此脚本基于之前的答案 ,如果您对替代方案感兴趣,那就是特定应用程序 ,并使用定期更新的.desktop文件。
  • 该脚本在16.04进行了测试,但也适用于早期版本。
  • 由于recently-used.xbel文件中存在错误,有时会提到两次文件; 一次,一次没有延期。 我通过过滤掉后者来解决这个问题。 结果是没有扩展名的文件不会出现在列表中。 如果这是一个问题,请告诉我,我们可以尝试在这种情况下找到另一个解决方案。
  • 如果文件不存在(不再),指示器(显然)不会打开文件。 最有可能的是,我将对脚本进行更多修改,以从recently-used.xbel文件中过滤掉那些过时的条目。

说明

许多应用程序,编辑文件和使用Gtk窗口,跟踪文件中打开的文件: ~/.local/share/recently-used.xbel 。 “记录”包括日期和时间,应用程序和打开的文件。

该脚本读取.xbel文件,按日期/时间对项目进行排序,包括指标菜单中的第一个n-(取决于您的设置)文件。 菜单每3秒更新一次(仅在必要时)。 随后,当选择一个项目时,将使用以下命令打开该文件:

 xdg-open  

因此,将使用默认应用程序打开所选文件。 很可能确保使用上次打开的实际应用程序打开文件。 然而,这需要更复杂的解析。 我将添加它作为Launchpad上计划的ppa版本的选项。

选项窗口设置一些选项也是如此,例如要显示的文件数等。

注意

该指标现在与其中的一些其他内容合并。

虽然你没有提到你正在使用的Ubuntu风格,但Xubuntu有一个不错的应用程序。 更具体地说,它是Xfce的一部分,所以如果你使用Xfce ,你仍然可以在Ubuntu中使用它。 该应用程序称为Places

要启用,右键单击面板,然后从菜单中选择面板 – >添加新项目…,然后选择位置。 这将显示您刚刚打开的文件夹和最近文档的列表。 见下文:

在此处输入图像描述

您也可以很好地配置它:

在此处输入图像描述

这是项目的页面 。


如果您不使用Xfce ,则可以非常轻松地安装它,如本答案中所述 。 只需执行sudo apt-get install xfce-panel ,然后运行(参见链接)。 请注意,Xfce面板可能与Unity(或其他)面板中的所有其他应用程序不兼容 。 根据我的经验,我完全没有问题,所以它可能仍然是一个很好的解决方案。

不是托盘图标,而是Unity统一启动器的快速列表脚本(适用于变音符号),您也可以固定文件: Ubuntu-Recentquicklists

URQ截图

[详细的安装说明]

快速说明:

  • 下载.zip
  • 提取
  • make install.shubuntu-recentquicklists.py可执行文件
  • 运行install.sh脚本

特征:

  • 自动添加最近的文件
  • 手动添加非近期文件/引脚最近的文件
  • 指定每个列表中您想要的项目数量
  • 这些物品可能有多大
  • 解决符号链接
  • 显示完整路径(或仅显示文件名,如屏幕截图所示)

2017年2月24日更新 :指标现在可以选择固定网络链接。

介绍

下面显示的文件指示器是访问用户文件和文件夹的简单指示器。 它允许检查最近使用的文件,为文件和目录添加书签。

在此处输入图像描述

更新 :指示器现在还支持启动已固定的.desktop文件。 例如,如果你有firefox.desktop固定,它将启动Firefox。 因此,该指标可以用作程序的快速启动器。 该function在撰写本文时正在进入PPA(11月19日,格林威治标准时间下午7:53,需要大约24小时才能完成),但已经在github以及更新的源代码中。

在此处输入图像描述

获得指标

该指标可从我的个人PPA和GitHub获得 。 使用以下步骤获取它:

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

源代码

 #!/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() 

配置

该指标通过存储在用户主目录中的两个json文件进行配置。

~/.files_indicator.json控制用户界面,菜单条目的长度和最近文件菜单中的最大数量。

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

~/.pinned_files.json控制固定文件和文件夹的列表。 每个项目都是一个列表/数组。

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