initial push

This commit is contained in:
itdominator 2024-09-04 00:59:52 -05:00
parent fda3040fc2
commit 033cf5b27d
1764 changed files with 2185 additions and 313 deletions

View File

@ -1,23 +1,11 @@
# Python-With-Gtk-Template # LSP Manager
A template project for Python with Gtk applications. A helpful tool to handle LSPs (Language Server Protocols) by creating the LSP process, sending client initialize and initialized messages, and allowing crafting of messages.
### Requirements ### Requirements
* PyGObject (Gtk introspection library) * PyGObject (Gtk introspection library)
* pyxdg (Desktop ".desktop" file parser) * pyxdg (Desktop ".desktop" file parser)
* setproctitle (Define process title to search and kill more easily) * setproctitle (Define process title to search and kill more easily)
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
### Note ### Note
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered. WIP
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
* \_\_builtins\_\_.py
* user_config/bin/app_name
* user_config/usr/share/app_name
* user_config/usr/share/app_name/icons/app_name.png
* user_config/usr/share/app_name/icons/app_name-64x64.png
* user_config/usr/share/applications/app_name.desktop
For the user_config, after changing names and files, copy all content to their respective destinations.
The logic follows Debian Dpkg packaging and its placement logic.

View File

@ -11,7 +11,7 @@ from libs.event_system import EventSystem
from libs.endpoint_registry import EndpointRegistry from libs.endpoint_registry import EndpointRegistry
from libs.keybindings import Keybindings from libs.keybindings import Keybindings
from libs.logger import Logger from libs.logger import Logger
from libs.settings_manager.manager import SettingsManager from libs.settings.manager import SettingsManager
@ -35,7 +35,7 @@ def daemon_threaded_wrapper(fn):
# NOTE: Just reminding myself we can add to builtins two different ways... # NOTE: Just reminding myself we can add to builtins two different ways...
# __builtins__.update({"event_system": Builtins()}) # __builtins__.update({"event_system": Builtins()})
builtins.app_name = "<change_me>" builtins.APP_NAME = "LSP_Manager"
builtins.keybindings = Keybindings() builtins.keybindings = Keybindings()
builtins.event_system = EventSystem() builtins.event_system = EventSystem()

View File

@ -18,7 +18,7 @@ from app import Application
def main(args, unknownargs): def main(args, unknownargs):
setproctitle(f'{app_name}') setproctitle(f'{APP_NAME}')
if args.debug == "true": if args.debug == "true":
settings_manager.set_debug(True) settings_manager.set_debug(True)

View File

@ -39,7 +39,7 @@ class Application:
message = f"FILE|{arg}" message = f"FILE|{arg}"
ipc_server.send_ipc_message(message) ipc_server.send_ipc_message(message)
raise AppLaunchException(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...") raise AppLaunchException(f"{APP_NAME} IPC Server Exists: Have sent path(s) to it and closing...")
def ipc_realization_check(self, ipc_server): def ipc_realization_check(self, ipc_server):
try: try:
@ -56,7 +56,7 @@ class Application:
try: try:
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows # kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
signal.signal( signal.signal(
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR1"), vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
debug_signal_handler debug_signal_handler
) )
except ValueError: except ValueError:

View File

@ -0,0 +1,33 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class BuilderWrapper(Gtk.Builder):
"""docstring for BuilderWrapper."""
def __init__(self):
super(BuilderWrapper, self).__init__()
self.objects = {}
def get_object(self, id: str, use_gtk: bool = True) -> any:
if not use_gtk:
return self.objects[id]
return super(BuilderWrapper, self).get_object(id)
def expose_object(self, id: str, object: any, use_gtk: bool = True) -> None:
if not use_gtk:
self.objects[id] = object
else:
super(BuilderWrapper, self).expose_object(id, object)
def dereference_object(self, id: str) -> None:
del self.objects[id]

View File

@ -22,7 +22,7 @@ class BaseContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all() self.show()
def _setup_styling(self): def _setup_styling(self):
@ -33,8 +33,8 @@ class BaseContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("update_transparency", self._update_transparency) event_system.subscribe("update-transparency", self._update_transparency)
event_system.subscribe("remove_transparency", self._remove_transparency) event_system.subscribe("remove-transparency", self._remove_transparency)
def _load_widgets(self): def _load_widgets(self):
self.add(HeaderContainer()) self.add(HeaderContainer())

View File

@ -23,13 +23,13 @@ class BodyContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all() self.show()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.HORIZONTAL) self.set_orientation(Gtk.Orientation.HORIZONTAL)
self.ctx.add_class("body-container") self.ctx.add_class("body-container")
self.set_homogeneous(True) # self.set_homogeneous(True)
def _setup_signals(self): def _setup_signals(self):
... ...

View File

@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.webkit.webkit_ui import WebkitUI from ..widgets.lsp_notebook import LSPNotebook
@ -21,6 +21,8 @@ class CenterContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
@ -31,18 +33,7 @@ class CenterContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
# event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
... ...
def _load_widgets(self): def _load_widgets(self):
glade_box = self._builder.get_object("glade_box") self.add( LSPNotebook() )
button = Gtk.Button(label = "Click Me!")
button.connect("clicked", self._hello_world)
self.add(button)
self.add(glade_box)
self.add( WebkitUI() )
def _hello_world(self, widget = None, eve = None):
logger.debug("Hello, World!")

View File

@ -6,7 +6,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.transparency_scale import TransparencyScale from ..widgets.controls.open_files_button import OpenFilesButton
from ..widgets.controls.transparency_scale import TransparencyScale
@ -21,8 +22,6 @@ class HeaderContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.HORIZONTAL) self.set_orientation(Gtk.Orientation.HORIZONTAL)
@ -32,15 +31,19 @@ class HeaderContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
... event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar)
def _load_widgets(self): def _load_widgets(self):
button = Gtk.Button(label = "Interactive Debug") button = Gtk.Button(label = "Interactive Debug")
button.connect("clicked", self._interactive_debug) button.connect("clicked", self._interactive_debug)
self.add(TransparencyScale()) self.add( OpenFilesButton() )
self.add( TransparencyScale() )
self.add(button) self.add(button)
def _interactive_debug(self, widget = None, eve = None): def _interactive_debug(self, widget = None, eve = None):
event_system.emit("load_interactive_debug") event_system.emit("load-interactive-debug")
def tggl_top_main_menubar(self):
self.hide() if self.is_visible() else self.show_all()

View File

@ -18,6 +18,8 @@ class LeftContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
@ -28,8 +30,7 @@ class LeftContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
# event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
... ...
def _load_widgets(self): def _load_widgets(self):
... ...

View File

@ -18,6 +18,8 @@ class RightContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
@ -28,8 +30,7 @@ class RightContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
# event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
... ...
def _load_widgets(self): def _load_widgets(self):
... ...

View File

