move add, remove, and delete page methods from guake_app to notebook

remove context menus from the glade file since the are edited in code anyways
rewrite the contextmenus to support per terminal menus
remove fullscreen logic from guake_app (to Fullscreenmanager in utils)
remove proxy code since it was diabled since the gtk3 port and needs to be ported but there also no issues on GH currently
move dialogs to dialogs.py
move a lot of code (menus, dialogs, customcommands, themes) to its own files
move terminal related code (like init) to the terminal class
remove a bunch of dead code
mark some methods as keybindings/dbusinterface only
This commit is contained in:
Mario Aichinger 2018-08-30 05:28:08 +02:00 committed by Gaetan Semet
parent 03c486a835
commit 0834bad4d1
13 changed files with 876 additions and 1035 deletions

View File

@ -2,215 +2,6 @@
<!-- Generated with glade 3.18.3 -->
<interface domain="guake">
<requires lib="gtk+" version="3.10"/>
<object class="GtkMenu" id="context-menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkImageMenuItem" id="context_copy">
<property name="label" translatable="yes">Copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="accel_copy_clipboard" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_paste">
<property name="label" translatable="yes">Paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="accel_paste_clipboard" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separador5">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_toggle_fullscreen">
<property name="label" translatable="yes">Toggle Fullscreen</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="accel_toggle_fullscreen" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separador2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_save_tab">
<property name="label" translatable="yes">Save to File...</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="save_tab" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_reset_terminal">
<property name="label" translatable="yes">Reset terminal</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="reset_terminal" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_find_tab">
<property name="label" translatable="yes">Find...</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="find_tab" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separador3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_add_tab">
<property name="label" translatable="yes">New Tab</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="add_tab" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_rename_tab">
<property name="label" translatable="yes">Rename Tab</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="accel_rename_current_tab" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_close_tab">
<property name="label" translatable="yes">Close Tab</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="close_tab" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separator_search">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_browse_on_web">
<property name="label" translatable="yes">Open link...</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="browse_on_web" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_search_on_web">
<property name="label" translatable="yes">Search on Web</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="search_on_web" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_quick_open">
<property name="label" translatable="yes">Quick Open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="quick_open" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separador1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_preferences">
<property name="label">gtk-preferences</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="show_prefs" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_about">
<property name="label">gtk-about</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="show_about" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separador4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="context_close">
<property name="label" translatable="yes">Quit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="accel_quit" swapped="no"/>
</object>
</child>
</object>
<object class="GtkMenu" id="tab-menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="add">
<property name="label" translatable="yes">New Tab</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="add_tab" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rename">
<property name="label" translatable="yes">Rename</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_rename_current_tab_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="close">
<property name="label" translatable="yes">Close</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_close_activate" swapped="no"/>
</object>
</child>
</object>
<object class="GtkMenu" id="tray-menu">
<property name="visible">True</property>
<property name="can_focus">False</property>

View File

