Compare commits

...

4 Commits

Author SHA1 Message Date
fac9ee028b fix: clean up GTK/WebKit integration and event handling
- fix split pane expansion/orientation setup logic
- remove deferred pane positioning on show events
- load VTE with interactive bash rcfile
- migrate WebKit2 bindings from 4.0 to 4.1
- add icons path accessor to PathManager
- normalize startup IPC file event payloads with FILE| prefix
- alias App_Event_Types and update plugin event references
- guard widget registry against unnamed builder objects
- fix application_dirs home path lookup
- remove unused GLib import from completion controller
- reorder setproctitle import in __main__
2026-05-22 21:16:01 -05:00
7ab919f925 Fix line-based editing behavior for cut and duplicate commands
- Cut to temp buffer:
  - Respect selections by expanding to full line boundaries
  - Normalize end iterator to include full line + newline
  - Ensure consistent newline handling for last line
  - Preserve cursor position after delete
  - Prevent line merging when accumulating cut buffer

- Duplicate line:
  - Simplify logic using line-based iter APIs
  - Fix incorrect selection handling and off-by-one issues
  - Ensure full-line duplication for both selection and cursor cases
  - Correct cursor/selection restoration after duplication
2026-04-15 23:05:05 -05:00
383db1270e feat(code): adjust split view shortcut and register text insert event
- Change split pane close shortcut from Ctrl+Shift+W to Alt+\
- Register new TextInsertEvent in event system exports
- Prepare groundwork for handling low-level text insertion events in editor pipeline
2026-04-15 01:58:25 -05:00
2d4c8e4f31 feat(lsp): improve shutdown lifecycle and response handling
- Add explicit shutdown and exit requests to LSP client
- Rename initialized message to initialized notification
- Handle initialize → send initialized notification
- Handle shutdown → send exit notification

fix(lsp): delay client teardown to allow graceful shutdown
- Close LSP client and response handlers after timeout (GLib)

feat(vte): major terminal widget improvements
- Switch to spawn_async with full environment inheritance
- Improve PROMPT_COMMAND handling for cwd tracking (OSC7)
- Enable scrollback, disable audible bell, enable scroll-on-output
- Add clipboard shortcuts (Ctrl+Shift+C/V), middle-click paste
- Track current directory changes and update UI label
- Add proper signal wiring and cleanup on destroy

feat(ui): improve Plugins dialog behavior
- Make dialog non-modal and centered on parent window
- Auto-hide on focus loss and Ctrl+Shift+P shortcut
- Ensure proper destroy-with-parent behavior

refactor: add type hints and minor cleanup
- Add explicit typing for VteWidget fields
- Improve signal naming consistency
- Minor formatting and cleanup in folding and LSP modules
2026-04-13 00:54:51 -05:00
24 changed files with 250 additions and 97 deletions

View File

