Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions guake/boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

gi.require_version("Vte", "2.91") # vte-0.42
gi.require_version("Gtk", "3.0")
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import Gio
Expand Down Expand Up @@ -636,10 +637,17 @@ def remove_dead_child(self, child):
print("I have never seen this widget!")


# Foreground color applied to a background tab's title when it has unseen
# activity. Chosen to stay readable on both light and dark tab bars.
TAB_ACTIVITY_COLOR = "#E8A33D"


class TabLabelEventBox(Gtk.EventBox):
def __init__(self, notebook, text, settings):
super().__init__()
self.notebook = notebook
self._text = text
self._activity = False
self.box = Gtk.Box(homogeneous=Gtk.Orientation.HORIZONTAL, spacing=0, visible=True)
self.label = Gtk.Label(label=text, visible=True)
self.close_button = Gtk.Button(
Expand All @@ -656,10 +664,36 @@ def __init__(self, notebook, text, settings):
self.connect("button-press-event", self.on_button_press, self.label)

def set_text(self, text):
self.label.set_text(text)
self._text = text
self._render()

def get_text(self):
return self.label.get_text()
return self._text

def set_activity(self, active):
"""Highlight (or clear) this tab's title to signal unseen output.

Returns True if the activity state actually changed, so callers can
avoid redundant re-rendering on the frequent contents-changed signal.
"""
active = bool(active)
if active == self._activity:
return False
self._activity = active
self._render()
return True

def get_activity(self):
return self._activity

def _render(self):
if self._activity:
self.label.set_markup(
f'<span foreground="{TAB_ACTIVITY_COLOR}" weight="bold">'
f"{GLib.markup_escape_text(self._text)}</span>"
)
else:
self.label.set_text(self._text)

def grab_focus_on_last_focused_terminal(self):
server_time = get_server_time(self.notebook.guake.window)
Expand Down
15 changes: 15 additions & 0 deletions guake/data/org.guake.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,21 @@
<summary>Audible bell</summary>
<description>If true, the system alert sound will be played on a bell character.</description>
</key>
<key name="display-tab-activity" type="b">
<default>false</default>
<summary>Highlight tabs with activity</summary>
<description>If true, the title of a background tab is highlighted when its terminal produces output, until the tab is selected.</description>
</key>
<key name="tab-activity-new-tab-grace" type="d">
<default>3.0</default>
<summary>Activity grace period for new tabs</summary>
<description>Number of seconds during which activity is ignored on a newly created tab, to avoid highlighting it because of its initial shell prompt.</description>
</key>
<key name="tab-activity-focus-loss-grace" type="d">
<default>1.0</default>
<summary>Activity grace period after losing focus</summary>
<description>Number of seconds during which activity is ignored on a tab right after it loses focus.</description>
</key>
<key name="window-width" type="i">
<default>100</default>
<summary>Window width.</summary>
Expand Down
110 changes: 110 additions & 0 deletions guake/data/prefs.glade
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkAdjustment" id="tab_activity_new_tab_grace_adj">
<property name="upper">60</property>
<property name="step-increment">0.5</property>
<property name="page-increment">5</property>
</object>
<object class="GtkAdjustment" id="tab_activity_focus_loss_grace_adj">
<property name="upper">60</property>
<property name="step-increment">0.5</property>
<property name="page-increment">5</property>
</object>
<object class="GtkAdjustment" id="scrollback_lines">
<property name="upper">10000000</property>
<property name="value">5000</property>
Expand Down Expand Up @@ -397,6 +407,106 @@
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="display_tab_activity">
<property name="label" translatable="yes">Highlight tabs with activity</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_display_tab_activity_toggled" swapped="no"/>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox_tab_activity_new_tab_grace">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="lbl_tab_activity_new_tab_grace">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Ignore activity on new tabs for (seconds):</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="tab_activity_new_tab_grace">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="text" translatable="yes">3.0</property>
<property name="adjustment">tab_activity_new_tab_grace_adj</property>
<property name="climb-rate">1</property>
<property name="digits">1</property>
<property name="numeric">True</property>
<signal name="value-changed" handler="on_tab_activity_new_tab_grace_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">6</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox_tab_activity_focus_loss_grace">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="lbl_tab_activity_focus_loss_grace">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Ignore activity after losing focus for (seconds):</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="tab_activity_focus_loss_grace">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="text" translatable="yes">1.0</property>
<property name="adjustment">tab_activity_focus_loss_grace_adj</property>
<property name="climb-rate">1</property>
<property name="digits">1</property>
<property name="numeric">True</property>
<signal name="value-changed" handler="on_tab_activity_focus_loss_grace_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">7</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
Expand Down
9 changes: 9 additions & 0 deletions guake/gsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def __init__(self, guake_inst):
settings.general.onChangedValue("max-tab-name-length", self.max_tab_name_length_changed)
settings.general.onChangedValue("display-tab-names", self.display_tab_names_changed)
settings.general.onChangedValue("hide-tabs-if-one-tab", self.hide_tabs_if_one_tab_changed)
settings.general.onChangedValue("display-tab-activity", self.display_tab_activity_changed)

def custom_command_file_changed(self, settings, key, user_data):
self.guake.load_custom_commands()
Expand Down Expand Up @@ -475,3 +476,11 @@ def hide_tabs_if_one_tab_changed(self, settings, key, user_data):
be called and will show/hide the tab bar if necessary
"""
self.guake.get_notebook().hide_tabbar_if_one_tab()

def display_tab_activity_changed(self, settings, key, user_data):
"""If the gconf var display-tab-activity was disabled, clear any
activity highlights that are currently shown on tabs.
"""
if not settings.get_boolean(key):
for notebook in self.guake.notebook_manager.iter_notebooks():
notebook.clear_all_tab_activity()
54 changes: 54 additions & 0 deletions guake/notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

import gi
import os
import time

gi.require_version("Gtk", "3.0")
gi.require_version("Wnck", "3.0")
Expand Down Expand Up @@ -90,6 +91,9 @@ def __init__(self, *args, **kwargs):
self.notebook_on_button_press_id = self.connect(
"button-press-event", self.on_button_press, None
)
# Clear the activity highlight from a tab once it becomes the current one.
self._activity_last_page = None
self.connect("switch-page", self.on_switch_page)

# Action box
self.pin_button = Gtk.ToggleButton(
Expand Down Expand Up @@ -371,6 +375,12 @@ def new_page(self, directory=None, position=None, empty=False, open_tab_cwd=Fals
terminal_box.set_terminal(terminal)
root_terminal_box = RootTerminalBox(self.guake, self)
root_terminal_box.set_child(terminal_box)
# Ignore activity on a freshly created tab for a grace period, so the
# initial shell prompt (especially on session restore) does not light up
# every background tab at once.
if self.guake:
grace = self.guake.settings.general.get_double("tab-activity-new-tab-grace")
root_terminal_box.activity_ignore_until = time.monotonic() + grace
page_num = self.insert_page(
root_terminal_box, None, position if position is not None else -1
)
Expand Down Expand Up @@ -408,6 +418,7 @@ def terminal_spawn(self, directory=None, open_tab_cwd=False):
"key-press-event",
lambda x, y: self.guake.accel_group.activate(x, y) if self.guake.accel_group else False,
)
terminal.connect("contents-changed", self.on_terminal_activity)
if not isinstance(directory, str):
directory = os.environ["HOME"]
try:
Expand All @@ -427,6 +438,49 @@ def terminal_spawn(self, directory=None, open_tab_cwd=False):
terminal.spawn_sync_pid(directory)
return terminal

def on_terminal_activity(self, terminal):
"""Highlight a background tab's title when its terminal produces output."""
if not getattr(self, "guake", None):
return
if not self.guake.settings.general.get_boolean("display-tab-activity"):
return
page_index = self.find_page_index_by_terminal(terminal)
if page_index < 0 or page_index == self.get_current_page():
return
page = self.get_nth_page(page_index)
if time.monotonic() < getattr(page, "activity_ignore_until", 0):
return
label = self.get_tab_label(page)
if hasattr(label, "set_activity"):
label.set_activity(True)

def on_switch_page(self, notebook, page, page_num):
"""Clear the highlight from the tab being switched to, and start a short
grace period on the tab that just lost focus."""
previous_page = self._activity_last_page
self._activity_last_page = page
if previous_page is not None and previous_page is not page and getattr(
self, "guake", None
):
grace = self.guake.settings.general.get_double("tab-activity-focus-loss-grace")
# Never shorten an existing (e.g. longer new-tab) grace: on session
# restore every tab briefly becomes current, and clobbering the
# creation grace with this one would let the startup burst through.
previous_page.activity_ignore_until = max(
getattr(previous_page, "activity_ignore_until", 0),
time.monotonic() + grace,
)
label = self.get_tab_label(page)
if hasattr(label, "set_activity"):
label.set_activity(False)

def clear_all_tab_activity(self):
"""Remove activity highlights from every tab (e.g. when the feature is
disabled)."""
for label in self.iter_tabs():
if hasattr(label, "set_activity"):
label.set_activity(False)

def terminal_attached(self, terminal):
terminal.emit("focus", Gtk.DirectionType.TAB_FORWARD)
self.emit("terminal-spawned", terminal, terminal.pid)
Expand Down
22 changes: 22 additions & 0 deletions guake/prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,18 @@ def on_use_audible_bell_toggled(self, chk):
"""Changes the value of use_audible_bell in dconf"""
self.settings.general.set_boolean("use-audible-bell", chk.get_active())

def on_display_tab_activity_toggled(self, chk):
"""Changes the value of display_tab_activity in dconf"""
self.settings.general.set_boolean("display-tab-activity", chk.get_active())

def on_tab_activity_new_tab_grace_changed(self, spin):
"""Changes the value of tab_activity_new_tab_grace in dconf"""
self.settings.general.set_double("tab-activity-new-tab-grace", spin.get_value())

def on_tab_activity_focus_loss_grace_changed(self, spin):
"""Changes the value of tab_activity_focus_loss_grace in dconf"""
self.settings.general.set_double("tab-activity-focus-loss-grace", spin.get_value())

# scrolling tab

def on_use_scrollbar_toggled(self, chk):
Expand Down Expand Up @@ -1195,6 +1207,16 @@ def load_configs(self):
value = self.settings.general.get_boolean("use-audible-bell")
self.get_widget("use_audible_bell").set_active(value)

# highlight tabs with activity
value = self.settings.general.get_boolean("display-tab-activity")
self.get_widget("display_tab_activity").set_active(value)

# tab activity grace periods
value = self.settings.general.get_double("tab-activity-new-tab-grace")
self.get_widget("tab_activity_new_tab_grace").set_value(value)
value = self.settings.general.get_double("tab-activity-focus-loss-grace")
self.get_widget("tab_activity_focus_loss_grace").set_value(value)

self._load_screen_settings()

value = self.settings.general.get_boolean("quick-open-enable")
Expand Down
Loading