@ -1,6 +1,17 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk
from gi.repository import Gtk
from guake.callbacks import TerminalContextMenuCallbacks
from guake.dialogs import RenameDialog
from guake.menus import TabContextMenu
from guake.menus import TerminalContextMenu
from guake.utils import TabNameUtils
from locale import gettext as _
gi.require_version('Vte', '2.91') # vte-0.42
from gi.repository import Vte
# TODO remove calls to guake
class TerminalHolder():
@ -17,6 +28,12 @@ class TerminalHolder():
def get_guake(self):
pass
def get_window(self):
pass
def get_settings(self):
pass
def get_root_box(self):
pass
@ -57,6 +74,12 @@ class RootTerminalBox(Gtk.Box, TerminalHolder):
def get_guake(self):
return self.guake
def get_window(self):
return self.guake.window
def get_settings(self):
return self.guake.settings
def get_root_box(self):
return self
@ -81,6 +104,7 @@ class TerminalBox(Gtk.Box, TerminalHolder):
raise RuntimeError("TerminalBox: terminal already set")
self.terminal = terminal
self.terminal.connect("focus", self.on_terminal_focus)
self.terminal.connect("button-press-event", self.on_button_press, None)
self.pack_start(self.terminal, True, True, 0)
self.terminal.show()
self.add_scroll_bar()
@ -122,12 +146,38 @@ class TerminalBox(Gtk.Box, TerminalHolder):
def get_guake(self):
return self.get_parent().get_guake()
def get_window(self):
return self.get_parent().get_window()
def get_settings(self):
return self.get_parent().get_settings()
def get_root_box(self):
return self.get_parent()
def on_terminal_focus(self, direction, user_data):
self.get_root_box().set_last_terminal_focused(self.terminal)
def on_button_press(self, target, event, user_data):
if event.button == 3:
# First send to background process if handled, do nothing else
if not event.get_state() & Gdk.ModifierType.SHIFT_MASK:
if Vte.Terminal.do_button_press_event(self.terminal, event):
return True
menu = TerminalContextMenu(
self.terminal, self.get_window(), self.get_settings(),
TerminalContextMenuCallbacks(
self.terminal, self.get_window(), self.get_settings(),
self.get_guake().notebook
)
)
menu.popup_at_pointer(event)
self.terminal.grab_focus()
return True
self.terminal.grab_focus()
return False
class DualTerminalBox(Gtk.Paned, TerminalHolder):
@ -172,5 +222,60 @@ class DualTerminalBox(Gtk.Paned, TerminalHolder):
def get_guake(self):
return self.get_parent().get_guake()
def get_window(self):
return self.get_parent().get_window()
def get_settings(self):
return self.get_parent().get_settings()
def get_root_box(self):
return self.get_parent()
class TabLabelEventBox(Gtk.EventBox):
def __init__(self, notebook, text):
super().__init__()
self.notebook = notebook
self.label = Gtk.Label(text)
self.add(self.label)
self.connect("button-press-event", self.on_button_press, self.label)
self.label.show()
def set_text(self, text):
self.label.set_text(text)
def get_text(self):
return self.label.get_text()
def on_button_press(self, target, event, user_data):
if event.button == 3:
menu = TabContextMenu(self)
menu.popup_at_pointer(event)
self.notebook.get_current_terminal().grab_focus()
return True
if event.button == 2:
self.notebook.delete_page_by_label(self)
return True
self.notebook.get_current_terminal().grab_focus()
return False
def on_new_tab(self, user_data):
self.notebook.new_page_with_focus()
def on_rename(self, user_data):
self.notebook.guake.preventHide = True
dialog = RenameDialog(self.notebook.guake.window, self.label.get_text())
r = dialog.run()
if r == Gtk.ResponseType.ACCEPT:
new_text = TabNameUtils.shorten(dialog.get_text(), self.notebook.guake.settings)
page_num = self.notebook.find_tab_index_by_label(self)
self.notebook.rename_page(page_num, new_text, True)
dialog.destroy()
self.notebook.guake.preventHide = False
# TODO
# self.set_terminal_focus()
def on_close(self, user_data):
self.notebook.delete_page_by_label(self)

89
guake/callbacks.py Normal file
View File

@ -0,0 +1,89 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from guake.utils import FullscreenManager
from guake.utils import TabNameUtils
from guake.dialogs import RenameDialog
from guake.dialogs import SaveTerminalDialog
from guake.about import AboutDialog
from guake.prefs import PrefsDialog
from urllib.parse import quote_plus
from guake.utils import get_server_time
class TerminalContextMenuCallbacks():
def __init__(self, terminal, window, settings, notebook):
self.terminal = terminal
self.window = window
self.settings = settings
self.notebook = notebook
def on_copy_clipboard(self, *args):
self.terminal.copy_clipboard()
def on_paste_clipboard(self, *args):
self.terminal.paste_clipboard()
def on_toggle_fullscreen(self, *args):
FullscreenManager(self.window).toggle()
def on_save_to_file(self, *args):
SaveTerminalDialog(self.terminal, self.window).run()
def on_reset_terminal(self, *args):
self.terminal.reset(True, True)
def on_find(self):
# this is not implemented jet
pass
def on_new_tab(self, *args):
self.notebook.new_page_with_focus(
directory=self.terminal.get_current_directory()
)
def on_rename_tab(self, *args):
page_num = self.notebook.find_page_index_by_terminal(self.terminal)
tab_text = self.notebook.get_tab_text_index(page_num)
dialog = RenameDialog(self.window, tab_text)
r = dialog.run()
if r == Gtk.ResponseType.ACCEPT:
new_text = TabNameUtils.shorten(dialog.get_text(), self.settings)
self.notebook.rename_page(page_num, new_text, True)
dialog.destroy()
def on_close_tab(self, *args):
page_num = self.notebook.find_page_index_by_terminal(self.terminal)
self.notebook.delete_page(page_num, False, True)
def on_open_link(self, *args):
self.terminal.browse_link_under_cursor()
def on_search_on_web(self, *args):
if self.terminal.get_has_selection():
self.terminal.copy_clipboard()
clipboard = Gtk.Clipboard.get_default(self.window.get_display())
query = clipboard.wait_for_text()
query = quote_plus(query)
if query:
search_url = "https://www.google.com/#q={!s}&safe=off".format(query)
Gtk.show_uri(self.window.get_screen(), search_url, get_server_time(self.window))
def on_quick_open(self, *args):
if self.terminal.get_has_selection():
self.terminal.quick_open()
def on_command_selected(self, command):
self.terminal.execute_command(command)
def on_show_preferences(self, *args):
self.notebook.guake.hide()
PrefsDialog(self.settings).show()
def on_show_about(self, *args):
self.notebook.guake.hide()
AboutDialog()
def on_quit(self, *args):
self.notebook.guake.accel_quit()

