From bf8d3ff6976fc5af0d829d2a9f55aa9135da129c Mon Sep 17 00:00:00 2001
From: gm10 <13855078+gm10@users.noreply.github.com>
Date: Mon, 11 Feb 2019 15:31:43 +0100
Subject: [PATCH] - Add run or open to search suggestions where appropriate -
Add tooltips to search and execute buttons - Replace the deprecated icon for
adding packages with package-x-generic - Remove unnecessary i18n duplication
in python, leave it to makepot
---
debian/control | 1 +
usr/lib/linuxmint/mintMenu/mintMenu.py | 2 +-
.../linuxmint/mintMenu/mintMenuConfig.glade | 127 ++++++++++++-
usr/lib/linuxmint/mintMenu/mintMenuConfig.py | 10 +
.../mintMenu/plugins/applications.glade | 31 ++-
.../mintMenu/plugins/applications.py | 178 +++++++++++++++++-
.../linuxmint/mintMenu/plugins/terminal.py | 100 ++++++++++
.../com.linuxmint.mintmenu.gschema.xml | 22 +++
8 files changed, 450 insertions(+), 21 deletions(-)
create mode 100644 usr/lib/linuxmint/mintMenu/plugins/terminal.py
diff --git a/debian/control b/debian/control
index cca03e8..9f26e2b 100644
--- a/debian/control
+++ b/debian/control
@@ -25,6 +25,7 @@ Depends:
gir1.2-gtk-3.0,
gir1.2-mate-desktop,
mozo
+Recommends: gir1.2-vte-2.91
Description: Advanced MATE menu
One of the most advanced menus under Linux. MintMenu supports filtering,
favorites, easy-uninstallation, autosession, and many other features.
diff --git a/usr/lib/linuxmint/mintMenu/mintMenu.py b/usr/lib/linuxmint/mintMenu/mintMenu.py
index 832141f..266e4d5 100755
--- a/usr/lib/linuxmint/mintMenu/mintMenu.py
+++ b/usr/lib/linuxmint/mintMenu/mintMenu.py
@@ -471,7 +471,7 @@ class MenuWin(object):
self.keybinder.connect("activate", self.onBindingPress)
self.keybinder.start()
self.settings.connect("changed::hot-key", self.hotkeyChanged)
- print("Binding to Hot Key: %s" % self.hotkeyText)
+ # print("Binding to Hot Key: %s" % self.hotkeyText)
except Exception as e:
self.keybinder = None
print("** WARNING ** - Keybinder Error")
diff --git a/usr/lib/linuxmint/mintMenu/mintMenuConfig.glade b/usr/lib/linuxmint/mintMenu/mintMenuConfig.glade
index 863d735..c0a4f30 100644
--- a/usr/lib/linuxmint/mintMenu/mintMenuConfig.glade
+++ b/usr/lib/linuxmint/mintMenu/mintMenuConfig.glade
@@ -12,6 +12,18 @@
1
1
+
+
@@ -787,12 +799,12 @@
@@ -823,7 +835,7 @@
False
False
adjustment4
- 100
+ 99.999999999776477
1
@@ -893,7 +905,7 @@
True
False
GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- 0.5
+ start
True
@@ -909,7 +921,7 @@
True
False
GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- 0.5
+ start
True
@@ -925,7 +937,7 @@
True
False
GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- 0.5
+ start
True
@@ -934,6 +946,101 @@
2
+
+
+
+ 0
+ 9
+ 2
+
+
+
+
+
+ 1
+ 12
+
+
+
+
+
+ 1
+ 11
+
+
+
+
+
+ 0
+ 12
+
+
+
+
+
+ 0
+ 11
+
+
+
+
+
+ 0
+ 10
+ 2
+
+
diff --git a/usr/lib/linuxmint/mintMenu/mintMenuConfig.py b/usr/lib/linuxmint/mintMenu/mintMenuConfig.py
index 2971482..bf54483 100755
--- a/usr/lib/linuxmint/mintMenu/mintMenuConfig.py
+++ b/usr/lib/linuxmint/mintMenu/mintMenuConfig.py
@@ -134,6 +134,16 @@ class mintMenuConfig(object):
self.bindGSettingsValueToWidget(self.settingsApplications, "bool", "remember-filter", self.rememberFilter, "toggled", self.rememberFilter.set_active, self.rememberFilter.get_active)
self.bindGSettingsValueToWidget(self.settingsApplications, "bool", "enable-internet-search", self.enableInternetSearch, "toggled", self.enableInternetSearch.set_active, self.enableInternetSearch.get_active)
+ self.allow_execute = self.builder.get_object("allow-execute")
+ self.integrated_terminal_enabled = self.builder.get_object("integrated-terminal-enabled")
+ self.integrated_terminal_width = self.builder.get_object("integrated-terminal-width")
+ self.integrated_terminal_height = self.builder.get_object("integrated-terminal-height")
+
+ self.bindGSettingsValueToWidget(self.settingsApplications, "bool", "allow-execute", self.allow_execute, "toggled", self.allow_execute.set_active, self.allow_execute.get_active)
+ self.bindGSettingsValueToWidget(self.settingsApplications, "bool", "integrated-terminal-enabled", self.integrated_terminal_enabled, "toggled", self.integrated_terminal_enabled.set_active, self.integrated_terminal_enabled.get_active)
+ self.bindGSettingsValueToWidget(self.settingsApplications, "int", "integrated-terminal-width", self.integrated_terminal_width, "value-changed", self.integrated_terminal_width.set_value, self.integrated_terminal_width.get_value)
+ self.bindGSettingsValueToWidget(self.settingsApplications, "int", "integrated-terminal-height", self.integrated_terminal_height, "value-changed", self.integrated_terminal_height.set_value, self.integrated_terminal_height.get_value)
+
self.bindGSettingsValueToWidget(self.settingsPlaces, "int", "icon-size", self.placesIconSize, "value-changed", self.placesIconSize.set_value, self.placesIconSize.get_value)
self.bindGSettingsValueToWidget(self.settingsSystem, "int", "icon-size", self.systemIconSize, "value-changed", self.systemIconSize.set_value, self.systemIconSize.get_value)
diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.glade b/usr/lib/linuxmint/mintMenu/plugins/applications.glade
index 7c8d294..1fd2eb6 100644
--- a/usr/lib/linuxmint/mintMenu/plugins/applications.glade
+++ b/usr/lib/linuxmint/mintMenu/plugins/applications.glade
@@ -362,7 +362,7 @@
+
+
+
+ False
+ False
+ end
+ 2
+
+
28
@@ -420,7 +447,7 @@
False
False
end
- 2
+ 3
diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.py b/usr/lib/linuxmint/mintMenu/plugins/applications.py
index 4d76c88..0488ee9 100755
--- a/usr/lib/linuxmint/mintMenu/plugins/applications.py
+++ b/usr/lib/linuxmint/mintMenu/plugins/applications.py
@@ -19,6 +19,12 @@ from plugins.easybuttons import (ApplicationLauncher, CategoryButton,
MenuApplicationLauncher)
from plugins.easygsettings import EasyGSettings
+try:
+ from plugins.terminal import IntegratedTerminal
+ hasVte = True
+except:
+ hasVte = False
+
# i18n
gettext.install("mintmenu", "/usr/share/linuxmint/locale")
home = os.path.expanduser("~")
@@ -29,6 +35,23 @@ class PackageDescriptor():
self.summary = summary
self.description = description
+class subprocess_thread(threading.Thread):
+
+ def __init__(self, _cmd, parent):
+ threading.Thread.__init__(self)
+ self.cmd = _cmd
+ self.parent = parent
+
+ def run(self):
+ try:
+ output = subprocess.check_output(self.cmd,
+ stderr=subprocess.STDOUT, cwd=home, shell=True)
+ if output:
+ GLib.timeout_add(0, self.parent.subprocess_thread_output, output, self.cmd)
+ except subprocess.CalledProcessError as e:
+ if e.output:
+ GLib.timeout_add(0, self.parent.subprocess_thread_output, e.output, self.cmd)
+
# import time
# def print_timing(func):
# def wrapper(*arg):
@@ -181,9 +204,8 @@ class pluginclass(object):
self.favoritesBox = self.builder.get_object("favoritesBox")
self.applicationsScrolledWindow = self.builder.get_object("applicationsScrolledWindow")
-
- self.headingstocolor = [self.builder.get_object("label6"), self.builder.get_object("label2")]
self.numApps = 0
+
# These properties are NECESSARY to maintain consistency
# Set 'window' property for the plugin (Must be the root widget)
@@ -233,13 +255,14 @@ class pluginclass(object):
self.settings.notifyAdd("use-apt", self.switchAPTUsage)
self.settings.notifyAdd("fav-cols", self.changeFavCols)
self.settings.notifyAdd("remember-filter", self.changeRememberFilter)
- self.settings.notifyAdd("enable-internet-search", self.changeEnableInternetSearch)
+ self.settings.notifyAdd("allow-execute", self.update_allow_execute)
self.settings.bindGSettingsEntryToVar("int", "category-hover-delay", self, "categoryhoverdelay")
self.settings.bindGSettingsEntryToVar("bool", "do-not-filter", self, "donotfilterapps")
self.settings.bindGSettingsEntryToVar("bool", "enable-internet-search", self, "enableInternetSearch")
self.settings.bindGSettingsEntryToVar("string", "search-command", self, "searchtool")
self.settings.bindGSettingsEntryToVar("int", "default-tab", self, "defaultTab")
+ self.settings.bindGSettingsEntryToVar("bool", "integrated-terminal-enabled", self, "integrated_terminal_enabled")
except Exception as e:
print(e)
@@ -275,6 +298,8 @@ class pluginclass(object):
self.panel_position = -1
self.builder.get_object("searchButton").connect("button-press-event", self.searchPopup)
+ self.builder.get_object("executeButton").connect("button-press-event", self.on_execute_button_pressed)
+ self.update_allow_execute()
# self.icon_theme = Gtk.IconTheme.get_default()
# self.icon_theme.connect("changed", self.on_icon_theme_changed)
@@ -383,8 +408,19 @@ class pluginclass(object):
def changeRememberFilter(self, settings, key, args):
self.rememberFilter = settings.get_boolean(key)
- def changeEnableInternetSearch(self, settings, key, args):
- self.enableInternetSearch = settings.get_boolean(key)
+ def update_allow_execute(self, settings=None, key=None, args=None):
+ if settings and key:
+ self.allow_execute = settings.get_boolean(key)
+ #self.builder.get_object("executeButton").set_visible(self.allow_execute)
+ if self.allow_execute:
+ self.builder.get_object("executeButton").show()
+ searchLabel_text = _("Search/Run:")
+ else:
+ self.builder.get_object("executeButton").hide()
+ searchLabel_text = _("Search:")
+ searchLabel = self.builder.get_object("searchLabel")
+ searchLabel.set_markup("%s" % searchLabel_text)
+ searchLabel.show()
def changeShowApplicationComments(self, settings, key, args):
self.showapplicationcomments = settings.get_boolean(key)
@@ -465,6 +501,10 @@ class pluginclass(object):
self.useAPT = self.settings.get("bool", "use-apt")
self.rememberFilter = self.settings.get("bool", "remember-filter")
self.enableInternetSearch = self.settings.get("bool", "enable-internet-search")
+ self.allow_execute = self.settings.get("bool", "allow-execute")
+ self.integrated_terminal_enabled = self.settings.get("bool", "integrated-terminal-enabled")
+ self.integrated_terminal_width = self.settings.get("int", "integrated-terminal-width")
+ self.integrated_terminal_height = self.settings.get("int", "integrated-terminal-height")
self.lastActiveTab = self.settings.get("int", "last-active-tab")
self.defaultTab = self.settings.get("int", "default-tab")
@@ -607,6 +647,22 @@ class pluginclass(object):
self.applicationsBox.get_children()[-1].grab_focus()
+ def add_execute_suggestions(self, user_text):
+ # Wait to see if the keyword has changed.. before doing anything
+ text = self.searchEntry.get_text()
+ if user_text != text:
+ return
+ commands = user_text.split()
+ cmd = self.verify_command(commands[0])
+ if cmd:
+ text = "%s" % cgi.escape(text)
+ commands[0] = cmd
+ if cmd.startswith("xdg-open"):
+ self.add_suggestion("document-open", _("Try to open %s") % text, None, self.execute_user_input, commands, False)
+ else:
+ self.add_suggestion("application-x-executable", _("Run %s") % text, None, self.execute_user_input, commands, False)
+ self.applicationsBox.get_children()[-1].grab_focus()
+
def add_apt_filter_results(self, keyword):
try:
# Wait to see if the keyword has changed.. before doing anything
@@ -660,7 +716,7 @@ class pluginclass(object):
for word in keywords:
if word != "":
name = name.replace(word, "%s" % word)
- self.add_suggestion(Gtk.STOCK_ADD,
+ self.add_suggestion("package-x-generic",
_("Install package '%s'") % name,
"%s\n\n%s\n\n%s" % (pkg.name, pkg.summary, pkg.description),
self.apturl_install, pkg.name)
@@ -725,7 +781,7 @@ class pluginclass(object):
if self.donotfilterapps:
widget.set_text("")
else:
- text = widget.get_text()
+ text = widget.get_text().strip()
if self.lastActiveTab != 1:
self.changeTab(1, clear = False)
text = widget.get_text()
@@ -750,8 +806,11 @@ class pluginclass(object):
if not showns:
if len(text) >= 3:
self.add_search_suggestions(text)
+ if self.allow_execute:
+ GLib.timeout_add(150, self.add_execute_suggestions, text)
if self.useAPT:
GLib.timeout_add(300, self.add_apt_filter_results, text)
+
for i in self.categoriesBox.get_children():
i.released()
i.set_relief(Gtk.ReliefStyle.NONE)
@@ -786,10 +845,17 @@ class pluginclass(object):
self.Filter(widget, category)
def keyPress(self, widget, event):
- """ Forward all text to the search box """
+ # Forward all text to the search box
if event.string.strip() or event.keyval == Gdk.KEY_space:
self.searchEntry.event(event)
return True
+
+ # Ctrl+Enter for the Run button
+ if self.allow_execute and \
+ (event.state & Gdk.ModifierType.CONTROL_MASK) and \
+ event.keyval == Gdk.KEY_Return:
+ self.on_execute_button_pressed(widget, event)
+ return True
return False
def favPopup(self, widget, event):
@@ -931,6 +997,102 @@ class pluginclass(object):
mTree.attach_to_widget(widget, None)
mTree.popup(None, None, None, None, event.button, event.time)
+ def subprocess_thread_output(self, output, command):
+ output = output.strip()
+ if not output:
+ return
+ self.mintMenuWin.hide()
+
+ def on_key_press_event(widget, event):
+ if event.keyval == Gdk.KEY_Escape or \
+ event.keyval == Gdk.KEY_Return or \
+ event.keyval == Gdk.KEY_KP_Enter:
+ widget.destroy()
+
+ window = Gtk.Window()
+ window.set_title(_("Output from your command: %s" % command))
+ window.set_transient_for(self.window)
+ window.set_icon_from_file("/usr/lib/linuxmint/mintMenu/icon.svg")
+ #window.set_skip_taskbar_hint(True)
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ output_box = Gtk.TextView()
+ output_box.set_editable(False)
+ output_box.set_monospace(True)
+ output_box.set_right_margin(16)
+ output_box.set_wrap_mode(Gtk.WrapMode.WORD)
+ scrolled_window = Gtk.ScrolledWindow()
+ scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+ scrolled_window.set_hexpand(True)
+ scrolled_window.set_vexpand(True)
+ scrolled_window.set_size_request(self.integrated_terminal_width, self.integrated_terminal_height)
+ scrolled_window.add(output_box)
+ box.pack_start(scrolled_window, False, True, 0)
+ window.add(box)
+
+ output_box.get_buffer().set_text(output)
+ window.show_all()
+ window.connect("key-press-event", on_key_press_event)
+
+ @staticmethod
+ def find_file_in_path(filename):
+ if os.path.isfile(filename):
+ return filename
+ path = os.environ.get("PATH", os.defpath)
+ paths = path.split(os.pathsep)
+ paths.append(home)
+ for p in paths:
+ f = os.path.join(p, filename)
+ if os.path.isfile(f):
+ return f
+ return None
+
+ def verify_command(self, cmd):
+ cmd = os.path.expanduser(os.path.expandvars(cmd))
+ cmd = self.find_file_in_path(cmd)
+ if cmd and not os.access(cmd, os.X_OK):
+ cmd = "xdg-open %s" % cmd
+ # TODO: We could check mime type first to ensure this goes through
+ return cmd
+
+ def on_execute_button_pressed(self, widget, event):
+ command = self.searchEntry.get_text().strip()
+ if command:
+ self.execute_user_input(widget, command.split())
+
+ def execute_user_input(self, widget, commands, verify=True):
+ self.mintMenuWin.hide()
+ if verify:
+ cmd = self.verify_command(commands[0])
+ if not cmd:
+ # file not found
+ dialog = Gtk.MessageDialog(self.window,
+ Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,
+ _("Invalid command or file reference."))
+ dialog.set_title("mintMenu")
+ dialog.run()
+ dialog.destroy()
+ return
+ commands[0] = cmd
+ command = " ".join(commands)
+ try:
+ global hasVte
+ if hasVte and self.integrated_terminal_enabled:
+ try:
+ IntegratedTerminal(command,
+ _("mintMenu Integrated Terminal"),
+ width=self.integrated_terminal_width,
+ height=self.integrated_terminal_height
+ )
+ except Exception as e:
+ print("IntegratedTerminal exception:", e)
+ hasVte = False
+ if not hasVte or not self.integrated_terminal_enabled:
+ thread = subprocess_thread(command, self)
+ thread.start()
+ except:
+ pass
+
def searchPopup(self, widget, event):
def add_menu_item(icon=None, text=None, callback=None):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
diff --git a/usr/lib/linuxmint/mintMenu/plugins/terminal.py b/usr/lib/linuxmint/mintMenu/plugins/terminal.py
new file mode 100644
index 0000000..45cc71e
--- /dev/null
+++ b/usr/lib/linuxmint/mintMenu/plugins/terminal.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os
+
+import gi
+gi.require_version("Gtk", "3.0")
+gi.require_version('Vte', '2.91')
+from gi.repository import Gtk, Gdk, GLib, Vte
+
+class IntegratedTerminal(Gtk.Window):
+
+ def __init__(self, command, title=None, shell=None, cwd=None, width=600, height=500):
+ try:
+ self.terminal=Vte.Terminal()
+ self.terminal.set_scrollback_lines(-1)
+ # Setting the font doesn't work for some reason, let's leave it
+ # self.terminal.set_font(Pango.FontDescription(string='Monospace'))
+ self.command = command
+ self.ready = False
+ self.output_handler = self.terminal.connect("cursor-moved",
+ self.on_cursor_moved)
+ # apparently Vte.Terminal.spawn_sync() is deprecated in favour of the
+ # non-existent Vte.Terminal.spawn_async()...
+ self.terminal.spawn_sync(
+ Vte.PtyFlags.DEFAULT, # pty_flags
+ cwd or os.environ.get("HOME"), # working_directory
+ shell or [os.environ.get("SHELL")], # argv
+ [], # envv
+ GLib.SpawnFlags.DO_NOT_REAP_CHILD, # spawn_flags
+ None, # child_setup
+ None, # child_setup_data
+ None # cancellable
+ )
+ except Exception as e:
+ self.close()
+ raise Exception(e)
+
+ Gtk.Window.__init__(self, title=title or "mintMenu Integrated Terminal")
+ self.set_icon_name("utilities-terminal")
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self.scrolled_window = Gtk.ScrolledWindow()
+ self.scrolled_window.set_hexpand(True)
+ self.scrolled_window.set_vexpand(True)
+ self.scrolled_window.set_size_request(width, height)
+ self.scrolled_window.add(self.terminal)
+ box.pack_start(self.scrolled_window, False, True, 0)
+ self.add(box)
+ self.connect("key-press-event", self.on_key_press_event)
+ self.terminal.connect("child-exited", self.exit)
+ self.terminal.connect("eof", self.exit)
+ self.terminal.set_rewrap_on_resize(True)
+
+ def on_cursor_moved(self, terminal):
+ if not self.ready:
+ # we have to run the command on a callback because the
+ # spawn_sync() method doesn't wait for the shell to load,
+ # so instead we have to wait for the shell to create a prompt
+ self.ready = True
+ # if we don't show the terminal once after this it won't
+ # always receive output, so we show it and hide it again right
+ # away. Whatever works...
+ self.show_all()
+ self.hide()
+ # Now we can go:
+ command = "%s\n" % self.command
+ self.terminal.feed_child(command, len(command))
+ return
+ # Unfortunately we cannot guarantee that the command is on the first
+ # line (the shell may display somethinge else first), so we have to
+ # search for it:
+ x, y = terminal.get_cursor_position()
+ contents, dummy = terminal.get_text_range(0, 0, x, y, None, None)
+ lines = contents.split("\n")
+ prefix = ""
+ command_found = False
+ for line in lines:
+ if not line:
+ continue
+ if command_found:
+ if not line.lstrip(prefix):
+ # we got a command prompt, exit
+ self.exit()
+ break
+ # the command generated output, show the terminal
+ terminal.disconnect(self.output_handler)
+ self.show_all()
+ break
+ if self.command in line:
+ # this is our command line
+ prefix = line.split(self.command, 1)[0]
+ command_found = True
+
+ def on_key_press_event(self, widget, event):
+ if event.keyval == Gdk.KEY_Escape:
+ self.exit()
+
+ def exit(self, *args):
+ # Gtk.main_quit()
+ self.close()
diff --git a/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml b/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml
index 65c00e9..4b0536e 100644
--- a/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml
+++ b/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml
@@ -307,6 +307,28 @@
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ 600
+
+
+
+
+
+ 500
+
+
+