@ -1,5 +1,4 @@
# Python imports # Python imports
import os
# Lib imports # Lib imports
import gi import gi
@ -19,6 +18,8 @@ from .bridge_controller import BridgeController
class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData): class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
self.collect_files_dirs(args, unknownargs)
self.setup_controller_data() self.setup_controller_data()
self._setup_styling() self._setup_styling()
@ -27,16 +28,13 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
self._load_controllers() self._load_controllers()
if args.no_plugins == "false": if args.no_plugins == "false":
self.plugins.launch_plugins() self.plugins_controller.pre_launch_plugins()
for arg in unknownargs + [args.new_tab,]: if args.no_plugins == "false":
if os.path.isfile(arg): self.plugins_controller.post_launch_plugins()
message = f"FILE|{arg}"
event_system.emit("post_file_to_ipc", message)
if os.path.isdir(arg): for file in settings_manager.get_starting_files():
message = f"DIR|{arg}" event_system.emit("post-file-to-ipc", file)
event_system.emit("post_file_to_ipc", message)
logger.info(f"Made it past {self.__class__} loading...") logger.info(f"Made it past {self.__class__} loading...")
@ -50,10 +48,10 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
self.window.connect("key-release-event", self.on_global_key_release_controller) self.window.connect("key-release-event", self.on_global_key_release_controller)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("shutting_down", lambda: print("Shutting down...")) event_system.subscribe("shutting-down", lambda: print("Shutting down..."))
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) event_system.subscribe("handle-file-from-ipc", self.handle_file_from_ipc)
event_system.subscribe("handle_dir_from_ipc", self.handle_dir_from_ipc) event_system.subscribe("handle-dir-from-ipc", self.handle_dir_from_ipc)
event_system.subscribe("tggl_top_main_menubar", self._tggl_top_main_menubar) event_system.subscribe("tggl-top-main-menubar", self._tggl_top_main_menubar)
def _load_controllers(self): def _load_controllers(self):
BridgeController() BridgeController()
@ -61,12 +59,12 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
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...")
def setup_builder_and_container(self): def _load_glade_file(self):
self.builder = Gtk.Builder()
self.builder.add_from_file(settings_manager.get_glade_file()) self.builder.add_from_file(settings_manager.get_glade_file())
self.builder.expose_object("main_window", self.window) self.builder.expose_object("main_window", self.window)
settings_manager.set_builder(self.builder) settings_manager.set_builder(self.builder)
self.base_container = BaseContainer() self.base_container = BaseContainer()
settings_manager.register_signals_to_builder([self, self.base_container]) settings_manager.register_signals_to_builder([self, self.base_container])

View File

