added clock, pager, and task list widgets for ref; added footer container; added option for key combo o universally toggle window with key combo if set
This commit is contained in:
@@ -8,6 +8,7 @@ from gi.repository import Gtk
|
||||
# Application imports
|
||||
from .header_container import HeaderContainer
|
||||
from .body_container import BodyContainer
|
||||
from .footer_container import FooterContainer
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +40,7 @@ class BaseContainer(Gtk.Box):
|
||||
def _load_widgets(self):
|
||||
self.add(HeaderContainer())
|
||||
self.add(BodyContainer())
|
||||
self.add(FooterContainer())
|
||||
|
||||
def _update_transparency(self):
|
||||
self.ctx.add_class(f"mw_transparency_{settings.theming.transparency}")
|
||||
|
||||
@@ -26,6 +26,10 @@ class CenterContainer(Gtk.Box):
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("center-container")
|
||||
|
||||
|
||||
41
src/core/containers/footer_container.py
Normal file
41
src/core/containers/footer_container.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class FooterContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(FooterContainer, self).__init__()
|
||||
|
||||
self.ctx = self.get_style_context()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.set_hexpand(True)
|
||||
|
||||
self.ctx.add_class("footer-container")
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
@@ -22,11 +22,14 @@ class HeaderContainer(Gtk.Box):
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.set_hexpand(True)
|
||||
|
||||
self.ctx.add_class("header-container")
|
||||
|
||||
def _setup_signals(self):
|
||||
|
||||
@@ -23,6 +23,9 @@ class LeftContainer(Gtk.Box):
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.set_vexpand(True)
|
||||
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("left-container")
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ class RightContainer(Gtk.Box):
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.set_vexpand(True)
|
||||
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("right-container")
|
||||
|
||||
|
||||
@@ -104,4 +104,4 @@ class BaseControllerData:
|
||||
proc = subprocess.Popen(command, stdin = subprocess.PIPE)
|
||||
proc.stdin.write(data.encode(encoding))
|
||||
proc.stdin.close()
|
||||
retcode = proc.wait()
|
||||
retcode = proc.wait()
|
||||
|
||||
110
src/core/widgets/clock_widget.py
Normal file
110
src/core/widgets/clock_widget.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# Python imports
|
||||
from datetime import datetime
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class CalendarWidget(Gtk.Popover):
|
||||
def __init__(self):
|
||||
super(CalendarWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.body = Gtk.Calendar()
|
||||
|
||||
self.body.show()
|
||||
self.add(self.body)
|
||||
|
||||
|
||||
class ClockWidget(Gtk.EventBox):
|
||||
def __init__(self):
|
||||
super(ClockWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_size_request(180, -1)
|
||||
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("clock-widget")
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("button_release_event", self._toggle_cal_popover)
|
||||
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.calendar = CalendarWidget()
|
||||
self.label = Gtk.Label()
|
||||
|
||||
self.calendar.set_relative_to(self)
|
||||
|
||||
self.label.set_justify(Gtk.Justification.CENTER)
|
||||
self.label.set_margin_top(15)
|
||||
self.label.set_margin_bottom(15)
|
||||
self.label.set_margin_left(15)
|
||||
self.label.set_margin_right(15)
|
||||
|
||||
self._update_face()
|
||||
self.add(self.label)
|
||||
|
||||
GObject.timeout_add(59000, self._update_face)
|
||||
|
||||
def _update_face(self):
|
||||
dt_now = datetime.now()
|
||||
hours_mins_sec = dt_now.strftime("%I:%M %p")
|
||||
month_day_year = dt_now.strftime("%m/%d/%Y")
|
||||
time_str = hours_mins_sec + "\n" + month_day_year
|
||||
|
||||
self.label.set_label(time_str)
|
||||
|
||||
def _toggle_cal_popover(self, widget, eve):
|
||||
if (self.calendar.get_visible() == True):
|
||||
self.calendar.popdown()
|
||||
return
|
||||
|
||||
now = datetime.now()
|
||||
timeStr = now.strftime("%m/%d/%Y")
|
||||
parts = timeStr.split("/")
|
||||
month = int(parts[0]) - 1
|
||||
day = int(parts[1])
|
||||
year = int(parts[2])
|
||||
|
||||
self.calendar.body.select_day(day)
|
||||
self.calendar.body.select_month(month, year)
|
||||
|
||||
self.calendar.popup()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
35
src/core/widgets/pager_widget.py
Normal file
35
src/core/widgets/pager_widget.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Wnck', '3.0')
|
||||
from gi.repository import Wnck
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class PagerWidget:
|
||||
def __init__(self):
|
||||
super(PagerWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def get_widget(self):
|
||||
return Wnck.Pager.new()
|
||||
54
src/core/widgets/task_list_widget.py
Normal file
54
src/core/widgets/task_list_widget.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Wnck', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Wnck
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class TaskListWidget(Gtk.ScrolledWindow):
|
||||
def __init__(self):
|
||||
super(TaskListWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_hexpand(False)
|
||||
self.set_size_request(180, -1)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
viewport = Gtk.Viewport()
|
||||
task_list = Wnck.Tasklist.new()
|
||||
vbox = Gtk.Box()
|
||||
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
|
||||
task_list.set_scroll_enabled(False)
|
||||
task_list.set_button_relief(2) # 0 = normal relief, 2 = no relief
|
||||
task_list.set_grouping(1) # 0 = mever group, 1 auto group, 2 = always group
|
||||
|
||||
task_list.set_vexpand(True)
|
||||
task_list.set_include_all_workspaces(False)
|
||||
task_list.set_orientation(1) # 0 = horizontal, 1 = vertical
|
||||
|
||||
vbox.add(task_list)
|
||||
viewport.add(vbox)
|
||||
self.add(viewport)
|
||||
@@ -10,6 +10,11 @@ from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
try:
|
||||
from gi.repository import GdkX11
|
||||
except ImportError:
|
||||
logger.debug("Could not import X11 gir module...")
|
||||
|
||||
# Application imports
|
||||
from libs.status_icon import StatusIcon
|
||||
from core.controllers.base_controller import BaseController
|
||||
@@ -31,6 +36,9 @@ class Window(Gtk.ApplicationWindow):
|
||||
self._status_icon = None
|
||||
self._controller = None
|
||||
|
||||
self.guake_key = settings_manager.get_guake_key()
|
||||
self.hidefunc = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
@@ -38,6 +46,7 @@ class Window(Gtk.ApplicationWindow):
|
||||
|
||||
self._set_window_data()
|
||||
self._set_size_constraints()
|
||||
self._setup_window_toggle_event()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -45,6 +54,9 @@ class Window(Gtk.ApplicationWindow):
|
||||
def _setup_styling(self):
|
||||
self.set_title(f"{APP_NAME}")
|
||||
self.set_icon_from_file( settings_manager.get_window_icon() )
|
||||
self.set_decorated(True)
|
||||
self.set_skip_pager_hint(False)
|
||||
self.set_skip_taskbar_hint(False)
|
||||
self.set_gravity(5) # 5 = CENTER
|
||||
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
|
||||
|
||||
@@ -74,6 +86,15 @@ class Window(Gtk.ApplicationWindow):
|
||||
|
||||
self.add( self._controller.get_base_container() )
|
||||
|
||||
def _display_manager(self):
|
||||
""" Try to detect which display manager we are running under... """
|
||||
|
||||
import os
|
||||
if os.environ.get('WAYLAND_DISPLAY'):
|
||||
return 'WAYLAND'
|
||||
|
||||
return 'X11'
|
||||
|
||||
def _set_size_constraints(self):
|
||||
_window_x = settings.config.main_window_x
|
||||
_window_y = settings.config.main_window_y
|
||||
@@ -118,6 +139,48 @@ class Window(Gtk.ApplicationWindow):
|
||||
def _load_interactive_debug(self):
|
||||
self.set_interactive_debugging(True)
|
||||
|
||||
def _setup_window_toggle_event(self) -> None:
|
||||
hidebound = None
|
||||
if not self.guake_key or not self._display_manager() == 'X11':
|
||||
return
|
||||
|
||||
try:
|
||||
import gi
|
||||
gi.require_version('Keybinder', '3.0')
|
||||
from gi.repository import Keybinder
|
||||
|
||||
Keybinder.init()
|
||||
Keybinder.set_use_cooked_accelerators(False)
|
||||
except (ImportError, ValueError) as e:
|
||||
logger.warning(e)
|
||||
logger.warning('Unable to load Keybinder module. This means the hide_window shortcut will be unavailable')
|
||||
|
||||
return
|
||||
|
||||
# Attempt to grab a global hotkey for hiding the window.
|
||||
# If we fail, we'll never hide the window, iconifying instead.
|
||||
try:
|
||||
hidebound = Keybinder.bind(self.guake_key, self._on_toggle_window, self)
|
||||
except (KeyError, NameError) as e:
|
||||
logger.warning(e)
|
||||
|
||||
if not hidebound:
|
||||
logger.debug('Unable to bind hide_window key, another instance/window has it.')
|
||||
self.hidefunc = self.iconify
|
||||
else:
|
||||
self.hidefunc = self.hide
|
||||
|
||||
def _on_toggle_window(self, data, window):
|
||||
"""Handle a request to hide/show the window"""
|
||||
if not window.get_property('visible'):
|
||||
window.show()
|
||||
# Note: Needed to properly grab widget focus when set_skip_taskbar_hint set to True
|
||||
window.present()
|
||||
# NOTE: Need here to enforce sticky after hide and reshow.
|
||||
window.stick()
|
||||
else:
|
||||
self.hidefunc()
|
||||
|
||||
|
||||
def start(self):
|
||||
Gtk.main()
|
||||
|
||||
@@ -16,7 +16,7 @@ class IPCServer(Singleton):
|
||||
""" Create a listener so that other {APP_NAME} instances send requests back to existing instance. """
|
||||
def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
|
||||
self.is_ipc_alive = False
|
||||
self._ipc_port = 4848
|
||||
self._ipc_port = 0 # Use 0 to let Listener chose port
|
||||
self._ipc_address = ipc_address
|
||||
self._conn_type = conn_type
|
||||
self._ipc_authkey = b'' + bytes(f'{APP_NAME}-ipc', 'utf-8')
|
||||
|
||||
@@ -91,7 +91,9 @@ class SettingsManager(StartCheckMixin, Singleton):
|
||||
|
||||
try:
|
||||
with open(self._KEY_BINDINGS_FILE) as file:
|
||||
bindings = json.load(file)["keybindings"]
|
||||
bindings = json.load(file)["keybindings"]
|
||||
self._guake_key = bindings["guake_key"]
|
||||
|
||||
keybindings.configure(bindings)
|
||||
except Exception as e:
|
||||
print( f"Settings Manager: {self._KEY_BINDINGS_FILE}\n\t\t{repr(e)}" )
|
||||
@@ -155,6 +157,7 @@ class SettingsManager(StartCheckMixin, Singleton):
|
||||
def get_window_icon(self) -> str: return self._WINDOW_ICON
|
||||
def get_home_path(self) -> str: return self._USER_HOME
|
||||
def get_starting_files(self) -> list: return self._starting_files
|
||||
def get_guake_key(self) -> tuple: return self._guake_key
|
||||
|
||||
def get_starting_args(self):
|
||||
return self.args, self.unknownargs
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"keybindings": {
|
||||
"help" : "F1",
|
||||
"guake_key" : "",
|
||||
"rename-files" : ["F2", "<Control>e"],
|
||||
"open-terminal" : "F4",
|
||||
"refresh-tab" : ["F5", "<Control>r"],
|
||||
|
||||
@@ -6,6 +6,7 @@ popover > box,
|
||||
.body-container,
|
||||
.center-container,
|
||||
.header-container,
|
||||
.footer-container,
|
||||
.left-containerm,
|
||||
.right-container {
|
||||
background: rgba(0, 0, 0, 0.0);
|
||||
|
||||
Reference in New Issue
Block a user