82
guake/customcommands.py Normal file
View File

@ -0,0 +1,82 @@
import os
import json
from locale import gettext as _
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class CustomCommands():
"""
Example for a custom commands file
[
{
"type": "menu",
"description": "dir listing",
"items": [
{
"description": "la",
"cmd":["ls", "-la"]
},
{
"description": "tree",
"cmd":["tree", ""]
}
]
},
{
"description": "less ls",
"cmd": ["ls | less", ""]
}
]
"""
def __init__(self, settings, callback):
self.settings = settings
self.callback = callback
def should_load(self):
file_path = self.settings.general.get_string('custom-command-file')
return file_path is not None
def get_file_path(self):
return os.path.expanduser(self.settings.general.get_string('custom-command-file'))
def _load_json(self, file_name):
try:
with open(file_name) as f:
data_file = f.read()
return json.loads(data_file)
except Exception as e:
log.exception("Invalid custom command file %s. Exception: %s", data_file, str(e))
def build_menu(self):
if not self.should_load():
return None
menu = Gtk.Menu()
for obj in self._load_json(self.get_file_path()):
self._parse_custom_commands(obj, menu)
return menu
def _parse_custom_commands(self, json_object, menu):
if json_object.get('type') == "menu":
newmenu = Gtk.Menu()
newmenuitem = Gtk.MenuItem(json_object['description'])
newmenuitem.set_submenu(newmenu)
newmenuitem.show()
menu.append(newmenuitem)
for item in json_object['items']:
self._parse_custom_commands(item, newmenu)
else:
menu_item = Gtk.MenuItem(json_object['description'])
custom_command = ""
space = ""
for command in json_object['cmd']:
custom_command += (space + command)
space = " "
menu_item.connect("activate", self.on_menu_item_activated, custom_command)
menu.append(menu_item)
menu_item.show()
def on_menu_item_activated(self, item, cmd):
self.callback.on_command_selected(cmd)

View File

@ -76,11 +76,11 @@ class DbusManager(dbus.service.Object):
@dbus.service.method(DBUS_NAME, out_signature='i')
def get_selected_tab(self):
return self.guake.get_selected_tab()
return self.guake.notebook.get_current_page()
@dbus.service.method(DBUS_NAME, out_signature='s')
def get_selected_tablabel(self):
return self.guake.get_selected_tablabel()
return self.guake.notebook.get_tab_text_index(self.guake.notebook.get_current_page())
@dbus.service.method(DBUS_NAME, out_signature='i')
def get_tab_count(self):
@ -108,7 +108,7 @@ class DbusManager(dbus.service.Object):
@dbus.service.method(DBUS_NAME, in_signature='is')
def rename_tab(self, tab_index, new_text):
self.guake.rename_tab(tab_index, new_text, True)
self.guake.notebook.rename_page(tab_index, new_text, True)
@dbus.service.method(DBUS_NAME, in_signature='s')
def rename_current_tab(self, new_text):

127
guake/dialogs.py Normal file
View File