@ -7,6 +7,7 @@ from shutil import which
# Application imports # Application imports
from plugins.plugins_controller import PluginsController from plugins.plugins_controller import PluginsController
from ..builder_wrapper import BuilderWrapper
@ -14,16 +15,32 @@ class BaseControllerData:
''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. ''' ''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
def setup_controller_data(self) -> None: def setup_controller_data(self) -> None:
self.window = settings_manager.get_main_window() self.window = settings_manager.get_main_window()
self.builder = None self.builder = BuilderWrapper()
self.base_container = None self.plugins_controller = PluginsController()
self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self.setup_builder_and_container() self.base_container = None
self.plugins = PluginsController() self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self._load_glade_file()
def collect_files_dirs(self, args, unknownargs):
files = []
for arg in unknownargs + [args.new_tab,]:
if os.path.isdir( arg.replace("file://", "") ):
files.append( f"DIR|{arg.replace('file://', '')}" )
# NOTE: If passing line number with file split against :
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
files.append( f"FILE|{arg.replace('file://', '')}" )
if len(files) > 0:
settings_manager.set_is_starting_with_file(True)
settings_manager.set_starting_files(files)
def get_base_container(self): def get_base_container(self):
return self.base_container return self.base_container
@ -80,4 +97,4 @@ class BaseControllerData:
proc = subprocess.Popen(command, stdin = subprocess.PIPE) proc = subprocess.Popen(command, stdin = subprocess.PIPE)
proc.stdin.write(data.encode(encoding)) proc.stdin.write(data.encode(encoding))
proc.stdin.close() proc.stdin.close()
retcode = proc.wait() retcode = proc.wait()

View File

@ -20,19 +20,19 @@ class BridgeController:
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("handle_bridge_event", self.handle_bridge_event) event_system.subscribe("handle-bridge-event", self.handle_bridge_event)
def handle_bridge_event(self, event): def handle_bridge_event(self, event):
match event.topic: match event.topic:
case "save": case "save":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "close": case "close":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "load_buffer": case "load_buffer":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "load_file": case "load_file":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "alert": case "alert":
content = base64.b64decode( event.content.encode() ).decode("utf-8") content = base64.b64decode( event.content.encode() ).decode("utf-8")
logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {content}") logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {content}")

View File

@ -0,0 +1,3 @@
"""
Widgets.Controls Module
"""

View File

@ -0,0 +1,86 @@
# Python imports
import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
# Application imports
class OpenFilesButton(Gtk.Button):
"""docstring for OpenFilesButton."""
def __init__(self):
super(OpenFilesButton, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_label("Open File(s)...")
self.set_image( Gtk.Image.new_from_icon_name("gtk-open", 4) )
self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1
self.set_hexpand(False)
def _setup_signals(self):
self.connect("button-release-event", self._open_files)
def _subscribe_to_events(self):
event_system.subscribe("open_files", self._open_files)
def _load_widgets(self):
...
def _open_files(self, widget = None, eve = None, gfile = None):
start_dir = None
_gfiles = []
if gfile and gfile.query_exists():
start_dir = gfile.get_parent()
chooser = Gtk.FileChooserDialog("Open File(s)...", None,
Gtk.FileChooserAction.OPEN,
(
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN,
Gtk.ResponseType.OK
)
)
chooser.set_select_multiple(True)
try:
folder = widget.get_current_file().get_parent() if not start_dir else start_dir
chooser.set_current_folder( folder.get_path() )
except Exception as e:
...
response = chooser.run()
if not response == Gtk.ResponseType.OK:
chooser.destroy()
return _gfiles
filenames = chooser.get_filenames()
if not filenames:
chooser.destroy()
return _gfiles
for file in filenames:
path = file if os.path.isabs(file) else os.path.abspath(file)
_gfiles.append( Gio.File.new_for_path(path) )
chooser.destroy()
logger.debug(_gfiles)
return _gfiles

View File

@ -37,12 +37,12 @@ class TransparencyScale(Gtk.Scale):
def _load_widgets(self): def _load_widgets(self):
adjust = self.get_adjustment() adjust = self.get_adjustment()
adjust.set_lower(0) adjust.set_lower(0)
adjust.set_upper(99) adjust.set_upper(100)
adjust.set_value(settings.theming.transparency) adjust.set_value(settings.theming.transparency)
adjust.set_step_increment(1.0) adjust.set_step_increment(1.0)
def _update_transparency(self, range): def _update_transparency(self, range):
event_system.emit("remove_transparency") event_system.emit("remove-transparency")
tp = int(range.get_value()) tp = int(range.get_value())
settings.theming.transparency = tp settings.theming.transparency = tp
event_system.emit("update_transparency") event_system.emit("update-transparency")

View File

@ -0,0 +1,380 @@
# Python imports
import os
import signal
import json
import subprocess
import threading
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GtkSource
# Application imports
# Request type formatting
# https://github.com/microsoft/multilspy/blob/main/src/multilspy/language_server.py#L417
content_part = """{
"method": "textDocument/definition",
"params": {
"textDocument": {
"uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/testing/lsp_manager/src/core/widgets/lsp_message_box.py",
"languageId": "python3",
"version": 1,
"text": ""
},
"position": {
"line": 5,
"character": 12,
"offset": 0
}
}
}
"""
references_query = """{
"method": "textDocument/references",
"params": {
"context": {
"includeDeclaration": false
},
"textDocument": {
"uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/testing/lsp_manager/src/core/widgets/lsp_message_box.py",
"languageId": "python3",
"version": 1,
"text": ""
},
"position": {
"line": 43,
"character": 13,
"offset": 0
}
}
}
"""
symbols_query = """{
"method": "textDocument/documentSymbol",
"params": {
"textDocument": {
"uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/testing/lsp_manager/src/core/widgets/lsp_message_box.py",
"languageId": "python3",
"version": 1,
"text": ""
}
}
}
"""
LEN_HEADER = "Content-Length: "
TYPE_HEADER = "Content-Type: "
class LSPMessageBox(Gtk.Box):
def __init__(self):
super(LSPMessageBox, self).__init__()
self._lsp_pid = -1
self._message_id = 0
# https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers
# initialize-params-slim.json was created off of jedi_language_server one
self._lsp_init_data = settings_manager.get_lsp_init_data()
self.read_lock = threading.Lock()
self.write_lock = threading.Lock()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("lsp-message-box")
self.set_orientation(Gtk.Orientation.VERTICAL)
self.set_hexpand(True)
self.set_vexpand(True)
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
scrolled_win = Gtk.ScrolledWindow()
self.top_buttons = Gtk.ButtonBox()
self.buttons = Gtk.ButtonBox()
file_choser_lbl = Gtk.Label(label="Workspace Folder:")
self.file_choser_btn = Gtk.FileChooserButton()
initialize_btn = Gtk.Button(label="Send Initialize Message")
notification_btn = Gtk.Button(label="Send Notification Message")
message_btn = Gtk.Button(label="Send Message")
start_stop_lsp_btn = Gtk.Button(label="Start LSP")
source_view = GtkSource.View()
language_manager = GtkSource.LanguageManager()
style_scheme_manager = GtkSource.StyleSchemeManager()
style_scheme = style_scheme_manager.get_scheme("peacocks-in-space")
style_scheme = style_scheme if style_scheme else style_scheme_manager.get_scheme("solarized-dark")
self.buffer = source_view.get_buffer()
self.file_choser_btn.set_title("Chose Workspace")
self.file_choser_btn.set_action( Gtk.FileChooserAction.SELECT_FOLDER )
self.file_choser_btn.set_uri("file:///home/abaddon/Coding/Projects/Active/Python_Projects/testing/lsp_manager")
self.buffer.set_language( language_manager.get_language("json") )
self.buffer.set_style_scheme(style_scheme)
self.buffer.set_text(content_part)
source_view.set_indent_width(4)
source_view.set_tab_width(4)
source_view.set_insert_spaces_instead_of_tabs(True)
source_view.set_hexpand(True)
source_view.set_vexpand(True)
initialize_btn.connect("clicked", self.send_initialize_message)
notification_btn.connect("clicked", self.send_notification_message)
message_btn.connect("clicked", self.send_message_message)
start_stop_lsp_btn.connect("clicked", self.start_stop_lsp)
scrolled_win.add(source_view)
self.top_buttons.add(Gtk.Label(label=f"Message ID: {self._message_id}"))
self.top_buttons.add(file_choser_lbl)
self.top_buttons.add(self.file_choser_btn)
self.buttons.add(initialize_btn)
self.buttons.add(notification_btn)
self.buttons.add(message_btn)
self.buttons.add(start_stop_lsp_btn)
self.add(self.top_buttons)
self.add(scrolled_win)
self.add(self.buttons)
self._disable_buttons()
start_stop_lsp_btn.set_sensitive(True)
def update_message_id_label(self):
self.top_buttons.get_children()[0].set_label(f"Message ID: {self._message_id}")
def post_int_run(self):
lsp_ui = self.get_parent()
self.alt_command_entry = lsp_ui.alt_command_entry
self.command_entry = lsp_ui.command_entry
self.socket_entry = lsp_ui.socket_entry
self.init_options_buffer = lsp_ui.init_options_text_vw.get_buffer()
del lsp_ui
def send_initialize_message(self, button, eve = None):
workspace_file = self.file_choser_btn.get_file().get_path()
workspace_uri = self.file_choser_btn.get_uri()
folder_name = os.path.basename(workspace_file)
self._lsp_init_data["processId"] = settings_manager.get_app_pid()
self._lsp_init_data["rootPath"] = workspace_file
self._lsp_init_data["rootUri"] = workspace_uri
self._lsp_init_data["workspaceFolders"] = [
{
"name": folder_name,
"uri": workspace_uri
}
]
start, end = self.init_options_buffer.get_bounds()
init_ops = self.init_options_buffer.get_text(start, end, True)
self._lsp_init_data["initializationOptions"] = json.loads(init_ops)
self.make_request("initialize", self._lsp_init_data)
button.set_sensitive(False)
def send_initialized_message():
self.make_request("initialized")
def make_notification(self, method: str, params: {} = {}):
data = {
"jsonrpc": "2.0",
"method": method,
"params": params
}
message_str = json.dumps(data)
message_size = len(message_str)
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
self.lsp_process.stdin.write( message.encode("utf-8") )
self.lsp_process.stdin.flush()
def make_request(self, method: str, params: {} = {}):
self._monitor_lsp_response()
data = {
"jsonrpc": "2.0",
"id": self._message_id,
"method": method,
"params": params
}
message_str = json.dumps(data)
message_size = len(message_str)
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
with self.write_lock:
self.lsp_process.stdin.write( message.encode("utf-8") )
self.lsp_process.stdin.flush()
self._message_id += 1
self.update_message_id_label()
def send_notification_message(self, button, eve = None):
pass
def send_message_message(self, button, eve = None):
start, end = self.buffer.get_bounds()
data = self.buffer.get_text(start, end, True)
message = json.loads(data)
self.make_request(message["method"], message["params"])
def start_stop_lsp(self, button, eve = None):
if self._lsp_pid == -1:
pid = self.start_lsp()
if not pid: return
button.set_label("Stop LSP")
self._lsp_pid = pid
self._monitor_lsp_response()
self._enable_buttons()
else:
button.set_label("Start LSP")
self.stop_lsp()
self._disable_buttons()
button.set_sensitive(True)
def start_lsp(self):
_command: str = self.alt_command_entry.get_text()
# _command: str = self.command_entry.get_text()
# _command: str = self.socket_entry.get_text()
command: [] = _command.split() if len( _command.split() ) > 0 else [ _command ]
self.lsp_process = subprocess.Popen(
command,
stdout = subprocess.PIPE,
stdin = subprocess.PIPE
)
return self.lsp_process.pid
def stop_lsp(self):
if self._lsp_pid == -1: return
self._lsp_pid = -1
self._message_id = 0
self.lsp_process.terminate()
self.update_message_id_label()
# https://github.com/sr-lab/coqpyt/blob/master/coqpyt/lsp/json_rpc_endpoint.py#L65
# Virtually this whole method unabashedly taken from ^ ...
@daemon_threaded
def _monitor_lsp_response(self):
with self.read_lock:
message_size = None
while True:
line = self.lsp_process.stdout.readline()
if not line: return None # Quit listener...
line = line.decode("utf-8")
if not line.endswith("\r\n"):
raise Exception(
"Bad header: missing newline"
)
line = line[:-2] # Strip the "\r\n"
if line == "": # We're done with the headers...
break
elif line.startswith(LEN_HEADER):
line = line[len(LEN_HEADER) :]
if not line.isdigit():
raise Exception(
"Bad header: size is not int",
)
message_size = int(line)
elif line.startswith(TYPE_HEADER):
# Not doing anything with type header, currently...
pass
else:
line = line.split(LEN_HEADER)
if len(line) == 2:
message_size = line[1]
raise Exception(
"Bad header: unknown header"
)
if not message_size: return
data = self.lsp_process.stdout.read(message_size)
jsonrpc_res = data.decode("utf-8")
lsp_result = json.loads( jsonrpc_res )
response_id = -1
if not lsp_result: return
if "id" in lsp_result.keys(): response_id = lsp_result["id"]
GLib.idle_add(self.handle_lsp_response, response_id, lsp_result)
def handle_lsp_response(self, id: int, lsp_result: {}):
keys = lsp_result.keys()
if "error" in keys:
lsp_result = lsp_result["error"]
logger.debug(f"LSP Error Code: {lsp_result['code']}")
logger.debug(f"LSP Error Message:\n{lsp_result['message']}")
return
if "result" in keys:
lsp_result = lsp_result["result"]
if isinstance(lsp_result, dict):
keys = lsp_result.keys()
if "capabilities" in keys:
logger.debug(f"LSP Capabilities Response:\n{lsp_result}")
self.send_initialized_message()
if isinstance(lsp_result, list):
print()
print( str(id) )
print(lsp_result)
print()
print()
if isinstance(lsp_result, tuple):
print()
print( str(id) )
print(lsp_result)
print()
print()
def _enable_buttons(self):
for button in self.buttons.get_children():
button.set_sensitive(True)
def _disable_buttons(self):
for button in self.buttons.get_children():
button.set_sensitive(False)

View File

@ -0,0 +1,51 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
# Application imports
from .lsp_window import LSPWindow
class LSPNotebook(Gtk.Notebook):
def __init__(self):
super(LSPNotebook, self).__init__()
self._builder = settings_manager.get_builder()
self.lsp_config_data = settings_manager.get_lsp_config_data()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_tab_pos(Gtk.PositionType.LEFT)
self.set_hexpand(True)
self.set_vexpand(True)
ctx = self.get_style_context()
ctx.add_class("lsp-notebook")
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
for language, data in self.lsp_config_data.items():
tab_widget = Gtk.Label(label=language)
lsp_window = LSPWindow(language, data)
self.append_page(lsp_window, tab_widget)
self.set_tab_detachable(lsp_window, False)
self.set_tab_reorderable(lsp_window, False)

124
src/core/widgets/lsp_ui.py Normal file
View File

@ -0,0 +1,124 @@
# Python imports
import json
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
from .lsp_message_box import LSPMessageBox
class LSPUI(Gtk.Grid):
def __init__(self, language, data):
super(LSPUI, self).__init__()
self._data = data
self._language = language
self._no_init_ops_text = "{\n \n}"
self.alt_command_entry = Gtk.Entry()
self.command_entry = Gtk.Entry()
self.socket_entry = Gtk.Entry()
self.init_options_text_vw = GtkSource.View()
self.message_box = LSPMessageBox()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.message_box.post_int_run()
self.show_all()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("lsp-ui")
self.set_hexpand(True)
self.set_vexpand(True)
self.set_margin_top(5)
self.set_margin_bottom(5)
self.set_margin_left(10)
self.set_margin_right(10)
self.alt_command_entry.set_hexpand(True)
self.command_entry.set_hexpand(True)
self.socket_entry.set_hexpand(True)
self.init_options_text_vw.set_vexpand(True)
self.init_options_text_vw.set_hexpand(True)
self.alt_command_entry.set_placeholder_text("Alt Command:")
self.command_entry.set_placeholder_text("Command:")
self.socket_entry.set_placeholder_text("Socket")
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
_language_manager = GtkSource.LanguageManager()
_style_scheme_manager = GtkSource.StyleSchemeManager()
style_scheme = _style_scheme_manager.get_scheme("peacocks-in-space")
style_scheme = style_scheme if style_scheme else _style_scheme_manager.get_scheme("solarized-dark")
buffer = self.init_options_text_vw.get_buffer()
scrolled_win1 = Gtk.ScrolledWindow()
buffer.set_language( _language_manager.get_language("json") )
buffer.set_style_scheme(style_scheme)
info_link = self._data["info"]
info_link_title = self._language.title() + " LSP Info" if info_link else "Generic LSP Info"
info_link = info_link if info_link else "https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#contentPart"
link_btn = Gtk.LinkButton(info_link)
alt_command_lbl = Gtk.Label(label="Alt Command:")
command_lbl = Gtk.Label(label="Command:")
socket_lbl = Gtk.Label(label="Socket:")
init_options_lbl = Gtk.Label(label="Initialization Options:")
message_box_lbl = Gtk.Label(label="Message Box:")
self.alt_command_entry.set_text(self._data["alt-command"])
self.command_entry.set_text(self._data["command"])
self.socket_entry.set_text(self._data["socket"])
init_ops_txt = json.dumps( self._data["initialization-options"], separators=(',', ':'), indent=4 )
init_ops_txt = init_ops_txt if not init_ops_txt in ["", "{}"] else self._no_init_ops_text
buffer.set_text(init_ops_txt)
link_btn.set_label(info_link_title)
init_options_lbl.set_margin_top(10)
message_box_lbl.set_margin_top(10)
self.init_options_text_vw.set_indent_width(4)
self.init_options_text_vw.set_tab_width(4)
self.init_options_text_vw.set_insert_spaces_instead_of_tabs(True)
self.init_options_text_vw.set_hexpand(True)
self.init_options_text_vw.set_vexpand(True)
scrolled_win1.add(self.init_options_text_vw)
# child, left, top, width, height
self.attach(link_btn, 0, 0, 3, 1)
self.attach(alt_command_lbl, 0, 1, 1, 1)
self.attach(command_lbl, 0, 2, 1, 1)
self.attach(socket_lbl, 0, 3, 1, 1)
self.attach(self.alt_command_entry, 1, 1, 2, 1)
self.attach(self.command_entry, 1, 2, 2, 1)
self.attach(self.socket_entry, 1, 3, 2, 1)
self.attach(init_options_lbl, 0, 4, 3, 1)
self.attach(scrolled_win1, 0, 5, 3, 1)
self.attach(message_box_lbl, 0, 6, 3, 1)
self.attach(self.message_box, 0, 7, 3, 1)

View File

@ -0,0 +1,38 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .lsp_ui import LSPUI
class LSPWindow(Gtk.ScrolledWindow):
def __init__(self, language, data):
super(LSPWindow, self).__init__()
self._builder = settings_manager.get_builder()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets(language, data)
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("lsp-window")
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self, language, data):
viewport = Gtk.Viewport()
viewport.add( LSPUI(language, data) )
self.add(viewport)

View File

@ -9,17 +9,14 @@ from gi.repository import Gdk
from gi.repository import WebKit2 from gi.repository import WebKit2
# Application imports # Application imports
from libs.data_types import Event from libs.settings.other.webkit_ui_settings import WebkitUISettings
from libs.dto.event import Event
class WebkitUI(WebKit2.WebView): class WebkitUI(WebKit2.WebView):
def __init__(self): def __init__(self):
super(WebkitUI, self).__init__() super(WebkitUI, self).__init__()
# self.get_context().set_sandbox_enabled(False)
self._load_settings()
self._setup_styling() self._setup_styling()
self._subscribe_to_events() self._subscribe_to_events()
self._load_view() self._load_view()
@ -27,10 +24,6 @@ class WebkitUI(WebKit2.WebView):
self.show_all() self.show_all()
if settings_manager.is_debug():
inspector = self.get_inspector()
inspector.show()
def _setup_styling(self): def _setup_styling(self):
self.set_vexpand(True) self.set_vexpand(True)
@ -38,8 +31,8 @@ class WebkitUI(WebKit2.WebView):
self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) ) self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) )
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe(f"ui_message", self.ui_message) event_system.subscribe(f"ui-message", self.ui_message)
def _load_settings(self): def _load_settings(self):
self.set_settings( WebkitUISettings() ) self.set_settings( WebkitUISettings() )
@ -54,7 +47,6 @@ class WebkitUI(WebKit2.WebView):
def _setup_content_manager(self): def _setup_content_manager(self):
content_manager = self.get_user_content_manager() content_manager = self.get_user_content_manager()
content_manager.connect("script-message-received", self._process_js_message) content_manager.connect("script-message-received", self._process_js_message)
content_manager.register_script_message_handler("backend") content_manager.register_script_message_handler("backend")
@ -64,45 +56,14 @@ class WebkitUI(WebKit2.WebView):
try: try:
event = Event( **json.loads(message) ) event = Event( **json.loads(message) )
event_system.emit("handle_bridge_event", (event,)) event_system.emit("handle-bridge-event", (event,))
except Exception as e: except Exception as e:
logger.info(e) logger.info(e)
def ui_message(self, mtype, message): def ui_message(self, message, mtype):
command = f"displayMessage('{message}', '{mtype}', '3')" command = f"displayMessage('{message}', '{mtype}', '3')"
self.run_javascript(command, None, None) self.run_javascript(command, None, None)
def run_javascript(self, script, cancellable, callback):
class WebkitUISettings(WebKit2.Settings): logger.debug(script)
def __init__(self): super().run_javascript(script, cancellable, callback)
super(WebkitUISettings, self).__init__()
self._set_default_settings()
# Note: Highly insecure setup but most "app" like setup I could think of.
# Audit heavily any scripts/links ran/clicked under this setup!
def _set_default_settings(self):
# self.set_enable_xss_auditor(True)
# self.set_enable_hyperlink_auditing(True)
self.set_enable_xss_auditor(False)
self.set_enable_hyperlink_auditing(False)
self.set_enable_dns_prefetching(False)
self.set_allow_file_access_from_file_urls(True)
self.set_allow_universal_access_from_file_urls(True)
# self.set_enable_java(True)
self.set_enable_page_cache(False)
self.set_enable_offline_web_application_cache(False)
self.set_enable_html5_local_storage(False)
self.set_enable_html5_database(False)
self.set_print_backgrounds(False)
self.set_enable_tabs_to_links(False)
self.set_enable_fullscreen(True)
self.set_enable_developer_extras(True)
self.set_enable_webrtc(True)
self.set_enable_webaudio(True)
self.set_enable_accelerated_2d_canvas(True)
self.set_user_agent(f"Mozilla/5.0 {app_name}")

View File

@ -1,64 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Gdk
from gi.repository import WebKit2
# Application imports
from libs.settings_manager.other.webkit_ui_settings import WebkitUISettings
class WebkitUI(WebKit2.WebView):
def __init__(self):
super(WebkitUI, self).__init__()
self._setup_styling()
self._subscribe_to_events()
self._load_view()
self._setup_content_manager()
self.show_all()
def _setup_styling(self):
self.set_vexpand(True)
self.set_hexpand(True)
self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) )
def _subscribe_to_events(self):
event_system.subscribe(f"ui_message", self.ui_message)
def _load_settings(self):
self.set_settings( WebkitUISettings() )
def _load_view(self):
path = settings_manager.get_context_path()
data = None
with open(f"{path}/index.html", "r") as f:
data = f.read()
self.load_html(content = data, base_uri = f"file://{path}/")
def _setup_content_manager(self):
content_manager = self.get_user_content_manager()
content_manager.connect("script-message-received", self._process_js_message)
content_manager.register_script_message_handler("backend")
def _process_js_message(self, user_content_manager, js_result):
js_value = js_result.get_js_value()
message = js_value.to_string()
try:
event = Event( **json.loads(message) )
event_system.emit("handle_bridge_event", (event,))
except Exception as e:
logger.info(e)
def ui_message(self, message, mtype):
command = f"displayMessage('{message}', '{mtype}', '3')"
self.run_javascript(command, None, None)

View File

@ -11,6 +11,7 @@ from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
# Application imports # Application imports
from libs.status_icon import StatusIcon
from core.controllers.base_controller import BaseController from core.controllers.base_controller import BaseController
@ -27,7 +28,8 @@ class Window(Gtk.ApplicationWindow):
super(Window, self).__init__() super(Window, self).__init__()
settings_manager.set_main_window(self) settings_manager.set_main_window(self)
self._controller = None self._status_icon = None
self._controller = None
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
@ -41,7 +43,7 @@ class Window(Gtk.ApplicationWindow):
def _setup_styling(self): def _setup_styling(self):
self.set_title(f"{app_name}") self.set_title(f"{APP_NAME}")
self.set_icon_from_file( settings_manager.get_window_icon() ) self.set_icon_from_file( settings_manager.get_window_icon() )
self.set_gravity(5) # 5 = CENTER self.set_gravity(5) # 5 = CENTER
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
@ -58,14 +60,15 @@ class Window(Gtk.ApplicationWindow):
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("tear_down", self._tear_down) event_system.subscribe("tear-down", self._tear_down)
event_system.subscribe("load_interactive_debug", self._load_interactive_debug) event_system.subscribe("load-interactive-debug", self._load_interactive_debug)
def _load_widgets(self, args, unknownargs): def _load_widgets(self, args, unknownargs):
if settings_manager.is_debug(): if settings_manager.is_debug():
self.set_interactive_debugging(True) self.set_interactive_debugging(True)
self._controller = BaseController(args, unknownargs) self._controller = BaseController(args, unknownargs)
self._status_icon = StatusIcon()
if not self._controller: if not self._controller:
raise ControllerStartException("BaseController exited and doesn't exist...") raise ControllerStartException("BaseController exited and doesn't exist...")
@ -90,7 +93,7 @@ class Window(Gtk.ApplicationWindow):
if visual and screen.is_composited() and settings.config.make_transparent == 0: if visual and screen.is_composited() and settings.config.make_transparent == 0:
self.set_visual(visual) self.set_visual(visual)
self.set_app_paintable(True) self.set_app_paintable(True)
self.connect("draw", self._area_draw) # self.connect("draw", self._area_draw)
# bind css file # bind css file
cssProvider = Gtk.CssProvider() cssProvider = Gtk.CssProvider()
@ -107,17 +110,17 @@ class Window(Gtk.ApplicationWindow):
def _on_focus_in_event(self, widget, event): def _on_focus_in_event(self, widget, event):
event_system.emit("pause_dnd_signals") event_system.emit("pause-dnd-signals")
def _on_focus_out_event(self, widget, event): def _on_focus_out_event(self, widget, event):
event_system.emit("listen_dnd_signals") event_system.emit("listen-dnd-signals")
def _load_interactive_debug(self): def _load_interactive_debug(self):
self.set_interactive_debugging(True) self.set_interactive_debugging(True)
def _tear_down(self, widget = None, eve = None): def _tear_down(self, widget = None, eve = None):
event_system.emit("shutting_down") event_system.emit("shutting-down")
size = self.get_size() size = self.get_size()
pos = self.get_position() pos = self.get_position()
@ -132,4 +135,4 @@ class Window(Gtk.ApplicationWindow):
Gtk.main_quit() Gtk.main_quit()
def main(self): def main(self):
Gtk.main() Gtk.main()

6
src/libs/db/__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""
DB module
"""
from .models import User
from .db import DB

View File

@ -18,7 +18,7 @@ def debug_signal_handler(signal, frame):
rpdb2.start_embedded_debugger("foobar", True, True) rpdb2.start_embedded_debugger("foobar", True, True)
rpdb2.setbreak(depth=1) rpdb2.setbreak(depth=1)
return return
except StandardError: except Exception:
... ...
try: try:
@ -26,7 +26,7 @@ def debug_signal_handler(signal, frame):
logger.debug("\n\nStarting embedded rconsole debugger...\n\n") logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
rconsole.spawn_server() rconsole.spawn_server()
return return
except StandardError as ex: except Exception as ex:
... ...
try: try:
@ -34,7 +34,15 @@ def debug_signal_handler(signal, frame):
logger.debug("\n\nStarting PuDB debugger...\n\n") logger.debug("\n\nStarting PuDB debugger...\n\n")
set_trace(paused = True) set_trace(paused = True)
return return
except StandardError as ex: except Exception as ex:
...
try:
import ipdb
logger.debug("\n\nStarting IPDB debugger...\n\n")
ipdb.set_trace()
return
except Exception as ex:
... ...
try: try:
@ -42,11 +50,11 @@ def debug_signal_handler(signal, frame):
logger.debug("\n\nStarting embedded PDB debugger...\n\n") logger.debug("\n\nStarting embedded PDB debugger...\n\n")
pdb.Pdb(skip=['gi.*']).set_trace() pdb.Pdb(skip=['gi.*']).set_trace()
return return
except StandardError as ex: except Exception as ex:
... ...
try: try:
import code import code
code.interact() code.interact()
except StandardError as ex: except Exception as ex:
logger.debug(f"{ex}, returning to normal program flow...") logger.debug(f"{ex}, returning to normal program flow...")

View File

@ -13,17 +13,17 @@ from .singleton import Singleton
class IPCServer(Singleton): class IPCServer(Singleton):
""" Create a listener so that other {app_name} instances send requests back to existing instance. """ """ 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"): def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
self.is_ipc_alive = False self.is_ipc_alive = False
self._ipc_port = 4848 self._ipc_port = 4848
self._ipc_address = ipc_address self._ipc_address = ipc_address
self._conn_type = conn_type self._conn_type = conn_type
self._ipc_authkey = b'' + bytes(f'{app_name}-ipc', 'utf-8') self._ipc_authkey = b'' + bytes(f'{APP_NAME}-ipc', 'utf-8')
self._ipc_timeout = 15.0 self._ipc_timeout = 15.0
if conn_type == "socket": if conn_type == "socket":
self._ipc_address = f'/tmp/{app_name}-ipc.sock' self._ipc_address = f'/tmp/{APP_NAME}-ipc.sock'
elif conn_type == "full_network": elif conn_type == "full_network":
self._ipc_address = '0.0.0.0' self._ipc_address = '0.0.0.0'
elif conn_type == "full_network_unsecured": elif conn_type == "full_network_unsecured":
@ -35,7 +35,7 @@ class IPCServer(Singleton):
self._subscribe_to_events() self._subscribe_to_events()
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("post_file_to_ipc", self.send_ipc_message) event_system.subscribe("post-file-to-ipc", self.send_ipc_message)
def create_ipc_listener(self) -> None: def create_ipc_listener(self) -> None:
@ -74,12 +74,12 @@ class IPCServer(Singleton):
if "FILE|" in msg: if "FILE|" in msg:
file = msg.split("FILE|")[1].strip() file = msg.split("FILE|")[1].strip()
if file: if file:
event_system.emit("handle_file_from_ipc", file) event_system.emit("handle-file-from-ipc", file)
if "DIR|" in msg: if "DIR|" in msg:
file = msg.split("DIR|")[1].strip() file = msg.split("DIR|")[1].strip()
if file: if file:
event_system.emit("handle_dir_from_ipc", file) event_system.emit("handle-dir-from-ipc", file)
conn.close() conn.close()
break break

View File

@ -67,4 +67,4 @@ class DnDMixin:
files.append(gfile) files.append(gfile)
event_system.emit('set_pre_drop_dnd', (files,)) event_system.emit('set-pre-drop-dnd', (files,))

View File

@ -68,11 +68,11 @@ class KeyboardSignalsMixin:
if mapping: if mapping:
self.handle_mapped_key_event(mapping) self.handle_mapped_key_event(mapping)
else: else:
self.handle_as_key_event_scope(mapping) self.handle_as_key_event_scope(keyname)
def handle_mapped_key_event(self, mapping): def handle_mapped_key_event(self, mapping):
try: try:
self.handle_as_controller_scope() self.handle_as_controller_scope(mapping)
except Exception: except Exception:
self.handle_as_plugin_scope(mapping) self.handle_as_plugin_scope(mapping)
@ -86,13 +86,11 @@ class KeyboardSignalsMixin:
sender = "" sender = ""
eve_type = mapping eve_type = mapping
self.handle_as_key_event_system(sender, eve_type) self.handle_key_event_system(sender, eve_type)
def handle_as_key_event_scope(self, mapping):
logger.debug(f"on_global_key_release_controller > key > {keyname}")
def handle_as_key_event_scope(self, keyname):
if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
self.handle_key_event_system(None, mapping) self.handle_key_event_system(None, keyname)
def handle_key_event_system(self, sender, eve_type): def handle_key_event_system(self, sender, eve_type):
event_system.emit(eve_type) event_system.emit(eve_type)

View File

@ -24,8 +24,8 @@ class SettingsManager(StartCheckMixin, Singleton):
def __init__(self): def __init__(self):
self._SCRIPT_PTH = path.dirname(path.realpath(__file__)) self._SCRIPT_PTH = path.dirname(path.realpath(__file__))
self._USER_HOME = path.expanduser('~') self._USER_HOME = path.expanduser('~')
self._HOME_CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}" self._HOME_CONFIG_PATH = f"{self._USER_HOME}/.config/{APP_NAME.lower()}"
self._USR_PATH = f"/usr/share/{app_name.lower()}" self._USR_PATH = f"/usr/share/{APP_NAME.lower()}"
self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json" self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json"
self._CONTEXT_PATH = f"{self._HOME_CONFIG_PATH}/context_path" self._CONTEXT_PATH = f"{self._HOME_CONFIG_PATH}/context_path"
@ -35,10 +35,14 @@ class SettingsManager(StartCheckMixin, Singleton):
self._GLADE_FILE = f"{self._HOME_CONFIG_PATH}/Main_Window.glade" self._GLADE_FILE = f"{self._HOME_CONFIG_PATH}/Main_Window.glade"
self._CSS_FILE = f"{self._HOME_CONFIG_PATH}/stylesheet.css" self._CSS_FILE = f"{self._HOME_CONFIG_PATH}/stylesheet.css"
self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json" self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json"
self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{app_name.lower()}.pid" self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{APP_NAME.lower()}.pid"
self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets" self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets"
self._CONTEXT_MENU = f"{self._HOME_CONFIG_PATH}/contexct_menu.json" self._CONTEXT_MENU = f"{self._HOME_CONFIG_PATH}/contexct_menu.json"
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" self._LSP_CONFIG = f"{self._HOME_CONFIG_PATH}/lsp-servers-config.json"
# https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers
# initialize-params-slim.json was created off of jedi_language_server one
self._LSP_INIT_CONFIG = f"{self._HOME_CONFIG_PATH}/initialize-params-slim.json"
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{APP_NAME.lower()}.png"
# self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json" # self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json"
# self._PLUGINS_PATH = f"plugins" # self._PLUGINS_PATH = f"plugins"
@ -46,8 +50,8 @@ class SettingsManager(StartCheckMixin, Singleton):
# self._GLADE_FILE = f"Main_Window.glade" # self._GLADE_FILE = f"Main_Window.glade"
# self._CSS_FILE = f"stylesheet.css" # self._CSS_FILE = f"stylesheet.css"
# self._KEY_BINDINGS_FILE = f"key-bindings.json" # self._KEY_BINDINGS_FILE = f"key-bindings.json"
# self._PID_FILE = f"{app_name.lower()}.pid" # self._PID_FILE = f"{APP_NAME.lower()}.pid"
# self._WINDOW_ICON = f"{app_name.lower()}.png" # self._WINDOW_ICON = f"{APP_NAME.lower()}.png"
# self._UI_WIDEGTS_PATH = f"ui_widgets" # self._UI_WIDEGTS_PATH = f"ui_widgets"
# self._CONTEXT_MENU = f"contexct_menu.json" # self._CONTEXT_MENU = f"contexct_menu.json"
# self._DEFAULT_ICONS = f"icons" # self._DEFAULT_ICONS = f"icons"
@ -79,7 +83,7 @@ class SettingsManager(StartCheckMixin, Singleton):
if not path.exists(self._CSS_FILE): if not path.exists(self._CSS_FILE):
raise MissingConfigError("Unable to find the application Stylesheet file.") raise MissingConfigError("Unable to find the application Stylesheet file.")
if not path.exists(self._WINDOW_ICON): if not path.exists(self._WINDOW_ICON):
self._WINDOW_ICON = f"{self._USR_PATH}/icons/{app_name.lower()}.png" self._WINDOW_ICON = f"{self._USR_PATH}/icons/{APP_NAME.lower()}.png"
if not path.exists(self._WINDOW_ICON): if not path.exists(self._WINDOW_ICON):
raise MissingConfigError("Unable to find the application icon.") raise MissingConfigError("Unable to find the application icon.")
if not path.exists(self._UI_WIDEGTS_PATH): if not path.exists(self._UI_WIDEGTS_PATH):
@ -101,6 +105,18 @@ class SettingsManager(StartCheckMixin, Singleton):
except Exception as e: except Exception as e:
print( f"Settings Manager: {self._CONTEXT_MENU}\n\t\t{repr(e)}" ) print( f"Settings Manager: {self._CONTEXT_MENU}\n\t\t{repr(e)}" )
try:
with open(self._LSP_CONFIG) as file:
self._lsp_config_data = json.load(file)
except Exception as e:
print( f"Settings Manager: {self._LSP_CONFIG}\n\t\t{repr(e)}" )
try:
with open(self._LSP_INIT_CONFIG) as file:
self._lsp_init_data = json.load(file)
except Exception as e:
print( f"Settings Manager: {self._LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
self.settings: Settings = None self.settings: Settings = None
self._main_window = None self._main_window = None
@ -110,6 +126,8 @@ class SettingsManager(StartCheckMixin, Singleton):
self._trace_debug = False self._trace_debug = False
self._debug = False self._debug = False
self._dirty_start = False self._dirty_start = False
self._passed_in_file = False
self._starting_files = []
def register_signals_to_builder(self, classes=None): def register_signals_to_builder(self, classes=None):
@ -128,7 +146,6 @@ class SettingsManager(StartCheckMixin, Singleton):
def set_main_window(self, window): self._main_window = window def set_main_window(self, window): self._main_window = window
def set_builder(self, builder) -> any: self._builder = builder def set_builder(self, builder) -> any: self._builder = builder
def get_monitor_data(self) -> list: def get_monitor_data(self) -> list:
screen = self._main_window.get_screen() screen = self._main_window.get_screen()
monitors = [] monitors = []
@ -144,6 +161,9 @@ class SettingsManager(StartCheckMixin, Singleton):
def get_glade_file(self) -> str: return self._GLADE_FILE def get_glade_file(self) -> str: return self._GLADE_FILE
def get_ui_widgets_path(self) -> str: return self._UI_WIDEGTS_PATH def get_ui_widgets_path(self) -> str: return self._UI_WIDEGTS_PATH
def get_context_menu_data(self) -> str: return self._context_menu_data def get_context_menu_data(self) -> str: return self._context_menu_data
def get_lsp_config_data(self) -> str: return self._lsp_config_data
def get_lsp_init_data(self) -> {}: return self._lsp_init_data
def get_app_pid(self) -> int: return self.pid
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
@ -152,21 +172,24 @@ class SettingsManager(StartCheckMixin, Singleton):
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
def get_home_path(self) -> str: return self._USER_HOME def get_home_path(self) -> str: return self._USER_HOME
def get_starting_files(self) -> []: return self._starting_files
def is_trace_debug(self) -> str: return self._trace_debug def is_trace_debug(self) -> str: return self._trace_debug
def is_debug(self) -> str: return self._debug def is_debug(self) -> str: return self._debug
def is_starting_with_file(self) -> bool: return self._passed_in_file
def call_method(self, target_class = None, _method_name = None, data = None): def call_method(self, target_class = None, _method_name = None, data = None):
method_name = str(_method_name) method_name = str(_method_name)
method = getattr(target_class, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") method = getattr(target_class, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}")
return method(data) if data else method() return method(data) if data else method()
def set_main_window_x(self, x = 0): self.settings.config.main_window_x = x def set_main_window_x(self, x = 0): self.settings.config.main_window_x = x
def set_main_window_y(self, y = 0): self.settings.config.main_window_y = y def set_main_window_y(self, y = 0): self.settings.config.main_window_y = y
def set_main_window_width(self, width = 800): self.settings.config.main_window_width = width def set_main_window_width(self, width = 800): self.settings.config.main_window_width = width
def set_main_window_height(self, height = 600): self.settings.config.main_window_height = height def set_main_window_height(self, height = 600): self.settings.config.main_window_height = height
def set_main_window_min_width(self, width = 720): self.settings.config.main_window_min_width = width def set_main_window_min_width(self, width = 720): self.settings.config.main_window_min_width = width
def set_main_window_min_height(self, height = 480): self.settings.config.main_window_min_height = height def set_main_window_min_height(self, height = 480): self.settings.config.main_window_min_height = height
def set_starting_files(self, files: []) -> None: self._starting_files = files
def set_trace_debug(self, trace_debug): def set_trace_debug(self, trace_debug):
self._trace_debug = trace_debug self._trace_debug = trace_debug
@ -174,6 +197,8 @@ class SettingsManager(StartCheckMixin, Singleton):
def set_debug(self, debug): def set_debug(self, debug):
self._debug = debug self._debug = debug
def set_is_starting_with_file(self, is_passed_in_file: False):
self._passed_in_file = is_passed_in_file
def load_settings(self): def load_settings(self):
if not path.exists(self._CONFIG_FILE): if not path.exists(self._CONFIG_FILE):

View File

@ -39,4 +39,4 @@ class WebkitUISettings(WebKit2.Settings):
self.set_enable_webaudio(True) self.set_enable_webaudio(True)
self.set_enable_accelerated_2d_canvas(True) self.set_enable_accelerated_2d_canvas(True)
self.set_user_agent(f"{app_name}") self.set_user_agent(f"{APP_NAME}")

View File

@ -41,19 +41,20 @@ class StartCheckMixin:
try: try:
os.kill(pid, 0) os.kill(pid, 0)
except OSError: except OSError:
print(f"{app_name} PID file exists but PID is irrelevant; starting dirty...") print(f"{APP_NAME} PID file exists but PID is irrelevant; starting dirty...")
self._dirty_start = True self._dirty_start = True
return False return False
return True return True
def _write_new_pid(self): def _write_new_pid(self):
pid = os.getpid() pid = os.getpid()
self.pid = pid
self._write_pid(pid) self._write_pid(pid)
self._print_pid(pid) self._print_pid(pid)
def _print_pid(self, pid): def _print_pid(self, pid):
print(f"{app_name} PID: {pid}") print(f"{APP_NAME} PID: {pid}")
def _clean_pid(self): def _clean_pid(self):
os.unlink(self._PID_FILE) os.unlink(self._PID_FILE)

67
src/libs/status_icon.py Normal file
View File

@ -0,0 +1,67 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import AppIndicator3
# Application imports
class StatusIcon():
""" StatusIcon for Application to go to Status Tray. """
def __init__(self):
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):
status_menu = Gtk.Menu()
icon_theme = Gtk.IconTheme.get_default()
check_menu_item = Gtk.CheckMenuItem.new_with_label("Update icon")
quit_menu_item = Gtk.MenuItem.new_with_label("Quit")
# Create StatusNotifierItem
self.indicator = AppIndicator3.Indicator.new(
f"{APP_NAME}-statusicon",
"gtk-info",
AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
check_menu_item.connect("activate", self.check_menu_item_cb)
quit_menu_item.connect("activate", self.quit_menu_item_cb)
icon_theme.connect('changed', self.icon_theme_changed_cb)
self.indicator.set_menu(status_menu)
status_menu.append(check_menu_item)
status_menu.append(quit_menu_item)
status_menu.show_all()
def update_icon(self, icon_name):
self.indicator.set_icon(icon_name)
def check_menu_item_cb(self, widget, data = None):
icon_name = "parole" if widget.get_active() else "gtk-info"
self.update_icon(icon_name)
def icon_theme_changed_cb(self, theme):
self.update_icon("gtk-info")
def quit_menu_item_cb(self, widget, data = None):
event_system.emit("tear-down")

View File

@ -15,13 +15,14 @@ class ManifestProcessor(Exception):
class Plugin: class Plugin:
path: str = None path: str = None
name: str = None name: str = None
author: str = None author: str = None
version: str = None version: str = None
support: str = None support: str = None
requests:{} = None requests:{} = None
reference: type = None reference: type = None
pre_launch: bool = False
class ManifestProcessor: class ManifestProcessor:
@ -46,23 +47,25 @@ class ManifestProcessor:
plugin.support = self._manifest["support"] plugin.support = self._manifest["support"]
plugin.requests = self._manifest["requests"] plugin.requests = self._manifest["requests"]
if "pre_launch" in self._manifest.keys():
plugin.pre_launch = True if self._manifest["pre_launch"] == "true" else False
return plugin return plugin
def get_loading_data(self): def get_loading_data(self):
loading_data = {} loading_data = {}
requests = self._plugin.requests requests = self._plugin.requests
keys = requests.keys()
if "pass_events" in keys: if "pass_events" in requests:
if requests["pass_events"] in ["true"]: if requests["pass_events"] in ["true"]:
loading_data["pass_events"] = True loading_data["pass_events"] = True
if "bind_keys" in keys: if "pass_ui_objects" in requests:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
if "pass_ui_objects" in keys:
if isinstance(requests["pass_ui_objects"], list): if isinstance(requests["pass_ui_objects"], list):
loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ] loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ]
if "bind_keys" in requests:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
return self._plugin, loading_data return self._plugin, loading_data

View File

@ -10,6 +10,7 @@ from os.path import isdir
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
# Application imports # Application imports
@ -35,11 +36,23 @@ class PluginsController:
self._plugins_dir_watcher = None self._plugins_dir_watcher = None
self._plugin_collection = [] self._plugin_collection = []
self._plugin_manifests = {}
self._load_manifests()
def launch_plugins(self) -> None: def _load_manifests(self):
logger.info(f"Loading manifests...")
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
manifest = ManifestProcessor(path, self._builder)
self._plugin_manifests[path] = {
"path": path,
"folder": folder,
"manifest": manifest
}
self._set_plugins_watcher() self._set_plugins_watcher()
self.load_plugins()
def _set_plugins_watcher(self) -> None: def _set_plugins_watcher(self) -> None:
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
@ -52,21 +65,47 @@ class PluginsController:
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file) self.reload_plugins(file)
def load_plugins(self, file: str = None) -> None: def pre_launch_plugins(self) -> None:
logger.info(f"Loading plugins...") logger.info(f"Loading pre-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests, is_pre_launch = True)
def post_launch_plugins(self) -> None:
logger.info(f"Loading post-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if not target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests)
def _load_plugins(self, plugin_manifests: {} = {}, is_pre_launch: bool = False) -> None:
parent_path = os.getcwd() parent_path = os.getcwd()
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]: for key in plugin_manifests:
try: target_manifest = plugin_manifests[key]
target = join(path, "plugin.py") path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
manifest = ManifestProcessor(path, self._builder)
try:
target = join(path, "plugin.py")
if not os.path.exists(target): if not os.path.exists(target):
raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
plugin, loading_data = manifest.get_loading_data() plugin, loading_data = manifest.get_loading_data()
module = self.load_plugin_module(path, folder, target) module = self.load_plugin_module(path, folder, target)
self.execute_plugin(module, plugin, loading_data)
if is_pre_launch:
self.execute_plugin(module, plugin, loading_data)
else:
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
except Exception as e: except Exception as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
logger.debug("Trace: ", traceback.print_exc()) logger.debug("Trace: ", traceback.print_exc())

View File

@ -11,19 +11,6 @@ function main() {
call_path=`pwd` call_path=`pwd`
path="" path=""
# NOTE: Remove if you want to pass file(s) besides directories...
if [[ ! "${1::1}" == /* ]]; then
path="${call_path}/${1}"
else
path="${1}"
fi
if [ ! -d "${path}" ]; then
echo "<change_me>: Path given not a directory..."
exit 1
fi
# End NOTE: Remove if you want to pass file(s) besides directories...
# Collect abs paths and stuff in 'files' array # Collect abs paths and stuff in 'files' array
files=() files=()
for f in "$@"; do for f in "$@"; do
@ -34,6 +21,6 @@ function main() {
done done
cd "/opt/" cd "/opt/"
python /opt/<change_me>.zip "$@" python /opt/lsp-manager.zip "$@"
} }
main "$@"; main "$@";

View File

@ -1,11 +0,0 @@
[Desktop Entry]
Name=<change_me>
GenericName=<change_me>
Comment=<change_me>
Exec=/bin/<change_me> %F
Icon=/usr/share/<change_me>/icons/<change_me>.png
Type=Application
StartupNotify=true
Categories=System;FileTools;Utility;Core;GTK;FileManager;
MimeType=
Terminal=false

View File

@ -0,0 +1,11 @@
[Desktop Entry]
Name=LSP Manager
GenericName=A Python + Gtk app to handle interacting LSP Client and Servers.
Comment=UI for LSP Client and Server Management
Exec=/bin/lsp-manager %F
Icon=/usr/share/lsp_manager/icons/lsp_manager.png
Type=Application
StartupNotify=true
Categories=System;FileTools;Utility;Core;GTK;
MimeType=
Terminal=false

View File

@ -1,23 +0,0 @@
{
"keybindings": {
"help" : "F1",
"rename_files" : ["F2", "<Control>e"],
"open_terminal" : "F4",
"refresh_tab" : ["F5", "<Control>r"],
"delete_files" : "Delete",
"tggl_top_main_menubar" : "Alt_L",
"trash_files" : "<Shift><Control>t",
"tear_down" : "<Control>q",
"go_up" : "<Control>Up",
"go_home" : "<Control>slash",
"grab_focus_path_entry" : "<Control>l",
"open_files" : "<Control>o",
"show_hide_hidden_files" : "<Control>h",
"keyboard_create_tab" : "<Control>t",
"keyboard_close_tab" : "<Control>w",
"keyboard_copy_files" : "<Control>c",
"keyboard_cut_files" : "<Control>x",
"paste_files" : "<Control>v",
"show_new_file_menu" : "<Control>n"
}
}

Some files were not shown because too many files have changed in this diff Show More