@@ -14,31 +14,44 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
class Handler: class Handler:
@staticmethod @staticmethod
def execute( def execute(view: GtkSource.View, *args, **kwargs):
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Cut to Temp Buffer") logger.debug("Command: Cut to Temp Buffer")
clear_temp_cut_buffer_delayed(view) clear_temp_cut_buffer_delayed(view)
buffer = view.get_buffer() buffer = view.get_buffer()
itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy() if buffer.get_has_selection():
start_itr.set_line_offset(0) start_itr, end_itr = buffer.get_selection_bounds()
end_itr = start_itr.copy() start_itr.set_line_offset(0)
if not end_itr.forward_line():
end_itr = buffer.get_end_iter() if not end_itr.ends_line():
end_itr.forward_to_line_end()
if not end_itr.is_end():
end_itr.forward_char()
else:
itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy()
start_itr.set_line_offset(0)
end_itr = start_itr.copy()
if not end_itr.forward_line():
end_itr = buffer.get_end_iter()
if not hasattr(view, "_cut_buffer"): if not hasattr(view, "_cut_buffer"):
view._cut_buffer = "" view._cut_buffer = ""
line_str = buffer.get_text(start_itr, end_itr, True) text = buffer.get_text(start_itr, end_itr, True)
view._cut_buffer += line_str
if not text.endswith("\n"):
text += "\n"
view._cut_buffer += text
buffer.delete(start_itr, end_itr) buffer.delete(start_itr, end_itr)
buffer.place_cursor(start_itr)
set_temp_cut_buffer_delayed(view) set_temp_cut_buffer_delayed(view)

View File

@@ -47,6 +47,8 @@ def execute(
pane.set_hexpand(True) pane.set_hexpand(True)
pane.set_vexpand(True) pane.set_vexpand(True)
scrolled_win1.set_hexpand(True)
scrolled_win2.set_vexpand(True)
pane.set_wide_handle(True) pane.set_wide_handle(True)
container.remove(scrolled_win1) container.remove(scrolled_win1)
@@ -54,21 +56,9 @@ def execute(
pane.pack2( scrolled_win2, True, True ) pane.pack2( scrolled_win2, True, True )
container.add(pane) container.add(pane)
def _show(pane, alloc, is_vertical: bool):
if is_vertical:
pane.set_position(alloc.width / 2)
else:
pane.set_position(alloc.height / 2)
pane.disconnect(pane.show_id)
is_control, is_shift, is_alt = modkeys_states
alloc = container.get_allocation()
if char_str == "|": if char_str == "|":
pane.show_id = pane.connect("show", _show, alloc, True)
pane.set_orientation(Gtk.Orientation.VERTICAL) pane.set_orientation(Gtk.Orientation.VERTICAL)
elif char_str == "\\": elif char_str == "\\":
pane.show_id = pane.connect("show", _show, alloc, False)
pane.set_orientation(Gtk.Orientation.HORIZONTAL) pane.set_orientation(Gtk.Orientation.HORIZONTAL)
pane.show_all() pane.show_all()

View File

@@ -55,7 +55,7 @@ class Plugin(PluginCode):
command_name = "close_split_view", command_name = "close_split_view",
command = _close_split_view, command = _close_split_view,
binding_mode = "released", binding_mode = "released",
binding = "<Shift><Control>w" binding = "<Alt>\\"
) )
self.emit_to("source_views", event) self.emit_to("source_views", event)

View File

@@ -15,6 +15,8 @@ from ..dto.code.lsp.lsp_messages import definition_request
from ..dto.code.lsp.lsp_messages import implementation_request from ..dto.code.lsp.lsp_messages import implementation_request
from ..dto.code.lsp.lsp_messages import references_request from ..dto.code.lsp.lsp_messages import references_request
from ..dto.code.lsp.lsp_messages import symbols_request from ..dto.code.lsp.lsp_messages import symbols_request
from ..dto.code.lsp.lsp_messages import shutdown_request
from ..dto.code.lsp.lsp_messages import exit_request
@@ -36,9 +38,15 @@ class LSPClientEvents:
self._init_params["initializationOptions"] = self._init_opts self._init_params["initializationOptions"] = self._init_opts
self.send_request("initialize", self._init_params) self.send_request("initialize", self._init_params)
def send_initialized_message(self): def send_initialized_notification(self):
self.send_notification("initialized") self.send_notification("initialized")
def send_shutdown_request(self):
self.send_request("shutdown")
def send_exit_notification(self):
self.send_notification("exit")
def _lsp_did_open(self, data: dict): def _lsp_did_open(self, data: dict):
method = "textDocument/didOpen" method = "textDocument/didOpen"
params = didopen_notification["params"] params = didopen_notification["params"]

View File

@@ -183,7 +183,6 @@ references_request = {
} }
} }
symbols_request = { symbols_request = {
"method": "textDocument/documentSymbol", "method": "textDocument/documentSymbol",
"params": { "params": {
@@ -195,3 +194,14 @@ symbols_request = {
} }
} }
} }
shutdown_request = {
"method": "shutdown",
"params": None
}
exit_request = {
"method": "exit",
"params": None
}

View File

@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import GLib
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -109,8 +112,16 @@ class LSPManager(ControllerBase):
return True return True
def close_client(self, lang_id: str) -> bool: def close_client(self, lang_id: str) -> bool:
self.client_manager.close_client(lang_id) controller = self.client_manager.get_active_client()
self.response_registry.close_handler(lang_id) controller.send_shutdown_request()
def _close():
self.client_manager.close_client(lang_id)
self.response_registry.close_handler(lang_id)
return False
GLib.timeout_add(5000, _close)
return True return True