@ -0,0 +1,127 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from locale import gettext as _
class RenameDialog(Gtk.Dialog):
def __init__(self, window, current_name):
super().__init__(
_("Rename tab"), window,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
)
self.entry = Gtk.Entry()
self.entry.set_text(current_name)
self.entry.set_property('can-default', True)
self.entry.show()
vbox = Gtk.VBox()
vbox.set_border_width(6)
vbox.show()
self.set_size_request(300, -1)
self.vbox.pack_start(vbox, True, True, 0)
self.set_border_width(4)
self.set_default_response(Gtk.ResponseType.ACCEPT)
self.add_action_widget(self.entry, Gtk.ResponseType.ACCEPT)
self.entry.reparent(vbox)
def get_text(self):
return self.entry.get_text()
class PromptQuitDialog(Gtk.MessageDialog):
"""Prompts the user whether to quit/close a tab.
"""
def __init__(self, parent, procs, tabs):
super(PromptQuitDialog, self).__init__(
parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO
)
if tabs == -1:
primary_msg = _("Do you want to close the tab?")
tab_str = ''
else:
primary_msg = _("Do you really want to quit Guake?")
if tabs == 1:
tab_str = _(" and one tab open")
else:
tab_str = _(" and {0} tabs open").format(tabs)
if procs == 0:
proc_str = _("There are no processes running")
elif procs == 1:
proc_str = _("There is a process still running")
else:
proc_str = _("There are {0} processes still running").format(procs)
self.set_markup(primary_msg)
self.format_secondary_markup("<b>{0}{1}.</b>".format(proc_str, tab_str))
def quit(self):
"""Run the "are you sure" dialog for quitting Guake
"""
# Stop an open "close tab" dialog from obstructing a quit
response = self.run() == Gtk.ResponseType.YES
self.destroy()
# Keep Guake focussed after dismissing tab-close prompt
# if tab == -1:
# self.window.present()
return response
def close_tab(self):
response = self.run() == Gtk.ResponseType.YES
self.destroy()
# Keep Guake focussed after dismissing tab-close prompt
# if tab == -1:
# self.window.present()
return response
class SaveTerminalDialog(Gtk.FileChooserDialog):
def __init__(self, terminal, window):
super().__init__(
_("Save to..."),
window,
Gtk.FileChooserAction.SAVE,
(
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE,
Gtk.ResponseType.OK
)
)
self.set_default_response(Gtk.ResponseType.OK)
self.terminal = terminal
self.parent_window = window
def run(self):
self.terminal.select_all()
self.terminal.copy_clipboard()
self.terminal.unselect_all()
clipboard = Gtk.Clipboard.get_default(self.parent_window.get_display())
selection = clipboard.wait_for_text()
if not selection:
return
selection = selection.rstrip()
filter = Gtk.FileFilter()
filter.set_name(_("All files"))
filter.add_pattern("*")
self.add_filter(filter)
filter = Gtk.FileFilter()
filter.set_name(_("Text and Logs"))
filter.add_pattern("*.log")
filter.add_pattern("*.txt")
self.add_filter(filter)
response = super().run()
if response == Gtk.ResponseType.OK:
filename = self.get_filename()
with open(filename, "w") as f:
f.write(selection)
self.destroy()

File diff suppressed because it is too large Load Diff

View File

@ -104,7 +104,6 @@ class Keybindings():
self.guake.window.remove_accel_group(self.accel_group)
self.accel_group = Gtk.AccelGroup()
self.guake.window.add_accel_group(self.accel_group)
self.guake.context_menu.set_accel_group(self.accel_group)
self.load_accelerators()
def load_accelerators(self):
@ -132,7 +131,9 @@ class Keybindings():
key, mask = Gtk.accelerator_parse(getk('close-tab'))
if key > 0:
self.accel_group.connect(key, mask, Gtk.AccelFlags.VISIBLE, self.guake.close_tab)
self.accel_group.connect(
key, mask, Gtk.AccelFlags.VISIBLE, self.guake.notebook.delete_page_current
)
key, mask = Gtk.accelerator_parse(getk('previous-tab'))
if key > 0:

163
guake/menus.py Normal file
View File

