Compare commits
	
		
			2 Commits
		
	
	
		
			plugins
			...
			0c26025a81
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0c26025a81 | ||
| 
						 | 
					0405200980 | 
							
								
								
									
										2
									
								
								HACKING
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								HACKING
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
Welcome to Cthulhu
 | 
			
		||||
 | 
			
		||||
We are excited to have you here and welcome your contributions to the Cthulhu screen reader project! This project is a fork of Cthulhu, with a focus on creating an open and collaborative community where contributions are encouraged.
 | 
			
		||||
We are excited to have you here and welcome your contributions to the Cthulhu screen reader project! This project is a fork of Orca, with a focus on creating an open and collaborative community where contributions are encouraged.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
How to Contribute
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
 | 
			
		||||
 | 
			
		||||
if BUILD_HELP
 | 
			
		||||
SUBDIRS = docs icons po src help
 | 
			
		||||
else
 | 
			
		||||
SUBDIRS = docs icons po src
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
DISTCHECK_CONFIGURE_FLAGS =     \
 | 
			
		||||
	--disable-scrollkeeper
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								autogen.sh
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								autogen.sh
									
									
									
									
									
								
							@@ -32,10 +32,14 @@ autoreconf --verbose --force --install -Wno-portability  || {
 | 
			
		||||
        exit 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
which yelp-build > /dev/null || {
 | 
			
		||||
       echo "Try installing the 'yelp-tools' package."
 | 
			
		||||
       exit 1
 | 
			
		||||
}
 | 
			
		||||
# Only check for yelp-build if help documentation will be built
 | 
			
		||||
# Skip check if SKIP_YELP environment variable is set
 | 
			
		||||
if [ "${SKIP_YELP}" != "1" ]; then
 | 
			
		||||
       which yelp-build > /dev/null || {
 | 
			
		||||
              echo "Try installing the 'yelp-tools' package, or set SKIP_YELP=1 to skip documentation."
 | 
			
		||||
              exit 1
 | 
			
		||||
       }
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
cd "$olddir"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								configure.ac
									
									
									
									
									
								
							@@ -24,8 +24,16 @@ GETTEXT_PACKAGE=AC_PACKAGE_TARNAME
 | 
			
		||||
AC_SUBST(GETTEXT_PACKAGE)
 | 
			
		||||
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [gettext package])
 | 
			
		||||
 | 
			
		||||
# User Documentation
 | 
			
		||||
YELP_HELP_INIT
 | 
			
		||||
# User Documentation (optional)
 | 
			
		||||
AC_ARG_ENABLE([help], 
 | 
			
		||||
    AS_HELP_STRING([--disable-help], [Disable building help documentation]))
 | 
			
		||||
AS_IF([test "x$enable_help" != "xno"], [
 | 
			
		||||
    YELP_HELP_INIT
 | 
			
		||||
    BUILD_HELP=yes
 | 
			
		||||
], [
 | 
			
		||||
    BUILD_HELP=no
 | 
			
		||||
])
 | 
			
		||||
AM_CONDITIONAL(BUILD_HELP, test "x$BUILD_HELP" = "xyes")
 | 
			
		||||
 | 
			
		||||
PKG_CHECK_MODULES([PYGOBJECT], [pygobject-3.0 >= pygobject_required_version])
 | 
			
		||||
PKG_CHECK_MODULES([ATSPI2], [atspi-2 >= atspi_required_version])
 | 
			
		||||
@@ -129,6 +137,7 @@ src/cthulhu/plugins/ByeCthulhu/Makefile
 | 
			
		||||
src/cthulhu/plugins/HelloCthulhu/Makefile
 | 
			
		||||
src/cthulhu/plugins/Clipboard/Makefile
 | 
			
		||||
src/cthulhu/plugins/DisplayVersion/Makefile
 | 
			
		||||
src/cthulhu/plugins/IndentationAudio/Makefile
 | 
			
		||||
src/cthulhu/plugins/hello_world/Makefile
 | 
			
		||||
src/cthulhu/plugins/self_voice/Makefile
 | 
			
		||||
src/cthulhu/plugins/SimplePluginSystem/Makefile
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
 | 
			
		||||
 | 
			
		||||
