From 8a3eacb5d240a402f5d8fe86536e279e201e45e5 Mon Sep 17 00:00:00 2001 From: Rai <58925163+rai68@users.noreply.github.com> Date: Wed, 27 Mar 2024 02:05:24 +1000 Subject: [PATCH 01/11] Update LCD_2inch4.py Signed-off-by: Rai <58925163+rai68@users.noreply.github.com> --- pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py b/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py index dc1da5ec..ff19d242 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py @@ -4,8 +4,8 @@ import numbers class LCD_2inch4(lcdconfig.RaspberryPi): - width = 240 - height = 320 + self.width = 240 + self.height = 320 def command(self, cmd): self.digital_write(self.DC_PIN, False) From 8a242a707b75c64cf33ebb90abde09c237d60d15 Mon Sep 17 00:00:00 2001 From: Rai <58925163+rai68@users.noreply.github.com> Date: Wed, 27 Mar 2024 02:06:05 +1000 Subject: [PATCH 02/11] Update LCD_2inch4.py Signed-off-by: Rai <58925163+rai68@users.noreply.github.com> --- pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py b/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py index ff19d242..dc1da5ec 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcd/lcdhat2in4/LCD_2inch4.py @@ -4,8 +4,8 @@ import numbers class LCD_2inch4(lcdconfig.RaspberryPi): - self.width = 240 - self.height = 320 + width = 240 + height = 320 def command(self, cmd): self.digital_write(self.DC_PIN, False) From 8eccd71c6bce315739a82387918fd59ecd265c05 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Thu, 13 Jun 2024 02:55:05 +1000 Subject: [PATCH 03/11] changes to ui --- pwnagotchi/ui/colors.py | 33 ++++++++ pwnagotchi/ui/hw/wavesharelcd1in8.py | 3 +- pwnagotchi/ui/state.py | 2 +- pwnagotchi/ui/view.py | 115 ++++++++++++++++++++++----- 4 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 pwnagotchi/ui/colors.py diff --git a/pwnagotchi/ui/colors.py b/pwnagotchi/ui/colors.py new file mode 100644 index 00000000..a81966c5 --- /dev/null +++ b/pwnagotchi/ui/colors.py @@ -0,0 +1,33 @@ +LOOK_R = '( ⚆_⚆)' +LOOK_L = '(☉_☉ )' +LOOK_R_HAPPY = '( ◕‿◕)' +LOOK_L_HAPPY = '(◕‿◕ )' +SLEEP = '(⇀‿‿↼)' +SLEEP2 = '(≖‿‿≖)' +AWAKE = '(◕‿‿◕)' +BORED = '(-__-)' +INTENSE = '(°▃▃°)' +COOL = '(⌐■_■)' +HAPPY = '(•‿‿•)' +GRATEFUL = '(^‿‿^)' +EXCITED = '(ᵔ◡◡ᵔ)' +MOTIVATED = '(☼‿‿☼)' +DEMOTIVATED = '(≖__≖)' +SMART = '(✜‿‿✜)' +LONELY = '(ب__ب)' +SAD = '(╥☁╥ )' +ANGRY = "(-_-')" +FRIEND = '(♥‿‿♥)' +BROKEN = '(☓‿‿☓)' +DEBUG = '(#__#)' +UPLOAD = '(1__0)' +UPLOAD1 = '(1__1)' +UPLOAD2 = '(0__1)' +PNG = False +POSITION_X = 0 +POSITION_Y = 40 + + +def load_from_config(config): + for face_name, face_value in config.items(): + globals()[face_name.upper()] = face_value diff --git a/pwnagotchi/ui/hw/wavesharelcd1in8.py b/pwnagotchi/ui/hw/wavesharelcd1in8.py index 9006d145..59eefad0 100644 --- a/pwnagotchi/ui/hw/wavesharelcd1in8.py +++ b/pwnagotchi/ui/hw/wavesharelcd1in8.py @@ -7,7 +7,8 @@ from pwnagotchi.ui.hw.base import DisplayImpl class Wavesharelcd1in8(DisplayImpl): def __init__(self, config): super(Wavesharelcd1in8, self).__init__(config, 'wavesharelcd1in8') - + self.mode = "RGB" + def layout(self): fonts.setup(10, 8, 10, 18, 25, 9) self._layout['width'] = 160 diff --git a/pwnagotchi/ui/state.py b/pwnagotchi/ui/state.py index b416b4a2..3c1a543c 100644 --- a/pwnagotchi/ui/state.py +++ b/pwnagotchi/ui/state.py @@ -3,7 +3,7 @@ from threading import Lock class State(object): def __init__(self, state={}): - self._state = state + self._state = state # all ui elements self._lock = Lock() self._listeners = {} self._changes = {} diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 8d31ffeb..cdab577f 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -5,6 +5,7 @@ import time from threading import Lock from PIL import ImageDraw +from PIL import ImageColor as colors import pwnagotchi import pwnagotchi.plugins as plugins @@ -12,29 +13,98 @@ import pwnagotchi.ui.faces as faces import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.web as web import pwnagotchi.utils as utils + from pwnagotchi.ui.components import * from pwnagotchi.ui.state import State from pwnagotchi.voice import Voice -WHITE = 0x00 -BLACK = 0xFF +WHITE = 0x00 # white is actually black on jays image +BLACK = 0xFF # black is actually white on jays image + +BACKGROUND_1 = 0 +FOREGROUND_1 = 1 + +BACKGROUND_L = 0 +FOREGROUND_L = 255 + +BACKGROUND_RGB = (0,0,0) +FOREGROUND_RGB = (255,255,255) + ROOT = None + + +#1 (1-bit pixels, black and white, stored with one pixel per byte) + +#L (8-bit pixels, grayscale) + +#P (8-bit pixels, mapped to any other mode using a color palette) + +#RGB (3x8-bit pixels, true color) + +#RGBA (4x8-bit pixels, true color with transparency mask) + +#CMYK (4x8-bit pixels, color separation) + +#YCbCr (3x8-bit pixels, color video format) + +#self.FOREGROUND is the main color +#self.FOREGROUND is the 2ndary color, used for background + + + class View(object): def __init__(self, config, impl, state=None): global ROOT, BLACK, WHITE + #values/code for display color mode + + self.mode = '1' # 1 = (1-bit pixels, black and white, stored with one pixel per byte) + if hasattr(impl, 'mode'): + self.mode = impl.mode + + + + match self.mode: + case '1': + self.BACKGROUND = BACKGROUND_1 + self.FOREGROUND = FOREGROUND_1 + # do stuff is color mode is 1 when View object is created. + case 'L': + self.BACKGROUND = BACKGROUND_L # black 0 to 255 + self.FOREGROUND = FOREGROUND_L + # do stuff is color mode is L when View object is created. + case 'P': + pass + # do stuff is color mode is P when View object is created. + case 'RGB': + self.BACKGROUND = BACKGROUND_RGB #black tuple + self.FOREGROUND = FOREGROUND_RGB #white tuple + # do stuff is color mode is RGB when View object is created. + case 'RGBA': + # do stuff is color mode is RGBA when View object is created. + pass + case 'CMYK': + # do stuff is color mode is CMYK when View object is created. + pass + case 'YCbCr': + # do stuff is color mode is YCbCr when View object is created. + pass + case _: + # do stuff when color mode doesnt exist for display + self.BACKGROUND = BACKGROUND_1 + self.FOREGROUND = FOREGROUND_1 + + self.invert = 0 - self._black = 0xFF - self._white = 0x00 + if 'invert' in config['ui'] and config['ui']['invert'] == True: logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert'])) self.invert = 1 - BLACK = 0x00 - WHITE = 0xFF - self._black = 0x00 - self._white = 0xFF + tmp = self.FOREGROUND + self.FOREGROUND = self.FOREGROUND + self.FOREGROUND = tmp # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) @@ -51,40 +121,40 @@ class View(object): self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ - 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], + 'channel': LabeledValue(color=self.FOREGROUND, label='CH', value='00', position=self._layout['channel'], label_font=fonts.Bold, text_font=fonts.Medium), - 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], + 'aps': LabeledValue(color=self.FOREGROUND, label='APS', value='0 (00)', position=self._layout['aps'], label_font=fonts.Bold, text_font=fonts.Medium), - 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], + 'uptime': LabeledValue(color=self.FOREGROUND, label='UP', value='00:00:00', position=self._layout['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), - 'line1': Line(self._layout['line1'], color=BLACK), - 'line2': Line(self._layout['line2'], color=BLACK), + 'line1': Line(self._layout['line1'], color=self.FOREGROUND), + 'line2': Line(self._layout['line2'], color=self.FOREGROUND), - 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']), + 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']), - # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), - 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK), + # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND), + 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND), - 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), + 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=self.FOREGROUND, font=fonts.Bold), 'status': Text(value=self._voice.default(), position=self._layout['status']['pos'], - color=BLACK, + color=self.FOREGROUND, font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=self._layout['status']['max']), - 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, + 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=self.FOREGROUND, position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=self._layout['mode'], - font=fonts.Bold, color=BLACK), + font=fonts.Bold, color=self.FOREGROUND), }) if state: @@ -387,8 +457,9 @@ class View(object): state = self._state changes = state.changes(ignore=self._ignore_changes) if force or len(changes): - self._canvas = Image.new('1', (self._width, self._height), self._white) - drawer = ImageDraw.Draw(self._canvas) + logging.info(self.mode) + self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND) + drawer = ImageDraw.Draw(self._canvas, self.mode) plugins.on('ui_update', self) From 689d39c450e1bf26dad267fae3474370c62ecf32 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Thu, 13 Jun 2024 03:37:51 +1000 Subject: [PATCH 04/11] more changes --- pwnagotchi/ui/hw/displayhatmini.py | 1 + pwnagotchi/ui/view.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/hw/displayhatmini.py b/pwnagotchi/ui/hw/displayhatmini.py index afc590db..81b5d72e 100644 --- a/pwnagotchi/ui/hw/displayhatmini.py +++ b/pwnagotchi/ui/hw/displayhatmini.py @@ -7,6 +7,7 @@ from pwnagotchi.ui.hw.base import DisplayImpl class DisplayHatMini(DisplayImpl): def __init__(self, config): super(DisplayHatMini, self).__init__(config, 'displayhatmini') + self.mode = "RGB" # its actually BGR;16 5,6,5 bit, but display lib converts it def layout(self): fonts.setup(12, 10, 12, 70, 25, 9) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index cdab577f..808feda5 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -27,9 +27,16 @@ FOREGROUND_1 = 1 BACKGROUND_L = 0 FOREGROUND_L = 255 + +BACKGROUND_BGR_16 = (0,0,0) +FOREGROUND_BGR_16 = (31,63,31) + BACKGROUND_RGB = (0,0,0) FOREGROUND_RGB = (255,255,255) +BACKGROUND_RGB_24 = (0,0,0) +FOREGROUND_RGB_24 = (255,255,255) + ROOT = None @@ -41,6 +48,8 @@ ROOT = None #P (8-bit pixels, mapped to any other mode using a color palette) + +#BGR;16 (5,6,5 ) #RGB (3x8-bit pixels, true color) #RGBA (4x8-bit pixels, true color with transparency mask) @@ -50,7 +59,7 @@ ROOT = None #YCbCr (3x8-bit pixels, color video format) #self.FOREGROUND is the main color -#self.FOREGROUND is the 2ndary color, used for background +#self.BACKGROUNDGROUND is the 2ndary color, used for background @@ -78,6 +87,9 @@ class View(object): case 'P': pass # do stuff is color mode is P when View object is created. + case 'BGR;16': + self.BACKGROUND = BACKGROUND_BGR_16 #black tuple + self.FOREGROUND = FOREGROUND_BGR_16 #white tuple case 'RGB': self.BACKGROUND = BACKGROUND_RGB #black tuple self.FOREGROUND = FOREGROUND_RGB #white tuple @@ -135,7 +147,7 @@ class View(object): 'line1': Line(self._layout['line1'], color=self.FOREGROUND), 'line2': Line(self._layout['line2'], color=self.FOREGROUND), - 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']), + 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=(255,0,255), font=fonts.Huge, png=config['ui']['faces']['png']), # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND), 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND), From 7d8b66e1cb866901e2fe42f98949b38377a3bf9d Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Thu, 13 Jun 2024 04:18:57 +1000 Subject: [PATCH 05/11] Update displayhatmini.py --- pwnagotchi/ui/hw/displayhatmini.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/displayhatmini.py b/pwnagotchi/ui/hw/displayhatmini.py index 81b5d72e..d404f962 100644 --- a/pwnagotchi/ui/hw/displayhatmini.py +++ b/pwnagotchi/ui/hw/displayhatmini.py @@ -7,7 +7,7 @@ from pwnagotchi.ui.hw.base import DisplayImpl class DisplayHatMini(DisplayImpl): def __init__(self, config): super(DisplayHatMini, self).__init__(config, 'displayhatmini') - self.mode = "RGB" # its actually BGR;16 5,6,5 bit, but display lib converts it + self.mode = "RGB" # its actually BGR;16 5,6,5 bit, but display lib converts it def layout(self): fonts.setup(12, 10, 12, 70, 25, 9) From b7eb86d55cb37332e81fc62611e0de039afe44a0 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Thu, 13 Jun 2024 05:45:42 +1000 Subject: [PATCH 06/11] Update view.py --- pwnagotchi/ui/view.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 808feda5..0d73257f 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -27,15 +27,12 @@ FOREGROUND_1 = 1 BACKGROUND_L = 0 FOREGROUND_L = 255 - BACKGROUND_BGR_16 = (0,0,0) FOREGROUND_BGR_16 = (31,63,31) BACKGROUND_RGB = (0,0,0) FOREGROUND_RGB = (255,255,255) -BACKGROUND_RGB_24 = (0,0,0) -FOREGROUND_RGB_24 = (255,255,255) ROOT = None @@ -48,8 +45,8 @@ ROOT = None #P (8-bit pixels, mapped to any other mode using a color palette) +#BGR;16 (5,6,5 bits, for 65k color) -#BGR;16 (5,6,5 ) #RGB (3x8-bit pixels, true color) #RGBA (4x8-bit pixels, true color with transparency mask) @@ -147,7 +144,7 @@ class View(object): 'line1': Line(self._layout['line1'], color=self.FOREGROUND), 'line2': Line(self._layout['line2'], color=self.FOREGROUND), - 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=(255,0,255), font=fonts.Huge, png=config['ui']['faces']['png']), + 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']), # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND), 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND), @@ -469,13 +466,13 @@ class View(object): state = self._state changes = state.changes(ignore=self._ignore_changes) if force or len(changes): - logging.info(self.mode) self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND) drawer = ImageDraw.Draw(self._canvas, self.mode) plugins.on('ui_update', self) for key, lv in state.items(): + #lv is a ui element lv.draw(self._canvas, drawer) web.update_frame(self._canvas) From 89a589af7299111b765f498f5d2362dbf48f8894 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Tue, 9 Jul 2024 04:22:18 +1000 Subject: [PATCH 07/11] changes to logging and threading --- .gitignore | 2 ++ pwnagotchi/agent.py | 9 +++++--- pwnagotchi/ai/train.py | 3 ++- pwnagotchi/defaults.toml | 1 + pwnagotchi/log.py | 42 ++++++++++++++++++++++++++-------- pwnagotchi/mesh/utils.py | 6 +++-- pwnagotchi/plugins/__init__.py | 5 +++- pwnagotchi/ui/display.py | 3 ++- pwnagotchi/ui/web/server.py | 7 ++++-- 9 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fd20fddf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.pyc diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index b13fd888..07f3665e 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -4,7 +4,8 @@ import os import re import logging import asyncio -import _thread +#import _thread +import threading import pwnagotchi import pwnagotchi.utils as utils @@ -304,7 +305,8 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): raise def start_session_fetcher(self): - _thread.start_new_thread(self._fetch_stats, ()) + #_thread.start_new_thread(self._fetch_stats, ()) + threading.Thread(target=self._fetch_stats, args=(), name="Session Fetcher").start() def _fetch_stats(self): while True: @@ -387,7 +389,8 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): def start_event_polling(self): # start a thread and pass in the mainloop - _thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),)) + #_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),)) + threading.Thread(target=self._event_poller, args=(asyncio.get_event_loop(),), name="Event Polling") def is_module_running(self, module): s = self.session() diff --git a/pwnagotchi/ai/train.py b/pwnagotchi/ai/train.py index 58758d6f..035ccfd0 100644 --- a/pwnagotchi/ai/train.py +++ b/pwnagotchi/ai/train.py @@ -111,7 +111,8 @@ class AsyncTrainer(object): return self._training_epochs def start_ai(self): - _thread.start_new_thread(self._ai_worker, ()) + #_thread.start_new_thread(self._ai_worker, ()) + threading.Thread(target=self._ai_worker, args=(), name="AI Worker").start() def _save_ai(self): logging.info("[AI] saving model to %s ..." % self._nn_path) diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml index 59a9ba47..3fa07753 100644 --- a/pwnagotchi/defaults.toml +++ b/pwnagotchi/defaults.toml @@ -112,6 +112,7 @@ main.mon_max_blind_epochs = 50 main.no_restart = false main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log" +main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi.log" main.log.rotation.enabled = true main.log.rotation.size = "10M" diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index 5cf120e9..7b56a2c4 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -217,25 +217,45 @@ class LastSession(object): def setup_logging(args, config): cfg = config['main']['log'] filename = cfg['path'] + filenameDebug = cfg['path'] - formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s") - root = logging.getLogger() - - root.setLevel(logging.DEBUG if args.debug else logging.INFO) + #global formatter + formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] [%(threadName)s] : %(message)s") + logger = logging.getLogger() + + for handler in logger.handlers: + handler.setLevel(logging.DEBUG if args.debug else logging.INFO) + handler.setFormatter(formatter) + + + logger.setLevel(logging.DEBUG if args.debug else logging.INFO) if filename: # since python default log rotation might break session data in different files, # we need to do log rotation ourselves log_rotation(filename, cfg) + log_rotation(filenameDebug, cfg) - file_handler = logging.FileHandler(filename) - file_handler.setFormatter(formatter) - root.addHandler(file_handler) + + + # File handler for logging all normal messages + file_handler = logging.FileHandler(filename) #creates new + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) - console_handler = logging.StreamHandler() - console_handler.setFormatter(formatter) - root.addHandler(console_handler) + # File handler for logging all debug messages + file_handler = logging.FileHandler(filenameDebug) #creates new + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + # Console handler for logging debug messages if args.debug is true else just log normal + #console_handler = logging.StreamHandler() #creates new + #console_handler.setLevel(logging.DEBUG if args.debug else logging.INFO) + #console_handler.setFormatter(formatter) + #logger.addHandler(console_handler) + if not args.debug: # disable scapy and tensorflow logging logging.getLogger("scapy").disabled = True @@ -250,6 +270,8 @@ def setup_logging(args, config): requests_log.prpagate = False + + def log_rotation(filename, cfg): rotation = cfg['rotation'] if not rotation['enabled']: diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index c46bae5d..27d7dee4 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -1,4 +1,5 @@ -import _thread +#import _thread +import threading import logging import time @@ -41,7 +42,8 @@ class AsyncAdvertiser(object): def start_advertising(self): if self._config['personality']['advertise']: - _thread.start_new_thread(self._adv_poller, ()) + #_thread.start_new_thread(self._adv_poller, ()) + threading.Thread(target=self._adv_poller,args=(), name="Grid").start() grid.set_advertisement_data(self._advertisement) grid.advertise(True) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 0d913b49..d0212db7 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -1,4 +1,5 @@ import _thread +import threading import glob import importlib import importlib.util @@ -95,8 +96,10 @@ def one(plugin_name, event_name, *args, **kwargs): if callback is not None and callable(callback): try: lock_name = "%s::%s" % (plugin_name, cb_name) + loggingFormat = "%s.%s" % (plugin_name, cb_name) locked_cb_args = (lock_name, callback, *args, *kwargs) - _thread.start_new_thread(locked_cb, locked_cb_args) + #_thread.start_new_thread(locked_cb, locked_cb_args) + threading.Thread(target=locked_cb, args=locked_cb_args, name=loggingFormat).start() except Exception as e: logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error(e, exc_info=True) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index c64adcb9..cc749a46 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -21,7 +21,8 @@ class Display(View): self._canvas_next = None self._render_thread_instance = threading.Thread( target=self._render_thread, - daemon=True + daemon=True, + name="Renderer" ) self._render_thread_instance.start() diff --git a/pwnagotchi/ui/web/server.py b/pwnagotchi/ui/web/server.py index a2e96613..8f4c9a2e 100644 --- a/pwnagotchi/ui/web/server.py +++ b/pwnagotchi/ui/web/server.py @@ -1,4 +1,5 @@ -import _thread +#import _thread +import threading import secrets import logging import os @@ -25,7 +26,9 @@ class Server: self._origin = self._config['origin'] if self._enabled: - _thread.start_new_thread(self._http_serve, ()) + #_thread.start_new_thread(self._http_serve, ()) + logging.info("Starting WebServer thread") + self._thread = threading.Thread(target=self._http_serve, name="WebServer").start() def _http_serve(self): if self._address is not None: From f9efbb56cd0bf805a104a976a590a8c0a3d1cd58 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Tue, 9 Jul 2024 04:24:43 +1000 Subject: [PATCH 08/11] Update defaults.toml --- pwnagotchi/defaults.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml index 3fa07753..52a82938 100644 --- a/pwnagotchi/defaults.toml +++ b/pwnagotchi/defaults.toml @@ -112,7 +112,7 @@ main.mon_max_blind_epochs = 50 main.no_restart = false main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log" -main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi.log" +main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi-debug.log" main.log.rotation.enabled = true main.log.rotation.size = "10M" From e3f0da6193a0128c7e3d265058d551c281e50c03 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Tue, 9 Jul 2024 04:57:52 +1000 Subject: [PATCH 09/11] small fixes --- pwnagotchi/fs/__init__.py | 6 ++++-- pwnagotchi/log.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/fs/__init__.py b/pwnagotchi/fs/__init__.py index d8a56e0a..f0df1f6a 100644 --- a/pwnagotchi/fs/__init__.py +++ b/pwnagotchi/fs/__init__.py @@ -3,7 +3,8 @@ import re import tempfile import contextlib import shutil -import _thread +#import _thread +import threading import logging from time import sleep @@ -85,7 +86,8 @@ def setup_mounts(config): if interval: logging.debug("[FS] Starting thread to sync %s (interval: %d)", options['mount'], interval) - _thread.start_new_thread(m.daemonize, (interval,)) + threading.Thread(target=m.daemonize, args=(interval,),name="File Sys").start() + #_thread.start_new_thread(m.daemonize, (interval,)) else: logging.debug("[FS] Not syncing %s, because interval is 0", options['mount']) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index 7b56a2c4..cca7bc52 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -217,7 +217,7 @@ class LastSession(object): def setup_logging(args, config): cfg = config['main']['log'] filename = cfg['path'] - filenameDebug = cfg['path'] + filenameDebug = cfg['path-debug'] #global formatter formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] [%(threadName)s] : %(message)s") From 1ee940c7985ea83e52fa506c0cec01db721c7a61 Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Tue, 9 Jul 2024 07:35:53 +1000 Subject: [PATCH 10/11] adds daemonise and plugin as threads --- pwnagotchi/agent.py | 4 +- pwnagotchi/ai/train.py | 2 +- pwnagotchi/fs/__init__.py | 2 +- pwnagotchi/mesh/utils.py | 2 +- pwnagotchi/plugins/__init__.py | 180 +++++++++++++++++++++++++-------- pwnagotchi/ui/web/server.py | 2 +- 6 files changed, 144 insertions(+), 48 deletions(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 07f3665e..29bc5db8 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -306,7 +306,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): def start_session_fetcher(self): #_thread.start_new_thread(self._fetch_stats, ()) - threading.Thread(target=self._fetch_stats, args=(), name="Session Fetcher").start() + threading.Thread(target=self._fetch_stats, args=(), name="Session Fetcher", daemon=True).start() def _fetch_stats(self): while True: @@ -390,7 +390,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer): def start_event_polling(self): # start a thread and pass in the mainloop #_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),)) - threading.Thread(target=self._event_poller, args=(asyncio.get_event_loop(),), name="Event Polling") + threading.Thread(target=self._event_poller, args=(asyncio.get_event_loop(),), name="Event Polling", daemon=True) def is_module_running(self, module): s = self.session() diff --git a/pwnagotchi/ai/train.py b/pwnagotchi/ai/train.py index 035ccfd0..a0d7056b 100644 --- a/pwnagotchi/ai/train.py +++ b/pwnagotchi/ai/train.py @@ -112,7 +112,7 @@ class AsyncTrainer(object): def start_ai(self): #_thread.start_new_thread(self._ai_worker, ()) - threading.Thread(target=self._ai_worker, args=(), name="AI Worker").start() + threading.Thread(target=self._ai_worker, args=(), name="AI Worker" daemon=True).start() def _save_ai(self): logging.info("[AI] saving model to %s ..." % self._nn_path) diff --git a/pwnagotchi/fs/__init__.py b/pwnagotchi/fs/__init__.py index f0df1f6a..5205ff92 100644 --- a/pwnagotchi/fs/__init__.py +++ b/pwnagotchi/fs/__init__.py @@ -86,7 +86,7 @@ def setup_mounts(config): if interval: logging.debug("[FS] Starting thread to sync %s (interval: %d)", options['mount'], interval) - threading.Thread(target=m.daemonize, args=(interval,),name="File Sys").start() + threading.Thread(target=m.daemonize, args=(interval,),name="File Sys", daemon=True).start() #_thread.start_new_thread(m.daemonize, (interval,)) else: logging.debug("[FS] Not syncing %s, because interval is 0", diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 27d7dee4..7f90f2c4 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -43,7 +43,7 @@ class AsyncAdvertiser(object): def start_advertising(self): if self._config['personality']['advertise']: #_thread.start_new_thread(self._adv_poller, ()) - threading.Thread(target=self._adv_poller,args=(), name="Grid").start() + threading.Thread(target=self._adv_poller,args=(), name="Grid", daemon=True).start() grid.set_advertisement_data(self._advertisement) grid.advertise(True) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index d0212db7..a4e3039e 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -1,19 +1,107 @@ +import os +import queue +import glob import _thread import threading -import glob -import importlib -import importlib.util +import importlib, importlib.util import logging -import os -import threading -import pwnagotchi.grid +import time +import prctl + + +#Idea and base code from NurseJackass default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") loaded = {} database = {} locks = {} +exitFlag = 0 +plugin_event_queues = {} +plugin_thread_workers = {} +class PluginHandler(): + def __init__(self, plugin_name): + try: + self._worker_thread = threading.Thread(target=self.doWork, daemon=True, name = "%s.sleeping" % plugin_name) + self._loop_thread = None + self.plugin_name = plugin_name + self.work_queue = queue.Queue() + self.queue_lock = threading.Lock() + self.load_handler = None + self.keep_going = True + logging.debug("Starting worker for %s" % plugin_name) + self._worker_thread.start() + except Exception as e: + logging.exception(e) + + def __del__(self): + self.keep_going = False + self._worker_thread.join() + if self.load_handler: + self.load_handler.join() + + def AddWork(self, event_name, *args, **kwargs): + if event_name == "loaded" or event_name == "loop": + # spawn separate thread, because many plugins use on_load as a "main" loop + # this way on_load can continue if it needs, while other events get processed + # for future use, use `on_loop` + try: + self._loop_thread = threading.Thread(target=self.doLoop, args = (self, event_name, *args), daemon=True, name = "%s.loop" % (self.plugin_name)).start() + except Exception as e: + logging.exception(e) + else: + self.work_queue.put([event_name, args, kwargs]) + + def run(self): + logging.debug("Worker thread starting for %s"%(self.plugin_name)) + self._worker_thread.start() + logging.info("Worker thread exited for %s"%(self.plugin_name)) + + def process_event(self, event_name, *args, **kwargs): + cb_name = 'on_%s' % event_name + callback = getattr(loaded[self.plugin_name], cb_name, None) + if callback: + callback(*args, **kwargs) + + def doWork(self): + global exitFlag + plugin_name = self.plugin_name + work_queue = self.work_queue + while not exitFlag and self.keep_going: + try: + data = work_queue.get(timeout=2) + (event_name, args, kwargs) = data + prctl.set_name("pwnagotchi.%s.%s" % (self.plugin_name, event_name )) + self._worker_thread.name = "%s.%s" % (self.plugin_name, event_name) + logging.debug("") + self.process_event(event_name, *args, **kwargs) + except queue.Empty as e: + self._worker_thread.name = "%s.sleeping" + prctl.set_name("pwnagotchi.%s.sleeping" % (self.plugin_name)) + pass + except Exception as e: + logging.exception(repr(e)) + + def doLoop(self, loopCB, event_name, *args, **kwargs): + global exitFlag + plugin_name = self.plugin_name + prctl.set_name("pwnagotchi.%s" % self.plugin_name) + + while not exitFlag and self.keep_going: + try: + self.process_event(event_name, *args, **kwargs) + self.keep_going = False + except Exception as e: + #error in plugin loop kill plugin + self.keep_going = False + logging.exception(repr(e)) + + def killLoop(self): + self._loop_thread.stop() + + + class Plugin: @classmethod def __init_subclass__(cls, **kwargs): @@ -45,16 +133,19 @@ def toggle_plugin(name, enable=True): global loaded, database if pwnagotchi.config: - if name not in pwnagotchi.config['main']['plugins']: + if not name in pwnagotchi.config['main']['plugins']: pwnagotchi.config['main']['plugins'][name] = dict() pwnagotchi.config['main']['plugins'][name]['enabled'] = enable - save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') if not enable and name in loaded: if getattr(loaded[name], 'on_unload', None): loaded[name].on_unload(view.ROOT) del loaded[name] - + if name in plugin_event_queues: + plugin_event_queues[name].keep_going = False + del plugin_event_queues[name] + if pwnagotchi.config: + save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') return True if enable and name in database and name not in loaded: @@ -62,47 +153,44 @@ def toggle_plugin(name, enable=True): if name in loaded and pwnagotchi.config and name in pwnagotchi.config['main']['plugins']: loaded[name].options = pwnagotchi.config['main']['plugins'][name] one(name, 'loaded') + time.sleep(3) if pwnagotchi.config: one(name, 'config_changed', pwnagotchi.config) one(name, 'ui_setup', view.ROOT) one(name, 'ready', view.ROOT._agent) + if pwnagotchi.config: + save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') return True return False def on(event_name, *args, **kwargs): + global loaded, plugin_event_queues + cb_name = 'on_%s' % event_name for plugin_name in loaded.keys(): - one(plugin_name, event_name, *args, **kwargs) + plugin = loaded[plugin_name] + callback = getattr(plugin, cb_name, None) + if callback is None or not callable(callback): + continue -def locked_cb(lock_name, cb, *args, **kwargs): - global locks - - if lock_name not in locks: - locks[lock_name] = threading.Lock() - - with locks[lock_name]: - cb(*args, *kwargs) + if plugin_name not in plugin_event_queues: + plugin_event_queues[plugin_name] = PluginHandler(plugin_name) + plugin_event_queues[plugin_name].AddWork(event_name, *args, **kwargs) def one(plugin_name, event_name, *args, **kwargs): - global loaded - + global loaded, plugin_event_queues if plugin_name in loaded: plugin = loaded[plugin_name] cb_name = 'on_%s' % event_name callback = getattr(plugin, cb_name, None) if callback is not None and callable(callback): - try: - lock_name = "%s::%s" % (plugin_name, cb_name) - loggingFormat = "%s.%s" % (plugin_name, cb_name) - locked_cb_args = (lock_name, callback, *args, *kwargs) - #_thread.start_new_thread(locked_cb, locked_cb_args) - threading.Thread(target=locked_cb, args=locked_cb_args, name=loggingFormat).start() - except Exception as e: - logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) - logging.error(e, exc_info=True) + if plugin_name not in plugin_event_queues: + plugin_event_queues[plugin_name] = PluginHandler(plugin_name) + + plugin_event_queues[plugin_name].AddWork(event_name, *args, **kwargs) def load_from_file(filename): @@ -111,6 +199,8 @@ def load_from_file(filename): spec = importlib.util.spec_from_file_location(plugin_name, filename) instance = importlib.util.module_from_spec(spec) spec.loader.exec_module(instance) + if plugin_name not in plugin_event_queues: + plugin_event_queues[plugin_name] = PluginHandler(plugin_name) return plugin_name, instance @@ -131,20 +221,26 @@ def load_from_path(path, enabled=()): def load(config): - enabled = [name for name, options in config['main']['plugins'].items() if - 'enabled' in options and options['enabled']] + try: + enabled = [name for name, options in config['main']['plugins'].items() if + 'enabled' in options and options['enabled']] - # load default plugins - load_from_path(default_path, enabled=enabled) + # load default plugins + load_from_path(default_path, enabled=enabled) - # load custom ones - custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None - if custom_path is not None: - load_from_path(custom_path, enabled=enabled) + # load custom ones + custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None + if custom_path is not None: + load_from_path(custom_path, enabled=enabled) - # propagate options - for name, plugin in loaded.items(): - plugin.options = config['main']['plugins'][name] + # propagate options + for name, plugin in loaded.items(): + if name in config['main']['plugins']: + plugin.options = config['main']['plugins'][name] + else: + plugin.options = {} - on('loaded') - on('config_changed', config) + on('loaded') + on('config_changed', config) + except Exception as e: + logging.exception(repr(e)) \ No newline at end of file diff --git a/pwnagotchi/ui/web/server.py b/pwnagotchi/ui/web/server.py index 8f4c9a2e..32aeef8b 100644 --- a/pwnagotchi/ui/web/server.py +++ b/pwnagotchi/ui/web/server.py @@ -28,7 +28,7 @@ class Server: if self._enabled: #_thread.start_new_thread(self._http_serve, ()) logging.info("Starting WebServer thread") - self._thread = threading.Thread(target=self._http_serve, name="WebServer").start() + self._thread = threading.Thread(target=self._http_serve, name="WebServer", daemon = True).start() def _http_serve(self): if self._address is not None: From 0f36a1056731ad8915587862194ef4a2d77d09ae Mon Sep 17 00:00:00 2001 From: XxKingsxX-Pinu <58925163+rai68@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:46:53 +1000 Subject: [PATCH 11/11] Update __init__.py --- pwnagotchi/plugins/__init__.py | 182 ++++++++------------------------- 1 file changed, 45 insertions(+), 137 deletions(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index a4e3039e..09074d93 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -1,107 +1,19 @@ -import os -import queue -import glob import _thread -import threading -import importlib, importlib.util +import glob +import importlib +import importlib.util import logging -import time +import os +import threading +import pwnagotchi.grid import prctl - -#Idea and base code from NurseJackass - default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") loaded = {} database = {} locks = {} -exitFlag = 0 -plugin_event_queues = {} -plugin_thread_workers = {} -class PluginHandler(): - def __init__(self, plugin_name): - try: - self._worker_thread = threading.Thread(target=self.doWork, daemon=True, name = "%s.sleeping" % plugin_name) - self._loop_thread = None - self.plugin_name = plugin_name - self.work_queue = queue.Queue() - self.queue_lock = threading.Lock() - self.load_handler = None - self.keep_going = True - logging.debug("Starting worker for %s" % plugin_name) - self._worker_thread.start() - except Exception as e: - logging.exception(e) - - def __del__(self): - self.keep_going = False - self._worker_thread.join() - if self.load_handler: - self.load_handler.join() - - def AddWork(self, event_name, *args, **kwargs): - if event_name == "loaded" or event_name == "loop": - # spawn separate thread, because many plugins use on_load as a "main" loop - # this way on_load can continue if it needs, while other events get processed - # for future use, use `on_loop` - try: - self._loop_thread = threading.Thread(target=self.doLoop, args = (self, event_name, *args), daemon=True, name = "%s.loop" % (self.plugin_name)).start() - except Exception as e: - logging.exception(e) - else: - self.work_queue.put([event_name, args, kwargs]) - - def run(self): - logging.debug("Worker thread starting for %s"%(self.plugin_name)) - self._worker_thread.start() - logging.info("Worker thread exited for %s"%(self.plugin_name)) - - def process_event(self, event_name, *args, **kwargs): - cb_name = 'on_%s' % event_name - callback = getattr(loaded[self.plugin_name], cb_name, None) - if callback: - callback(*args, **kwargs) - - def doWork(self): - global exitFlag - plugin_name = self.plugin_name - work_queue = self.work_queue - while not exitFlag and self.keep_going: - try: - data = work_queue.get(timeout=2) - (event_name, args, kwargs) = data - prctl.set_name("pwnagotchi.%s.%s" % (self.plugin_name, event_name )) - self._worker_thread.name = "%s.%s" % (self.plugin_name, event_name) - logging.debug("") - self.process_event(event_name, *args, **kwargs) - except queue.Empty as e: - self._worker_thread.name = "%s.sleeping" - prctl.set_name("pwnagotchi.%s.sleeping" % (self.plugin_name)) - pass - except Exception as e: - logging.exception(repr(e)) - - def doLoop(self, loopCB, event_name, *args, **kwargs): - global exitFlag - plugin_name = self.plugin_name - prctl.set_name("pwnagotchi.%s" % self.plugin_name) - - while not exitFlag and self.keep_going: - try: - self.process_event(event_name, *args, **kwargs) - self.keep_going = False - except Exception as e: - #error in plugin loop kill plugin - self.keep_going = False - logging.exception(repr(e)) - - def killLoop(self): - self._loop_thread.stop() - - - class Plugin: @classmethod def __init_subclass__(cls, **kwargs): @@ -133,19 +45,16 @@ def toggle_plugin(name, enable=True): global loaded, database if pwnagotchi.config: - if not name in pwnagotchi.config['main']['plugins']: + if name not in pwnagotchi.config['main']['plugins']: pwnagotchi.config['main']['plugins'][name] = dict() pwnagotchi.config['main']['plugins'][name]['enabled'] = enable + save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') if not enable and name in loaded: if getattr(loaded[name], 'on_unload', None): loaded[name].on_unload(view.ROOT) del loaded[name] - if name in plugin_event_queues: - plugin_event_queues[name].keep_going = False - del plugin_event_queues[name] - if pwnagotchi.config: - save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') + return True if enable and name in database and name not in loaded: @@ -153,44 +62,51 @@ def toggle_plugin(name, enable=True): if name in loaded and pwnagotchi.config and name in pwnagotchi.config['main']['plugins']: loaded[name].options = pwnagotchi.config['main']['plugins'][name] one(name, 'loaded') - time.sleep(3) if pwnagotchi.config: one(name, 'config_changed', pwnagotchi.config) one(name, 'ui_setup', view.ROOT) one(name, 'ready', view.ROOT._agent) - if pwnagotchi.config: - save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') return True return False def on(event_name, *args, **kwargs): - global loaded, plugin_event_queues - cb_name = 'on_%s' % event_name for plugin_name in loaded.keys(): - plugin = loaded[plugin_name] - callback = getattr(plugin, cb_name, None) + + one(plugin_name, event_name, *args, **kwargs) - if callback is None or not callable(callback): - continue - if plugin_name not in plugin_event_queues: - plugin_event_queues[plugin_name] = PluginHandler(plugin_name) +def locked_cb(lock_name, cb, *args, **kwargs): + global locks + + if lock_name not in locks: + locks[lock_name] = threading.Lock() + + with locks[lock_name]: + # Setting the thread name using prctl + plugin_name, plugin_cb = lock_name.split("::") + prctl.set_name(f"{plugin_name}.{plugin_cb}") + cb(*args, **kwargs) - plugin_event_queues[plugin_name].AddWork(event_name, *args, **kwargs) def one(plugin_name, event_name, *args, **kwargs): - global loaded, plugin_event_queues + global loaded + if plugin_name in loaded: plugin = loaded[plugin_name] cb_name = 'on_%s' % event_name callback = getattr(plugin, cb_name, None) if callback is not None and callable(callback): - if plugin_name not in plugin_event_queues: - plugin_event_queues[plugin_name] = PluginHandler(plugin_name) + try: + lock_name = "%s::%s" % (plugin_name, cb_name) + thread_name = f'{plugin_name}.{cb_name}' + thread = threading.Thread(target=locked_cb, args=(lock_name, callback, *args, *kwargs), name=thread_name, daemon=True) + thread.start() - plugin_event_queues[plugin_name].AddWork(event_name, *args, **kwargs) + except Exception as e: + logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) + logging.error(e, exc_info=True) def load_from_file(filename): @@ -199,8 +115,6 @@ def load_from_file(filename): spec = importlib.util.spec_from_file_location(plugin_name, filename) instance = importlib.util.module_from_spec(spec) spec.loader.exec_module(instance) - if plugin_name not in plugin_event_queues: - plugin_event_queues[plugin_name] = PluginHandler(plugin_name) return plugin_name, instance @@ -221,26 +135,20 @@ def load_from_path(path, enabled=()): def load(config): - try: - enabled = [name for name, options in config['main']['plugins'].items() if - 'enabled' in options and options['enabled']] + enabled = [name for name, options in config['main']['plugins'].items() if + 'enabled' in options and options['enabled']] - # load default plugins - load_from_path(default_path, enabled=enabled) + # load default plugins + load_from_path(default_path, enabled=enabled) - # load custom ones - custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None - if custom_path is not None: - load_from_path(custom_path, enabled=enabled) + # load custom ones + custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None + if custom_path is not None: + load_from_path(custom_path, enabled=enabled) - # propagate options - for name, plugin in loaded.items(): - if name in config['main']['plugins']: - plugin.options = config['main']['plugins'][name] - else: - plugin.options = {} + # propagate options + for name, plugin in loaded.items(): + plugin.options = config['main']['plugins'][name] - on('loaded') - on('config_changed', config) - except Exception as e: - logging.exception(repr(e)) \ No newline at end of file + on('loaded') + on('config_changed', config) \ No newline at end of file