@ -0,0 +1,163 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from locale import gettext as _
gi.require_version('Vte', '2.91') # vte-0.42
from gi.repository import Vte
from guake.customcommands import CustomCommands
import logging
log = logging.getLogger(__name__)
def TabContextMenu(callback_object):
"""Create the context menu for a notebook tab
"""
menu = Gtk.Menu()
mi1 = Gtk.MenuItem(_("New Tab"))
mi1.connect("activate", callback_object.on_new_tab)
menu.add(mi1)
mi2 = Gtk.MenuItem(_("Rename"))
mi2.connect("activate", callback_object.on_rename)
menu.add(mi2)
mi3 = Gtk.MenuItem(_("Close"))
mi3.connect("activate", callback_object.on_close)
menu.add(mi3)
menu.show_all()
return menu
SEARCH_SELECTION_LENGTH = 20
FILE_SELECTION_LENGTH = 30
def TerminalContextMenu(terminal, window, settings, callback_object):
"""Create the context menu for a terminal
"""
menu = Gtk.Menu()
mi = Gtk.MenuItem(_("Copy"))
mi.connect("activate", callback_object.on_copy_clipboard)
menu.add(mi)
mi = Gtk.MenuItem(_("Paste"))
mi.connect("activate", callback_object.on_paste_clipboard)
# check if clipboard has text, if not disable the paste menuitem
clipboard = Gtk.Clipboard.get_default(window.get_display())
mi.set_sensitive(clipboard.wait_is_text_available())
menu.add(mi)
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.MenuItem(_("Toggle Fullscreen"))
mi.connect("activate", callback_object.on_toggle_fullscreen)
menu.add(mi)
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.MenuItem(_("Save to File..."))
mi.connect("activate", callback_object.on_save_to_file)
menu.add(mi)
mi = Gtk.MenuItem(_("Reset terminal"))
mi.connect("activate", callback_object.on_reset_terminal)
menu.add(mi)
# TODO SEARCH uncomment menu.add()
mi = Gtk.MenuItem(_("Find..."))
mi.connect("activate", callback_object.on_find)
# menu.add(mi)
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.MenuItem(_("New Tab"))
mi.connect("activate", callback_object.on_new_tab)
menu.add(mi)
mi = Gtk.MenuItem(_("Rename Tab"))
mi.connect("activate", callback_object.on_rename_tab)
menu.add(mi)
mi = Gtk.MenuItem(_("Close Tab"))
mi.connect("activate", callback_object.on_close_tab)
menu.add(mi)
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.MenuItem(_("Open link..."))
mi.connect("activate", callback_object.on_open_link)
link = get_link_under_cursor(terminal)
# TODO CONTEXTMENU this is a mess Quick open should also be sensible
# if the text in the selection is a url the current terminal
# implementation does not support this at the moment
if link:
if len(link) >= FILE_SELECTION_LENGTH:
mi.set_label(_("Open Link: {!s}...").format(link[:FILE_SELECTION_LENGTH-3]))
else:
mi.set_label(_("Open Link: {!s}").format(link))
mi.set_sensitive(True)
else:
mi.set_sensitive(False)
menu.add(mi)
mi = Gtk.MenuItem(_("Search on Web"))
mi.connect("activate", callback_object.on_search_on_web)
selection = get_current_selection(terminal, window)
if selection:
search_text = selection.rstrip()
if len(search_text) > SEARCH_SELECTION_LENGTH:
search_text = search_text[:SEARCH_SELECTION_LENGTH-3] + "..."
mi.set_label(_("Search on Web: '%s'") % search_text)
mi.set_sensitive(True)
else:
mi.set_sensitive(False)
menu.add(mi)
mi = Gtk.MenuItem(_("Quick Open..."))
mi.connect("activate", callback_object.on_quick_open)
if selection:
filename = get_filename_under_cursor(terminal, selection)
if filename:
filename_str = str(filename)
if len(filename_str) > FILE_SELECTION_LENGTH:
mi.set_label(
_("Quick Open: {!s}...").format(
filename_str[:FILE_SELECTION_LENGTH-3]
)
)
else:
mi.set_label(_("Quick Open: {!s}").format(filename_str))
mi.set_sensitive(True)
else:
mi.set_sensitive(False)
else:
mi.set_sensitive(False)
menu.add(mi)
customcommands = CustomCommands(settings, callback_object)
if customcommands.should_load():
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.MenuItem(_("Custom Commands"))
mi.set_submenu(customcommands.build_menu())
menu.add(mi)
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.ImageMenuItem("gtk-preferences")
mi.set_use_stock(True)
mi.connect("activate", callback_object.on_show_preferences)
menu.add(mi)
mi = Gtk.ImageMenuItem("gtk-about")
mi.set_use_stock(True)
mi.connect("activate", callback_object.on_show_about)
menu.add(mi)
menu.add(Gtk.SeparatorMenuItem())
mi = Gtk.ImageMenuItem(_("Quit"))
mi.connect("activate", callback_object.on_quit)
menu.add(mi)
menu.show_all()
return menu
def get_current_selection(terminal, window):
if terminal.get_has_selection():
terminal.copy_clipboard()
clipboard = Gtk.Clipboard.get_default(window.get_display())
return clipboard.wait_for_text()
return None
def get_filename_under_cursor(terminal, selection):
filename, _1, _2 = terminal.is_file_on_local_server(selection)
log.info("Current filename under cursor: %s", filename)
if filename:
return filename
return None
def get_link_under_cursor(terminal):
link = terminal.found_link
log.info("Current link under cursor: %s", link)
if link:
return link
return None