pkgname=cthulhu
 | 
			
		||||
pkgver=0.4
 | 
			
		||||
pkgver=2025.07.01
 | 
			
		||||
pkgrel=1
 | 
			
		||||
pkgdesc="Screen reader for individuals who are blind or visually impaired forked from Orca"
 | 
			
		||||
url="https://git.stormux.org/storm/cthulhu"
 | 
			
		||||
@@ -25,6 +25,7 @@ depends=(
 | 
			
		||||
  python-atspi
 | 
			
		||||
  python-cairo
 | 
			
		||||
  python-gobject
 | 
			
		||||
  python-pluggy
 | 
			
		||||
  python-setproctitle
 | 
			
		||||
  socat
 | 
			
		||||
  speech-dispatcher
 | 
			
		||||
@@ -33,15 +34,14 @@ depends=(
 | 
			
		||||
)
 | 
			
		||||
makedepends=(
 | 
			
		||||
  git
 | 
			
		||||
  itstool
 | 
			
		||||
  yelp-tools
 | 
			
		||||
)
 | 
			
		||||
source=("git+https://git.stormux.org/storm/cthulhu.git")
 | 
			
		||||
b2sums=('SKIP')
 | 
			
		||||
 | 
			
		||||
prepare() {
 | 
			
		||||
  cd cthulhu
 | 
			
		||||
  NOCONFIGURE=1 ./autogen.sh
 | 
			
		||||
  git checkout testing
 | 
			
		||||
  NOCONFIGURE=1 SKIP_YELP=1 ./autogen.sh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pkgver() {
 | 
			
		||||
@@ -51,7 +51,7 @@ pkgver() {
 | 
			
		||||
 | 
			
		||||
build() {
 | 
			
		||||
  cd cthulhu
 | 
			
		||||
  ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
 | 
			
		||||
  ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-help
 | 
			
		||||
  make
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,5 +23,5 @@
 | 
			
		||||
# Fork of Orca Screen Reader (GNOME)
 | 
			
		||||
# Original source: https://gitlab.gnome.org/GNOME/orca
 | 
			
		||||
 | 
			
		||||
version = "2025.06.05"
 | 
			
		||||
codeName = "plugins"
 | 
			
		||||
version = "2025.07.01"
 | 
			
		||||
codeName = "testing"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/cthulhu/plugins/IndentationAudio/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/cthulhu/plugins/IndentationAudio/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
indentationaudio_PYTHON = \
 | 
			
		||||
	__init__.py \
 | 
			
		||||
	plugin.py
 | 
			
		||||
 | 
			
		||||
indentationaudiodir = $(pkgdatadir)/cthulhu/plugins/IndentationAudio
 | 
			
		||||
 | 
			
		||||
indentationaudio_DATA = \
 | 
			
		||||
	plugin.info
 | 
			
		||||
 | 
			
		||||
EXTRA_DIST = $(indentationaudio_DATA)
 | 
			
		||||
							
								
								
									
										14
									
								
								src/cthulhu/plugins/IndentationAudio/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/cthulhu/plugins/IndentationAudio/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2025 Stormux
 | 
			
		||||
#
 | 
			
		||||
# This library is free software; you can redistribute it and/or
 | 
			
		||||
# modify it under the terms of the GNU Lesser General Public
 | 
			
		||||
# License as published by the Free Software Foundation; either
 | 
			
		||||
# version 2.1 of the License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
"""IndentationAudio plugin package."""
 | 
			
		||||
 | 
			
		||||
from .plugin import IndentationAudio
 | 
			
		||||
 | 
			
		||||
__all__ = ['IndentationAudio']
 | 
			
		||||
							
								
								
									
										9
									
								
								src/cthulhu/plugins/IndentationAudio/plugin.info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/cthulhu/plugins/IndentationAudio/plugin.info
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
[Core]
 | 
			
		||||
Name = IndentationAudio
 | 
			
		||||
Module = IndentationAudio
 | 
			
		||||
 | 
			
		||||
[Documentation]
 | 
			
		||||
Description = Provides audio feedback for indentation level changes when navigating code or text
 | 
			
		||||
Author = Stormux
 | 
			
		||||
Version = 1.0.0
 | 
			
		||||
Website = https://git.stormux.org/storm/cthulhu
 | 
			
		||||
							
								
								
									
										334
									
								
								src/cthulhu/plugins/IndentationAudio/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								src/cthulhu/plugins/IndentationAudio/plugin.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,334 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2025 Stormux
 | 
			
		||||
#
 | 
			
		||||
# This library is free software; you can redistribute it and/or
 | 
			
		||||
# modify it under the terms of the GNU Lesser General Public
 | 
			
		||||
# License as published by the Free Software Foundation; either
 | 
			
		||||
# version 2.1 of the License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
"""IndentationAudio plugin for Cthulhu - Provides audio feedback for indentation level changes."""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import re
 | 
			
		||||
from gi.repository import GLib
 | 
			
		||||
 | 
			
		||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
 | 
			
		||||
from cthulhu import cmdnames
 | 
			
		||||
from cthulhu import debug
 | 
			
		||||
from cthulhu import input_event
 | 
			
		||||
from cthulhu import keybindings
 | 
			
		||||
from cthulhu import messages
 | 
			
		||||
from cthulhu import script_utilities
 | 
			
		||||
from cthulhu import settings_manager
 | 
			
		||||
from cthulhu import sound
 | 
			
		||||
from cthulhu.sound_generator import Tone
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
_settingsManager = settings_manager.getManager()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IndentationAudio(Plugin):
 | 
			
		||||
    """Plugin that provides audio cues for indentation level changes."""
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """Initialize the IndentationAudio plugin."""
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self._enabled = True  # Start enabled by default
 | 
			
		||||
        self._last_indentation_level = {}  # Track per-object indentation
 | 
			
		||||
        self._event_listener_id = None
 | 
			
		||||
        self._kb_binding = None
 | 
			
		||||
        self._player = None
 | 
			
		||||
        
 | 
			
		||||
        # Audio settings
 | 
			
		||||
        self._base_frequency = 200  # Base frequency in Hz
 | 
			
		||||
        self._frequency_step = 80   # Hz per indentation level
 | 
			
		||||
        self._tone_duration = 0.15  # Seconds
 | 
			
		||||
        self._max_frequency = 1200  # Cap frequency to avoid harsh sounds
 | 
			
		||||
        
 | 
			
		||||
        logger.info("IndentationAudio plugin initialized")
 | 
			
		||||
    
 | 
			
		||||
    @cthulhu_hookimpl
 | 
			
		||||
    def activate(self, plugin=None):
 | 
			
		||||
        """Activate the IndentationAudio plugin."""
 | 
			
		||||
        if plugin is not None and plugin is not self:
 | 
			
		||||
            return
 | 
			
		||||
            
 | 
			
		||||
        try:
 | 
			
		||||
            logger.info("=== IndentationAudio plugin activation starting ===")
 | 
			
		||||
            
 | 
			
		||||
            # Initialize sound player
 | 
			
		||||
            self._player = sound.getPlayer()
 | 
			
		||||
            
 | 
			
		||||
            # Register keybinding for toggle (Cthulhu+I)
 | 
			
		||||
            self._register_keybinding()
 | 
			
		||||
            
 | 
			
		||||
            # Connect to text caret movement events
 | 
			
		||||
            self._connect_to_events()
 | 
			
		||||
            
 | 
			
		||||
            logger.info("IndentationAudio plugin activated successfully")
 | 
			
		||||
            return True
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"Failed to activate IndentationAudio plugin: {e}")
 | 
			
		||||
            return False
 | 
			
		||||
    
 | 
			
		||||
    @cthulhu_hookimpl  
 | 
			
		||||
    def deactivate(self, plugin=None):
 | 
			
		||||
        """Deactivate the IndentationAudio plugin."""
 | 
			
		||||
        if plugin is not None and plugin is not self:
 | 
			
		||||
            return
 | 
			
		||||
            
 | 
			
		||||
        try:
 | 
			
		||||
            logger.info("=== IndentationAudio plugin deactivation starting ===")
 | 
			
		||||
            
 | 
			
		||||
            # Disconnect from events
 | 
			
		||||
            self._disconnect_from_events()
 | 
			
		||||
            
 | 
			
		||||
            # Clear tracking data
 | 
			
		||||
            self._last_indentation_level.clear()
 | 
			
		||||
            
 | 
			
		||||
            logger.info("IndentationAudio plugin deactivated successfully")
 | 
			
		||||
            return True
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"Failed to deactivate IndentationAudio plugin: {e}")
 | 
			
		||||
            return False
 | 
			
		||||
    
 | 
			
		||||
    def _register_keybinding(self):
 | 
			
		||||
        """Register the Cthulhu+I keybinding for toggling the plugin."""
 | 
			
		||||
        try:
 | 
			
		||||
            if not self.app:
 | 
			
		||||
                logger.error("IndentationAudio: No app reference available for keybinding")
 | 
			
		||||
                return
 | 
			
		||||
                
 | 
			
		||||
            api_helper = self.app.getAPIHelper()
 | 
			
		||||
            if not api_helper:
 | 
			
		||||
                logger.error("IndentationAudio: No API helper available")
 | 
			
		||||
                return
 | 
			
		||||
            
 | 
			
		||||
            # Register Cthulhu+I keybinding
 | 
			
		||||
            gesture_string = "Cthulhu+i"
 | 
			
		||||
            description = "Toggle indentation audio feedback"
 | 
			
		||||
            handler = self._toggle_indentation_audio
 | 
			
		||||
            
 | 
			
		||||
            self._kb_binding = api_helper.registerGestureByString(
 | 
			
		||||
                gesture_string, handler, description
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
            if self._kb_binding:
 | 
			
		||||
                logger.info(f"IndentationAudio: Registered keybinding {gesture_string}")
 | 
			
		||||
            else:
 | 
			
		||||
                logger.error(f"IndentationAudio: Failed to register keybinding {gesture_string}")
 | 
			
		||||
                
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error registering keybinding: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def _connect_to_events(self):
 | 
			
		||||
        """Connect to text navigation events."""
 | 
			
		||||
        try:
 | 
			
		||||
            # Hook into the dynamic API to make ourselves available to scripts
 | 
			
		||||
            if self.app:
 | 
			
		||||
                api_manager = self.app.getDynamicApiManager()
 | 
			
		||||
                api_manager.registerAPI('IndentationAudioPlugin', self)
 | 
			
		||||
                logger.info("IndentationAudio: Registered with dynamic API manager")
 | 
			
		||||
            
 | 
			
		||||
            # We'll also monkey-patch the default script's sayLine method
 | 
			
		||||
            self._monkey_patch_script_methods()
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error connecting to events: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def _disconnect_from_events(self):
 | 
			
		||||
        """Disconnect from text navigation events."""
 | 
			
		||||
        try:
 | 
			
		||||
            # Unregister from dynamic API
 | 
			
		||||
            if self.app:
 | 
			
		||||
                api_manager = self.app.getDynamicApiManager()
 | 
			
		||||
                api_manager.unregisterAPI('IndentationAudioPlugin')
 | 
			
		||||
                
 | 
			
		||||
            # Restore original script methods
 | 
			
		||||
            self._restore_script_methods()
 | 
			
		||||
                
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error disconnecting from events: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def _monkey_patch_script_methods(self):
 | 
			
		||||
        """Monkey-patch the default script's line navigation methods."""
 | 
			
		||||
        try:
 | 
			
		||||
            # Get the current active script
 | 
			
		||||
            if self.app:
 | 
			
		||||
                state = self.app.getDynamicApiManager().getAPI('CthulhuState')
 | 
			
		||||
                if state and hasattr(state, 'activeScript') and state.activeScript:
 | 
			
		||||
                    script = state.activeScript
 | 
			
		||||
                    
 | 
			
		||||
                    # Store original method
 | 
			
		||||
                    if hasattr(script, 'sayLine'):
 | 
			
		||||
                        self._original_sayLine = script.sayLine
 | 
			
		||||
                        
 | 
			
		||||
                        # Create wrapped version
 | 
			
		||||
                        def wrapped_sayLine(obj):
 | 
			
		||||
                            # Call original method first
 | 
			
		||||
                            result = self._original_sayLine(obj)
 | 
			
		||||
                            
 | 
			
		||||
                            # Add our indentation audio check
 | 
			
		||||
                            try:
 | 
			
		||||
                                line, caretOffset, startOffset = script.getTextLineAtCaret(obj)
 | 
			
		||||
                                self.check_indentation_change(obj, line)
 | 
			
		||||
                            except Exception as e:
 | 
			
		||||
                                logger.error(f"IndentationAudio: Error in wrapped_sayLine: {e}")
 | 
			
		||||
                            
 | 
			
		||||
                            return result
 | 
			
		||||
                        
 | 
			
		||||
                        # Replace the method
 | 
			
		||||
                        script.sayLine = wrapped_sayLine
 | 
			
		||||
                        logger.info("IndentationAudio: Successfully monkey-patched sayLine method")
 | 
			
		||||
                    
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error monkey-patching script methods: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def _restore_script_methods(self):
 | 
			
		||||
        """Restore original script methods."""
 | 
			
		||||
        try:
 | 
			
		||||
            if self.app and hasattr(self, '_original_sayLine'):
 | 
			
		||||
                state = self.app.getDynamicApiManager().getAPI('CthulhuState')
 | 
			
		||||
                if state and hasattr(state, 'activeScript') and state.activeScript:
 | 
			
		||||
                    script = state.activeScript
 | 
			
		||||
                    if hasattr(script, 'sayLine'):
 | 
			
		||||
                        script.sayLine = self._original_sayLine
 | 
			
		||||
                        logger.info("IndentationAudio: Restored original sayLine method")
 | 
			
		||||
                        
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error restoring script methods: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def _toggle_indentation_audio(self, script, inputEvent=None):
 | 
			
		||||
        """Toggle the indentation audio feedback on/off."""
 | 
			
		||||
        try:
 | 
			
		||||
            self._enabled = not self._enabled
 | 
			
		||||
            state = "enabled" if self._enabled else "disabled"
 | 
			
		||||
            
 | 
			
		||||
            # Announce the state change
 | 
			
		||||
            message = f"Indentation audio {state}"
 | 
			
		||||
            if hasattr(script, 'speakMessage'):
 | 
			
		||||
                script.speakMessage(message)
 | 
			
		||||
            
 | 
			
		||||
            logger.info(f"IndentationAudio: Toggled to {state}")
 | 
			
		||||
            return True
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error toggling state: {e}")
 | 
			
		||||
            return False
 | 
			
		||||
    
 | 
			
		||||
    def _calculate_indentation_level(self, line_text):
 | 
			
		||||
        """Calculate the indentation level of a line."""
 | 
			
		||||
        if not line_text:
 | 
			
		||||
            return 0
 | 
			
		||||
            
 | 
			
		||||
        # Remove non-breaking spaces and convert to regular spaces
 | 
			
		||||
        line = line_text.replace("\u00a0", " ")
 | 
			
		||||
        
 | 
			
		||||
        # Find the first non-whitespace character
 | 
			
		||||
        match = re.search(r"[^ \t]", line)
 | 
			
		||||
        if not match:
 | 
			
		||||
            return 0  # Empty or whitespace-only line
 | 
			
		||||
            
 | 
			
		||||
        indent_text = line[:match.start()]
 | 
			
		||||
        
 | 
			
		||||
        # Calculate indentation level (4 spaces = 1 level, 1 tab = 1 level)
 | 
			
		||||
        level = 0
 | 
			
		||||
        for char in indent_text:
 | 
			
		||||
            if char == '\t':
 | 
			
		||||
                level += 1
 | 
			
		||||
            elif char == ' ':
 | 
			
		||||
                level += 0.25  # 4 spaces = 1 level
 | 
			
		||||
                
 | 
			
		||||
        return int(level)
 | 
			
		||||
    
 | 
			
		||||
    def _generate_indentation_tone(self, new_level, old_level):
 | 
			
		||||
        """Generate an audio tone for indentation level change."""
 | 
			
		||||
        if not self._enabled or not self._player:
 | 
			
		||||
            return
 | 
			
		||||
            
 | 
			
		||||
        # Calculate frequency based on new indentation level
 | 
			
		||||
        frequency = min(
 | 
			
		||||
            self._base_frequency + (new_level * self._frequency_step),
 | 
			
		||||
            self._max_frequency
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        # Determine stereo panning based on change direction
 | 
			
		||||
        # Left channel for indent increase, right for decrease
 | 
			
		||||
        volume_multiplier = 0.7
 | 
			
		||||
        
 | 
			
		||||
        try:
 | 
			
		||||
            # Create tone
 | 
			
		||||
            tone = Tone(
 | 
			
		||||
                duration=self._tone_duration,
 | 
			
		||||
                frequency=frequency,
 | 
			
		||||
                volumeMultiplier=volume_multiplier,
 | 
			
		||||
                wave=Tone.SINE_WAVE
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
            # Play the tone
 | 
			
		||||
            self._player.play(tone, interrupt=False)
 | 
			
		||||
            
 | 
			
		||||
            debug_msg = f"IndentationAudio: Played tone - Level: {new_level}, Freq: {frequency}Hz"
 | 
			
		||||
            logger.debug(debug_msg)
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error generating tone: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def check_indentation_change(self, obj, line_text):
 | 
			
		||||
        """Check if indentation has changed and play audio cue if needed.
 | 
			
		||||
        
 | 
			
		||||
        This method is intended to be called by scripts during line navigation.
 | 
			
		||||
        """
 | 
			
		||||
        if not self._enabled or not line_text:
 | 
			
		||||
            return
 | 
			
		||||
            
 | 
			
		||||
        try:
 | 
			
		||||
            # Get object identifier for tracking
 | 
			
		||||
            obj_id = str(obj) if obj else "unknown"
 | 
			
		||||
            
 | 
			
		||||
            # Calculate current indentation level
 | 
			
		||||
            current_level = self._calculate_indentation_level(line_text)
 | 
			
		||||
            
 | 
			
		||||
            # Get previous level for this object
 | 
			
		||||
            previous_level = self._last_indentation_level.get(obj_id, current_level)
 | 
			
		||||
            
 | 
			
		||||
            # Update tracking
 | 
			
		||||
            self._last_indentation_level[obj_id] = current_level
 | 
			
		||||
            
 | 
			
		||||
            # Play audio cue if indentation changed
 | 
			
		||||
            if current_level != previous_level:
 | 
			
		||||
                self._generate_indentation_tone(current_level, previous_level)
 | 
			
		||||
                
 | 
			
		||||
                debug_msg = f"IndentationAudio: Indentation changed from {previous_level} to {current_level}"
 | 
			
		||||
                logger.debug(debug_msg)
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error checking indentation change: {e}")
 | 
			
		||||
    
 | 
			
		||||
    def is_enabled(self):
 | 
			
		||||
        """Return whether the plugin is currently enabled."""
 | 
			
		||||
        return self._enabled
 | 
			
		||||
    
 | 
			
		||||
    def set_enabled(self, enabled):
 | 
			
		||||
        """Set the enabled state of the plugin."""
 | 
			
		||||
        self._enabled = enabled
 | 
			
		||||
    
 | 
			
		||||
    def on_script_change(self, new_script):
 | 
			
		||||
        """Handle when the active script changes."""
 | 
			
		||||
        try:
 | 
			
		||||
            # Restore previous script if it was patched
 | 
			
		||||
            self._restore_script_methods()
 | 
			
		||||
            
 | 
			
		||||
            # Re-apply patches to new script
 | 
			
		||||
            self._monkey_patch_script_methods()
 | 
			
		||||
            
 | 
			
		||||
            # Clear tracking data for new context
 | 
			
		||||
            self._last_indentation_level.clear()
 | 
			
		||||
            
 | 
			
		||||
            logger.info("IndentationAudio: Handled script change")
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"IndentationAudio: Error handling script change: {e}")
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
SUBDIRS = Clipboard DisplayVersion hello_world self_voice ByeCthulhu HelloCthulhu SimplePluginSystem
 | 
			
		||||
SUBDIRS = Clipboard DisplayVersion IndentationAudio hello_world self_voice ByeCthulhu HelloCthulhu SimplePluginSystem
 | 
			
		||||
 | 
			
		||||
cthulhu_pythondir=$(pkgpythondir)/plugins
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user