View File

@@ -17,6 +17,10 @@ class DefaultHandler(BaseHandler):
def handle(self, method: str, response, controller): def handle(self, method: str, response, controller):
match method: match method:
case "initialize":
controller.send_initialized_notification()
case "shutdown":
controller.send_exit_notification()
case "textDocument/completion": case "textDocument/completion":
self._handle_completion(response) self._handle_completion(response)
case "textDocument/definition": case "textDocument/definition":

View File

@@ -4,12 +4,12 @@
import argparse import argparse
import faulthandler import faulthandler
import traceback import traceback
from setproctitle import setproctitle
import tracemalloc import tracemalloc
tracemalloc.start() tracemalloc.start()
# Lib imports # Lib imports
from setproctitle import setproctitle
# Application imports # Application imports
from __builtins__ import * from __builtins__ import *

View File

@@ -81,7 +81,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
def _load_files(self): def _load_files(self):
for file in settings_manager.get_starting_files(): for file in settings_manager.get_starting_files():
event_system.emit("post-file-to-ipc", file) event_system.emit("post-file-to-ipc", f"FILE|{file}")
def _tggl_top_main_menubar(self): def _tggl_top_main_menubar(self):
logger.debug("_tggl_top_main_menubar > stub...") logger.debug("_tggl_top_main_menubar > stub...")

View File

@@ -11,45 +11,45 @@ from gi.repository import GtkSource
def execute( def execute(view: GtkSource.View, *args, **kwargs):
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Duplicate Line") logger.debug("Command: Duplicate Line")
buffer = view.get_buffer() buffer = view.get_buffer()
if not buffer.get_has_selection(): if buffer.get_has_selection():
had_selection = False start_itr, \
itr = buffer.get_iter_at_mark( buffer.get_insert() ) end_itr = buffer.get_selection_bounds()
start_itr = itr.copy() start_line = start_itr.get_line()
end_itr = itr.copy() end_line = end_itr.get_line()
start_line = itr.get_line() + 1 scol = start_itr.get_line_offset()
start_char = itr.get_line_offset() ecol = end_itr.get_line_offset()
else: else:
had_selection = True itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr, end_itr = buffer.get_selection_bounds() start_line = end_line = itr.get_line()
sline = start_itr.get_line() col = itr.get_line_offset()
eline = end_itr.get_line()
start_line = eline + 1
start_char = start_itr.get_line_offset()
end_char = end_itr.get_line_offset()
range_line_size = eline - sline
start_itr.backward_visible_line() start_itr = buffer.get_iter_at_line(start_line)
start_itr.forward_line() end_itr = buffer.get_iter_at_line(end_line)
end_itr.forward_line()
end_itr.backward_char()
line_str = buffer.get_slice(start_itr, end_itr, True) if not end_itr.ends_line():
end_itr.forward_char() end_itr.forward_to_line_end()
buffer.insert(end_itr, f"{line_str}\n", -1)
if not had_selection: if not end_itr.is_end():
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) end_itr.forward_char()
buffer.place_cursor(new_itr)
text = buffer.get_text(start_itr, end_itr, True)
insert_itr = buffer.get_iter_at_line(end_line)
insert_itr.forward_to_line_end()
if not insert_itr.is_end():
insert_itr.forward_char()
buffer.insert(insert_itr, text)
if buffer.get_has_selection():
new_start = buffer.get_iter_at_line_offset(end_line + 1, scol)
new_end = buffer.get_iter_at_line_offset(end_line + 1 + (end_line - start_line), ecol)
buffer.select_range(new_start, new_end)
else: else:
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) new_start = buffer.get_iter_at_line_offset(end_line + 1, col)
new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char) buffer.place_cursor(new_start)
buffer.select_range(new_itr, new_end_itr)

View File

@@ -4,7 +4,6 @@
import gi import gi
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports

View File