View File

@ -20,12 +20,17 @@ Boston, MA 02110-1301 USA
from guake.boxes import DualTerminalBox
from guake.boxes import RootTerminalBox
from guake.boxes import TabLabelEventBox
from guake.boxes import TerminalBox
from guake.dialogs import PromptQuitDialog
import gi
import os
gi.require_version('Gtk', '3.0')
from gi.repository import GObject
from gi.repository import Gtk
from guake.terminal import GuakeTerminal
from locale import gettext as _
import logging
import posix
@ -40,6 +45,24 @@ class TerminalNotebook(Gtk.Notebook):
self.guake = guake
self.last_terminal_focused = None
self.set_name("notebook-teminals")
self.set_tab_pos(Gtk.PositionType.BOTTOM)
self.set_property("show-tabs", True)
self.set_property("enable-popup", False)
self.set_property("scrollable", True)
self.set_property("show-border", False)
self.set_property("visible", True)
self.set_property("has-focus", True)
self.set_property("can-focus", True)
self.set_property("is-focus", True)
self.set_property("expand", True)
GObject.signal_new(
'terminal-spawned', self, GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
(GObject.TYPE_PYOBJECT, GObject.TYPE_INT)
)
GObject.signal_new('page-deleted', self, GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ())
def set_last_terminal_focused(self, terminal):
self.last_terminal_focused = terminal
@ -103,21 +126,48 @@ class TerminalNotebook(Gtk.Notebook):
for page_num in range(self.get_n_pages()):
yield self.get_nth_page(page_num)
def delete_page(self, page_num, kill=True):
def delete_page(self, page_num, kill=True, prompt=False):
if page_num >= self.get_n_pages():
log.debug("Can not delete page %s no such index", page_num)
return
# TODO NOTEBOOK it would be nice if none of the "ui" stuff
# (PromptQuitDialog) would be in here
if prompt:
procs = self.get_running_fg_processes_count_page(page_num)
# TODO NOTEBOOK remove call to guake
prompt_cfg = self.guake.settings.general.get_int('prompt-on-close-tab')
if (prompt_cfg == 1 and procs > 0) or (prompt_cfg == 2):
if not PromptQuitDialog(self.guake.window, procs, -1).close_tab():
return
for terminal in self.get_terminals_for_page(page_num):
if kill:
terminal.kill()
terminal.destroy()
self.remove_page(page_num)
self.emit('page-deleted')
def delete_page_by_label(self, label, kill=True):
self.delete_page(self.find_tab_index_label(label), True)
self.delete_page(self.find_tab_index_by_label(label), kill)
def delete_page_current(self, kill=True):
self.delete_page(self.get_current_page(), kill)
def new_page(self, directory=None):
terminal = GuakeTerminal(self.guake)
terminal.grab_focus()
if not isinstance(directory, str):
directory = os.environ['HOME']
try:
if self.guake.settings.general.get_boolean('open-tab-cwd'):
active_terminal = self.get_current_terminal()
if not active_terminal:
directory = os.path.expanduser('~')
directory = active_terminal.get_current_directory()
except: # pylint: disable=bare-except
pass
terminal.spawn_sync_pid(directory)
def new_page(self):
terminal = self.guake.setup_new_terminal()
terminal_box = TerminalBox()
terminal_box.set_terminal(terminal)
root_terminal_box = RootTerminalBox(self.guake)
@ -128,18 +178,37 @@ class TerminalNotebook(Gtk.Notebook):
# this is needed to initially set the last_terminal_focused,
# one could also call terminal.get_parent().on_terminal_focus()
terminal.emit("focus", Gtk.DirectionType.TAB_FORWARD)
return root_terminal_box, page_num
self.emit('terminal-spawned', terminal, terminal.pid)
return root_terminal_box, page_num, terminal
def find_tab_index_eventbox(self, eventbox):
def new_page_with_focus(self, directory=None):
box, page_num, terminal = self.new_page()
self.set_current_page(page_num)
self.rename_page(page_num, _("Terminal"), False)
terminal.grab_focus()
def rename_page(self, page_index, new_text, user_set=False):
"""Rename an already added page by its index. Use user_set to define
if the rename was triggered by the user (eg. rename dialog) or by
an update from the vte (eg. vte:window-title-changed)
"""
page = self.get_nth_page(page_index)
if not getattr(page, "custom_label_set", False) or user_set:
old_label = self.get_tab_label(page)
if isinstance(old_label, TabLabelEventBox):
old_label.set_text(new_text)
else:
self.set_tab_label(page, TabLabelEventBox(self, new_text))
if user_set:
setattr(page, "custom_label_set", new_text != "-")
def find_tab_index_by_label(self, eventbox):
for index, tab_eventbox in enumerate(self.iter_tabs()):
if eventbox is tab_eventbox:
return index
return -1
def find_tab_index_label(self, label):
return self.find_tab_index_eventbox(label.get_parent())
def find_page_index_for_terminal(self, terminal):
def find_page_index_by_terminal(self, terminal):
for index, page in enumerate(self.iter_pages()):
for t in page.iter_terminals():
if t is terminal:
@ -147,7 +216,7 @@ class TerminalNotebook(Gtk.Notebook):
return -1
def get_tab_text_index(self, index):
return self.get_tab_label(self.get_nth_page(index)).get_children()[0].get_text()
return self.get_tab_label(self.get_nth_page(index)).get_text()
def get_tab_text_page(self, page):
return self.get_tab_label(page).get_children()[0].get_text()
return self.get_tab_label(page).get_text()

View File

@ -50,6 +50,30 @@ from guake.globals import TERMINAL_MATCH_TAGS
log = logging.getLogger(__name__)
libutempter = None
try:
# this allow to run some commands that requires libuterm to
# be injected in current process, as: wall
from atexit import register as at_exit_call
from ctypes import cdll
libutempter = cdll.LoadLibrary('libutempter.so.0')
if libutempter is not None:
# We absolutely need to remove the old tty from the utmp !!!
at_exit_call(libutempter.utempter_remove_added_record)
except Exception as e:
libutempter = None
sys.stderr.write("[WARN] ===================================================================\n")
sys.stderr.write("[WARN] Unable to load the library libutempter !\n")
sys.stderr.write(
"[WARN] Some feature might not work:\n"
"[WARN] - 'exit' command might freeze the terminal instead of closing the tab\n"
"[WARN] - the 'wall' command is know to work badly\n"
)
sys.stderr.write("[WARN] Error: " + str(e) + '\n')
sys.stderr.write(
"[WARN] ===================================================================²\n"
)
def halt(loc):
code.interact(local=loc)
@ -71,6 +95,8 @@ class GuakeTerminal(Vte.Terminal):
self.configure_terminal()
self.add_matches()
self.connect('button-press-event', self.button_press)
self.connect('button-press-event', self.button_press)
self.connect('child-exited', self.on_child_exited)
self.matched_value = ''
self.font_scale_index = 0
self._pid = None
@ -102,6 +128,11 @@ class GuakeTerminal(Vte.Terminal):
else:
super().feed_child(resolved_cmdline, len(resolved_cmdline))
def execute_command(self, command):
if command[-1] != '\n':
command += "\n"
self.feed_child(command)
def copy_clipboard(self):
if self.get_has_selection():
super(GuakeTerminal, self).copy_clipboard()
@ -125,6 +156,12 @@ class GuakeTerminal(Vte.Terminal):
if (Vte.MAJOR_VERSION, Vte.MINOR_VERSION) >= (0, 50):
self.set_allow_hyperlink(True)
# TODO PORT is this still the case with the newer vte version?
# -- Ubuntu has a patch to libvte which disables mouse scrolling in apps
# -- like vim and less by default. If this is the case, enable it back.
if hasattr(self, "set_alternate_screen_scroll"):
self.set_alternate_screen_scroll(True)
self.set_can_default(True)
self.set_can_focus(True)
@ -269,6 +306,10 @@ class GuakeTerminal(Vte.Terminal):
self.found_link = self.handleTerminalMatch(matched_string)
self.matched_value = matched_string[0]
def on_child_exited(self, target, status, *user_data):
if libutempter is not None:
libutempter.utempter_remove_record(self.get_pty())
def quick_open(self):
self.copy_clipboard()
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
@ -427,3 +468,35 @@ class GuakeTerminal(Vte.Terminal):
# if this part of code was reached, means that SIGTERM
# did the work and SIGKILL wasnt needed.
pass
def spawn_sync_pid(self, directory):
argv = list()
user_shell = self.guake.settings.general.get_string('default-shell')
if user_shell and os.path.exists(user_shell):
argv.append(user_shell)
else:
argv.append(os.environ['SHELL'])
login_shell = self.guake.settings.general.get_boolean('use-login-shell')
if login_shell:
argv.append('--login')
pid = self.spawn_sync(
Vte.PtyFlags.DEFAULT, directory, argv, [], GLib.SpawnFlags.DO_NOT_REAP_CHILD, None,
None, None
)
try:
tuple_type = gi._gi.ResultTuple # pylint: disable=c-extension-no-member
except: # pylint: disable=bare-except
tuple_type = tuple
if isinstance(pid, (tuple, tuple_type)):
# Return a tuple in 2.91
# https://lazka.github.io/pgi-docs/Vte-2.91/classes/Terminal.html#Vte.Terminal.spawn_sync
pid = pid[1]
assert isinstance(pid, int)
if libutempter is not None:
libutempter.utempter_add_record(self.get_pty().get_fd(), os.uname()[1])
self.pid = pid
return pid