@@ -61,7 +61,20 @@ class SourceFile(GtkSource.File):
location: Gtk.TextIter, location: Gtk.TextIter,
text: str, length: int text: str, length: int
): ):
... event = Event_Factory.create_event(
"text_insert",
file = self,
buffer = self.buffer,
location = location,
text = text,
length = length
)
# Note: 'idle_add' needed b/c markers don't get thir positions
# updated relative to the initial insert.
# If not used, seg faults galor during multi insert.
# GLib.idle_add(self.emit, event)
self.emit(event)
def _after_insert_text( def _after_insert_text(
self, self,

View File

@@ -28,8 +28,8 @@ class VteWidget(Vte.Terminal):
def __init__(self): def __init__(self):
super(VteWidget, self).__init__() super(VteWidget, self).__init__()
self.cd_cmd_prefix = ("cd".encode(), "cd ".encode()) self.cd_cmd_prefix: tuple = ("cd".encode(), "cd ".encode())
self.dont_process = False self.dont_process: bool = False
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
@@ -48,9 +48,17 @@ class VteWidget(Vte.Terminal):
self.set_hexpand(True) self.set_hexpand(True)
self.set_enable_sixel(True) self.set_enable_sixel(True)
self.set_cursor_shape( Vte.CursorShape.IBEAM ) self.set_cursor_shape( Vte.CursorShape.IBEAM )
self.set_audible_bell(False)
self.set_scroll_on_output(True)
def _setup_signals(self): def _setup_signals(self):
self.connect("commit", self._commit) self.connect("commit", self._handle_commit)
self.connect("current-directory-uri-changed", self._handle_path_change)
self.connect("selection-changed", self._handle_selection)
self.connect("button-press-event", self._on_button_press)
self.connect("key-press-event", self._on_key_press)
self.connect("key-release-event", self._on_key_release)
self.connect("destroy", self._handle_destroy)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("update_term_path", self.update_term_path) event_system.subscribe("update_term_path", self.update_term_path)
@@ -59,35 +67,87 @@ class VteWidget(Vte.Terminal):
... ...
def _do_session_spawn(self): def _do_session_spawn(self):
env = [ env_dict = os.environ.copy()
"DISPLAY=:0", existing_pc = env_dict.get("PROMPT_COMMAND", "")
"LC_ALL=C", # Note: Needed for 'current-directory-uri-changed' to work.
"TERM='xterm-256color'", # Make sure user .bashrc doesn't affect it...
f"HOME='{settings_manager.path_manager.get_home_path()}'", osc7 = 'printf "\\033]7;file://%s%s\\007" "$PWD"'
"XDG_RUNTIME_DIR='/run/user/1000'",
f"XAUTHORITY='{settings_manager.path_manager.get_home_path()}/.Xauthority'",
"HISTFILE=/dev/null",
"HISTSIZE=0",
"HISTFILESIZE=0",
"PS1=\\h@\\u \\W -->: ",
]
self.spawn_sync( env_dict.update({
"LC_ALL": "C",
"TERM": "xterm-256color",
"HISTFILE": "/dev/null",
"HISTSIZE": "0",
"HISTFILESIZE": "0",
"PS1": "\\h@\\u \\W -->: ",
"PROMPT_COMMAND": f"{osc7};{existing_pc}" if existing_pc else osc7,
})
env = [f"{k}={v}" for k, v in env_dict.items()]
self.spawn_async(
Vte.PtyFlags.DEFAULT, Vte.PtyFlags.DEFAULT,
settings_manager.path_manager.get_home_path(), settings_manager.path_manager.get_home_path(),
["/bin/bash"], ["/bin/bash", "--rcfile", f"{settings_manager.path_manager.get_home_path()}/.bashrc", "-i"],
env, env,
GLib.SpawnFlags.DEFAULT, GLib.SpawnFlags.DEFAULT,
None, None, None, None, -1, None, None,
) )
startup_cmds = [ startup_cmds = [
] ]
self.set_scrollback_lines(15000)
for i in startup_cmds: for i in startup_cmds:
self.run_command(i) self.run_command(i)
def _commit(self, terminal, text, size): def _handle_destroy(self, terminal):
logger.debug("Destroying terminal...")
terminal.disconnect_by_func(terminal._handle_commit)
terminal.disconnect_by_func(terminal._handle_path_change)
terminal.disconnect_by_func(terminal._handle_selection)
terminal.disconnect_by_func(terminal._on_button_press)
terminal.disconnect_by_func(terminal._on_key_press)
terminal.disconnect_by_func(terminal._on_key_release)
terminal.disconnect_by_func(terminal._handle_destroy)
def _handle_path_change(self, terminal):
if not hasattr(self, "label"): return
uri = terminal.get_current_directory_uri().replace("file://", "")
terminal.label.set_text(uri)
terminal.label.set_tooltip_text(uri)
def _handle_selection(self, *args):
if self.get_has_selection():
self.copy_primary()
def _on_button_press(self, widget, event):
if event.button == 2: # middle click
self.paste_clipboard()
return True
def _on_key_press(self, widget, event):
ctrl_pressed = event.state & Gdk.ModifierType.CONTROL_MASK
shift_pressed = event.state & Gdk.ModifierType.SHIFT_MASK
if ctrl_pressed:
if shift_pressed:
if event.keyval in [Gdk.KEY_C, Gdk.KEY_V]:
if event.keyval == Gdk.KEY_C:
self.copy_clipboard()
elif event.keyval == Gdk.KEY_V:
self.paste_clipboard()
return True
return False
def _on_key_release(self, widget, event):
...
def _handle_commit(self, terminal, text, size):
if self.dont_process: if self.dont_process:
self.dont_process = False self.dont_process = False
return return

View File

@@ -5,7 +5,7 @@ import json
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
gi.require_version('WebKit2', '4.0') gi.require_version('WebKit2', '4.1')
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gio from gi.repository import Gio

View File

@@ -29,6 +29,7 @@ from .cursor_moved_event import CursorMovedEvent
from .delete_range_event import DeleteRangeEvent from .delete_range_event import DeleteRangeEvent
from .modified_changed_event import ModifiedChangedEvent from .modified_changed_event import ModifiedChangedEvent
from .text_changed_event import TextChangedEvent from .text_changed_event import TextChangedEvent
from .text_insert_event import TextInsertEvent
from .text_inserted_event import TextInsertedEvent from .text_inserted_event import TextInsertedEvent
from .focused_view_event import FocusedViewEvent from .focused_view_event import FocusedViewEvent
from .set_active_file_event import SetActiveFileEvent from .set_active_file_event import SetActiveFileEvent

View File

@@ -0,0 +1,20 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .code_event import CodeEvent
@dataclass
class TextInsertEvent(CodeEvent):
location: Gtk.TextIter = None
text: str = ""
length: int = 0

View File

@@ -31,7 +31,7 @@ class EventFactory(Singleton):
event_type = self._class_name_to_event_type(name) event_type = self._class_name_to_event_type(name)
self._event_classes[event_type] = obj self._event_classes[event_type] = obj
Code_Event_Types.add_event_class(name, obj) App_Event_Types.add_event_class(name, obj)
i += 1 i += 1
logger.debug(f"Registered {i} event types:") logger.debug(f"Registered {i} event types:")
@@ -44,7 +44,7 @@ class EventFactory(Singleton):
event_type = self._class_name_to_event_type(name) event_type = self._class_name_to_event_type(name)
del self._event_classes[event_type] del self._event_classes[event_type]
Code_Event_Types.remove_event_class(name) App_Event_Types.remove_event_class(name)
i += 1 i += 1
logger.debug(f"Unregistered {i} event types:") logger.debug(f"Unregistered {i} event types:")
@@ -98,6 +98,7 @@ class EventNamespace:
Code_Event_Types = EventNamespace() App_Event_Types = EventNamespace()
Code_Event_Types = App_Event_Types
Event_Factory = EventFactory() Event_Factory = EventFactory()

View File

@@ -35,5 +35,5 @@ class Config:
main_window_height: int = 600 main_window_height: int = 600
application_dirs: list = field(default_factory=lambda: [ application_dirs: list = field(default_factory=lambda: [
"/usr/share/applications", "/usr/share/applications",
f"{settings_manager.get_home_path()}/.local/share/applications" f"{settings_manager.path_manager.get_home_path()}/.local/share/applications"
]) ])

View File

@@ -88,6 +88,7 @@ class PathManager:
def get_ui_widgets_path(self) -> str: return self._UI_WIDGETS_PATH def get_ui_widgets_path(self) -> str: return self._UI_WIDGETS_PATH
def get_context_path(self) -> str: return self._CONTEXT_PATH def get_context_path(self) -> str: return self._CONTEXT_PATH
def get_plugins_path(self) -> str: return self._PLUGINS_PATH def get_plugins_path(self) -> str: return self._PLUGINS_PATH
def get_icons_path(self) -> str: return self._DEFAULT_ICONS
def get_css_file(self) -> str: return self._CSS_FILE def get_css_file(self) -> str: return self._CSS_FILE
def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH
def get_window_icon(self) -> str: return self._WINDOW_ICON def get_window_icon(self) -> str: return self._WINDOW_ICON

View File

@@ -2,7 +2,7 @@
# Lib imports # Lib imports
import gi import gi
gi.require_version('WebKit2', '4.0') gi.require_version('WebKit2', '4.1')
from gi.repository import WebKit2 from gi.repository import WebKit2
# Application imports # Application imports

View File

@@ -30,6 +30,7 @@ class WidgetRegisteryController(ControllerBase):
widgets = self._builder.get_objects() widgets = self._builder.get_objects()
for widget in widgets: for widget in widgets:
if not hasattr(widget, "get_name"): continue
self.builder_keys.append( widget.get_name() ) self.builder_keys.append( widget.get_name() )
def _controller_message(self, event: BaseEvent): def _controller_message(self, event: BaseEvent):

View File

@@ -13,7 +13,7 @@ from gi.repository import Gtk
from gi.repository import GLib from gi.repository import GLib
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, App_Event_Types, Code_Event_Types
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
from libs.dto.plugins.manifest_meta import ManifestMeta from libs.dto.plugins.manifest_meta import ManifestMeta
from libs.dto.base_event import BaseEvent from libs.dto.base_event import BaseEvent
@@ -58,7 +58,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
item = Gtk.MenuItem(label = "Plugins") item = Gtk.MenuItem(label = "Plugins")
item.connect("activate", self.toggle_plugins_ui) item.connect("activate", self.toggle_plugins_ui)
event.menu.append(item) event.menu.append(item)
elif isinstance(event, Code_Event_Types.TogglePluginsUiEvent): elif isinstance(event, App_Event_Types.TogglePluginsUiEvent):
self.toggle_plugins_ui() self.toggle_plugins_ui()
def _collect_search_locations(self, path: str, locations: list): def _collect_search_locations(self, path: str, locations: list):

View File

@@ -2,7 +2,10 @@
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
# Application imports # Application imports
@@ -25,6 +28,7 @@ class PluginsUI(Gtk.Dialog):
self.set_title("Plugins") self.set_title("Plugins")
self.set_size_request(450, 530) self.set_size_request(450, 530)
self.set_modal(False)
self.set_deletable(False) self.set_deletable(False)
self.set_skip_pager_hint(True) self.set_skip_pager_hint(True)
self.set_skip_taskbar_hint(True) self.set_skip_taskbar_hint(True)
@@ -35,9 +39,13 @@ class PluginsUI(Gtk.Dialog):
window = widget_registery.get_object("main-window") window = widget_registery.get_object("main-window")
self.set_transient_for(window) self.set_transient_for(window)
self.set_destroy_with_parent(True)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
def _setup_signals(self): def _setup_signals(self):
... self.connect("focus-out-event", self._on_focus_out)
self.connect("key-release-event", self._on_key_release)
def _subscribe_to_events(self): def _subscribe_to_events(self):
... ...
@@ -59,6 +67,19 @@ class PluginsUI(Gtk.Dialog):
scrolled_win.show_all() scrolled_win.show_all()
def _on_key_release(self, widget, event):
ctrl_pressed = event.state & Gdk.ModifierType.CONTROL_MASK
shift_pressed = event.state & Gdk.ModifierType.SHIFT_MASK
if ctrl_pressed:
if shift_pressed:
if event.keyval == Gdk.KEY_P:
self.hide()
def _on_focus_out(self, *args):
self.hide()
GLib.idle_add(self.hide)
def add_row(self, manifest_meta, callback: callable): def add_row(self, manifest_meta, callback: callable):
box = Gtk.Box() box = Gtk.Box()
plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name) plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name)