View File

@ -8,6 +8,9 @@ import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import Gdk
from textwrap import dedent
from guake.paths import GUAKE_THEME_DIR
@ -58,3 +61,44 @@ def get_gtk_theme(settings):
gtk_theme_name = settings.general.get_string('gtk-theme-name')
prefer_dark_theme = settings.general.get_boolean('gtk-prefer-dark-theme')
return (gtk_theme_name, "dark" if prefer_dark_theme else None)
def patch_gtk_theme(style_context, settings):
theme_name, variant = get_gtk_theme(settings)
def rgba_to_hex(color):
return "#{0:02x}{1:02x}{2:02x}".format(
int(color.red * 255), int(color.green * 255), int(color.blue * 255)
)
# for n in [
# "inverted_bg_color",
# "inverted_fg_color",
# "selected_bg_color",
# "selected_fg_color",
# "theme_inverted_bg_color",
# "theme_inverted_fg_color",
# "theme_selected_bg_color",
# "theme_selected_fg_color",
# ]:
# s = style_context.lookup_color(n)
# print(n, s, rgba_to_hex(s[1]))
selected_fg_color = rgba_to_hex(style_context.lookup_color("theme_selected_fg_color")[1])
selected_bg_color = rgba_to_hex(style_context.lookup_color("theme_selected_bg_color")[1])
log.debug(
"Patching theme '%s' (prefer dark = '%r'), overriding tab 'checked' state': "
"foreground: %r, background: %r", theme_name, "yes"
if variant == "dark" else "no", selected_fg_color, selected_bg_color
)
css_data = dedent(
"""
.custom_tab:checked {{
color: {selected_fg_color};
background: {selected_bg_color};
}}
""".format(selected_bg_color=selected_bg_color, selected_fg_color=selected_fg_color)
).encode()
style_provider = Gtk.CssProvider()
style_provider.load_from_data(css_data)
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)

View File

@ -35,3 +35,40 @@ def get_server_time(widget):
# Use local timestamp instead
ts = time.time()
return ts
class TabNameUtils():
@classmethod
def shorten(cls, text, settings):
use_vte_titles = settings.general.get_boolean('use-vte-titles')
if not use_vte_titles:
return text
max_name_length = settings.general.get_int("max-tab-name-length")
if max_name_length != 0 and len(text) > max_name_length:
text = "..." + text[-max_name_length:]
return text
class FullscreenManager():
def __init__(self, window):
self.window = window
self.is_in_fullscreen = False
def is_fullscreen(self):
return getattr(self.window, 'is_fullscreen', False)
def fullscreen(self):
self.window.fullscreen()
setattr(self.window, 'is_fullscreen', True)
def unfullscreen(self):
# TODO do we still need this fix with gtk3?
# Fixes "Guake cannot restore from fullscreen" (#628)
self.window.unmaximize()
self.window.unfullscreen()
setattr(self.window, 'is_fullscreen', False)
def toggle(self):
if self.is_fullscreen():
self.unfullscreen()
else:
self.fullscreen()