Merged everything into master, at a decent point to save progress.
This commit is contained in:
+13
@@ -50,8 +50,21 @@ src/cthulhu/cthulhu_platform.py
|
||||
|
||||
# Python bytecode
|
||||
*.pyc
|
||||
*.pyo
|
||||
__pycache__/
|
||||
|
||||
# Editor backup files
|
||||
*~
|
||||
*.bak
|
||||
*.swp
|
||||
*.tmp
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
# AT-SPI test/debug files
|
||||
debug*.log
|
||||
debug*.out
|
||||
|
||||
# Local build directory and artifacts
|
||||
local-build/
|
||||
debug-*.out
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
Cthulhu Authors
|
||||
|
||||
Marc Mulcahy
|
||||
Willie Walker
|
||||
Mike Pedersen
|
||||
Rich Burridge
|
||||
Joanmarie Diggs
|
||||
Eitan Isaacson
|
||||
Scott Haeger
|
||||
|
||||
|
||||
Cthulhu authors
|
||||
|
||||
Storm Dragon
|
||||
@@ -791,29 +791,32 @@ busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service org.
|
||||
- 🔄 **Module registration**: Ready for individual managers to register D-Bus commands
|
||||
- 🔄 **Plugin integration**: Plugins can expose D-Bus commands using decorators
|
||||
|
||||
### **✅ COMPLETED - D-Bus Remote Controller Integration**
|
||||
The D-Bus Remote Controller from Orca v49.alpha has been successfully integrated into Cthulhu and is fully functional.
|
||||
### **✅ COMPLETED - Enhanced D-Bus Remote Controller with Speech and Key Echo Controls**
|
||||
The D-Bus Remote Controller from Orca v49.alpha has been successfully re-ported and enhanced with comprehensive speech and typing echo controls.
|
||||
|
||||
**Root Cause of Issues**: D-Bus service startup timing conflicts with ATSPI registry initialization.
|
||||
**Latest Enhancement (2025)**:
|
||||
- **SpeechManager Module**: Complete D-Bus control over speech settings (muting, verbosity, punctuation, capitalization, number pronunciation)
|
||||
- **TypingEchoManager Module**: Granular key echo controls (character/word/sentence echo, per-key-type settings)
|
||||
- **No systemd dependency**: Direct session bus registration without service files
|
||||
- **Real-time effect**: All settings take effect immediately
|
||||
|
||||
**Solution Implemented**:
|
||||
- Deferred D-Bus service startup using `GObject.idle_add()` after ATSPI event loop is running
|
||||
- Fixed all API naming convention differences between Orca and Cthulhu
|
||||
**Files Created/Modified for Enhanced D-Bus Integration**:
|
||||
- `src/cthulhu/speech_and_verbosity_manager.py` - Enhanced with D-Bus getters/setters for all speech settings
|
||||
- `src/cthulhu/typing_echo_presenter.py` (NEW FILE) - Complete typing echo system with D-Bus controls
|
||||
- `src/cthulhu/cthulhu.py` - D-Bus service registration for speech and typing echo managers
|
||||
- `src/cthulhu/meson.build` - Added typing_echo_presenter.py to build
|
||||
- `README-REMOTE-CONTROLLER.md` - Updated with comprehensive speech and key echo examples
|
||||
|
||||
**Files Modified for D-Bus Integration**:
|
||||
- `src/cthulhu/dbus_service.py` (NEW FILE) - Complete D-Bus service port with Cthulhu API fixes
|
||||
- `src/cthulhu/input_event.py` - Added RemoteControllerEvent + GDK version fix
|
||||
- `src/cthulhu/cthulhu.py` - D-Bus integration + lazy BrailleEvent import + settings manager activation + deferred startup
|
||||
- `src/cthulhu/Makefile.am` - Added dbus_service.py to build
|
||||
- Multiple presenter files - Converted to lazy initialization pattern
|
||||
- `src/cthulhu/keybindings.py` - Fixed GDK version requirement
|
||||
- `README-REMOTE-CONTROLLER.md` (NEW FILE) - Complete documentation with examples
|
||||
**Available D-Bus Modules**:
|
||||
- **SpeechManager**: Speech muting, verbosity, punctuation, capitalization, number styles, indentation speech
|
||||
- **TypingEchoManager**: Master key echo, character/word/sentence echo, per-key-type controls (alphabetic, numeric, punctuation, space, modifier, function, action, navigation, diacritical keys)
|
||||
- **DefaultScript**: Core Cthulhu commands
|
||||
|
||||
**API Fixes Applied**:
|
||||
- `debug.print_message` → `debug.printMessage`
|
||||
- `script_manager.get_manager()` → `script_manager.getManager()`
|
||||
- `get_active_script()` → `cthulhu_state.activeScript`
|
||||
- `get_default_script()` → `getDefaultScript()`
|
||||
**D-Bus Interface Design**:
|
||||
- Service: `org.stormux.Cthulhu.Service`
|
||||
- Module paths: `/org/stormux/Cthulhu/Service/ModuleName`
|
||||
- Generic interface: `org.stormux.Cthulhu.Module`
|
||||
- Methods: `ExecuteRuntimeGetter`, `ExecuteRuntimeSetter`, `ExecuteCommand`
|
||||
|
||||
### Bug Fixes Applied
|
||||
- Fixed circular imports in presenter modules (learn_mode_presenter, notification_presenter, etc.)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
2009-06-09 Willie Walker <william.walker@sun.com>
|
||||
|
||||
As of June 9, 2009, the ChangeLog is auto-generated when releasing.
|
||||
If you are seeing this, use 'git log' for a detailed list of changes.
|
||||
-20252
File diff suppressed because it is too large
Load Diff
+165
-179
@@ -1,8 +1,15 @@
|
||||
# Cthulhu Remote Controller (D-Bus Interface)
|
||||
|
||||
> **✅ STABLE**: This D-Bus interface has been successfully ported from Orca v49.alpha and integrated
|
||||
> into Cthulhu. The API is functional and ready for use, providing external control and automation
|
||||
> capabilities for the Cthulhu screen reader.
|
||||
> **⚠️⚠️ WORK IN PROGRESS**: This D-Bus interface is brand new and not yet feature complete.
|
||||
Low-risk feature additions will continue to be made. The API may be
|
||||
modified beyond bug fixes in future versions based on feedback from consumers of this support.
|
||||
Such changes will be documented here.
|
||||
|
||||
> **💡 Desktop-Agnostic Design**: Cthulhu's D-Bus Remote Controller is built on standard D-Bus
|
||||
session bus infrastructure and works across all desktop environments (GNOME, KDE Plasma, XFCE,
|
||||
i3, Sway, etc.). The D-Bus service uses no desktop-specific dependencies and follows universal
|
||||
D-Bus conventions, making it suitable for integration with any application or automation tool
|
||||
on any Linux desktop environment or window manager.
|
||||
|
||||
[TOC]
|
||||
|
||||
@@ -12,16 +19,42 @@ Cthulhu exposes a D-Bus service at:
|
||||
|
||||
- **Service Name**: `org.stormux.Cthulhu.Service`
|
||||
- **Main Object Path**: `/org/stormux/Cthulhu/Service`
|
||||
- **Module Object Paths**: `/org/stormux/Cthulhu/Service/ModuleName`
|
||||
- **Module Object Paths**: `/org/stormux.Cthulhu/Service/ModuleName`
|
||||
(e.g., `/org/stormux/Cthulhu/Service/SpeechAndVerbosityManager`)
|
||||
|
||||
See [REMOTE-CONTROLLER-COMMANDS.md](REMOTE-CONTROLLER-COMMANDS.md) for a complete
|
||||
list of available commands.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The D-Bus interface requires:
|
||||
|
||||
- **dasbus** - Python D-Bus library used by Cthulhu for the remote controller implementation.
|
||||
([Installation instructions](https://dasbus.readthedocs.io/en/latest/index.html))
|
||||
- **python-dasbus** package (available on most distributions)
|
||||
|
||||
## Alternative Tools for D-Bus Interaction
|
||||
|
||||
While this documentation primarily uses `gdbus` for examples, you can use any D-Bus tool or library:
|
||||
|
||||
### Using `busctl` (systemd D-Bus tool)
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service GetVersion
|
||||
```
|
||||
|
||||
### Using Python with `dasbus`
|
||||
```python
|
||||
from dasbus.connection import SessionMessageBus
|
||||
bus = SessionMessageBus()
|
||||
proxy = bus.get_proxy("org.stormux.Cthulhu.Service", "/org/stormux/Cthulhu/Service")
|
||||
version = proxy.GetVersion()
|
||||
```
|
||||
|
||||
### Using `qdbus` (Qt D-Bus tool - available on KDE)
|
||||
```bash
|
||||
qdbus org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service.GetVersion
|
||||
```
|
||||
|
||||
## Service-Level Commands
|
||||
|
||||
@@ -29,13 +62,6 @@ Commands available directly on the main service (`/org/stormux/Cthulhu/Service`)
|
||||
|
||||
### Get Cthulhu's Version
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service GetVersion
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service \
|
||||
@@ -44,17 +70,8 @@ gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
|
||||
**Returns:** String containing the version (and revision if available)
|
||||
|
||||
**Example output:** `s "Cthulhu screen reader version 2025.06.05-plugins (rev 408fb85)"`
|
||||
|
||||
### Present a Custom Message in Speech and/or Braille
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service PresentMessage s "Your message here"
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service \
|
||||
@@ -67,15 +84,28 @@ gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
|
||||
**Returns:** Boolean indicating success
|
||||
|
||||
### List Available Service Commands
|
||||
### Show Cthulhu's Preferences GUI
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service ListCommands
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service \
|
||||
--method org.stormux.Cthulhu.Service.ShowPreferences
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
**Returns:** Boolean indicating success
|
||||
|
||||
### Quit Cthulhu
|
||||
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service \
|
||||
--method org.stormux.Cthulhu.Service.Quit
|
||||
```
|
||||
|
||||
**Returns:** Boolean indicating if the quit request was accepted
|
||||
|
||||
### List Available Service Commands
|
||||
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service \
|
||||
@@ -86,13 +116,6 @@ gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
|
||||
### List Registered Modules
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service ListModules
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service \
|
||||
@@ -119,13 +142,6 @@ You can discover and execute these for each module.
|
||||
|
||||
#### List Commands for a Module
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ListCommands
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
@@ -136,15 +152,29 @@ Replace `ModuleName` with an actual module name from `ListModules`.
|
||||
|
||||
**Returns:** List of (command_name, description) tuples.
|
||||
|
||||
#### List Runtime Getters for a Module
|
||||
#### List Parameterized Commands for a Module
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ListRuntimeGetters
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
--method org.stormux.Cthulhu.Module.ListParameterizedCommands
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
Replace `ModuleName` with an actual module name from `ListModules`.
|
||||
|
||||
**Returns:** List of (command_name, description, parameters) tuples, where `parameters` is a
|
||||
list of (parameter_name, parameter_type) tuples.
|
||||
|
||||
**Example output:**
|
||||
|
||||
```bash
|
||||
([('GetVoicesForLanguage',
|
||||
'Returns a list of available voices for the specified language.',
|
||||
[('language', 'str'), ('variant', 'str'), ('notify_user', 'bool')])],)
|
||||
```
|
||||
|
||||
#### List Runtime Getters for a Module
|
||||
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
@@ -157,13 +187,6 @@ Replace `ModuleName` with an actual module name from `ListModules`.
|
||||
|
||||
#### List Runtime Setters for a Module
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ListRuntimeSetters
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
@@ -178,13 +201,6 @@ Replace `ModuleName` with an actual module name from `ListModules`.
|
||||
|
||||
#### Execute a Runtime Getter
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ExecuteRuntimeGetter s 'PropertyName'
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
@@ -200,22 +216,16 @@ gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
##### Example: Get the current speech rate
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
org.stormux.Cthulhu.Module ExecuteRuntimeGetter s 'Rate'
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
--method org.stormux.Cthulhu.Module.ExecuteRuntimeGetter 'Rate'
|
||||
|
||||
```
|
||||
|
||||
This will return the rate as a GLib Variant.
|
||||
|
||||
#### Execute a Runtime Setter
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ExecuteRuntimeSetter s 'PropertyName' v <value>
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
@@ -232,26 +242,13 @@ gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
##### Example: Set the current speech rate
|
||||
|
||||
```bash
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
org.stormux.Cthulhu.Module ExecuteRuntimeSetter s 'Rate' v '<90>'
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
--method org.stormux.Cthulhu.Module.ExecuteRuntimeSetter 'Rate' '<90>'
|
||||
```
|
||||
|
||||
#### Execute a Module Command
|
||||
|
||||
```bash
|
||||
# With user notification
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ExecuteCommand s 'CommandName' b true
|
||||
|
||||
# Without user notification (silent)
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/ModuleName \
|
||||
org.stormux.Cthulhu.Module ExecuteCommand s 'CommandName' b false
|
||||
```
|
||||
|
||||
**Alternative using gdbus:**
|
||||
```bash
|
||||
# With user notification
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
@@ -271,116 +268,105 @@ gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
|
||||
**Returns:** Boolean indicating success
|
||||
|
||||
### Please Note
|
||||
#### Execute a Parameterized Command
|
||||
|
||||
**Setting `notify_user=true` is not a guarantee that feedback will be presented.** Some commands
|
||||
inherently don't make sense to announce. For example:
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/ModuleName \
|
||||
--method org.stormux.Cthulhu.Module.ExecuteParameterizedCommand 'CommandName' \
|
||||
'{"param1": <"value1">, "param2": <"value2">}' false
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `CommandName` (string): The name of the parameterized command to execute
|
||||
- `parameters` (dict): Dictionary of parameter names and values as GLib variants
|
||||
- `notify_user` (boolean): Whether to notify the user of the action
|
||||
|
||||
**Returns:** The result returned by the command as a GLib variant (type depends on the command)
|
||||
|
||||
##### Example: Get voices for a specific language
|
||||
|
||||
```bash
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
--method org.stormux.Cthulhu.Module.ExecuteParameterizedCommand 'GetVoicesForLanguage' \
|
||||
'{"language": <"en-us">, "variant": <"">}' false
|
||||
```
|
||||
|
||||
This will return a list of available voices for US English.
|
||||
|
||||
### User Notification Applicability
|
||||
|
||||
**Setting `notify_user=true` is not a guarantee that feedback will be presented.**
|
||||
|
||||
Some commands inherently don't make sense to announce. For example:
|
||||
|
||||
```bash
|
||||
# This command should simply stop speech, not announce that it is stopping speech.
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
org.stormux.Cthulhu.Module ExecuteCommand s 'InterruptSpeech' b true
|
||||
gdbus call --session --dest org.stormux.Cthulhu.Service \
|
||||
--object-path /org/stormux/Cthulhu/Service/SpeechAndVerbosityManager \
|
||||
--method org.stormux.Cthulhu.Module.ExecuteCommand 'InterruptSpeech' true
|
||||
```
|
||||
|
||||
In those cases Cthulhu will ignore the value of `notify_user`.
|
||||
|
||||
**Setting `notify_user=false` is a guarantee that Cthulhu will remain silent.** If Cthulhu provides any
|
||||
feedback when `notify_user=false`, it should be considered a bug.
|
||||
**Setting `notify_user=false` is not a guarantee that Cthulhu will remain silent**, though for the
|
||||
most part Cthulhu will try to respect this value. The exceptions are:
|
||||
|
||||
## Integration with Cthulhu's Plugin System
|
||||
1. If executing the command has resulted in UI being shown, such as a dialog or menu, the
|
||||
newly-shown UI will be presented in speech and/or braille based on the user's settings.
|
||||
Failure to announce that the user has been removed from one window and placed in another
|
||||
could be extremely confusing.
|
||||
2. If the *sole* purpose of the command is to announce something without making any other
|
||||
changes, e.g. `PresentTime`, executing it with `notify_user=false` makes no sense. Adding
|
||||
checks and early returns to handle this possibility does not seem worth doing. If you
|
||||
don't want Cthulhu to present the time, don't ask Cthulhu to present the time. 😃
|
||||
|
||||
The D-Bus Remote Controller integrates seamlessly with Cthulhu's pluggy-based plugin system. Plugins can:
|
||||
### Navigator Module "Enabled" State Applicability
|
||||
|
||||
- Register their own D-Bus commands using the `@cthulhu_hookimpl` decorator
|
||||
- Expose plugin-specific functionality via the remote controller
|
||||
- Access the D-Bus service through the dynamic API manager
|
||||
**In the Remote Controller, Navigator commands are expected to work even when not "enabled."**
|
||||
|
||||
See the main `CLAUDE.md` file for more details on plugin development with D-Bus integration.
|
||||
Some of Cthulhu's Navigator modules, namely Table Navigator, Caret Navigator, and Structural Navigator,
|
||||
have an "enabled" state. The reason for this is very much tied to the keyboard-centric nature of
|
||||
Cthulhu's commands. For instance, if Cthulhu always grabbed "H" (for heading navigation) and the arrow
|
||||
keys (for caret navigation), normal interaction with applications would be completely broken. For
|
||||
this reason, Navigator modules whose commands will prevent normal, native interaction with
|
||||
applications are typically not enabled by default and can be easily disabled.
|
||||
|
||||
## Troubleshooting
|
||||
In contrast, performing Navigator commands via D-Bus does not prevent native interaction with
|
||||
applications. For instance, one could use the Remote Controller to move to the next heading without
|
||||
causing H to stop functioning in editable fields. For this reason, and to avoid a performance hit,
|
||||
the decision was made to not check if (keyboard-centric) navigation commands were enabled. As a
|
||||
result, it should be possible to use Remote Controller navigation even in "focus mode" or other
|
||||
cases where Cthulhu is not controlling the caret. This is by design.
|
||||
|
||||
### Service Not Available
|
||||
Given the keyboard-centric nature of Cthulhu's commands, there may be instances in which one uses the
|
||||
Remote Controller for navigation and Cthulhu fails to correctly update its location in response. If
|
||||
Cthulhu correctly updates its location when the same navigation command is executed via keyboard,
|
||||
please report the Remote Controller failure as a new bug in Cthulhu's issue tracker.
|
||||
|
||||
If you get "The name is not activatable" or similar errors:
|
||||
### The "Stickiness" (or Lack Thereof) of On-The-Fly Settings Changes
|
||||
|
||||
1. **Check if Cthulhu is running:**
|
||||
```bash
|
||||
ps aux | grep cthulhu
|
||||
```
|
||||
Cthulhu has a number of keyboard commands to temporarily change settings such as speech rate, pitch,
|
||||
volume; capitalization style; punctuation level; etc., etc. The question is: how long should
|
||||
on-the-fly modifications to settings persist?
|
||||
|
||||
2. **Check if the D-Bus service is registered:**
|
||||
```bash
|
||||
busctl --user list | grep -i cthulhu
|
||||
```
|
||||
Early on in Cthulhu's development, the conclusion was that on-the-fly settings changes should be
|
||||
seen as quite temporary, presumed to be used to address a specific one-time need. For instance,
|
||||
if reading some difficult-to-understand text, one might want to reduce the speed just for that text.
|
||||
If one were doing a final proofread of some content, one might want to briefly set the punctuation
|
||||
level to all. If one needs slow speed and/or verbose punctuation all the time, those should be set
|
||||
in Cthulhu's Preferences dialogs -- either globally or on a per-app basis. Cthulhu also has a profile
|
||||
feature through which the user can save settings and quickly load/unload them by switching profiles*.
|
||||
|
||||
3. **Verify dasbus is installed:**
|
||||
```bash
|
||||
python3 -c "import dasbus; print('dasbus available')"
|
||||
```
|
||||
Whether or not that historical decision was the right decision goes beyond the scope of the
|
||||
Remote Controller. The primary purpose of the Remote Controller is to provide D-Bus access to
|
||||
commands and runtime settings as if they were performed by the user via keyboard command. Thus if
|
||||
a setting changed via Remote Controller persists (or fails to persist) in the same way as when
|
||||
changed via keyboard command, it is not a Remote Controller bug. (It may be a general Cthulhu
|
||||
bug or feature request, and you are encouraged to file it as such.) On the other hand, if the
|
||||
behavior of the Remote Controller differs from that of the corresponding or related keyboard
|
||||
command, please report that Remote Controller failure as a new bug in Cthulhu's issue tracker.
|
||||
|
||||
4. **Check Cthulhu debug output:**
|
||||
```bash
|
||||
DISPLAY=:0 ~/.local/bin/cthulhu --debug 2>&1 | grep -i dbus
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Timing Issues**: The D-Bus service starts after ATSPI initialization. Wait a few seconds after Cthulhu startup before attempting D-Bus calls.
|
||||
- **Permissions**: Ensure you're using `--user` with busctl/gdbus for session bus access.
|
||||
- **Display**: Make sure `DISPLAY=:0` is set when running Cthulhu in terminal sessions.
|
||||
|
||||
## Examples
|
||||
|
||||
### Quick Test Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Test Cthulhu D-Bus Remote Controller
|
||||
|
||||
echo "Testing Cthulhu D-Bus Remote Controller..."
|
||||
|
||||
# Get version
|
||||
echo "Version:"
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service GetVersion
|
||||
|
||||
# Present a message
|
||||
echo "Presenting message..."
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service PresentMessage s "Hello from D-Bus!"
|
||||
|
||||
# List available modules
|
||||
echo "Available modules:"
|
||||
busctl --user call org.stormux.Cthulhu.Service \
|
||||
/org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service ListModules
|
||||
|
||||
echo "D-Bus test complete!"
|
||||
```
|
||||
|
||||
## Integration Status
|
||||
|
||||
- ✅ **Core D-Bus service**: Fully integrated with Cthulhu
|
||||
- ✅ **Service lifecycle**: Automatic start/shutdown with Cthulhu
|
||||
- ✅ **Message presentation**: `PresentMessage()` method working
|
||||
- ✅ **Version info**: `GetVersion()` method working
|
||||
- ✅ **Deferred startup**: D-Bus service starts after ATSPI initialization to prevent crashes
|
||||
- ✅ **Error handling**: Proper exception handling and logging
|
||||
- 🔄 **Module registration**: Ready for individual managers to register D-Bus commands
|
||||
- 🔄 **Plugin integration**: Plugins can expose D-Bus commands using decorators
|
||||
|
||||
## Future Development
|
||||
|
||||
- Add more speech configuration commands, getters, and setters
|
||||
- Expose Cthulhu's plugin system commands via D-Bus
|
||||
- Integrate with Cthulhu's advanced features (indentation audio, self-voicing, etc.)
|
||||
- Progressively expose all of Cthulhu's commands and settings via the remote controller interface
|
||||
|
||||
## Related Files
|
||||
|
||||
- `src/cthulhu/dbus_service.py` - Main D-Bus service implementation
|
||||
- `src/cthulhu/cthulhu.py` - Integration and startup logic
|
||||
- `CLAUDE.md` - Main development guide with plugin integration details
|
||||
\* *Note: Remote Controller support for profile management is still pending.*
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Cthulhu Remote Controller - Available Commands
|
||||
|
||||
This document lists the currently available D-Bus commands in Cthulhu's Remote Controller interface.
|
||||
|
||||
> **Note**: This is a work-in-progress. As more modules are exposed via D-Bus, this document will be expanded. Eventually this will be auto-generated using `tools/generate_dbus_documentation.py`.
|
||||
|
||||
## Service-Level Commands
|
||||
|
||||
Available on the main service object `/org/stormux/Cthulhu/Service`:
|
||||
|
||||
### Service Commands
|
||||
|
||||
| Command | Description | Parameters | Returns |
|
||||
|---------|-------------|------------|---------|
|
||||
| `GetVersion` | Returns Cthulhu's version string | None | String (version + revision) |
|
||||
| `PresentMessage` | Present a message via speech/braille | `message` (string) | Boolean (success) |
|
||||
| `ShowPreferences` | Opens Cthulhu's preferences GUI | None | Boolean (success) |
|
||||
| `Quit` | Exits Cthulhu | None | Boolean (accepted) |
|
||||
| `ListCommands` | Lists available service commands | None | List of (name, description) tuples |
|
||||
| `ListModules` | Lists registered D-Bus modules | None | List of module names |
|
||||
|
||||
### Example Usage
|
||||
|
||||
```bash
|
||||
# Get Cthulhu version
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service GetVersion
|
||||
|
||||
# Present a custom message
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service PresentMessage s "Hello from D-Bus"
|
||||
|
||||
# List available commands
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service ListCommands
|
||||
|
||||
# List registered modules
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service ListModules
|
||||
|
||||
# Open preferences
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service ShowPreferences
|
||||
|
||||
# Quit Cthulhu
|
||||
busctl --user call org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service \
|
||||
org.stormux.Cthulhu.Service Quit
|
||||
```
|
||||
|
||||
## Module-Level Commands
|
||||
|
||||
Currently, no additional modules are exposed via D-Bus beyond the base service commands.
|
||||
|
||||
**Planned modules** (to be implemented):
|
||||
- `SpeechAndVerbosityManager` - Speech settings control (muting, verbosity, punctuation, etc.)
|
||||
- `TypingEchoManager` - Typing echo settings (character/word/sentence echo)
|
||||
- `DefaultScript` - Core Cthulhu commands
|
||||
- Additional navigation and presenter modules
|
||||
|
||||
See [README-REMOTE-CONTROLLER.md](README-REMOTE-CONTROLLER.md) for comprehensive D-Bus API documentation and usage examples.
|
||||
|
||||
## Verifying D-Bus Service Status
|
||||
|
||||
```bash
|
||||
# Check if Cthulhu's D-Bus service is running
|
||||
busctl --user list | grep Cthulhu
|
||||
|
||||
# Introspect the service to see all available methods
|
||||
busctl --user introspect org.stormux.Cthulhu.Service /org/stormux/Cthulhu/Service
|
||||
|
||||
# Get detailed service information
|
||||
busctl --user status org.stormux.Cthulhu.Service
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
To add new D-Bus-accessible functionality:
|
||||
|
||||
1. Add `@dbus_service.command`, `@dbus_service.getter`, or `@dbus_service.setter` decorators to methods
|
||||
2. Register the module with the D-Bus service in `src/cthulhu/cthulhu.py`
|
||||
3. Rebuild and test with busctl
|
||||
4. Update this documentation (or regenerate using `tools/generate_dbus_documentation.py` when available)
|
||||
|
||||
## Desktop Environment Compatibility
|
||||
|
||||
Cthulhu's D-Bus Remote Controller is desktop-agnostic and works across all Linux desktop environments:
|
||||
- GNOME, KDE Plasma, XFCE, LXDE
|
||||
- Tiling window managers (i3, Sway, bspwm, etc.)
|
||||
- Any environment with D-Bus session bus support
|
||||
|
||||
The service uses only standard D-Bus infrastructure with no desktop-specific dependencies.
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
dnl a macro to check for ability to create python extensions
|
||||
dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
|
||||
dnl function also defines PYTHON_INCLUDES
|
||||
AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
|
||||
[AC_REQUIRE([AM_PATH_PYTHON])
|
||||
AC_MSG_CHECKING(for headers required to compile python extensions)
|
||||
dnl deduce PYTHON_INCLUDES
|
||||
py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
|
||||
py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
|
||||
PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
|
||||
if test "$py_prefix" != "$py_exec_prefix"; then
|
||||
PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
|
||||
fi
|
||||
AC_SUBST(PYTHON_INCLUDES)
|
||||
dnl check if the headers exist:
|
||||
save_CPPFLAGS="$CPPFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
|
||||
AC_TRY_CPP([#include <Python.h>],dnl
|
||||
[AC_MSG_RESULT(found)
|
||||
$1],dnl
|
||||
[AC_MSG_RESULT(not found)
|
||||
$2])
|
||||
CPPFLAGS="$save_CPPFLAGS"
|
||||
])
|
||||
|
||||
dnl AM_CHECK_PYMOD(MODNAME [,SYMBOL [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]]])
|
||||
dnl Check if a module containing a given symbol is visible to python.
|
||||
AC_DEFUN([AM_CHECK_PYMOD],
|
||||
[AC_REQUIRE([AM_PATH_PYTHON])
|
||||
py_mod_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'`
|
||||
AC_MSG_CHECKING(for ifelse([$2],[],,[$2 in ])python module $1)
|
||||
AC_CACHE_VAL(py_cv_mod_$py_mod_var, [
|
||||
ifelse([$2],[], [prog="
|
||||
import sys
|
||||
try:
|
||||
from gi.repository import GObject
|
||||
import $1
|
||||
except ImportError:
|
||||
sys.exit(1)
|
||||
except:
|
||||
sys.exit(0)
|
||||
sys.exit(0)"], [prog="
|
||||
import $1
|
||||
import $1.$2"])
|
||||
if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC
|
||||
then
|
||||
eval "py_cv_mod_$py_mod_var=yes"
|
||||
else
|
||||
eval "py_cv_mod_$py_mod_var=no"
|
||||
fi
|
||||
])
|
||||
py_val=`eval "echo \`echo '$py_cv_mod_'$py_mod_var\`"`
|
||||
if test "x$py_val" != xno; then
|
||||
AC_MSG_RESULT(yes)
|
||||
ifelse([$3], [],, [$3
|
||||
])dnl
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
ifelse([$4], [],, [$4
|
||||
])dnl
|
||||
fi
|
||||
])
|
||||
|
||||
dnl PYDOC_CHECK()
|
||||
AC_DEFUN([PYDOC_CHECK],
|
||||
[
|
||||
dnl enable/disable documentation building
|
||||
AC_ARG_ENABLE(pydoc,
|
||||
AC_HELP_STRING([--enable-pydoc],
|
||||
[use pydoc to build documentation [default=no]]),,
|
||||
enable_pydoc=no)
|
||||
|
||||
have_pydoc=no
|
||||
if test x$enable_pydoc = xyes; then
|
||||
AC_CHECK_FILE("$prefix/bin/pydoc", PYDOC="$prefix/bin/pydoc")
|
||||
fi
|
||||
|
||||
if test -z "$PYDOC"; then
|
||||
enable_pydoc=no
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_PYDOC, test x$enable_pydoc = xyes)
|
||||
])
|
||||
|
||||
dnl
|
||||
dnl JH_ADD_CFLAG(FLAG)
|
||||
dnl checks whether the C compiler supports the given flag, and if so, adds
|
||||
dnl it to $CFLAGS. If the flag is already present in the list, then the
|
||||
dnl check is not performed.
|
||||
AC_DEFUN([JH_ADD_CFLAG],
|
||||
[
|
||||
case " $CFLAGS " in
|
||||
*@<:@\ \ @:>@$1@<:@\ \ @:>@*)
|
||||
;;
|
||||
*)
|
||||
save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $1"
|
||||
AC_MSG_CHECKING([whether [$]CC understands $1])
|
||||
AC_TRY_COMPILE([], [], [jh_has_option=yes], [jh_has_option=no])
|
||||
AC_MSG_RESULT($jh_has_option)
|
||||
if test $jh_has_option = no; then
|
||||
CFLAGS="$save_CFLAGS"
|
||||
fi
|
||||
;;
|
||||
esac])
|
||||
@@ -1,16 +1,15 @@
|
||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||
|
||||
pkgname=cthulhu
|
||||
pkgver=2025.08.19
|
||||
pkgver=2025.12.12
|
||||
pkgrel=1
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
arch=(any)
|
||||
license=(LGPL)
|
||||
depends=(
|
||||
# Core AT-SPI accessibility
|
||||
# Core AT-SPI accessibility
|
||||
at-spi2-core
|
||||
python-atspi
|
||||
gobject-introspection-runtime
|
||||
python-gobject
|
||||
python-cairo
|
||||
@@ -31,7 +30,6 @@ depends=(
|
||||
python-dasbus
|
||||
|
||||
# AI Assistant dependencies (for screenshots, HTTP requests, and actions)
|
||||
python-pillow
|
||||
python-requests
|
||||
python-pyautogui
|
||||
|
||||
@@ -58,6 +56,15 @@ optdepends=(
|
||||
'openai-codex: ChatGPT AI provider support'
|
||||
'gemini-cli: Gemini AI provider support'
|
||||
'ollama: Local AI model support'
|
||||
|
||||
# OCR plugin dependencies (optional)
|
||||
'python-pillow: Image processing for OCR and AI Assistant'
|
||||
'python-pytesseract: Python wrapper for Tesseract OCR engine'
|
||||
'python-pdf2image: PDF to image conversion for OCR'
|
||||
'python-scipy: Scientific computing for OCR color analysis'
|
||||
'python-webcolors: Color name lookup for OCR text decoration'
|
||||
'tesseract: OCR engine for text recognition'
|
||||
'tesseract-data-eng: English language data for Tesseract'
|
||||
)
|
||||
makedepends=(
|
||||
git
|
||||
|
||||
@@ -1,652 +0,0 @@
|
||||
# Makefile.in generated by automake 1.18.1 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994-2025 Free Software Foundation, Inc.
|
||||
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
# with or without modifications, as long as this notice is preserved.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
@SET_MAKE@
|
||||
VPATH = @srcdir@
|
||||
am__is_gnu_make = { \
|
||||
if test -z '$(MAKELEVEL)'; then \
|
||||
false; \
|
||||
elif test -n '$(MAKE_HOST)'; then \
|
||||
true; \
|
||||
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
|
||||
true; \
|
||||
else \
|
||||
false; \
|
||||
fi; \
|
||||
}
|
||||
am__make_running_with_option = \
|
||||
case $${target_option-} in \
|
||||
?) ;; \
|
||||
*) echo "am__make_running_with_option: internal error: invalid" \
|
||||
"target option '$${target_option-}' specified" >&2; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
has_opt=no; \
|
||||
sane_makeflags=$$MAKEFLAGS; \
|
||||
if $(am__is_gnu_make); then \
|
||||
sane_makeflags=$$MFLAGS; \
|
||||
else \
|
||||
case $$MAKEFLAGS in \
|
||||
*\\[\ \ ]*) \
|
||||
bs=\\; \
|
||||
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
|
||||
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
|
||||
esac; \
|
||||
fi; \
|
||||
skip_next=no; \
|
||||
strip_trailopt () \
|
||||
{ \
|
||||
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
|
||||
}; \
|
||||
for flg in $$sane_makeflags; do \
|
||||
test $$skip_next = yes && { skip_next=no; continue; }; \
|
||||
case $$flg in \
|
||||
*=*|--*) continue;; \
|
||||
-*I) strip_trailopt 'I'; skip_next=yes;; \
|
||||
-*I?*) strip_trailopt 'I';; \
|
||||
-*O) strip_trailopt 'O'; skip_next=yes;; \
|
||||
-*O?*) strip_trailopt 'O';; \
|
||||
-*l) strip_trailopt 'l'; skip_next=yes;; \
|
||||
-*l?*) strip_trailopt 'l';; \
|
||||
-[dEDm]) skip_next=yes;; \
|
||||
-[JT]) skip_next=yes;; \
|
||||
esac; \
|
||||
case $$flg in \
|
||||
*$$target_option*) has_opt=yes; break;; \
|
||||
esac; \
|
||||
done; \
|
||||
test $$has_opt = yes
|
||||
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
|
||||
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
|
||||
am__rm_f = rm -f $(am__rm_f_notfound)
|
||||
am__rm_rf = rm -rf $(am__rm_f_notfound)
|
||||
pkgdatadir = $(datadir)/@PACKAGE@
|
||||
pkgincludedir = $(includedir)/@PACKAGE@
|
||||
pkglibdir = $(libdir)/@PACKAGE@
|
||||
pkglibexecdir = $(libexecdir)/@PACKAGE@
|
||||
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||
install_sh_DATA = $(install_sh) -c -m 644
|
||||
install_sh_PROGRAM = $(install_sh) -c
|
||||
install_sh_SCRIPT = $(install_sh) -c
|
||||
INSTALL_HEADER = $(INSTALL_DATA)
|
||||
transform = $(program_transform_name)
|
||||
NORMAL_INSTALL = :
|
||||
PRE_INSTALL = :
|
||||
POST_INSTALL = :
|
||||
NORMAL_UNINSTALL = :
|
||||
PRE_UNINSTALL = :
|
||||
POST_UNINSTALL = :
|
||||
build_triplet = @build@
|
||||
host_triplet = @host@
|
||||
subdir = docs
|
||||
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||
am__aclocal_m4_deps = $(top_srcdir)/m4/build-to-host.m4 \
|
||||
$(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/host-cpu-c-abi.m4 \
|
||||
$(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
|
||||
$(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
|
||||
$(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/nls.m4 \
|
||||
$(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
|
||||
$(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
|
||||
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
|
||||
$(ACLOCAL_M4)
|
||||
DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
|
||||
mkinstalldirs = $(install_sh) -d
|
||||
CONFIG_CLEAN_FILES =
|
||||
CONFIG_CLEAN_VPATH_FILES =
|
||||
AM_V_P = $(am__v_P_@AM_V@)
|
||||
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
|
||||
am__v_P_0 = false
|
||||
am__v_P_1 = :
|
||||
AM_V_GEN = $(am__v_GEN_@AM_V@)
|
||||
am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
|
||||
am__v_GEN_0 = @echo " GEN " $@;
|
||||
am__v_GEN_1 =
|
||||
AM_V_at = $(am__v_at_@AM_V@)
|
||||
am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
|
||||
am__v_at_0 = @
|
||||
am__v_at_1 =
|
||||
SOURCES =
|
||||
DIST_SOURCES =
|
||||
RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
|
||||
ctags-recursive dvi-recursive html-recursive info-recursive \
|
||||
install-data-recursive install-dvi-recursive \
|
||||
install-exec-recursive install-html-recursive \
|
||||
install-info-recursive install-pdf-recursive \
|
||||
install-ps-recursive install-recursive installcheck-recursive \
|
||||
installdirs-recursive pdf-recursive ps-recursive \
|
||||
tags-recursive uninstall-recursive
|
||||
am__can_run_installinfo = \
|
||||
case $$AM_UPDATE_INFO_DIR in \
|
||||
n|no|NO) false;; \
|
||||
*) (install-info --version) >/dev/null 2>&1;; \
|
||||
esac
|
||||
RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
|
||||
distclean-recursive maintainer-clean-recursive
|
||||
am__recursive_targets = \
|
||||
$(RECURSIVE_TARGETS) \
|
||||
$(RECURSIVE_CLEAN_TARGETS) \
|
||||
$(am__extra_recursive_targets)
|
||||
AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
|
||||
distdir distdir-am
|
||||
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
|
||||
# Read a list of newline-separated strings from the standard input,
|
||||
# and print each of them once, without duplicates. Input order is
|
||||
# *not* preserved.
|
||||
am__uniquify_input = $(AWK) '\
|
||||
BEGIN { nonempty = 0; } \
|
||||
{ items[$$0] = 1; nonempty = 1; } \
|
||||
END { if (nonempty) { for (i in items) print i; }; } \
|
||||
'
|
||||
# Make sure the list of sources is unique. This is necessary because,
|
||||
# e.g., the same source file might be shared among _SOURCES variables
|
||||
# for different programs/libraries.
|
||||
am__define_uniq_tagged_files = \
|
||||
list='$(am__tagged_files)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | $(am__uniquify_input)`
|
||||
DIST_SUBDIRS = $(SUBDIRS)
|
||||
am__DIST_COMMON = $(srcdir)/Makefile.in
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
am__relativize = \
|
||||
dir0=`pwd`; \
|
||||
sed_first='s,^\([^/]*\)/.*$$,\1,'; \
|
||||
sed_rest='s,^[^/]*/*,,'; \
|
||||
sed_last='s,^.*/\([^/]*\)$$,\1,'; \
|
||||
sed_butlast='s,/*[^/]*$$,,'; \
|
||||
while test -n "$$dir1"; do \
|
||||
first=`echo "$$dir1" | sed -e "$$sed_first"`; \
|
||||
if test "$$first" != "."; then \
|
||||
if test "$$first" = ".."; then \
|
||||
dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
|
||||
dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
|
||||
else \
|
||||
first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
|
||||
if test "$$first2" = "$$first"; then \
|
||||
dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
|
||||
else \
|
||||
dir2="../$$dir2"; \
|
||||
fi; \
|
||||
dir0="$$dir0"/"$$first"; \
|
||||
fi; \
|
||||
fi; \
|
||||
dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
|
||||
done; \
|
||||
reldir="$$dir2"
|
||||
ACLOCAL = @ACLOCAL@
|
||||
AMTAR = @AMTAR@
|
||||
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
|
||||
ATKBRIDGE_CFLAGS = @ATKBRIDGE_CFLAGS@
|
||||
ATKBRIDGE_LIBS = @ATKBRIDGE_LIBS@
|
||||
ATSPI2_CFLAGS = @ATSPI2_CFLAGS@
|
||||
ATSPI2_LIBS = @ATSPI2_LIBS@
|
||||
AUTOCONF = @AUTOCONF@
|
||||
AUTOHEADER = @AUTOHEADER@
|
||||
AUTOMAKE = @AUTOMAKE@
|
||||
AWK = @AWK@
|
||||
CC = @CC@
|
||||
CCDEPMODE = @CCDEPMODE@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CSCOPE = @CSCOPE@
|
||||
CTAGS = @CTAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DESIRED_LINGUAS = @DESIRED_LINGUAS@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
ETAGS = @ETAGS@
|
||||
EXEEXT = @EXEEXT@
|
||||
GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
|
||||
GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
|
||||
GMSGFMT = @GMSGFMT@
|
||||
GMSGFMT_015 = @GMSGFMT_015@
|
||||
GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@
|
||||
GSTREAMER_LIBS = @GSTREAMER_LIBS@
|
||||
INSTALL = @INSTALL@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||
INSTALL_SCRIPT = @INSTALL_SCRIPT@
|
||||
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
|
||||
INTLLIBS = @INTLLIBS@
|
||||
INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBICONV = @LIBICONV@
|
||||
LIBINTL = @LIBINTL@
|
||||
LIBOBJS = @LIBOBJS@
|
||||
LIBPEAS_CFLAGS = @LIBPEAS_CFLAGS@
|
||||
LIBPEAS_LIBS = @LIBPEAS_LIBS@
|
||||
LIBS = @LIBS@
|
||||
LOUIS_TABLE_DIR = @LOUIS_TABLE_DIR@
|
||||
LTLIBICONV = @LTLIBICONV@
|
||||
LTLIBINTL = @LTLIBINTL@
|
||||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAINT = @MAINT@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
MKDIR_P = @MKDIR_P@
|
||||
MSGFMT = @MSGFMT@
|
||||
MSGMERGE = @MSGMERGE@
|
||||
MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
|
||||
OBJEXT = @OBJEXT@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
PACKAGE_NAME = @PACKAGE_NAME@
|
||||
PACKAGE_STRING = @PACKAGE_STRING@
|
||||
PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
PACKAGE_URL = @PACKAGE_URL@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
PKG_CONFIG = @PKG_CONFIG@
|
||||
PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
|
||||
PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
|
||||
PLATFORM_PATH = @PLATFORM_PATH@
|
||||
POSUB = @POSUB@
|
||||
PYGOBJECT_CFLAGS = @PYGOBJECT_CFLAGS@
|
||||
PYGOBJECT_LIBS = @PYGOBJECT_LIBS@
|
||||
PYTHON = @PYTHON@
|
||||
PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
|
||||
PYTHON_PLATFORM = @PYTHON_PLATFORM@
|
||||
PYTHON_PREFIX = @PYTHON_PREFIX@
|
||||
PYTHON_VERSION = @PYTHON_VERSION@
|
||||
REVISION = @REVISION@
|
||||
SED = @SED@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
USE_NLS = @USE_NLS@
|
||||
VERSION = @VERSION@
|
||||
XGETTEXT = @XGETTEXT@
|
||||
XGETTEXT_015 = @XGETTEXT_015@
|
||||
XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
|
||||
abs_builddir = @abs_builddir@
|
||||
abs_srcdir = @abs_srcdir@
|
||||
abs_top_builddir = @abs_top_builddir@
|
||||
abs_top_srcdir = @abs_top_srcdir@
|
||||
ac_ct_CC = @ac_ct_CC@
|
||||
am__include = @am__include@
|
||||
am__leading_dot = @am__leading_dot@
|
||||
am__quote = @am__quote@
|
||||
am__rm_f_notfound = @am__rm_f_notfound@
|
||||
am__tar = @am__tar@
|
||||
am__untar = @am__untar@
|
||||
am__xargs_n = @am__xargs_n@
|
||||
bindir = @bindir@
|
||||
build = @build@
|
||||
build_alias = @build_alias@
|
||||
build_cpu = @build_cpu@
|
||||
build_os = @build_os@
|
||||
build_vendor = @build_vendor@
|
||||
builddir = @builddir@
|
||||
datadir = @datadir@
|
||||
datarootdir = @datarootdir@
|
||||
docdir = @docdir@
|
||||
dvidir = @dvidir@
|
||||
exec_prefix = @exec_prefix@
|
||||
host = @host@
|
||||
host_alias = @host_alias@
|
||||
host_cpu = @host_cpu@
|
||||
host_os = @host_os@
|
||||
host_vendor = @host_vendor@
|
||||
htmldir = @htmldir@
|
||||
includedir = @includedir@
|
||||
infodir = @infodir@
|
||||
install_sh = @install_sh@
|
||||
libdir = @libdir@
|
||||
libexecdir = @libexecdir@
|
||||
localedir = @localedir@
|
||||
localedir_c = @localedir_c@
|
||||
localedir_c_make = @localedir_c_make@
|
||||
localstatedir = @localstatedir@
|
||||
mandir = @mandir@
|
||||
mkdir_p = @mkdir_p@
|
||||
oldincludedir = @oldincludedir@
|
||||
pdfdir = @pdfdir@
|
||||
pkgpyexecdir = @pkgpyexecdir@
|
||||
pkgpythondir = @pkgpythondir@
|
||||
prefix = @prefix@
|
||||
program_transform_name = @program_transform_name@
|
||||
psdir = @psdir@
|
||||
pyexecdir = @pyexecdir@
|
||||
pythondir = @pythondir@
|
||||
runstatedir = @runstatedir@
|
||||
sbindir = @sbindir@
|
||||
sharedstatedir = @sharedstatedir@
|
||||
srcdir = @srcdir@
|
||||
sysconfdir = @sysconfdir@
|
||||
target_alias = @target_alias@
|
||||
top_build_prefix = @top_build_prefix@
|
||||
top_builddir = @top_builddir@
|
||||
top_srcdir = @top_srcdir@
|
||||
SUBDIRS = man
|
||||
all: all-recursive
|
||||
|
||||
.SUFFIXES:
|
||||
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
|
||||
@for dep in $?; do \
|
||||
case '$(am__configure_deps)' in \
|
||||
*$$dep*) \
|
||||
( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
|
||||
&& { if test -f $@; then exit 0; else break; fi; }; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
done; \
|
||||
echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu docs/Makefile'; \
|
||||
$(am__cd) $(top_srcdir) && \
|
||||
$(AUTOMAKE) --gnu docs/Makefile
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||
@case '$?' in \
|
||||
*config.status*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
|
||||
*) \
|
||||
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
|
||||
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
|
||||
esac;
|
||||
|
||||
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(am__aclocal_m4_deps):
|
||||
|
||||
# This directory's subdirectories are mostly independent; you can cd
|
||||
# into them and run 'make' without going through this Makefile.
|
||||
# To change the values of 'make' variables: instead of editing Makefiles,
|
||||
# (1) if the variable is set in 'config.status', edit 'config.status'
|
||||
# (which will cause the Makefiles to be regenerated when you run 'make');
|
||||
# (2) otherwise, pass the desired values on the 'make' command line.
|
||||
$(am__recursive_targets):
|
||||
@fail=; \
|
||||
if $(am__make_keepgoing); then \
|
||||
failcom='fail=yes'; \
|
||||
else \
|
||||
failcom='exit 1'; \
|
||||
fi; \
|
||||
dot_seen=no; \
|
||||
target=`echo $@ | sed s/-recursive//`; \
|
||||
case "$@" in \
|
||||
distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
|
||||
*) list='$(SUBDIRS)' ;; \
|
||||
esac; \
|
||||
for subdir in $$list; do \
|
||||
echo "Making $$target in $$subdir"; \
|
||||
if test "$$subdir" = "."; then \
|
||||
dot_seen=yes; \
|
||||
local_target="$$target-am"; \
|
||||
else \
|
||||
local_target="$$target"; \
|
||||
fi; \
|
||||
($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|
||||
|| eval $$failcom; \
|
||||
done; \
|
||||
if test "$$dot_seen" = "no"; then \
|
||||
$(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
|
||||
fi; test -z "$$fail"
|
||||
|
||||
ID: $(am__tagged_files)
|
||||
$(am__define_uniq_tagged_files); mkid -fID $$unique
|
||||
tags: tags-recursive
|
||||
TAGS: tags
|
||||
|
||||
tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
|
||||
set x; \
|
||||
here=`pwd`; \
|
||||
if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
|
||||
include_option=--etags-include; \
|
||||
empty_fix=.; \
|
||||
else \
|
||||
include_option=--include; \
|
||||
empty_fix=; \
|
||||
fi; \
|
||||
list='$(SUBDIRS)'; for subdir in $$list; do \
|
||||
if test "$$subdir" = .; then :; else \
|
||||
test ! -f $$subdir/TAGS || \
|
||||
set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
|
||||
fi; \
|
||||
done; \
|
||||
$(am__define_uniq_tagged_files); \
|
||||
shift; \
|
||||
if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
|
||||
test -n "$$unique" || unique=$$empty_fix; \
|
||||
if test $$# -gt 0; then \
|
||||
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||
"$$@" $$unique; \
|
||||
else \
|
||||
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||
$$unique; \
|
||||
fi; \
|
||||
fi
|
||||
ctags: ctags-recursive
|
||||
|
||||
CTAGS: ctags
|
||||
ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
|
||||
$(am__define_uniq_tagged_files); \
|
||||
test -z "$(CTAGS_ARGS)$$unique" \
|
||||
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
|
||||
$$unique
|
||||
|
||||
GTAGS:
|
||||
here=`$(am__cd) $(top_builddir) && pwd` \
|
||||
&& $(am__cd) $(top_srcdir) \
|
||||
&& gtags -i $(GTAGS_ARGS) "$$here"
|
||||
cscopelist: cscopelist-recursive
|
||||
|
||||
cscopelist-am: $(am__tagged_files)
|
||||
list='$(am__tagged_files)'; \
|
||||
case "$(srcdir)" in \
|
||||
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
|
||||
*) sdir=$(subdir)/$(srcdir) ;; \
|
||||
esac; \
|
||||
for i in $$list; do \
|
||||
if test -f "$$i"; then \
|
||||
echo "$(subdir)/$$i"; \
|
||||
else \
|
||||
echo "$$sdir/$$i"; \
|
||||
fi; \
|
||||
done >> $(top_builddir)/cscope.files
|
||||
|
||||
distclean-tags:
|
||||
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
|
||||
|
||||
distdir: $(BUILT_SOURCES)
|
||||
$(MAKE) $(AM_MAKEFLAGS) distdir-am
|
||||
|
||||
distdir-am: $(DISTFILES)
|
||||
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||
list='$(DISTFILES)'; \
|
||||
dist_files=`for file in $$list; do echo $$file; done | \
|
||||
sed -e "s|^$$srcdirstrip/||;t" \
|
||||
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
|
||||
case $$dist_files in \
|
||||
*/*) $(MKDIR_P) `echo "$$dist_files" | \
|
||||
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
|
||||
sort -u` ;; \
|
||||
esac; \
|
||||
for file in $$dist_files; do \
|
||||
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||
if test -d $$d/$$file; then \
|
||||
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||
if test -d "$(distdir)/$$file"; then \
|
||||
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||
fi; \
|
||||
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
|
||||
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||
fi; \
|
||||
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
|
||||
else \
|
||||
test -f "$(distdir)/$$file" \
|
||||
|| cp -p $$d/$$file "$(distdir)/$$file" \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
|
||||
if test "$$subdir" = .; then :; else \
|
||||
$(am__make_dryrun) \
|
||||
|| test -d "$(distdir)/$$subdir" \
|
||||
|| $(MKDIR_P) "$(distdir)/$$subdir" \
|
||||
|| exit 1; \
|
||||
dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
|
||||
$(am__relativize); \
|
||||
new_distdir=$$reldir; \
|
||||
dir1=$$subdir; dir2="$(top_distdir)"; \
|
||||
$(am__relativize); \
|
||||
new_top_distdir=$$reldir; \
|
||||
echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
|
||||
echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
|
||||
($(am__cd) $$subdir && \
|
||||
$(MAKE) $(AM_MAKEFLAGS) \
|
||||
top_distdir="$$new_top_distdir" \
|
||||
distdir="$$new_distdir" \
|
||||
am__remove_distdir=: \
|
||||
am__skip_length_check=: \
|
||||
am__skip_mode_fix=: \
|
||||
distdir) \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
check-am: all-am
|
||||
check: check-recursive
|
||||
all-am: Makefile
|
||||
installdirs: installdirs-recursive
|
||||
installdirs-am:
|
||||
install: install-recursive
|
||||
install-exec: install-exec-recursive
|
||||
install-data: install-data-recursive
|
||||
uninstall: uninstall-recursive
|
||||
|
||||
install-am: all-am
|
||||
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||
|
||||
installcheck: installcheck-recursive
|
||||
install-strip:
|
||||
if test -z '$(STRIP)'; then \
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
install; \
|
||||
else \
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
|
||||
fi
|
||||
mostlyclean-generic:
|
||||
|
||||
clean-generic:
|
||||
|
||||
distclean-generic:
|
||||
-$(am__rm_f) $(CONFIG_CLEAN_FILES)
|
||||
-test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@echo "it deletes files that may require special tools to rebuild."
|
||||
clean: clean-recursive
|
||||
|
||||
clean-am: clean-generic mostlyclean-am
|
||||
|
||||
distclean: distclean-recursive
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-generic distclean-tags
|
||||
|
||||
dvi: dvi-recursive
|
||||
|
||||
dvi-am:
|
||||
|
||||
html: html-recursive
|
||||
|
||||
html-am:
|
||||
|
||||
info: info-recursive
|
||||
|
||||
info-am:
|
||||
|
||||
install-data-am:
|
||||
|
||||
install-dvi: install-dvi-recursive
|
||||
|
||||
install-dvi-am:
|
||||
|
||||
install-exec-am:
|
||||
|
||||
install-html: install-html-recursive
|
||||
|
||||
install-html-am:
|
||||
|
||||
install-info: install-info-recursive
|
||||
|
||||
install-info-am:
|
||||
|
||||
install-man:
|
||||
|
||||
install-pdf: install-pdf-recursive
|
||||
|
||||
install-pdf-am:
|
||||
|
||||
install-ps: install-ps-recursive
|
||||
|
||||
install-ps-am:
|
||||
|
||||
installcheck-am:
|
||||
|
||||
maintainer-clean: maintainer-clean-recursive
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
mostlyclean: mostlyclean-recursive
|
||||
|
||||
mostlyclean-am: mostlyclean-generic
|
||||
|
||||
pdf: pdf-recursive
|
||||
|
||||
pdf-am:
|
||||
|
||||
ps: ps-recursive
|
||||
|
||||
ps-am:
|
||||
|
||||
uninstall-am:
|
||||
|
||||
.MAKE: $(am__recursive_targets) install-am install-strip
|
||||
|
||||
.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
|
||||
check-am clean clean-generic cscopelist-am ctags ctags-am \
|
||||
distclean distclean-generic distclean-tags distdir dvi dvi-am \
|
||||
html html-am info info-am install install-am install-data \
|
||||
install-data-am install-dvi install-dvi-am install-exec \
|
||||
install-exec-am install-html install-html-am install-info \
|
||||
install-info-am install-man install-pdf install-pdf-am \
|
||||
install-ps install-ps-am install-strip installcheck \
|
||||
installcheck-am installdirs installdirs-am maintainer-clean \
|
||||
maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
|
||||
pdf-am ps ps-am tags tags-am uninstall uninstall-am
|
||||
|
||||
.PRECIOUS: Makefile
|
||||
|
||||
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
||||
|
||||
# Tell GNU make to disable its built-in pattern rules.
|
||||
%:: %,v
|
||||
%:: RCS/%,v
|
||||
%:: RCS/%
|
||||
%:: s.%
|
||||
%:: SCCS/s.%
|
||||
@@ -1,554 +0,0 @@
|
||||
# Makefile.in generated by automake 1.18.1 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994-2025 Free Software Foundation, Inc.
|
||||
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
# with or without modifications, as long as this notice is preserved.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
@SET_MAKE@
|
||||
VPATH = @srcdir@
|
||||
am__is_gnu_make = { \
|
||||
if test -z '$(MAKELEVEL)'; then \
|
||||
false; \
|
||||
elif test -n '$(MAKE_HOST)'; then \
|
||||
true; \
|
||||
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
|
||||
true; \
|
||||
else \
|
||||
false; \
|
||||
fi; \
|
||||
}
|
||||
am__make_running_with_option = \
|
||||
case $${target_option-} in \
|
||||
?) ;; \
|
||||
*) echo "am__make_running_with_option: internal error: invalid" \
|
||||
"target option '$${target_option-}' specified" >&2; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
has_opt=no; \
|
||||
sane_makeflags=$$MAKEFLAGS; \
|
||||
if $(am__is_gnu_make); then \
|
||||
sane_makeflags=$$MFLAGS; \
|
||||
else \
|
||||
case $$MAKEFLAGS in \
|
||||
*\\[\ \ ]*) \
|
||||
bs=\\; \
|
||||
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
|
||||
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
|
||||
esac; \
|
||||
fi; \
|
||||
skip_next=no; \
|
||||
strip_trailopt () \
|
||||
{ \
|
||||
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
|
||||
}; \
|
||||
for flg in $$sane_makeflags; do \
|
||||
test $$skip_next = yes && { skip_next=no; continue; }; \
|
||||
case $$flg in \
|
||||
*=*|--*) continue;; \
|
||||
-*I) strip_trailopt 'I'; skip_next=yes;; \
|
||||
-*I?*) strip_trailopt 'I';; \
|
||||
-*O) strip_trailopt 'O'; skip_next=yes;; \
|
||||
-*O?*) strip_trailopt 'O';; \
|
||||
-*l) strip_trailopt 'l'; skip_next=yes;; \
|
||||
-*l?*) strip_trailopt 'l';; \
|
||||
-[dEDm]) skip_next=yes;; \
|
||||
-[JT]) skip_next=yes;; \
|
||||
esac; \
|
||||
case $$flg in \
|
||||
*$$target_option*) has_opt=yes; break;; \
|
||||
esac; \
|
||||
done; \
|
||||
test $$has_opt = yes
|
||||
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
|
||||
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
|
||||
am__rm_f = rm -f $(am__rm_f_notfound)
|
||||
am__rm_rf = rm -rf $(am__rm_f_notfound)
|
||||
pkgdatadir = $(datadir)/@PACKAGE@
|
||||
pkgincludedir = $(includedir)/@PACKAGE@
|
||||
pkglibdir = $(libdir)/@PACKAGE@
|
||||
pkglibexecdir = $(libexecdir)/@PACKAGE@
|
||||
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||
install_sh_DATA = $(install_sh) -c -m 644
|
||||
install_sh_PROGRAM = $(install_sh) -c
|
||||
install_sh_SCRIPT = $(install_sh) -c
|
||||
INSTALL_HEADER = $(INSTALL_DATA)
|
||||
transform = $(program_transform_name)
|
||||
NORMAL_INSTALL = :
|
||||
PRE_INSTALL = :
|
||||
POST_INSTALL = :
|
||||
NORMAL_UNINSTALL = :
|
||||
PRE_UNINSTALL = :
|
||||
POST_UNINSTALL = :
|
||||
build_triplet = @build@
|
||||
host_triplet = @host@
|
||||
subdir = docs/man
|
||||
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||
am__aclocal_m4_deps = $(top_srcdir)/m4/build-to-host.m4 \
|
||||
$(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/host-cpu-c-abi.m4 \
|
||||
$(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
|
||||
$(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
|
||||
$(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/nls.m4 \
|
||||
$(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
|
||||
$(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
|
||||
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
|
||||
$(ACLOCAL_M4)
|
||||
DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
|
||||
mkinstalldirs = $(install_sh) -d
|
||||
CONFIG_CLEAN_FILES =
|
||||
CONFIG_CLEAN_VPATH_FILES =
|
||||
AM_V_P = $(am__v_P_@AM_V@)
|
||||
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
|
||||
am__v_P_0 = false
|
||||
am__v_P_1 = :
|
||||
AM_V_GEN = $(am__v_GEN_@AM_V@)
|
||||
am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
|
||||
am__v_GEN_0 = @echo " GEN " $@;
|
||||
am__v_GEN_1 =
|
||||
AM_V_at = $(am__v_at_@AM_V@)
|
||||
am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
|
||||
am__v_at_0 = @
|
||||
am__v_at_1 =
|
||||
SOURCES =
|
||||
DIST_SOURCES =
|
||||
am__can_run_installinfo = \
|
||||
case $$AM_UPDATE_INFO_DIR in \
|
||||
n|no|NO) false;; \
|
||||
*) (install-info --version) >/dev/null 2>&1;; \
|
||||
esac
|
||||
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
|
||||
am__vpath_adj = case $$p in \
|
||||
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
|
||||
*) f=$$p;; \
|
||||
esac;
|
||||
am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
|
||||
am__install_max = 40
|
||||
am__nobase_strip_setup = \
|
||||
srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
|
||||
am__nobase_strip = \
|
||||
for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
|
||||
am__nobase_list = $(am__nobase_strip_setup); \
|
||||
for p in $$list; do echo "$$p $$p"; done | \
|
||||
sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
|
||||
$(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
|
||||
if (++n[$$2] == $(am__install_max)) \
|
||||
{ print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
|
||||
END { for (dir in files) print dir, files[dir] }'
|
||||
am__base_list = \
|
||||
sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
|
||||
sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
|
||||
am__uninstall_files_from_dir = { \
|
||||
{ test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
|
||||
|| { echo " ( cd '$$dir' && rm -f" $$files ")"; \
|
||||
$(am__cd) "$$dir" && echo $$files | $(am__xargs_n) 40 $(am__rm_f); }; \
|
||||
}
|
||||
man1dir = $(mandir)/man1
|
||||
am__installdirs = "$(DESTDIR)$(man1dir)"
|
||||
NROFF = nroff
|
||||
MANS = $(man1_MANS)
|
||||
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
|
||||
am__DIST_COMMON = $(srcdir)/Makefile.in
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
ACLOCAL = @ACLOCAL@
|
||||
AMTAR = @AMTAR@
|
||||
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
|
||||
ATKBRIDGE_CFLAGS = @ATKBRIDGE_CFLAGS@
|
||||
ATKBRIDGE_LIBS = @ATKBRIDGE_LIBS@
|
||||
ATSPI2_CFLAGS = @ATSPI2_CFLAGS@
|
||||
ATSPI2_LIBS = @ATSPI2_LIBS@
|
||||
AUTOCONF = @AUTOCONF@
|
||||
AUTOHEADER = @AUTOHEADER@
|
||||
AUTOMAKE = @AUTOMAKE@
|
||||
AWK = @AWK@
|
||||
CC = @CC@
|
||||
CCDEPMODE = @CCDEPMODE@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CSCOPE = @CSCOPE@
|
||||
CTAGS = @CTAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DESIRED_LINGUAS = @DESIRED_LINGUAS@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
ETAGS = @ETAGS@
|
||||
EXEEXT = @EXEEXT@
|
||||
GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
|
||||
GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
|
||||
GMSGFMT = @GMSGFMT@
|
||||
GMSGFMT_015 = @GMSGFMT_015@
|
||||
GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@
|
||||
GSTREAMER_LIBS = @GSTREAMER_LIBS@
|
||||
INSTALL = @INSTALL@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||
INSTALL_SCRIPT = @INSTALL_SCRIPT@
|
||||
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
|
||||
INTLLIBS = @INTLLIBS@
|
||||
INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBICONV = @LIBICONV@
|
||||
LIBINTL = @LIBINTL@
|
||||
LIBOBJS = @LIBOBJS@
|
||||
LIBPEAS_CFLAGS = @LIBPEAS_CFLAGS@
|
||||
LIBPEAS_LIBS = @LIBPEAS_LIBS@
|
||||
LIBS = @LIBS@
|
||||
LOUIS_TABLE_DIR = @LOUIS_TABLE_DIR@
|
||||
LTLIBICONV = @LTLIBICONV@
|
||||
LTLIBINTL = @LTLIBINTL@
|
||||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAINT = @MAINT@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
MKDIR_P = @MKDIR_P@
|
||||
MSGFMT = @MSGFMT@
|
||||
MSGMERGE = @MSGMERGE@
|
||||
MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
|
||||
OBJEXT = @OBJEXT@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
PACKAGE_NAME = @PACKAGE_NAME@
|
||||
PACKAGE_STRING = @PACKAGE_STRING@
|
||||
PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
PACKAGE_URL = @PACKAGE_URL@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
PKG_CONFIG = @PKG_CONFIG@
|
||||
PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
|
||||
PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
|
||||
PLATFORM_PATH = @PLATFORM_PATH@
|
||||
POSUB = @POSUB@
|
||||
PYGOBJECT_CFLAGS = @PYGOBJECT_CFLAGS@
|
||||
PYGOBJECT_LIBS = @PYGOBJECT_LIBS@
|
||||
PYTHON = @PYTHON@
|
||||
PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
|
||||
PYTHON_PLATFORM = @PYTHON_PLATFORM@
|
||||
PYTHON_PREFIX = @PYTHON_PREFIX@
|
||||
PYTHON_VERSION = @PYTHON_VERSION@
|
||||
REVISION = @REVISION@
|
||||
SED = @SED@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
USE_NLS = @USE_NLS@
|
||||
VERSION = @VERSION@
|
||||
XGETTEXT = @XGETTEXT@
|
||||
XGETTEXT_015 = @XGETTEXT_015@
|
||||
XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
|
||||
abs_builddir = @abs_builddir@
|
||||
abs_srcdir = @abs_srcdir@
|
||||
abs_top_builddir = @abs_top_builddir@
|
||||
abs_top_srcdir = @abs_top_srcdir@
|
||||
ac_ct_CC = @ac_ct_CC@
|
||||
am__include = @am__include@
|
||||
am__leading_dot = @am__leading_dot@
|
||||
am__quote = @am__quote@
|
||||
am__rm_f_notfound = @am__rm_f_notfound@
|
||||
am__tar = @am__tar@
|
||||
am__untar = @am__untar@
|
||||
am__xargs_n = @am__xargs_n@
|
||||
bindir = @bindir@
|
||||
build = @build@
|
||||
build_alias = @build_alias@
|
||||
build_cpu = @build_cpu@
|
||||
build_os = @build_os@
|
||||
build_vendor = @build_vendor@
|
||||
builddir = @builddir@
|
||||
datadir = @datadir@
|
||||
datarootdir = @datarootdir@
|
||||
docdir = @docdir@
|
||||
dvidir = @dvidir@
|
||||
exec_prefix = @exec_prefix@
|
||||
host = @host@
|
||||
host_alias = @host_alias@
|
||||
host_cpu = @host_cpu@
|
||||
host_os = @host_os@
|
||||
host_vendor = @host_vendor@
|
||||
htmldir = @htmldir@
|
||||
includedir = @includedir@
|
||||
infodir = @infodir@
|
||||
install_sh = @install_sh@
|
||||
libdir = @libdir@
|
||||
libexecdir = @libexecdir@
|
||||
localedir = @localedir@
|
||||
localedir_c = @localedir_c@
|
||||
localedir_c_make = @localedir_c_make@
|
||||
localstatedir = @localstatedir@
|
||||
mandir = @mandir@
|
||||
mkdir_p = @mkdir_p@
|
||||
oldincludedir = @oldincludedir@
|
||||
pdfdir = @pdfdir@
|
||||
pkgpyexecdir = @pkgpyexecdir@
|
||||
pkgpythondir = @pkgpythondir@
|
||||
prefix = @prefix@
|
||||
program_transform_name = @program_transform_name@
|
||||
psdir = @psdir@
|
||||
pyexecdir = @pyexecdir@
|
||||
pythondir = @pythondir@
|
||||
runstatedir = @runstatedir@
|
||||
sbindir = @sbindir@
|
||||
sharedstatedir = @sharedstatedir@
|
||||
srcdir = @srcdir@
|
||||
sysconfdir = @sysconfdir@
|
||||
target_alias = @target_alias@
|
||||
top_build_prefix = @top_build_prefix@
|
||||
top_builddir = @top_builddir@
|
||||
top_srcdir = @top_srcdir@
|
||||
man1_MANS = cthulhu.1
|
||||
EXTRA_DIST = \
|
||||
$(man1_MANS)
|
||||
|
||||
all: all-am
|
||||
|
||||
.SUFFIXES:
|
||||
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
|
||||
@for dep in $?; do \
|
||||
case '$(am__configure_deps)' in \
|
||||
*$$dep*) \
|
||||
( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
|
||||
&& { if test -f $@; then exit 0; else break; fi; }; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
done; \
|
||||
echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu docs/man/Makefile'; \
|
||||
$(am__cd) $(top_srcdir) && \
|
||||
$(AUTOMAKE) --gnu docs/man/Makefile
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||
@case '$?' in \
|
||||
*config.status*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
|
||||
*) \
|
||||
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
|
||||
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
|
||||
esac;
|
||||
|
||||
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(am__aclocal_m4_deps):
|
||||
install-man1: $(man1_MANS)
|
||||
@$(NORMAL_INSTALL)
|
||||
@list1='$(man1_MANS)'; \
|
||||
list2=''; \
|
||||
test -n "$(man1dir)" \
|
||||
&& test -n "`echo $$list1$$list2`" \
|
||||
|| exit 0; \
|
||||
echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \
|
||||
$(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \
|
||||
{ for i in $$list1; do echo "$$i"; done; \
|
||||
if test -n "$$list2"; then \
|
||||
for i in $$list2; do echo "$$i"; done \
|
||||
| sed -n '/\.1[a-z]*$$/p'; \
|
||||
fi; \
|
||||
} | while read p; do \
|
||||
if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
|
||||
echo "$$d$$p"; echo "$$p"; \
|
||||
done | \
|
||||
sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
|
||||
-e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
|
||||
sed 'N;N;s,\n, ,g' | { \
|
||||
list=; while read file base inst; do \
|
||||
if test "$$base" = "$$inst"; then list="$$list $$file"; else \
|
||||
echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \
|
||||
$(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \
|
||||
fi; \
|
||||
done; \
|
||||
for i in $$list; do echo "$$i"; done | $(am__base_list) | \
|
||||
while read files; do \
|
||||
test -z "$$files" || { \
|
||||
echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \
|
||||
$(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \
|
||||
done; }
|
||||
|
||||
uninstall-man1:
|
||||
@$(NORMAL_UNINSTALL)
|
||||
@list='$(man1_MANS)'; test -n "$(man1dir)" || exit 0; \
|
||||
files=`{ for i in $$list; do echo "$$i"; done; \
|
||||
} | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
|
||||
-e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
|
||||
dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir)
|
||||
tags TAGS:
|
||||
|
||||
ctags CTAGS:
|
||||
|
||||
cscope cscopelist:
|
||||
|
||||
|
||||
distdir: $(BUILT_SOURCES)
|
||||
$(MAKE) $(AM_MAKEFLAGS) distdir-am
|
||||
|
||||
distdir-am: $(DISTFILES)
|
||||
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||
list='$(DISTFILES)'; \
|
||||
dist_files=`for file in $$list; do echo $$file; done | \
|
||||
sed -e "s|^$$srcdirstrip/||;t" \
|
||||
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
|
||||
case $$dist_files in \
|
||||
*/*) $(MKDIR_P) `echo "$$dist_files" | \
|
||||
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
|
||||
sort -u` ;; \
|
||||
esac; \
|
||||
for file in $$dist_files; do \
|
||||
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||
if test -d $$d/$$file; then \
|
||||
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||
if test -d "$(distdir)/$$file"; then \
|
||||
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||
fi; \
|
||||
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
|
||||
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||
fi; \
|
||||
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
|
||||
else \
|
||||
test -f "$(distdir)/$$file" \
|
||||
|| cp -p $$d/$$file "$(distdir)/$$file" \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
check-am: all-am
|
||||
check: check-am
|
||||
all-am: Makefile $(MANS)
|
||||
installdirs:
|
||||
for dir in "$(DESTDIR)$(man1dir)"; do \
|
||||
test -z "$$dir" || $(MKDIR_P) "$$dir"; \
|
||||
done
|
||||
install: install-am
|
||||
install-exec: install-exec-am
|
||||
install-data: install-data-am
|
||||
uninstall: uninstall-am
|
||||
|
||||
install-am: all-am
|
||||
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||
|
||||
installcheck: installcheck-am
|
||||
install-strip:
|
||||
if test -z '$(STRIP)'; then \
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
install; \
|
||||
else \
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
|
||||
fi
|
||||
mostlyclean-generic:
|
||||
|
||||
clean-generic:
|
||||
|
||||
distclean-generic:
|
||||
-$(am__rm_f) $(CONFIG_CLEAN_FILES)
|
||||
-test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@echo "it deletes files that may require special tools to rebuild."
|
||||
clean: clean-am
|
||||
|
||||
clean-am: clean-generic mostlyclean-am
|
||||
|
||||
distclean: distclean-am
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-generic
|
||||
|
||||
dvi: dvi-am
|
||||
|
||||
dvi-am:
|
||||
|
||||
html: html-am
|
||||
|
||||
html-am:
|
||||
|
||||
info: info-am
|
||||
|
||||
info-am:
|
||||
|
||||
install-data-am: install-man
|
||||
|
||||
install-dvi: install-dvi-am
|
||||
|
||||
install-dvi-am:
|
||||
|
||||
install-exec-am:
|
||||
|
||||
install-html: install-html-am
|
||||
|
||||
install-html-am:
|
||||
|
||||
install-info: install-info-am
|
||||
|
||||
install-info-am:
|
||||
|
||||
install-man: install-man1
|
||||
|
||||
install-pdf: install-pdf-am
|
||||
|
||||
install-pdf-am:
|
||||
|
||||
install-ps: install-ps-am
|
||||
|
||||
install-ps-am:
|
||||
|
||||
installcheck-am:
|
||||
|
||||
maintainer-clean: maintainer-clean-am
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
mostlyclean: mostlyclean-am
|
||||
|
||||
mostlyclean-am: mostlyclean-generic
|
||||
|
||||
pdf: pdf-am
|
||||
|
||||
pdf-am:
|
||||
|
||||
ps: ps-am
|
||||
|
||||
ps-am:
|
||||
|
||||
uninstall-am: uninstall-man
|
||||
|
||||
uninstall-man: uninstall-man1
|
||||
|
||||
.MAKE: install-am install-strip
|
||||
|
||||
.PHONY: all all-am check check-am clean clean-generic cscopelist-am \
|
||||
ctags-am distclean distclean-generic distdir dvi dvi-am html \
|
||||
html-am info info-am install install-am install-data \
|
||||
install-data-am install-dvi install-dvi-am install-exec \
|
||||
install-exec-am install-html install-html-am install-info \
|
||||
install-info-am install-man install-man1 install-pdf \
|
||||
install-pdf-am install-ps install-ps-am install-strip \
|
||||
installcheck installcheck-am installdirs maintainer-clean \
|
||||
maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
|
||||
pdf-am ps ps-am tags-am uninstall uninstall-am uninstall-man \
|
||||
uninstall-man1
|
||||
|
||||
.PRECIOUS: Makefile
|
||||
|
||||
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
||||
|
||||
# Tell GNU make to disable its built-in pattern rules.
|
||||
%:: %,v
|
||||
%:: RCS/%,v
|
||||
%:: RCS/%
|
||||
%:: s.%
|
||||
%:: SCCS/s.%
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
project('cthulhu',
|
||||
version: '2025.08.11',
|
||||
version: '2025.12.09',
|
||||
meson_version: '>= 1.0.0',
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ if not python3.language_version().version_compare(f'>= @python_minimum_version@'
|
||||
endif
|
||||
|
||||
# Hard dependencies (checked via pkg-config)
|
||||
dependency('atspi-2', version: '>= 2.48.0')
|
||||
dependency('atspi-2', version: '>= 2.52.0')
|
||||
dependency('atk-bridge-2.0', version: '>= 2.26.0')
|
||||
dependency('pygobject-3.0', version: '>= 3.18')
|
||||
|
||||
@@ -54,6 +54,9 @@ optional_modules = {
|
||||
'dasbus': 'D-Bus remote controller',
|
||||
'psutil': 'system information commands',
|
||||
'gi.repository.Wnck': 'mouse review',
|
||||
'pdf2image': 'PDF processing for OCR',
|
||||
'scipy': 'Scientific computing for OCR analysis',
|
||||
'webcolors': 'Color name resolution for OCR',
|
||||
}
|
||||
|
||||
summary = {}
|
||||
|
||||
-652
@@ -1,652 +0,0 @@
|
||||
# Makefile.in generated by automake 1.18.1 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994-2025 Free Software Foundation, Inc.
|
||||
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
# with or without modifications, as long as this notice is preserved.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
@SET_MAKE@
|
||||
VPATH = @srcdir@
|
||||
am__is_gnu_make = { \
|
||||
if test -z '$(MAKELEVEL)'; then \
|
||||
false; \
|
||||
elif test -n '$(MAKE_HOST)'; then \
|
||||
true; \
|
||||
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
|
||||
true; \
|
||||
else \
|
||||
false; \
|
||||
fi; \
|
||||
}
|
||||
am__make_running_with_option = \
|
||||
case $${target_option-} in \
|
||||
?) ;; \
|
||||
*) echo "am__make_running_with_option: internal error: invalid" \
|
||||
"target option '$${target_option-}' specified" >&2; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
has_opt=no; \
|
||||
sane_makeflags=$$MAKEFLAGS; \
|
||||
if $(am__is_gnu_make); then \
|
||||
sane_makeflags=$$MFLAGS; \
|
||||
else \
|
||||
case $$MAKEFLAGS in \
|
||||
*\\[\ \ ]*) \
|
||||
bs=\\; \
|
||||
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
|
||||
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
|
||||
esac; \
|
||||
fi; \
|
||||
skip_next=no; \
|
||||
strip_trailopt () \
|
||||
{ \
|
||||
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
|
||||
}; \
|
||||
for flg in $$sane_makeflags; do \
|
||||
test $$skip_next = yes && { skip_next=no; continue; }; \
|
||||
case $$flg in \
|
||||
*=*|--*) continue;; \
|
||||
-*I) strip_trailopt 'I'; skip_next=yes;; \
|
||||
-*I?*) strip_trailopt 'I';; \
|
||||
-*O) strip_trailopt 'O'; skip_next=yes;; \
|
||||
-*O?*) strip_trailopt 'O';; \
|
||||
-*l) strip_trailopt 'l'; skip_next=yes;; \
|
||||
-*l?*) strip_trailopt 'l';; \
|
||||
-[dEDm]) skip_next=yes;; \
|
||||
-[JT]) skip_next=yes;; \
|
||||
esac; \
|
||||
case $$flg in \
|
||||
*$$target_option*) has_opt=yes; break;; \
|
||||
esac; \
|
||||
done; \
|
||||
test $$has_opt = yes
|
||||
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
|
||||
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
|
||||
am__rm_f = rm -f $(am__rm_f_notfound)
|
||||
am__rm_rf = rm -rf $(am__rm_f_notfound)
|
||||
pkgdatadir = $(datadir)/@PACKAGE@
|
||||
pkgincludedir = $(includedir)/@PACKAGE@
|
||||
pkglibdir = $(libdir)/@PACKAGE@
|
||||
pkglibexecdir = $(libexecdir)/@PACKAGE@
|
||||
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||
install_sh_DATA = $(install_sh) -c -m 644
|
||||
install_sh_PROGRAM = $(install_sh) -c
|
||||
install_sh_SCRIPT = $(install_sh) -c
|
||||
INSTALL_HEADER = $(INSTALL_DATA)
|
||||
transform = $(program_transform_name)
|
||||
NORMAL_INSTALL = :
|
||||
PRE_INSTALL = :
|
||||
POST_INSTALL = :
|
||||
NORMAL_UNINSTALL = :
|
||||
PRE_UNINSTALL = :
|
||||
POST_UNINSTALL = :
|
||||
build_triplet = @build@
|
||||
host_triplet = @host@
|
||||
subdir = src
|
||||
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||
am__aclocal_m4_deps = $(top_srcdir)/m4/build-to-host.m4 \
|
||||
$(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/host-cpu-c-abi.m4 \
|
||||
$(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
|
||||
$(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
|
||||
$(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/nls.m4 \
|
||||
$(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
|
||||
$(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
|
||||
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
|
||||
$(ACLOCAL_M4)
|
||||
DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
|
||||
mkinstalldirs = $(install_sh) -d
|
||||
CONFIG_CLEAN_FILES =
|
||||
CONFIG_CLEAN_VPATH_FILES =
|
||||
AM_V_P = $(am__v_P_@AM_V@)
|
||||
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
|
||||
am__v_P_0 = false
|
||||
am__v_P_1 = :
|
||||
AM_V_GEN = $(am__v_GEN_@AM_V@)
|
||||
am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
|
||||
am__v_GEN_0 = @echo " GEN " $@;
|
||||
am__v_GEN_1 =
|
||||
AM_V_at = $(am__v_at_@AM_V@)
|
||||
am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
|
||||
am__v_at_0 = @
|
||||
am__v_at_1 =
|
||||
SOURCES =
|
||||
DIST_SOURCES =
|
||||
RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
|
||||
ctags-recursive dvi-recursive html-recursive info-recursive \
|
||||
install-data-recursive install-dvi-recursive \
|
||||
install-exec-recursive install-html-recursive \
|
||||
install-info-recursive install-pdf-recursive \
|
||||
install-ps-recursive install-recursive installcheck-recursive \
|
||||
installdirs-recursive pdf-recursive ps-recursive \
|
||||
tags-recursive uninstall-recursive
|
||||
am__can_run_installinfo = \
|
||||
case $$AM_UPDATE_INFO_DIR in \
|
||||
n|no|NO) false;; \
|
||||
*) (install-info --version) >/dev/null 2>&1;; \
|
||||
esac
|
||||
RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
|
||||
distclean-recursive maintainer-clean-recursive
|
||||
am__recursive_targets = \
|
||||
$(RECURSIVE_TARGETS) \
|
||||
$(RECURSIVE_CLEAN_TARGETS) \
|
||||
$(am__extra_recursive_targets)
|
||||
AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
|
||||
distdir distdir-am
|
||||
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
|
||||
# Read a list of newline-separated strings from the standard input,
|
||||
# and print each of them once, without duplicates. Input order is
|
||||
# *not* preserved.
|
||||
am__uniquify_input = $(AWK) '\
|
||||
BEGIN { nonempty = 0; } \
|
||||
{ items[$$0] = 1; nonempty = 1; } \
|
||||
END { if (nonempty) { for (i in items) print i; }; } \
|
||||
'
|
||||
# Make sure the list of sources is unique. This is necessary because,
|
||||
# e.g., the same source file might be shared among _SOURCES variables
|
||||
# for different programs/libraries.
|
||||
am__define_uniq_tagged_files = \
|
||||
list='$(am__tagged_files)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | $(am__uniquify_input)`
|
||||
DIST_SUBDIRS = $(SUBDIRS)
|
||||
am__DIST_COMMON = $(srcdir)/Makefile.in
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
am__relativize = \
|
||||
dir0=`pwd`; \
|
||||
sed_first='s,^\([^/]*\)/.*$$,\1,'; \
|
||||
sed_rest='s,^[^/]*/*,,'; \
|
||||
sed_last='s,^.*/\([^/]*\)$$,\1,'; \
|
||||
sed_butlast='s,/*[^/]*$$,,'; \
|
||||
while test -n "$$dir1"; do \
|
||||
first=`echo "$$dir1" | sed -e "$$sed_first"`; \
|
||||
if test "$$first" != "."; then \
|
||||
if test "$$first" = ".."; then \
|
||||
dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
|
||||
dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
|
||||
else \
|
||||
first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
|
||||
if test "$$first2" = "$$first"; then \
|
||||
dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
|
||||
else \
|
||||
dir2="../$$dir2"; \
|
||||
fi; \
|
||||
dir0="$$dir0"/"$$first"; \
|
||||
fi; \
|
||||
fi; \
|
||||
dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
|
||||
done; \
|
||||
reldir="$$dir2"
|
||||
ACLOCAL = @ACLOCAL@
|
||||
AMTAR = @AMTAR@
|
||||
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
|
||||
ATKBRIDGE_CFLAGS = @ATKBRIDGE_CFLAGS@
|
||||
ATKBRIDGE_LIBS = @ATKBRIDGE_LIBS@
|
||||
ATSPI2_CFLAGS = @ATSPI2_CFLAGS@
|
||||
ATSPI2_LIBS = @ATSPI2_LIBS@
|
||||
AUTOCONF = @AUTOCONF@
|
||||
AUTOHEADER = @AUTOHEADER@
|
||||
AUTOMAKE = @AUTOMAKE@
|
||||
AWK = @AWK@
|
||||
CC = @CC@
|
||||
CCDEPMODE = @CCDEPMODE@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CSCOPE = @CSCOPE@
|
||||
CTAGS = @CTAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DESIRED_LINGUAS = @DESIRED_LINGUAS@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
ETAGS = @ETAGS@
|
||||
EXEEXT = @EXEEXT@
|
||||
GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
|
||||
GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
|
||||
GMSGFMT = @GMSGFMT@
|
||||
GMSGFMT_015 = @GMSGFMT_015@
|
||||
GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@
|
||||
GSTREAMER_LIBS = @GSTREAMER_LIBS@
|
||||
INSTALL = @INSTALL@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||
INSTALL_SCRIPT = @INSTALL_SCRIPT@
|
||||
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
|
||||
INTLLIBS = @INTLLIBS@
|
||||
INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBICONV = @LIBICONV@
|
||||
LIBINTL = @LIBINTL@
|
||||
LIBOBJS = @LIBOBJS@
|
||||
LIBPEAS_CFLAGS = @LIBPEAS_CFLAGS@
|
||||
LIBPEAS_LIBS = @LIBPEAS_LIBS@
|
||||
LIBS = @LIBS@
|
||||
LOUIS_TABLE_DIR = @LOUIS_TABLE_DIR@
|
||||
LTLIBICONV = @LTLIBICONV@
|
||||
LTLIBINTL = @LTLIBINTL@
|
||||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAINT = @MAINT@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
MKDIR_P = @MKDIR_P@
|
||||
MSGFMT = @MSGFMT@
|
||||
MSGMERGE = @MSGMERGE@
|
||||
MSGMERGE_FOR_MSGFMT_OPTION = @MSGMERGE_FOR_MSGFMT_OPTION@
|
||||
OBJEXT = @OBJEXT@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
PACKAGE_NAME = @PACKAGE_NAME@
|
||||
PACKAGE_STRING = @PACKAGE_STRING@
|
||||
PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
PACKAGE_URL = @PACKAGE_URL@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
PKG_CONFIG = @PKG_CONFIG@
|
||||
PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
|
||||
PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
|
||||
PLATFORM_PATH = @PLATFORM_PATH@
|
||||
POSUB = @POSUB@
|
||||
PYGOBJECT_CFLAGS = @PYGOBJECT_CFLAGS@
|
||||
PYGOBJECT_LIBS = @PYGOBJECT_LIBS@
|
||||
PYTHON = @PYTHON@
|
||||
PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
|
||||
PYTHON_PLATFORM = @PYTHON_PLATFORM@
|
||||
PYTHON_PREFIX = @PYTHON_PREFIX@
|
||||
PYTHON_VERSION = @PYTHON_VERSION@
|
||||
REVISION = @REVISION@
|
||||
SED = @SED@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
USE_NLS = @USE_NLS@
|
||||
VERSION = @VERSION@
|
||||
XGETTEXT = @XGETTEXT@
|
||||
XGETTEXT_015 = @XGETTEXT_015@
|
||||
XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
|
||||
abs_builddir = @abs_builddir@
|
||||
abs_srcdir = @abs_srcdir@
|
||||
abs_top_builddir = @abs_top_builddir@
|
||||
abs_top_srcdir = @abs_top_srcdir@
|
||||
ac_ct_CC = @ac_ct_CC@
|
||||
am__include = @am__include@
|
||||
am__leading_dot = @am__leading_dot@
|
||||
am__quote = @am__quote@
|
||||
am__rm_f_notfound = @am__rm_f_notfound@
|
||||
am__tar = @am__tar@
|
||||
am__untar = @am__untar@
|
||||
am__xargs_n = @am__xargs_n@
|
||||
bindir = @bindir@
|
||||
build = @build@
|
||||
build_alias = @build_alias@
|
||||
build_cpu = @build_cpu@
|
||||
build_os = @build_os@
|
||||
build_vendor = @build_vendor@
|
||||
builddir = @builddir@
|
||||
datadir = @datadir@
|
||||
datarootdir = @datarootdir@
|
||||
docdir = @docdir@
|
||||
dvidir = @dvidir@
|
||||
exec_prefix = @exec_prefix@
|
||||
host = @host@
|
||||
host_alias = @host_alias@
|
||||
host_cpu = @host_cpu@
|
||||
host_os = @host_os@
|
||||
host_vendor = @host_vendor@
|
||||
htmldir = @htmldir@
|
||||
includedir = @includedir@
|
||||
infodir = @infodir@
|
||||
install_sh = @install_sh@
|
||||
libdir = @libdir@
|
||||
libexecdir = @libexecdir@
|
||||
localedir = @localedir@
|
||||
localedir_c = @localedir_c@
|
||||
localedir_c_make = @localedir_c_make@
|
||||
localstatedir = @localstatedir@
|
||||
mandir = @mandir@
|
||||
mkdir_p = @mkdir_p@
|
||||
oldincludedir = @oldincludedir@
|
||||
pdfdir = @pdfdir@
|
||||
pkgpyexecdir = @pkgpyexecdir@
|
||||
pkgpythondir = @pkgpythondir@
|
||||
prefix = @prefix@
|
||||
program_transform_name = @program_transform_name@
|
||||
psdir = @psdir@
|
||||
pyexecdir = @pyexecdir@
|
||||
pythondir = @pythondir@
|
||||
runstatedir = @runstatedir@
|
||||
sbindir = @sbindir@
|
||||
sharedstatedir = @sharedstatedir@
|
||||
srcdir = @srcdir@
|
||||
sysconfdir = @sysconfdir@
|
||||
target_alias = @target_alias@
|
||||
top_build_prefix = @top_build_prefix@
|
||||
top_builddir = @top_builddir@
|
||||
top_srcdir = @top_srcdir@
|
||||
SUBDIRS = cthulhu
|
||||
all: all-recursive
|
||||
|
||||
.SUFFIXES:
|
||||
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
|
||||
@for dep in $?; do \
|
||||
case '$(am__configure_deps)' in \
|
||||
*$$dep*) \
|
||||
( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
|
||||
&& { if test -f $@; then exit 0; else break; fi; }; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
done; \
|
||||
echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
|
||||
$(am__cd) $(top_srcdir) && \
|
||||
$(AUTOMAKE) --gnu src/Makefile
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||
@case '$?' in \
|
||||
*config.status*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
|
||||
*) \
|
||||
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
|
||||
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
|
||||
esac;
|
||||
|
||||
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(am__aclocal_m4_deps):
|
||||
|
||||
# This directory's subdirectories are mostly independent; you can cd
|
||||
# into them and run 'make' without going through this Makefile.
|
||||
# To change the values of 'make' variables: instead of editing Makefiles,
|
||||
# (1) if the variable is set in 'config.status', edit 'config.status'
|
||||
# (which will cause the Makefiles to be regenerated when you run 'make');
|
||||
# (2) otherwise, pass the desired values on the 'make' command line.
|
||||
$(am__recursive_targets):
|
||||
@fail=; \
|
||||
if $(am__make_keepgoing); then \
|
||||
failcom='fail=yes'; \
|
||||
else \
|
||||
failcom='exit 1'; \
|
||||
fi; \
|
||||
dot_seen=no; \
|
||||
target=`echo $@ | sed s/-recursive//`; \
|
||||
case "$@" in \
|
||||
distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
|
||||
*) list='$(SUBDIRS)' ;; \
|
||||
esac; \
|
||||
for subdir in $$list; do \
|
||||
echo "Making $$target in $$subdir"; \
|
||||
if test "$$subdir" = "."; then \
|
||||
dot_seen=yes; \
|
||||
local_target="$$target-am"; \
|
||||
else \
|
||||
local_target="$$target"; \
|
||||
fi; \
|
||||
($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|
||||
|| eval $$failcom; \
|
||||
done; \
|
||||
if test "$$dot_seen" = "no"; then \
|
||||
$(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
|
||||
fi; test -z "$$fail"
|
||||
|
||||
ID: $(am__tagged_files)
|
||||
$(am__define_uniq_tagged_files); mkid -fID $$unique
|
||||
tags: tags-recursive
|
||||
TAGS: tags
|
||||
|
||||
tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
|
||||
set x; \
|
||||
here=`pwd`; \
|
||||
if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
|
||||
include_option=--etags-include; \
|
||||
empty_fix=.; \
|
||||
else \
|
||||
include_option=--include; \
|
||||
empty_fix=; \
|
||||
fi; \
|
||||
list='$(SUBDIRS)'; for subdir in $$list; do \
|
||||
if test "$$subdir" = .; then :; else \
|
||||
test ! -f $$subdir/TAGS || \
|
||||
set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
|
||||
fi; \
|
||||
done; \
|
||||
$(am__define_uniq_tagged_files); \
|
||||
shift; \
|
||||
if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
|
||||
test -n "$$unique" || unique=$$empty_fix; \
|
||||
if test $$# -gt 0; then \
|
||||
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||
"$$@" $$unique; \
|
||||
else \
|
||||
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||
$$unique; \
|
||||
fi; \
|
||||
fi
|
||||
ctags: ctags-recursive
|
||||
|
||||
CTAGS: ctags
|
||||
ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
|
||||
$(am__define_uniq_tagged_files); \
|
||||
test -z "$(CTAGS_ARGS)$$unique" \
|
||||
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
|
||||
$$unique
|
||||
|
||||
GTAGS:
|
||||
here=`$(am__cd) $(top_builddir) && pwd` \
|
||||
&& $(am__cd) $(top_srcdir) \
|
||||
&& gtags -i $(GTAGS_ARGS) "$$here"
|
||||
cscopelist: cscopelist-recursive
|
||||
|
||||
cscopelist-am: $(am__tagged_files)
|
||||
list='$(am__tagged_files)'; \
|
||||
case "$(srcdir)" in \
|
||||
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
|
||||
*) sdir=$(subdir)/$(srcdir) ;; \
|
||||
esac; \
|
||||
for i in $$list; do \
|
||||
if test -f "$$i"; then \
|
||||
echo "$(subdir)/$$i"; \
|
||||
else \
|
||||
echo "$$sdir/$$i"; \
|
||||
fi; \
|
||||
done >> $(top_builddir)/cscope.files
|
||||
|
||||
distclean-tags:
|
||||
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
|
||||
|
||||
distdir: $(BUILT_SOURCES)
|
||||
$(MAKE) $(AM_MAKEFLAGS) distdir-am
|
||||
|
||||
distdir-am: $(DISTFILES)
|
||||
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||
list='$(DISTFILES)'; \
|
||||
dist_files=`for file in $$list; do echo $$file; done | \
|
||||
sed -e "s|^$$srcdirstrip/||;t" \
|
||||
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
|
||||
case $$dist_files in \
|
||||
*/*) $(MKDIR_P) `echo "$$dist_files" | \
|
||||
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
|
||||
sort -u` ;; \
|
||||
esac; \
|
||||
for file in $$dist_files; do \
|
||||
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||
if test -d $$d/$$file; then \
|
||||
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||
if test -d "$(distdir)/$$file"; then \
|
||||
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||
fi; \
|
||||
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
|
||||
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||
fi; \
|
||||
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
|
||||
else \
|
||||
test -f "$(distdir)/$$file" \
|
||||
|| cp -p $$d/$$file "$(distdir)/$$file" \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
|
||||
if test "$$subdir" = .; then :; else \
|
||||
$(am__make_dryrun) \
|
||||
|| test -d "$(distdir)/$$subdir" \
|
||||
|| $(MKDIR_P) "$(distdir)/$$subdir" \
|
||||
|| exit 1; \
|
||||
dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
|
||||
$(am__relativize); \
|
||||
new_distdir=$$reldir; \
|
||||
dir1=$$subdir; dir2="$(top_distdir)"; \
|
||||
$(am__relativize); \
|
||||
new_top_distdir=$$reldir; \
|
||||
echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
|
||||
echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
|
||||
($(am__cd) $$subdir && \
|
||||
$(MAKE) $(AM_MAKEFLAGS) \
|
||||
top_distdir="$$new_top_distdir" \
|
||||
distdir="$$new_distdir" \
|
||||
am__remove_distdir=: \
|
||||
am__skip_length_check=: \
|
||||
am__skip_mode_fix=: \
|
||||
distdir) \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
check-am: all-am
|
||||
check: check-recursive
|
||||
all-am: Makefile
|
||||
installdirs: installdirs-recursive
|
||||
installdirs-am:
|
||||
install: install-recursive
|
||||
install-exec: install-exec-recursive
|
||||
install-data: install-data-recursive
|
||||
uninstall: uninstall-recursive
|
||||
|
||||
install-am: all-am
|
||||
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||
|
||||
installcheck: installcheck-recursive
|
||||
install-strip:
|
||||
if test -z '$(STRIP)'; then \
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
install; \
|
||||
else \
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
|
||||
fi
|
||||
mostlyclean-generic:
|
||||
|
||||
clean-generic:
|
||||
|
||||
distclean-generic:
|
||||
-$(am__rm_f) $(CONFIG_CLEAN_FILES)
|
||||
-test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@echo "it deletes files that may require special tools to rebuild."
|
||||
clean: clean-recursive
|
||||
|
||||
clean-am: clean-generic mostlyclean-am
|
||||
|
||||
distclean: distclean-recursive
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-generic distclean-tags
|
||||
|
||||
dvi: dvi-recursive
|
||||
|
||||
dvi-am:
|
||||
|
||||
html: html-recursive
|
||||
|
||||
html-am:
|
||||
|
||||
info: info-recursive
|
||||
|
||||
info-am:
|
||||
|
||||
install-data-am:
|
||||
|
||||
install-dvi: install-dvi-recursive
|
||||
|
||||
install-dvi-am:
|
||||
|
||||
install-exec-am:
|
||||
|
||||
install-html: install-html-recursive
|
||||
|
||||
install-html-am:
|
||||
|
||||
install-info: install-info-recursive
|
||||
|
||||
install-info-am:
|
||||
|
||||
install-man:
|
||||
|
||||
install-pdf: install-pdf-recursive
|
||||
|
||||
install-pdf-am:
|
||||
|
||||
install-ps: install-ps-recursive
|
||||
|
||||
install-ps-am:
|
||||
|
||||
installcheck-am:
|
||||
|
||||
maintainer-clean: maintainer-clean-recursive
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
mostlyclean: mostlyclean-recursive
|
||||
|
||||
mostlyclean-am: mostlyclean-generic
|
||||
|
||||
pdf: pdf-recursive
|
||||
|
||||
pdf-am:
|
||||
|
||||
ps: ps-recursive
|
||||
|
||||
ps-am:
|
||||
|
||||
uninstall-am:
|
||||
|
||||
.MAKE: $(am__recursive_targets) install-am install-strip
|
||||
|
||||
.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
|
||||
check-am clean clean-generic cscopelist-am ctags ctags-am \
|
||||
distclean distclean-generic distclean-tags distdir dvi dvi-am \
|
||||
html html-am info info-am install install-am install-data \
|
||||
install-data-am install-dvi install-dvi-am install-exec \
|
||||
install-exec-am install-html install-html-am install-info \
|
||||
install-info-am install-man install-pdf install-pdf-am \
|
||||
install-ps install-ps-am install-strip installcheck \
|
||||
installcheck-am installdirs installdirs-am maintainer-clean \
|
||||
maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
|
||||
pdf-am ps ps-am tags tags-am uninstall uninstall-am
|
||||
|
||||
.PRECIOUS: Makefile
|
||||
|
||||
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
||||
|
||||
# Tell GNU make to disable its built-in pattern rules.
|
||||
%:: %,v
|
||||
%:: RCS/%,v
|
||||
%:: RCS/%
|
||||
%:: s.%
|
||||
%:: SCCS/s.%
|
||||
@@ -7,6 +7,20 @@
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="ocrScaleAdjustment">
|
||||
<property name="lower">1</property>
|
||||
<property name="upper">10</property>
|
||||
<property name="value">3</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">1</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="ocrBlackWhiteValueAdjustment">
|
||||
<property name="lower">0</property>
|
||||
<property name="upper">255</property>
|
||||
<property name="value">200</property>
|
||||
<property name="step_increment">10</property>
|
||||
<property name="page_increment">50</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="liststore1">
|
||||
<columns>
|
||||
<!-- column-name gchararray1 -->
|
||||
@@ -3636,6 +3650,181 @@
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="ocrGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">12</property>
|
||||
<property name="margin_top">12</property>
|
||||
<property name="margin_bottom">12</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="column_spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ocrLanguageLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">_Language Code:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">ocrLanguageEntry</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="ocrLanguageEntry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="text" translatable="yes">eng</property>
|
||||
<signal name="changed" handler="ocrLanguageChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ocrScaleLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">_Scale Factor:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">ocrScaleSpinButton</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="ocrScaleSpinButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">ocrScaleAdjustment</property>
|
||||
<property name="value">3</property>
|
||||
<signal name="value-changed" handler="ocrScaleChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="ocrGrayscaleCheckButton">
|
||||
<property name="label" translatable="yes">_Grayscale Image</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="ocrGrayscaleToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="ocrInvertCheckButton">
|
||||
<property name="label" translatable="yes">_Invert Image</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="ocrInvertToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="ocrBlackWhiteCheckButton">
|
||||
<property name="label" translatable="yes">_Black and White Image</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="ocrBlackWhiteToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ocrBlackWhiteValueLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Black/White _Threshold:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">ocrBlackWhiteValueSpinButton</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="ocrBlackWhiteValueSpinButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">ocrBlackWhiteValueAdjustment</property>
|
||||
<property name="value">200</property>
|
||||
<signal name="value-changed" handler="ocrBlackWhiteValueChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="ocrColorCalculationCheckButton">
|
||||
<property name="label" translatable="yes">_Analyze Colors</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="ocrColorCalculationToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="ocrCopyToClipboardCheckButton">
|
||||
<property name="label" translatable="yes">Copy Results to _Clipboard</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="ocrCopyToClipboardToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">7</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="aiTabLabel">
|
||||
<property name="visible">True</property>
|
||||
@@ -3647,6 +3836,17 @@
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="ocrTabLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">OCR</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">9</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
||||
@@ -858,7 +858,21 @@ def _start_dbus_service():
|
||||
"""Starts the D-Bus remote controller service in an idle callback."""
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting D-Bus remote controller', True)
|
||||
try:
|
||||
# Start the D-Bus service
|
||||
dbus_service.get_remote_controller().start()
|
||||
|
||||
# Register speech and verbosity manager
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Registering SpeechManager D-Bus module', True)
|
||||
from . import speech_and_verbosity_manager
|
||||
speech_manager = speech_and_verbosity_manager.getManager()
|
||||
dbus_service.get_remote_controller().register_decorated_module("SpeechManager", speech_manager)
|
||||
|
||||
# Register typing echo presenter
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Registering TypingEchoManager D-Bus module', True)
|
||||
from . import typing_echo_presenter
|
||||
typing_echo_manager = typing_echo_presenter.getManager()
|
||||
dbus_service.get_remote_controller().register_decorated_module("TypingEchoManager", typing_echo_manager)
|
||||
|
||||
except Exception as e:
|
||||
msg = f"CTHULHU: Failed to start D-Bus service: {e}"
|
||||
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
# Fork of Orca Screen Reader (GNOME)
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
version = "2025.08.22"
|
||||
version = "2025.12.12"
|
||||
codeName = "master"
|
||||
|
||||
@@ -1821,6 +1821,10 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
# AI Assistant settings
|
||||
#
|
||||
self._initAIState()
|
||||
|
||||
# OCR Plugin settings
|
||||
#
|
||||
self._initOCRState()
|
||||
|
||||
def __initProfileCombo(self):
|
||||
"""Adding available profiles and setting active as the active one"""
|
||||
@@ -1945,6 +1949,47 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self.aiApiKeyEntry.set_placeholder_text("No API key needed - uses local Ollama")
|
||||
else:
|
||||
self.aiApiKeyEntry.set_placeholder_text("Path to API key file")
|
||||
|
||||
def _initOCRState(self):
|
||||
"""Initialize OCR Plugin tab widgets with current settings."""
|
||||
prefs = self.prefsDict
|
||||
|
||||
# Store widget references
|
||||
self.ocrLanguageEntry = self.get_widget("ocrLanguageEntry")
|
||||
self.ocrScaleSpinButton = self.get_widget("ocrScaleSpinButton")
|
||||
self.ocrGrayscaleCheckButton = self.get_widget("ocrGrayscaleCheckButton")
|
||||
self.ocrInvertCheckButton = self.get_widget("ocrInvertCheckButton")
|
||||
self.ocrBlackWhiteCheckButton = self.get_widget("ocrBlackWhiteCheckButton")
|
||||
self.ocrBlackWhiteValueSpinButton = self.get_widget("ocrBlackWhiteValueSpinButton")
|
||||
self.ocrColorCalculationCheckButton = self.get_widget("ocrColorCalculationCheckButton")
|
||||
self.ocrCopyToClipboardCheckButton = self.get_widget("ocrCopyToClipboardCheckButton")
|
||||
|
||||
# Set language code
|
||||
languageCode = prefs.get("ocrLanguageCode", settings.ocrLanguageCode)
|
||||
self.ocrLanguageEntry.set_text(languageCode)
|
||||
|
||||
# Set scale factor
|
||||
scaleFactor = prefs.get("ocrScaleFactor", settings.ocrScaleFactor)
|
||||
self.ocrScaleSpinButton.set_value(scaleFactor)
|
||||
|
||||
# Set checkboxes
|
||||
grayscale = prefs.get("ocrGrayscaleImg", settings.ocrGrayscaleImg)
|
||||
self.ocrGrayscaleCheckButton.set_active(grayscale)
|
||||
|
||||
invert = prefs.get("ocrInvertImg", settings.ocrInvertImg)
|
||||
self.ocrInvertCheckButton.set_active(invert)
|
||||
|
||||
blackWhite = prefs.get("ocrBlackWhiteImg", settings.ocrBlackWhiteImg)
|
||||
self.ocrBlackWhiteCheckButton.set_active(blackWhite)
|
||||
|
||||
blackWhiteValue = prefs.get("ocrBlackWhiteImgValue", settings.ocrBlackWhiteImgValue)
|
||||
self.ocrBlackWhiteValueSpinButton.set_value(blackWhiteValue)
|
||||
|
||||
colorCalculation = prefs.get("ocrColorCalculation", settings.ocrColorCalculation)
|
||||
self.ocrColorCalculationCheckButton.set_active(colorCalculation)
|
||||
|
||||
copyToClipboard = prefs.get("ocrCopyToClipboard", settings.ocrCopyToClipboard)
|
||||
self.ocrCopyToClipboardCheckButton.set_active(copyToClipboard)
|
||||
|
||||
def _updateCthulhuModifier(self):
|
||||
combobox = self.get_widget("cthulhuModifierComboBox")
|
||||
@@ -3835,4 +3880,37 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
if 0 <= activeIndex < len(qualities):
|
||||
self.prefsDict["aiScreenshotQuality"] = qualities[activeIndex]
|
||||
|
||||
# OCR Plugin Settings Handlers
|
||||
def ocrLanguageChanged(self, widget):
|
||||
"""OCR language code entry changed handler"""
|
||||
self.prefsDict["ocrLanguageCode"] = widget.get_text()
|
||||
|
||||
def ocrScaleChanged(self, widget):
|
||||
"""OCR scale factor spin button changed handler"""
|
||||
self.prefsDict["ocrScaleFactor"] = int(widget.get_value())
|
||||
|
||||
def ocrGrayscaleToggled(self, widget):
|
||||
"""OCR grayscale image checkbox toggled handler"""
|
||||
self.prefsDict["ocrGrayscaleImg"] = widget.get_active()
|
||||
|
||||
def ocrInvertToggled(self, widget):
|
||||
"""OCR invert image checkbox toggled handler"""
|
||||
self.prefsDict["ocrInvertImg"] = widget.get_active()
|
||||
|
||||
def ocrBlackWhiteToggled(self, widget):
|
||||
"""OCR black and white image checkbox toggled handler"""
|
||||
self.prefsDict["ocrBlackWhiteImg"] = widget.get_active()
|
||||
|
||||
def ocrBlackWhiteValueChanged(self, widget):
|
||||
"""OCR black/white threshold spin button changed handler"""
|
||||
self.prefsDict["ocrBlackWhiteImgValue"] = int(widget.get_value())
|
||||
|
||||
def ocrColorCalculationToggled(self, widget):
|
||||
"""OCR color calculation checkbox toggled handler"""
|
||||
self.prefsDict["ocrColorCalculation"] = widget.get_active()
|
||||
|
||||
def ocrCopyToClipboardToggled(self, widget):
|
||||
"""OCR copy to clipboard checkbox toggled handler"""
|
||||
self.prefsDict["ocrCopyToClipboard"] = widget.get_active()
|
||||
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ cthulhu_python_sources = files([
|
||||
'translation_context.py',
|
||||
'translation_manager.py',
|
||||
'tutorialgenerator.py',
|
||||
'typing_echo_presenter.py',
|
||||
'where_am_i_presenter.py',
|
||||
])
|
||||
|
||||
|
||||
@@ -46,9 +46,7 @@ class AIAssistant(Plugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the AI Assistant plugin."""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Use print to ensure we see this message
|
||||
print("DEBUG: AI ASSISTANT __init__ called")
|
||||
|
||||
logger.info("AI ASSISTANT: Plugin __init__ starting")
|
||||
logger.info("AI ASSISTANT: Plugin initialized successfully")
|
||||
|
||||
@@ -79,51 +77,40 @@ class AIAssistant(Plugin):
|
||||
# Prevent multiple activations
|
||||
if self._enabled:
|
||||
logger.info("AI ASSISTANT: Already activated, skipping")
|
||||
print("DEBUG: AI ASSISTANT already activated, skipping")
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("AI ASSISTANT: === Plugin activation starting ===")
|
||||
print("DEBUG: AI ASSISTANT activation starting")
|
||||
|
||||
|
||||
# Check if AI Assistant is enabled in settings
|
||||
enabled = self._settings_manager.getSetting('aiAssistantEnabled')
|
||||
logger.info(f"AI ASSISTANT: Enabled setting: {enabled}")
|
||||
print(f"DEBUG: AI ASSISTANT enabled setting: {enabled}")
|
||||
if not enabled:
|
||||
logger.info("AI Assistant is disabled in settings, skipping activation")
|
||||
print("DEBUG: AI Assistant disabled, skipping activation")
|
||||
return
|
||||
|
||||
# Load AI settings
|
||||
self._load_ai_settings()
|
||||
print(f"DEBUG: AI settings loaded - provider: {self._provider_type}")
|
||||
|
||||
|
||||
# Check if we have valid configuration
|
||||
config_valid = self._validate_configuration()
|
||||
logger.info(f"AI Assistant configuration valid: {config_valid}")
|
||||
print(f"DEBUG: AI Assistant configuration valid: {config_valid}")
|
||||
|
||||
|
||||
# Initialize AI provider (may fail but we still want menu access)
|
||||
if config_valid:
|
||||
provider_init = self._initialize_ai_provider()
|
||||
print(f"DEBUG: AI provider initialization: {provider_init}")
|
||||
else:
|
||||
logger.warning("AI Assistant configuration invalid, menu will show error messages")
|
||||
print("DEBUG: AI Assistant configuration invalid, menu will show error messages")
|
||||
provider_init = False
|
||||
|
||||
|
||||
# Always register keybindings so menu is accessible even with config issues
|
||||
self._register_keybindings()
|
||||
print("DEBUG: AI keybindings registered")
|
||||
|
||||
|
||||
self._enabled = True
|
||||
logger.info("AI Assistant plugin activated successfully")
|
||||
print("DEBUG: AI Assistant plugin activated successfully")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error activating AI Assistant plugin: {e}")
|
||||
print(f"DEBUG: Error activating AI Assistant plugin: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@@ -144,44 +131,35 @@ class AIAssistant(Plugin):
|
||||
"""Refresh plugin settings and reinitialize provider. Called when settings change."""
|
||||
try:
|
||||
logger.info("AI Assistant: Refreshing settings")
|
||||
print("DEBUG: AI Assistant refreshing settings")
|
||||
|
||||
|
||||
# Reload settings
|
||||
self._load_ai_settings()
|
||||
|
||||
|
||||
# Validate new configuration
|
||||
config_valid = self._validate_configuration()
|
||||
print(f"DEBUG: New configuration valid: {config_valid}")
|
||||
|
||||
|
||||
# Reinitialize provider if configuration is valid
|
||||
if config_valid:
|
||||
old_provider = self._ai_provider
|
||||
provider_init = self._initialize_ai_provider()
|
||||
print(f"DEBUG: Provider reinitialization: {provider_init}")
|
||||
if provider_init:
|
||||
logger.info(f"AI Assistant provider changed to: {self._provider_type}")
|
||||
print(f"DEBUG: Provider successfully changed to: {self._provider_type}")
|
||||
else:
|
||||
logger.warning("Failed to initialize new provider")
|
||||
print("DEBUG: Failed to initialize new provider")
|
||||
self._ai_provider = None
|
||||
else:
|
||||
logger.warning("New configuration invalid, clearing provider")
|
||||
print("DEBUG: New configuration invalid, clearing provider")
|
||||
self._ai_provider = None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing AI Assistant settings: {e}")
|
||||
print(f"DEBUG: Error refreshing settings: {e}")
|
||||
|
||||
def _load_ai_settings(self):
|
||||
"""Load AI Assistant settings from Cthulhu configuration."""
|
||||
try:
|
||||
# Get provider
|
||||
provider = self._settings_manager.getSetting('aiProvider')
|
||||
print(f"DEBUG: Raw provider setting: '{provider}'")
|
||||
self._provider_type = provider or settings.AI_PROVIDER_CLAUDE_CODE
|
||||
print(f"DEBUG: Final provider type: '{self._provider_type}'")
|
||||
|
||||
# Load API key from file
|
||||
api_key_file = self._settings_manager.getSetting('aiApiKeyFile')
|
||||
@@ -308,10 +286,9 @@ class AIAssistant(Plugin):
|
||||
"Show AI Assistant menu",
|
||||
'kb:cthulhu+shift+control+a'
|
||||
)
|
||||
|
||||
|
||||
logger.info("AI Assistant menu keybinding registered")
|
||||
print(f"DEBUG: AI Assistant menu keybinding registered: {self._kb_binding_menu}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error registering AI menu keybinding: {e}")
|
||||
|
||||
@@ -325,22 +302,18 @@ class AIAssistant(Plugin):
|
||||
"""Show the AI Assistant menu."""
|
||||
try:
|
||||
logger.info("AI ASSISTANT: _show_ai_menu called!")
|
||||
print("DEBUG: AI ASSISTANT _show_ai_menu called!")
|
||||
|
||||
|
||||
# IMPORTANT: Capture screen data BEFORE showing menu
|
||||
# This ensures we get the actual screen content, not the menu itself
|
||||
self._pre_menu_screen_data = self._collect_ai_data()
|
||||
logger.info("Pre-captured screen data for menu actions")
|
||||
print("DEBUG: Pre-captured screen data for menu actions")
|
||||
|
||||
|
||||
# Now show the menu
|
||||
self._menu_gui = AIAssistantMenu(self._handle_menu_selection)
|
||||
self._menu_gui.show_gui()
|
||||
print("DEBUG: AI menu GUI shown")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error showing AI menu: {e}")
|
||||
print(f"DEBUG: Error showing AI menu: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
@@ -407,22 +380,17 @@ class AIAssistant(Plugin):
|
||||
"""Handle browsing for an image file to analyze."""
|
||||
try:
|
||||
logger.info("AI image file browsing requested")
|
||||
print("DEBUG: _handle_browse_image_file called")
|
||||
|
||||
|
||||
if not self._enabled:
|
||||
print("DEBUG: AI Assistant not enabled")
|
||||
self._present_message("AI Assistant is not enabled")
|
||||
return True
|
||||
|
||||
|
||||
if not self._ai_provider:
|
||||
print("DEBUG: AI provider not available")
|
||||
self._present_message("AI provider not available. Check configuration.")
|
||||
return True
|
||||
|
||||
|
||||
# Show file chooser dialog
|
||||
print("DEBUG: About to show file chooser")
|
||||
image_file = self._show_image_file_chooser()
|
||||
print(f"DEBUG: File chooser returned: {image_file}")
|
||||
|
||||
if image_file:
|
||||
provider_name = self._provider_type.replace('_', ' ').title()
|
||||
@@ -536,10 +504,8 @@ class AIAssistant(Plugin):
|
||||
"""Handle main AI Assistant activation - now shows action dialog."""
|
||||
try:
|
||||
logger.info("AI Assistant activation requested")
|
||||
print("DEBUG: AI Assistant activation keybinding triggered!")
|
||||
|
||||
|
||||
if not self._enabled:
|
||||
print("DEBUG: AI Assistant not enabled, presenting message")
|
||||
self._present_message("AI Assistant is not enabled")
|
||||
return True
|
||||
|
||||
@@ -2077,13 +2043,9 @@ class AIAssistantMenu(Gtk.Dialog):
|
||||
|
||||
# Connect keyboard events for Enter key handling
|
||||
self.connect("key-press-event", self._on_key_press)
|
||||
|
||||
print("DEBUG: AIAssistantMenu dialog created with radio buttons")
|
||||
|
||||
def _on_response(self, dialog, response_id):
|
||||
"""Handler for dialog response."""
|
||||
print(f"DEBUG: Dialog response: {response_id}")
|
||||
|
||||
if response_id == Gtk.ResponseType.OK:
|
||||
# Determine which radio button is selected
|
||||
if self.radio_ask.get_active():
|
||||
@@ -2098,9 +2060,8 @@ class AIAssistantMenu(Gtk.Dialog):
|
||||
action_id = "browse_image_file"
|
||||
else:
|
||||
action_id = None
|
||||
|
||||
|
||||
if action_id:
|
||||
print(f"DEBUG: Selected action: {action_id}")
|
||||
self.on_option_selected(action_id)
|
||||
|
||||
self.destroy()
|
||||
@@ -2116,15 +2077,12 @@ class AIAssistantMenu(Gtk.Dialog):
|
||||
def show_gui(self):
|
||||
"""Shows the AI Assistant dialog."""
|
||||
try:
|
||||
print("DEBUG: Starting dialog show_gui")
|
||||
self.show_all()
|
||||
print("DEBUG: Dialog show_all() called - should be visible and accessible now")
|
||||
|
||||
|
||||
# Present the dialog to ensure it gets focus
|
||||
self.present()
|
||||
print("DEBUG: Dialog presented")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error in show_gui: {e}")
|
||||
logger.error(f"Error in show_gui: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
# OCR Plugin for Cthulhu Screen Reader
|
||||
|
||||
A powerful OCR (Optical Character Recognition) plugin that enables Cthulhu users to extract text from visual content including windows, desktop areas, and clipboard images. Originally based on the ocrdesktop project by Chrys, this plugin integrates seamlessly with Cthulhu's accessibility framework.
|
||||
A powerful OCR (Optical Character Recognition) plugin that enables Cthulhu users to extract text from visual content and interact with it through precise coordinate mapping. Originally based on the ocrdesktop project by Chrys, this plugin has been enhanced with interactive features and comprehensive settings integration.
|
||||
|
||||
## Features
|
||||
|
||||
### 🔍 **OCR Operations**
|
||||
- **Window OCR**: Extract text from the currently active window
|
||||
- **Desktop OCR**: Extract text from the entire desktop screen
|
||||
- **Clipboard OCR**: Extract text from images copied to the clipboard
|
||||
- **Voice Announcements**: Clear audio feedback about OCR operations
|
||||
- **Multi-threading**: Non-blocking OCR processing with progress tracking
|
||||
- **Text Cleanup**: Automatic post-processing to improve OCR text quality
|
||||
- **Interactive OCR**: OCR with coordinate mapping for clicking and navigation
|
||||
|
||||
### 🎯 **Interactive Coordinate Mapping**
|
||||
- **Precise Clicking**: Click any text found in OCR results using exact coordinates
|
||||
- **Dual View Modes**: Toggle between text view and interactive coordinate table
|
||||
- **Safety Confirmation**: Preview click coordinates before executing
|
||||
- **Real-time Navigation**: Browse OCR results and click immediately
|
||||
|
||||
### ⚙️ **Comprehensive Settings**
|
||||
- **Language Configuration**: Support for all Tesseract language packs
|
||||
- **Image Processing**: Grayscale, invert, black/white, and scaling options
|
||||
- **Clipboard Integration**: Automatic copying of OCR results to clipboard
|
||||
- **Quality Tuning**: Adjustable parameters for optimal OCR accuracy
|
||||
|
||||
### 🖥️ **Accessibility Integration**
|
||||
- **Voice Announcements**: Clear audio feedback for all operations
|
||||
- **Keyboard Navigation**: Full keyboard control of interactive features
|
||||
- **Settings GUI**: Integrated settings tab in Cthulhu preferences
|
||||
- **Non-blocking Processing**: Multi-threaded operation with progress tracking
|
||||
|
||||
## Keybindings
|
||||
|
||||
@@ -18,6 +35,15 @@ A powerful OCR (Optical Character Recognition) plugin that enables Cthulhu users
|
||||
| `Cthulhu+Control+W` | OCR Active Window | Performs OCR on the currently focused window |
|
||||
| `Cthulhu+Control+D` | OCR Desktop | Performs OCR on the entire desktop screen |
|
||||
| `Cthulhu+Control+Shift+C` | OCR Clipboard | Performs OCR on image data from clipboard |
|
||||
| `Cthulhu+Control+F` | **Interactive OCR** | **Opens OCR results window with coordinate mapping** |
|
||||
|
||||
### Interactive OCR Window Controls
|
||||
| Key | Action | Description |
|
||||
|-----|--------|-------------|
|
||||
| `Alt+V` | Toggle View | Switch between text view and coordinate table |
|
||||
| `Enter` | Click Selected | Click the text at the selected coordinates |
|
||||
| `Escape` | Close Window | Close the OCR results window |
|
||||
| `Arrow Keys` | Navigate | Move through OCR results in table view |
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -67,34 +93,68 @@ To add support for other languages, install additional Tesseract language packs:
|
||||
- Wait for processing to complete
|
||||
- OCR results will be announced via speech
|
||||
|
||||
3. **Best Practices**:
|
||||
3. **Interactive OCR Workflow**:
|
||||
- Press `Cthulhu+Control+F` to open OCR results window
|
||||
- Wait for "Performing OCR on window for interactive results"
|
||||
- Use `Alt+V` to toggle between text and coordinate table views
|
||||
- Navigate with arrow keys in table view to find desired text
|
||||
- Press `Enter` to click on the selected text location
|
||||
- Confirm the click action in the safety dialog
|
||||
|
||||
4. **Best Practices**:
|
||||
- Ensure good contrast between text and background for better results
|
||||
- Use window OCR for focused content (faster processing)
|
||||
- Use desktop OCR for content spanning multiple windows
|
||||
- Use clipboard OCR for images from web browsers or image viewers
|
||||
- Enable "Copy Results to Clipboard" for easy text retrieval
|
||||
- Adjust scale factor for small or blurry text (try 5-7)
|
||||
|
||||
## Configuration
|
||||
|
||||
### OCR Settings
|
||||
The plugin uses the following default settings (configurable in plugin.py):
|
||||
### OCR Settings GUI
|
||||
Access comprehensive OCR settings through Cthulhu Preferences:
|
||||
|
||||
```python
|
||||
self._languageCode = 'eng' # Tesseract language code
|
||||
self._scaleFactor = 3 # Image scaling for better OCR
|
||||
self._grayscaleImg = False # Convert to grayscale
|
||||
self._invertImg = False # Invert image colors
|
||||
self._blackWhiteImg = False # Convert to black/white
|
||||
self._blackWhiteImgValue = 200 # B/W threshold value
|
||||
```
|
||||
1. **Open Cthulhu Preferences**: `~/.local/bin/cthulhu -s`
|
||||
2. **Navigate to OCR Tab**: Use keyboard navigation to find the OCR settings tab
|
||||
3. **Configure Settings**: Adjust all OCR parameters through the accessible interface
|
||||
|
||||
### Changing OCR Language
|
||||
To change the default OCR language, modify `self._languageCode` in the plugin's `__init__` method:
|
||||
### Available Settings
|
||||
|
||||
```python
|
||||
# Examples:
|
||||
self._languageCode = 'fra' # French
|
||||
self._languageCode = 'deu' # German
|
||||
self._languageCode = 'spa' # Spanish
|
||||
#### **Language Configuration**
|
||||
- **Language Code**: Tesseract language pack to use (default: 'eng')
|
||||
- Examples: 'fra' (French), 'deu' (German), 'spa' (Spanish)
|
||||
- Use '+' for multiple languages: 'eng+fra' for English and French
|
||||
|
||||
#### **Image Processing**
|
||||
- **Scale Factor**: Image scaling multiplier (1-10, default: 3)
|
||||
- Higher values improve OCR accuracy for small text
|
||||
- Lower values process faster but may miss details
|
||||
- **Grayscale Image**: Convert to grayscale for better text recognition
|
||||
- **Invert Image**: Invert colors (useful for white text on dark backgrounds)
|
||||
- **Black and White Image**: Convert to pure black/white with threshold
|
||||
- **Black/White Threshold**: Threshold value for black/white conversion (0-255, default: 200)
|
||||
|
||||
#### **Advanced Features**
|
||||
- **Analyze Colors**: Extract color information from OCR regions (requires scipy/webcolors)
|
||||
- **Copy Results to Clipboard**: Automatically copy all OCR results to system clipboard
|
||||
|
||||
### Configuration File
|
||||
Settings are automatically stored in Cthulhu's configuration system:
|
||||
- **Global Settings**: `~/.local/share/cthulhu/user-settings.conf`
|
||||
- **Profile Settings**: `~/.local/share/cthulhu/app-settings/[profile]/`
|
||||
|
||||
### Example Configuration Values
|
||||
```json
|
||||
{
|
||||
"ocrLanguageCode": "eng",
|
||||
"ocrScaleFactor": 3,
|
||||
"ocrGrayscaleImg": false,
|
||||
"ocrInvertImg": false,
|
||||
"ocrBlackWhiteImg": false,
|
||||
"ocrBlackWhiteImgValue": 200,
|
||||
"ocrColorCalculation": false,
|
||||
"ocrCopyToClipboard": true
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -127,6 +187,28 @@ self._languageCode = 'spa' # Spanish
|
||||
- Test other Cthulhu speech functions
|
||||
- Verify audio system is working
|
||||
|
||||
#### Interactive OCR window doesn't open
|
||||
- **Cause**: GTK dependencies missing or display issues
|
||||
- **Solutions**:
|
||||
- Ensure GTK3 development packages are installed
|
||||
- Check display/Wayland/X11 compatibility
|
||||
- Verify Cthulhu GUI components are working
|
||||
|
||||
#### Click coordinates are inaccurate
|
||||
- **Cause**: Window movement, scaling, or coordinate calculation errors
|
||||
- **Solutions**:
|
||||
- Ensure window hasn't moved since OCR capture
|
||||
- Try recapturing with `Cthulhu+Control+F`
|
||||
- Check display scaling settings
|
||||
- Verify no window decoration changes occurred
|
||||
|
||||
#### Clipboard copy not working
|
||||
- **Cause**: Clipboard setting disabled or GTK clipboard issues
|
||||
- **Solutions**:
|
||||
- Enable "Copy Results to Clipboard" in OCR settings
|
||||
- Test clipboard functionality with other applications
|
||||
- Check GTK clipboard permissions
|
||||
|
||||
### Debug Information
|
||||
OCR plugin debug messages are logged to Cthulhu's debug output. To enable debug logging:
|
||||
|
||||
@@ -176,8 +258,18 @@ src/cthulhu/plugins/OCR/
|
||||
- `_ocrActiveWindow()`: Captures and OCRs active window
|
||||
- `_ocrDesktop()`: Captures and OCRs entire desktop
|
||||
- `_ocrClipboard()`: OCRs image from clipboard
|
||||
- `_performOCR()`: Core OCR processing logic
|
||||
- `_presentOCRResult()`: Announces results via speech
|
||||
- `_showOCRResultsWindow()`: **NEW** - Interactive OCR with coordinate mapping
|
||||
- `_performOCR()`: Core OCR processing with coordinate extraction
|
||||
- `_presentOCRResult()`: Announces results via speech and clipboard
|
||||
- `_createOCRResultsWindow()`: **NEW** - Creates interactive GTK results window
|
||||
- `_clickSelectedText()`: **NEW** - Executes click at OCR coordinates
|
||||
|
||||
### Interactive Features Architecture
|
||||
- **Coordinate Mapping**: Uses `pytesseract.image_to_data()` to extract word positions
|
||||
- **Screen Transformation**: Converts OCR coordinates to actual screen coordinates
|
||||
- **GTK Interface**: Accessible results window with text and table views
|
||||
- **Click Safety**: Confirmation dialogs before executing click actions
|
||||
- **Settings Integration**: Full integration with Cthulhu's preferences system
|
||||
|
||||
### Extending the Plugin
|
||||
To add new OCR modes or features:
|
||||
@@ -186,11 +278,30 @@ To add new OCR modes or features:
|
||||
2. Create handler method following pattern `_ocrNewMode()`
|
||||
3. Implement image capture logic for new mode
|
||||
4. Use existing `_performOCR()` and `_presentOCRResult()` methods
|
||||
5. For interactive features, extend `_createOCRResultsWindow()` functionality
|
||||
|
||||
## Version History
|
||||
|
||||
### Version 2.0 (Enhanced Interactive Features)
|
||||
- **Interactive OCR Window**: `Cthulhu+Control+F` for coordinate mapping
|
||||
- **Precise Clicking**: Click any text found in OCR results
|
||||
- **Settings Integration**: Full GUI settings tab in Cthulhu preferences
|
||||
- **Clipboard Integration**: Automatic copying with toggle setting
|
||||
- **Dual View Modes**: Text view and coordinate table with Alt+V toggle
|
||||
- **Safety Features**: Click confirmation dialogs
|
||||
- **Enhanced Processing**: Coordinate extraction with quality metrics
|
||||
|
||||
### Version 1.0 (Original Implementation)
|
||||
- Basic OCR for window, desktop, and clipboard
|
||||
- Text extraction and speech output
|
||||
- Multi-threading support
|
||||
- Text cleanup and formatting
|
||||
|
||||
## Credits
|
||||
|
||||
- **Original ocrdesktop**: Created by Chrys (chrys87@users.noreply.github.com)
|
||||
- **Cthulhu Integration**: Adapted by Storm Dragon for Cthulhu plugin system
|
||||
- **Interactive Features**: Enhanced coordinate mapping and GUI integration
|
||||
- **Cthulhu Screen Reader**: https://git.stormux.org/storm/cthulhu
|
||||
- **Tesseract OCR**: https://github.com/tesseract-ocr/tesseract
|
||||
|
||||
|
||||
@@ -20,8 +20,13 @@ import tempfile
|
||||
import threading
|
||||
from mimetypes import MimeTypes
|
||||
|
||||
import gi
|
||||
gi.require_version('Atspi', '2.0')
|
||||
from gi.repository import Atspi
|
||||
|
||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||
from cthulhu import debug
|
||||
from cthulhu import settings_manager
|
||||
|
||||
# Note: Removed complex beep system - simple announcements work perfectly!
|
||||
|
||||
@@ -88,8 +93,12 @@ class OCRDesktop(Plugin):
|
||||
self._kb_binding_window = None
|
||||
self._kb_binding_desktop = None
|
||||
self._kb_binding_clipboard = None
|
||||
self._kb_binding_results_window = None
|
||||
|
||||
# OCR settings
|
||||
# Settings manager
|
||||
self._settings_manager = settings_manager.getManager()
|
||||
|
||||
# OCR settings (will be loaded from settings)
|
||||
self._languageCode = 'eng'
|
||||
self._scaleFactor = 3
|
||||
self._grayscaleImg = False
|
||||
@@ -98,15 +107,24 @@ class OCRDesktop(Plugin):
|
||||
self._blackWhiteImgValue = 200
|
||||
self._colorCalculation = False
|
||||
self._colorCalculationMax = 3
|
||||
self._copyToClipboard = False
|
||||
|
||||
# Internal state
|
||||
self._img = []
|
||||
self._modifiedImg = []
|
||||
self._OCRText = ''
|
||||
self._OCRWords = {}
|
||||
self._OCRWordList = []
|
||||
self._offsetXpos = 0
|
||||
self._offsetYpos = 0
|
||||
self._activated = False
|
||||
|
||||
# OCR Results Window
|
||||
self._results_window = None
|
||||
self._results_tree = None
|
||||
self._results_textview = None
|
||||
self._current_view_mode = 0 # 0 = text, 1 = tree
|
||||
|
||||
# Progress feedback
|
||||
self._is_processing = False
|
||||
|
||||
@@ -118,6 +136,9 @@ class OCRDesktop(Plugin):
|
||||
# Set locale for tesseract
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
# Load OCR settings from configuration
|
||||
self._loadOCRSettings()
|
||||
|
||||
# Check dependencies
|
||||
self._checkDependencies()
|
||||
|
||||
@@ -138,6 +159,23 @@ class OCRDesktop(Plugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _loadOCRSettings(self):
|
||||
"""Load OCR settings from Cthulhu configuration."""
|
||||
try:
|
||||
self._languageCode = self._settings_manager.getSetting('ocrLanguageCode') or 'eng'
|
||||
self._scaleFactor = self._settings_manager.getSetting('ocrScaleFactor') or 3
|
||||
self._grayscaleImg = self._settings_manager.getSetting('ocrGrayscaleImg') or False
|
||||
self._invertImg = self._settings_manager.getSetting('ocrInvertImg') or False
|
||||
self._blackWhiteImg = self._settings_manager.getSetting('ocrBlackWhiteImg') or False
|
||||
self._blackWhiteImgValue = self._settings_manager.getSetting('ocrBlackWhiteImgValue') or 200
|
||||
self._colorCalculation = self._settings_manager.getSetting('ocrColorCalculation') or False
|
||||
self._colorCalculationMax = self._settings_manager.getSetting('ocrColorCalculationMax') or 3
|
||||
self._copyToClipboard = self._settings_manager.getSetting('ocrCopyToClipboard') or False
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCR settings loaded: lang={self._languageCode}, scale={self._scaleFactor}, clipboard={self._copyToClipboard}", True)
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCR settings load error: {e}, using defaults", True)
|
||||
|
||||
@cthulhu_hookimpl
|
||||
def activate(self, plugin=None):
|
||||
"""Activate the plugin."""
|
||||
@@ -179,6 +217,19 @@ class OCRDesktop(Plugin):
|
||||
self._activated = False
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Plugin deactivated", True)
|
||||
|
||||
def refresh_settings(self):
|
||||
"""Refresh plugin settings when configuration changes."""
|
||||
try:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Refreshing settings", True)
|
||||
|
||||
# Reload OCR settings from configuration
|
||||
self._loadOCRSettings()
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Settings refreshed successfully", True)
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error refreshing settings: {e}", True)
|
||||
|
||||
def _registerKeybindings(self):
|
||||
"""Register plugin keybindings."""
|
||||
try:
|
||||
@@ -203,6 +254,13 @@ class OCRDesktop(Plugin):
|
||||
'kb:cthulhu+control+shift+c'
|
||||
)
|
||||
|
||||
# OCR results window
|
||||
self._kb_binding_results_window = self.registerGestureByString(
|
||||
self._showOCRResultsWindow,
|
||||
"Show OCR results window for current window",
|
||||
'kb:cthulhu+control+f'
|
||||
)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Keybindings registered", True)
|
||||
|
||||
except Exception as e:
|
||||
@@ -409,18 +467,27 @@ class OCRDesktop(Plugin):
|
||||
return modifiedImg
|
||||
|
||||
def _performOCR(self):
|
||||
"""Perform OCR on captured images."""
|
||||
"""Perform OCR on captured images with coordinate data extraction."""
|
||||
if not PYTESSERACT_AVAILABLE:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Tesseract not available", True)
|
||||
return
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Starting OCR", True)
|
||||
self._OCRText = ''
|
||||
self._OCRWords = {}
|
||||
self._OCRWordList = []
|
||||
|
||||
for img in self._img:
|
||||
modifiedImg = self._transformImg(img)
|
||||
try:
|
||||
# Simple text extraction
|
||||
# Extract coordinate data using image_to_data
|
||||
from pytesseract import Output
|
||||
OCRWords = pytesseract.image_to_data(modifiedImg, output_type=Output.DICT,
|
||||
lang=self._languageCode, config='--psm 4')
|
||||
self._appendToOCRWords(OCRWords)
|
||||
self._processOCRWords(OCRWords, modifiedImg)
|
||||
|
||||
# Also extract simple text for speech output
|
||||
text = pytesseract.image_to_string(modifiedImg, lang=self._languageCode, config='--psm 4')
|
||||
self._OCRText += text + '\n'
|
||||
except Exception as e:
|
||||
@@ -433,31 +500,36 @@ class OCRDesktop(Plugin):
|
||||
def _cleanOCRText(self):
|
||||
"""Clean up OCR text output."""
|
||||
# Remove multiple spaces
|
||||
regexSpace = re.compile('[^\S\r\n]{2,}')
|
||||
regexSpace = re.compile(r'[^\S\r\n]{2,}')
|
||||
self._OCRText = regexSpace.sub(' ', self._OCRText)
|
||||
|
||||
# Remove empty lines
|
||||
regexSpace = re.compile('\n\s*\n')
|
||||
regexSpace = re.compile(r'\n\s*\n')
|
||||
self._OCRText = regexSpace.sub('\n', self._OCRText)
|
||||
|
||||
# Remove trailing spaces
|
||||
regexSpace = re.compile('\s*\n')
|
||||
regexSpace = re.compile(r'\s*\n')
|
||||
self._OCRText = regexSpace.sub('\n', self._OCRText)
|
||||
|
||||
# Remove leading spaces
|
||||
regexSpace = re.compile('^\s')
|
||||
regexSpace = re.compile(r'^\s')
|
||||
self._OCRText = regexSpace.sub('', self._OCRText)
|
||||
|
||||
# Remove trailing newlines
|
||||
self._OCRText = self._OCRText.strip()
|
||||
|
||||
def _presentOCRResult(self):
|
||||
"""Present OCR result to user via speech."""
|
||||
"""Present OCR result to user via speech and optionally copy to clipboard."""
|
||||
try:
|
||||
if not self._OCRText.strip():
|
||||
message = "No text found in OCR scan"
|
||||
else:
|
||||
message = f"OCR result: {self._OCRText}"
|
||||
|
||||
# Copy to clipboard if enabled
|
||||
if self._copyToClipboard:
|
||||
self._copyTextToClipboard(self._OCRText)
|
||||
message += " (copied to clipboard)"
|
||||
|
||||
if self.app:
|
||||
state = self.app.getDynamicApiManager().getAPI('CthulhuState')
|
||||
@@ -467,4 +539,334 @@ class OCRDesktop(Plugin):
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Presented result: {len(self._OCRText)} characters", True)
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error presenting result: {e}", True)
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error presenting result: {e}", True)
|
||||
|
||||
def _appendToOCRWords(self, OCRWords):
|
||||
"""Append OCR words to the main OCR data structure."""
|
||||
for k, v in OCRWords.items():
|
||||
try:
|
||||
x = self._OCRWords[k]
|
||||
if isinstance(v, list):
|
||||
self._OCRWords[k].extend(v)
|
||||
except KeyError:
|
||||
self._OCRWords[k] = v
|
||||
|
||||
def _processOCRWords(self, OCRWords, img):
|
||||
"""Process OCR words to extract coordinate data."""
|
||||
boxCounter = len(OCRWords['level'])
|
||||
if boxCounter == 0:
|
||||
return False
|
||||
|
||||
lastPage = -1
|
||||
lastBlock = -1
|
||||
lastPar = -1
|
||||
lastLine = -1
|
||||
|
||||
for i in range(boxCounter):
|
||||
if (len(OCRWords['text'][i]) == 0) or OCRWords['text'][i].isspace():
|
||||
continue
|
||||
|
||||
# Add word to coordinate list
|
||||
self._OCRWordList.append([
|
||||
OCRWords['text'][i], # Text
|
||||
round(OCRWords['height'][i] / 3 * 0.78, 0), # Calculated fontsize
|
||||
self._getColorString(OCRWords, i, img), # Color info
|
||||
'text', # Object type
|
||||
int(OCRWords['width'][i] / 2 + OCRWords['left'][i]), # X coordinate (center)
|
||||
int(OCRWords['height'][i] / 2 + OCRWords['top'][i]), # Y coordinate (center)
|
||||
int(float(OCRWords['conf'][i])) # Confidence
|
||||
])
|
||||
|
||||
lastPage = OCRWords['page_num'][i]
|
||||
lastBlock = OCRWords['block_num'][i]
|
||||
lastPar = OCRWords['par_num'][i]
|
||||
lastLine = OCRWords['line_num'][i]
|
||||
|
||||
return True
|
||||
|
||||
def _getColorString(self, box, index, img):
|
||||
"""Get color information for OCR text (simplified version)."""
|
||||
if not self._colorCalculation:
|
||||
return 'unknown'
|
||||
if not SCIPY_AVAILABLE or not WEBCOLORS_AVAILABLE:
|
||||
return 'unknown'
|
||||
|
||||
# Simplified color calculation - just return "unknown" for now
|
||||
# Full implementation would require the color analysis from ocrdesktop
|
||||
return 'unknown'
|
||||
|
||||
def _showOCRResultsWindow(self, script=None, inputEvent=None):
|
||||
"""Show OCR results window for current window with coordinate mapping."""
|
||||
try:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: OCR results window requested", True)
|
||||
|
||||
if self._is_processing:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Already processing, ignoring request", True)
|
||||
return True
|
||||
|
||||
self._is_processing = True
|
||||
self._announceOCRStart("window for interactive results")
|
||||
|
||||
try:
|
||||
if self._screenShotWindow():
|
||||
self._performOCR()
|
||||
self._createOCRResultsWindow()
|
||||
finally:
|
||||
self._is_processing = False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self._is_processing = False
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error in OCR results window: {e}", True)
|
||||
return False
|
||||
|
||||
def _createOCRResultsWindow(self):
|
||||
"""Create and show the OCR results window with coordinate mapping."""
|
||||
if not GTK_AVAILABLE:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for results window", True)
|
||||
return
|
||||
|
||||
try:
|
||||
# Create main window
|
||||
self._results_window = Gtk.Window(title="OCR Results - Cthulhu")
|
||||
self._results_window.set_default_size(800, 600)
|
||||
self._results_window.set_modal(True)
|
||||
|
||||
# Create main container
|
||||
vbox = Gtk.VBox()
|
||||
|
||||
# Create menu bar
|
||||
menubar = self._createResultsMenuBar()
|
||||
vbox.pack_start(menubar, False, False, 0)
|
||||
|
||||
# Create text view for OCR text
|
||||
scrolled_text = Gtk.ScrolledWindow()
|
||||
self._results_textview = Gtk.TextView()
|
||||
self._results_textview.set_editable(False)
|
||||
buffer = self._results_textview.get_buffer()
|
||||
buffer.set_text(self._OCRText)
|
||||
scrolled_text.add(self._results_textview)
|
||||
|
||||
# Create tree view for coordinate data
|
||||
scrolled_tree = Gtk.ScrolledWindow()
|
||||
self._results_tree = self._createResultsTreeView()
|
||||
scrolled_tree.add(self._results_tree)
|
||||
|
||||
# Add both views to container
|
||||
vbox.pack_start(scrolled_text, True, True, 0)
|
||||
vbox.pack_start(scrolled_tree, True, True, 0)
|
||||
|
||||
# Set initial view (text only)
|
||||
scrolled_tree.hide()
|
||||
|
||||
self._results_window.add(vbox)
|
||||
self._results_window.connect("destroy", self._onResultsWindowDestroy)
|
||||
self._results_window.connect("key-press-event", self._onResultsKeyPress)
|
||||
|
||||
# Show window
|
||||
self._results_window.show_all()
|
||||
scrolled_tree.hide() # Hide tree initially
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Results window created", True)
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error creating results window: {e}", True)
|
||||
|
||||
def _createResultsMenuBar(self):
|
||||
"""Create menu bar for results window."""
|
||||
menubar = Gtk.MenuBar()
|
||||
|
||||
# View menu
|
||||
view_menu = Gtk.Menu()
|
||||
view_item = Gtk.MenuItem(label="View")
|
||||
view_item.set_submenu(view_menu)
|
||||
|
||||
# Toggle view option
|
||||
toggle_item = Gtk.MenuItem(label="Toggle View (Alt+V)")
|
||||
toggle_item.connect("activate", self._toggleResultsView)
|
||||
view_menu.append(toggle_item)
|
||||
|
||||
menubar.append(view_item)
|
||||
|
||||
# Actions menu
|
||||
actions_menu = Gtk.Menu()
|
||||
actions_item = Gtk.MenuItem(label="Actions")
|
||||
actions_item.set_submenu(actions_menu)
|
||||
|
||||
# Click action
|
||||
click_item = Gtk.MenuItem(label="Click Selected (Enter)")
|
||||
click_item.connect("activate", self._clickSelectedText)
|
||||
actions_menu.append(click_item)
|
||||
|
||||
menubar.append(actions_item)
|
||||
|
||||
return menubar
|
||||
|
||||
def _createResultsTreeView(self):
|
||||
"""Create tree view for OCR results with coordinates."""
|
||||
# Create list store
|
||||
store = Gtk.ListStore(str, str, int, str, str, int, int, int)
|
||||
|
||||
# Create tree view
|
||||
tree = Gtk.TreeView(model=store)
|
||||
tree.set_search_column(0)
|
||||
|
||||
# Add columns
|
||||
columns = [
|
||||
("Text", 0),
|
||||
("Font Size", 2),
|
||||
("Color", 3),
|
||||
("Type", 4),
|
||||
("X Position", 5),
|
||||
("Y Position", 6),
|
||||
("Confidence", 7)
|
||||
]
|
||||
|
||||
for title, col_id in columns:
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn(title, renderer, text=col_id)
|
||||
column.set_sort_column_id(col_id)
|
||||
tree.append_column(column)
|
||||
|
||||
# Populate with OCR data
|
||||
for row in self._OCRWordList:
|
||||
# Transform coordinates back to screen coordinates
|
||||
x_coord = int(row[4] / self._scaleFactor + self._offsetXpos)
|
||||
y_coord = int(row[5] / self._scaleFactor + self._offsetYpos)
|
||||
|
||||
store.append([
|
||||
row[0], # Text
|
||||
str(row[1]), # Font size (as string for display)
|
||||
int(row[1]), # Font size (as int for sorting)
|
||||
row[2], # Color
|
||||
row[3], # Type
|
||||
x_coord, # X coordinate (screen)
|
||||
y_coord, # Y coordinate (screen)
|
||||
row[6] # Confidence
|
||||
])
|
||||
|
||||
tree.connect("row-activated", self._onTreeRowActivated)
|
||||
|
||||
return tree
|
||||
|
||||
def _toggleResultsView(self, widget):
|
||||
"""Toggle between text and tree view."""
|
||||
if not self._results_window:
|
||||
return
|
||||
|
||||
# Get the container
|
||||
vbox = self._results_window.get_child()
|
||||
scrolled_text = vbox.get_children()[1] # Second child (after menubar)
|
||||
scrolled_tree = vbox.get_children()[2] # Third child
|
||||
|
||||
if self._current_view_mode == 0: # Currently showing text
|
||||
scrolled_text.hide()
|
||||
scrolled_tree.show()
|
||||
self._current_view_mode = 1
|
||||
self._results_tree.grab_focus()
|
||||
else: # Currently showing tree
|
||||
scrolled_tree.hide()
|
||||
scrolled_text.show()
|
||||
self._current_view_mode = 0
|
||||
self._results_textview.grab_focus()
|
||||
|
||||
def _onTreeRowActivated(self, tree, path, column):
|
||||
"""Handle double-click or Enter on tree row."""
|
||||
self._clickSelectedText(None)
|
||||
|
||||
def _clickSelectedText(self, widget):
|
||||
"""Click at the coordinates of the selected text."""
|
||||
if not self._results_tree:
|
||||
return
|
||||
|
||||
selection = self._results_tree.get_selection()
|
||||
if not selection:
|
||||
return
|
||||
|
||||
model, tree_iter = selection.get_selected()
|
||||
if not tree_iter:
|
||||
return
|
||||
|
||||
# Get coordinates
|
||||
x_coord = model.get_value(tree_iter, 5) # X position
|
||||
y_coord = model.get_value(tree_iter, 6) # Y position
|
||||
text = model.get_value(tree_iter, 0) # Text for confirmation
|
||||
|
||||
# Confirm click action
|
||||
dialog = Gtk.MessageDialog(
|
||||
self._results_window,
|
||||
Gtk.DialogFlags.MODAL,
|
||||
Gtk.MessageType.QUESTION,
|
||||
Gtk.ButtonsType.YES_NO,
|
||||
f"Click at coordinates ({x_coord}, {y_coord}) for text '{text}'?"
|
||||
)
|
||||
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
if response == Gtk.ResponseType.YES:
|
||||
try:
|
||||
# Hide window before clicking
|
||||
self._results_window.hide()
|
||||
|
||||
# Perform click using AT-SPI2
|
||||
time.sleep(0.5) # Brief delay
|
||||
Atspi.generate_mouse_event(x_coord, y_coord, "b1c")
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Clicked at ({x_coord}, {y_coord})", True)
|
||||
|
||||
# Destroy window after successful click
|
||||
self._results_window.destroy()
|
||||
self._results_window = None
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error clicking: {e}", True)
|
||||
# Show window again on error
|
||||
self._results_window.show()
|
||||
|
||||
def _onResultsKeyPress(self, widget, event):
|
||||
"""Handle key presses in results window."""
|
||||
keyval = event.keyval
|
||||
state = event.state
|
||||
|
||||
# Alt+V to toggle view
|
||||
if (keyval == Gdk.KEY_v or keyval == Gdk.KEY_V) and (state & Gdk.ModifierType.MOD1_MASK):
|
||||
self._toggleResultsView(None)
|
||||
return True
|
||||
|
||||
# Enter to click selected
|
||||
if keyval == Gdk.KEY_Return or keyval == Gdk.KEY_KP_Enter:
|
||||
if self._current_view_mode == 1: # Tree view
|
||||
self._clickSelectedText(None)
|
||||
return True
|
||||
|
||||
# Escape to close
|
||||
if keyval == Gdk.KEY_Escape:
|
||||
self._results_window.destroy()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _onResultsWindowDestroy(self, widget):
|
||||
"""Handle results window destruction."""
|
||||
self._results_window = None
|
||||
self._results_tree = None
|
||||
self._results_textview = None
|
||||
self._current_view_mode = 0
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Results window destroyed", True)
|
||||
|
||||
def _copyTextToClipboard(self, text):
|
||||
"""Copy text to system clipboard."""
|
||||
if not GTK_AVAILABLE:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for clipboard", True)
|
||||
return False
|
||||
|
||||
try:
|
||||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
clipboard.set_text(text, -1)
|
||||
clipboard.store()
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Copied {len(text)} characters to clipboard", True)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error copying to clipboard: {e}", True)
|
||||
return False
|
||||
@@ -0,0 +1 @@
|
||||
from .plugin import SpeechHistory
|
||||
@@ -0,0 +1,14 @@
|
||||
speechhistory_python_sources = files([
|
||||
'__init__.py',
|
||||
'plugin.py'
|
||||
])
|
||||
|
||||
python3.install_sources(
|
||||
speechhistory_python_sources,
|
||||
subdir: 'cthulhu/plugins/SpeechHistory'
|
||||
)
|
||||
|
||||
install_data(
|
||||
'plugin.info',
|
||||
install_dir: python3.get_install_dir() / 'cthulhu' / 'plugins' / 'SpeechHistory'
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
name = Speech History
|
||||
version = 1.0.0
|
||||
description = Keeps a history of all speech output with navigation and clipboard support
|
||||
authors = Cthulhu Plugin System
|
||||
website = https://git.stormux.org/storm/cthulhu
|
||||
copyright = Copyright 2024 Stormux
|
||||
builtin = true
|
||||
hidden = false
|
||||
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
from collections import deque
|
||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||
from cthulhu import settings_manager
|
||||
from cthulhu import debug
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SpeechHistory(Plugin):
|
||||
"""Speech History plugin - SAFE manual-only version (no automatic capture)."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory SAFE plugin initialized", True)
|
||||
|
||||
# History storage - start with some sample items
|
||||
self._max_history_size = 50
|
||||
self._history = deque([
|
||||
"Welcome to safe speech history",
|
||||
"This version doesn't auto-capture to prevent crashes",
|
||||
"Use add_to_history() method to manually add items",
|
||||
"Navigate with Cthulhu+Control+Shift+H (previous)",
|
||||
"Navigate with Cthulhu+Control+H (next)",
|
||||
"Copy with Cthulhu+Control+Y"
|
||||
], maxlen=self._max_history_size)
|
||||
self._current_history_index = -1
|
||||
|
||||
# Keybinding storage
|
||||
self._kb_nav_prev = None
|
||||
self._kb_nav_next = None
|
||||
self._kb_copy_last = None
|
||||
|
||||
# Settings integration
|
||||
self._settings_manager = settings_manager.getManager()
|
||||
|
||||
@cthulhu_hookimpl
|
||||
def activate(self, plugin=None):
|
||||
"""Activate the plugin."""
|
||||
if plugin is not None and plugin is not self:
|
||||
return
|
||||
|
||||
try:
|
||||
debug.printMessage(debug.LEVEL_INFO, "=== SpeechHistory SAFE activation starting ===", True)
|
||||
|
||||
# Load settings
|
||||
self._load_settings()
|
||||
|
||||
# Register keybindings only - NO speech capture
|
||||
self._register_keybindings()
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory SAFE plugin activated successfully", True)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error activating SpeechHistory SAFE: {e}", True)
|
||||
return False
|
||||
|
||||
@cthulhu_hookimpl
|
||||
def deactivate(self, plugin=None):
|
||||
"""Deactivate the plugin."""
|
||||
if plugin is not None and plugin is not self:
|
||||
return
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "Deactivating SpeechHistory SAFE plugin", True)
|
||||
|
||||
# Clear keybindings
|
||||
self._kb_nav_prev = None
|
||||
self._kb_nav_next = None
|
||||
self._kb_copy_last = None
|
||||
|
||||
return True
|
||||
|
||||
def _load_settings(self):
|
||||
"""Load plugin settings."""
|
||||
try:
|
||||
self._max_history_size = self._settings_manager.getSetting('speechHistorySize') or 50
|
||||
# Update deque maxlen if needed
|
||||
if self._history.maxlen != self._max_history_size:
|
||||
old_history = list(self._history)
|
||||
self._history = deque(old_history[-self._max_history_size:], maxlen=self._max_history_size)
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Speech history size: {self._max_history_size}", True)
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error loading settings: {e}", True)
|
||||
self._max_history_size = 50
|
||||
|
||||
def _register_keybindings(self):
|
||||
"""Register plugin keybindings."""
|
||||
try:
|
||||
# Cthulhu+Control+Shift+H (History previous)
|
||||
self._kb_nav_prev = self.registerGestureByString(
|
||||
self._navigate_history_prev,
|
||||
"Speech history previous",
|
||||
'kb:cthulhu+control+shift+h'
|
||||
)
|
||||
|
||||
# Cthulhu+Control+H (History next)
|
||||
self._kb_nav_next = self.registerGestureByString(
|
||||
self._navigate_history_next,
|
||||
"Speech history next",
|
||||
'kb:cthulhu+control+h'
|
||||
)
|
||||
|
||||
# Cthulhu+Control+Y (Copy history)
|
||||
self._kb_copy_last = self.registerGestureByString(
|
||||
self._copy_last_spoken,
|
||||
"Copy speech history item to clipboard",
|
||||
'kb:cthulhu+control+y'
|
||||
)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Registered keybindings: {bool(self._kb_nav_prev)}, {bool(self._kb_nav_next)}, {bool(self._kb_copy_last)}", True)
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error registering keybindings: {e}", True)
|
||||
|
||||
def _navigate_history_prev(self, script=None, inputEvent=None):
|
||||
"""Navigate to previous item in speech history."""
|
||||
try:
|
||||
if not self._history:
|
||||
self._present_message("Speech history is empty")
|
||||
return True
|
||||
|
||||
# Move backward in history (to older items)
|
||||
if self._current_history_index == -1:
|
||||
self._current_history_index = len(self._history) - 1
|
||||
elif self._current_history_index > 0:
|
||||
self._current_history_index -= 1
|
||||
else:
|
||||
self._current_history_index = len(self._history) - 1
|
||||
|
||||
# Present the history item
|
||||
history_item = self._history[self._current_history_index]
|
||||
position = self._current_history_index + 1
|
||||
self._present_message(f"History {position} of {len(self._history)}: {history_item}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error navigating to previous: {e}", True)
|
||||
return False
|
||||
|
||||
def _navigate_history_next(self, script=None, inputEvent=None):
|
||||
"""Navigate to next item in speech history."""
|
||||
try:
|
||||
if not self._history:
|
||||
self._present_message("Speech history is empty")
|
||||
return True
|
||||
|
||||
# Move forward in history (to newer items)
|
||||
if self._current_history_index == -1:
|
||||
self._current_history_index = 0
|
||||
elif self._current_history_index < len(self._history) - 1:
|
||||
self._current_history_index += 1
|
||||
else:
|
||||
self._current_history_index = 0
|
||||
|
||||
# Present the history item
|
||||
history_item = self._history[self._current_history_index]
|
||||
position = self._current_history_index + 1
|
||||
self._present_message(f"History {position} of {len(self._history)}: {history_item}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error navigating to next: {e}", True)
|
||||
return False
|
||||
|
||||
def _copy_last_spoken(self, script=None, inputEvent=None):
|
||||
"""Copy the last spoken text to clipboard."""
|
||||
try:
|
||||
if not self._history:
|
||||
self._present_message("No speech history to copy")
|
||||
return True
|
||||
|
||||
# Copy the most recent speech
|
||||
last_spoken = self._history[-1]
|
||||
|
||||
try:
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
clipboard.set_text(last_spoken, -1)
|
||||
clipboard.store()
|
||||
|
||||
# Show confirmation
|
||||
preview = last_spoken[:50] + ('...' if len(last_spoken) > 50 else '')
|
||||
self._present_message(f"Copied to clipboard: {preview}")
|
||||
|
||||
except Exception as clipboard_error:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Clipboard error: {clipboard_error}", True)
|
||||
self._present_message("Error copying to clipboard")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error copying: {e}", True)
|
||||
return False
|
||||
|
||||
def _present_message(self, message):
|
||||
"""Present a message to the user via speech."""
|
||||
try:
|
||||
if self.app:
|
||||
state = self.app.getDynamicApiManager().getAPI('CthulhuState')
|
||||
if state and state.activeScript:
|
||||
state.activeScript.presentMessage(message, resetStyles=False)
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Message: {message}", True)
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error presenting message: {e}", True)
|
||||
|
||||
def add_to_history(self, text):
|
||||
"""Public method to safely add items to history."""
|
||||
try:
|
||||
if not text or not text.strip():
|
||||
return
|
||||
|
||||
clean_text = text.strip()
|
||||
if len(clean_text) < 2:
|
||||
return
|
||||
|
||||
# Simple duplicate prevention
|
||||
if self._history and self._history[-1] == clean_text:
|
||||
return
|
||||
|
||||
# Add to history
|
||||
self._history.append(clean_text)
|
||||
self._current_history_index = -1
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Manually added to history: {clean_text[:50]}{'...' if len(clean_text) > 50 else ''}", True)
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"Error adding to history: {e}", True)
|
||||
+21
-1
@@ -157,7 +157,16 @@ userCustomizableSettings = [
|
||||
"aiConfirmationRequired",
|
||||
"aiActionTimeout",
|
||||
"aiScreenshotQuality",
|
||||
"aiMaxContextLength"
|
||||
"aiMaxContextLength",
|
||||
"ocrLanguageCode",
|
||||
"ocrScaleFactor",
|
||||
"ocrGrayscaleImg",
|
||||
"ocrInvertImg",
|
||||
"ocrBlackWhiteImg",
|
||||
"ocrBlackWhiteImgValue",
|
||||
"ocrColorCalculation",
|
||||
"ocrColorCalculationMax",
|
||||
"ocrCopyToClipboard"
|
||||
]
|
||||
|
||||
GENERAL_KEYBOARD_LAYOUT_DESKTOP = 1
|
||||
@@ -443,3 +452,14 @@ aiConfirmationRequired = True
|
||||
aiActionTimeout = 30
|
||||
aiScreenshotQuality = AI_SCREENSHOT_QUALITY_MEDIUM
|
||||
aiMaxContextLength = 4000
|
||||
|
||||
# OCR Plugin settings
|
||||
ocrLanguageCode = 'eng'
|
||||
ocrScaleFactor = 3
|
||||
ocrGrayscaleImg = False
|
||||
ocrInvertImg = False
|
||||
ocrBlackWhiteImg = False
|
||||
ocrBlackWhiteImgValue = 200
|
||||
ocrColorCalculation = False
|
||||
ocrColorCalculationMax = 3
|
||||
ocrCopyToClipboard = False
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,515 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2025 Stormux
|
||||
# Copyright (c) 2025 Igalia, S.L.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||
# Boston MA 02110-1301 USA.
|
||||
|
||||
"""Enhanced speech settings management for D-Bus remote controller."""
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2025 Stormux"
|
||||
__license__ = "LGPL"
|
||||
|
||||
from . import cthulhu_state
|
||||
from . import debug
|
||||
from . import dbus_service
|
||||
from . import messages
|
||||
from . import settings
|
||||
from . import settings_manager
|
||||
|
||||
class SpeechDBusManager:
|
||||
"""Enhanced speech settings for D-Bus remote control."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the speech D-Bus manager."""
|
||||
self._settings_manager = settings_manager.getManager()
|
||||
|
||||
@dbus_service.getter
|
||||
def get_verbosity_level(self) -> str:
|
||||
"""Returns the current speech verbosity level."""
|
||||
|
||||
level = self._settings_manager.getSetting("speechVerbosityLevel")
|
||||
if level == settings.VERBOSITY_LEVEL_BRIEF:
|
||||
return "brief"
|
||||
else:
|
||||
return "verbose"
|
||||
|
||||
@dbus_service.setter
|
||||
def set_verbosity_level(self, value: str) -> bool:
|
||||
"""Sets the speech verbosity level."""
|
||||
|
||||
if value.lower() == "brief":
|
||||
setting_value = settings.VERBOSITY_LEVEL_BRIEF
|
||||
elif value.lower() == "verbose":
|
||||
setting_value = settings.VERBOSITY_LEVEL_VERBOSE
|
||||
else:
|
||||
msg = f"SPEECH DBUS MANAGER: Invalid verbosity level: {value}"
|
||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||
return False
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting verbosity level to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("speechVerbosityLevel", setting_value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_capitalization_style(self) -> str:
|
||||
"""Returns the current capitalization style."""
|
||||
|
||||
style = self._settings_manager.getSetting("capitalizationStyle")
|
||||
if style == settings.CAPITALIZATION_STYLE_NONE:
|
||||
return "none"
|
||||
elif style == settings.CAPITALIZATION_STYLE_SPELL:
|
||||
return "spell"
|
||||
elif style == settings.CAPITALIZATION_STYLE_ICON:
|
||||
return "icon"
|
||||
else:
|
||||
return "none"
|
||||
|
||||
@dbus_service.setter
|
||||
def set_capitalization_style(self, value: str) -> bool:
|
||||
"""Sets the capitalization style."""
|
||||
|
||||
value_lower = value.lower()
|
||||
if value_lower == "none":
|
||||
setting_value = settings.CAPITALIZATION_STYLE_NONE
|
||||
elif value_lower == "spell":
|
||||
setting_value = settings.CAPITALIZATION_STYLE_SPELL
|
||||
elif value_lower == "icon":
|
||||
setting_value = settings.CAPITALIZATION_STYLE_ICON
|
||||
else:
|
||||
msg = f"SPEECH DBUS MANAGER: Invalid capitalization style: {value}"
|
||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||
return False
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting capitalization style to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("capitalizationStyle", setting_value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_punctuation_level(self) -> str:
|
||||
"""Returns the current punctuation level."""
|
||||
|
||||
level = self._settings_manager.getSetting("verbalizePunctuationStyle")
|
||||
if level == settings.PUNCTUATION_STYLE_NONE:
|
||||
return "none"
|
||||
elif level == settings.PUNCTUATION_STYLE_SOME:
|
||||
return "some"
|
||||
elif level == settings.PUNCTUATION_STYLE_MOST:
|
||||
return "most"
|
||||
elif level == settings.PUNCTUATION_STYLE_ALL:
|
||||
return "all"
|
||||
else:
|
||||
return "some"
|
||||
|
||||
@dbus_service.setter
|
||||
def set_punctuation_level(self, value: str) -> bool:
|
||||
"""Sets the punctuation level."""
|
||||
|
||||
value_lower = value.lower()
|
||||
if value_lower == "none":
|
||||
setting_value = settings.PUNCTUATION_STYLE_NONE
|
||||
elif value_lower == "some":
|
||||
setting_value = settings.PUNCTUATION_STYLE_SOME
|
||||
elif value_lower == "most":
|
||||
setting_value = settings.PUNCTUATION_STYLE_MOST
|
||||
elif value_lower == "all":
|
||||
setting_value = settings.PUNCTUATION_STYLE_ALL
|
||||
else:
|
||||
msg = f"SPEECH DBUS MANAGER: Invalid punctuation level: {value}"
|
||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||
return False
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting punctuation level to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("verbalizePunctuationStyle", setting_value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_speak_numbers_as_digits(self) -> bool:
|
||||
"""Returns whether numbers are spoken as digits."""
|
||||
|
||||
return self._settings_manager.getSetting("speakNumbersAsDigits")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_speak_numbers_as_digits(self, value: bool) -> bool:
|
||||
"""Sets whether numbers are spoken as digits."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting speak numbers as digits to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("speakNumbersAsDigits", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_speech_is_muted(self) -> bool:
|
||||
"""Returns whether speech output is temporarily muted."""
|
||||
|
||||
return self._settings_manager.getSetting("silenceSpeech")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_speech_is_muted(self, value: bool) -> bool:
|
||||
"""Sets whether speech output is temporarily muted."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting speech muted to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("silenceSpeech", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_only_speak_displayed_text(self) -> bool:
|
||||
"""Returns whether only displayed text should be spoken."""
|
||||
|
||||
return self._settings_manager.getSetting("onlySpeakDisplayedText")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_only_speak_displayed_text(self, value: bool) -> bool:
|
||||
"""Sets whether only displayed text should be spoken."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting only speak displayed text to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("onlySpeakDisplayedText", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_speak_indentation_and_justification(self) -> bool:
|
||||
"""Returns whether speaking of indentation and justification is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableSpeechIndentation")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_speak_indentation_and_justification(self, value: bool) -> bool:
|
||||
"""Sets whether speaking of indentation and justification is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting speak indentation and justification to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableSpeechIndentation", value)
|
||||
return True
|
||||
|
||||
@dbus_service.command
|
||||
def toggle_speech(self, script=None, event=None):
|
||||
"""Toggles speech on and off."""
|
||||
|
||||
tokens = ["SPEECH DBUS MANAGER: toggle_speech. Script:", script, "Event:", event]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
if script is not None:
|
||||
script.presentationInterrupt()
|
||||
|
||||
if self.get_speech_is_muted():
|
||||
self.set_speech_is_muted(False)
|
||||
if script is not None:
|
||||
script.presentMessage(messages.SPEECH_ENABLED)
|
||||
elif not self._settings_manager.getSetting("enableSpeech"):
|
||||
self._settings_manager.setSetting("enableSpeech", True)
|
||||
if script is not None:
|
||||
script.presentMessage(messages.SPEECH_ENABLED)
|
||||
else:
|
||||
if script is not None:
|
||||
script.presentMessage(messages.SPEECH_DISABLED)
|
||||
self.set_speech_is_muted(True)
|
||||
|
||||
@dbus_service.command
|
||||
def toggle_verbosity(self, script=None, event=None):
|
||||
"""Toggles speech verbosity level between verbose and brief."""
|
||||
|
||||
tokens = ["SPEECH DBUS MANAGER: toggle_verbosity. Script:", script, "Event:", event]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
current_level = self._settings_manager.getSetting("speechVerbosityLevel")
|
||||
if current_level == settings.VERBOSITY_LEVEL_BRIEF:
|
||||
if script is not None:
|
||||
script.presentMessage(messages.SPEECH_VERBOSITY_VERBOSE)
|
||||
self._settings_manager.setSetting("speechVerbosityLevel", settings.VERBOSITY_LEVEL_VERBOSE)
|
||||
else:
|
||||
if script is not None:
|
||||
script.presentMessage(messages.SPEECH_VERBOSITY_BRIEF)
|
||||
self._settings_manager.setSetting("speechVerbosityLevel", settings.VERBOSITY_LEVEL_BRIEF)
|
||||
|
||||
@dbus_service.command
|
||||
def change_number_style(self, script=None, event=None):
|
||||
"""Changes spoken number style between digits and words."""
|
||||
|
||||
tokens = ["SPEECH DBUS MANAGER: change_number_style. Script:", script, "Event:", event]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
speak_digits = self.get_speak_numbers_as_digits()
|
||||
if speak_digits:
|
||||
brief = messages.NUMBER_STYLE_WORDS_BRIEF
|
||||
full = messages.NUMBER_STYLE_WORDS_FULL
|
||||
else:
|
||||
brief = messages.NUMBER_STYLE_DIGITS_BRIEF
|
||||
full = messages.NUMBER_STYLE_DIGITS_FULL
|
||||
|
||||
self.set_speak_numbers_as_digits(not speak_digits)
|
||||
if script is not None:
|
||||
script.presentMessage(full, brief)
|
||||
|
||||
@dbus_service.command
|
||||
def say_all(self, script=None, event=None):
|
||||
"""Speaks the entire document or text, starting from the current position."""
|
||||
|
||||
tokens = ["SPEECH DBUS MANAGER: say_all. Script:", script, "Event:", event]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
# Use the current active script if not provided
|
||||
if script is None:
|
||||
script = cthulhu_state.activeScript
|
||||
|
||||
if script is None:
|
||||
msg = "SPEECH DBUS MANAGER: No active script available for Say All"
|
||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||
return False
|
||||
|
||||
# Call the script's Say All method
|
||||
try:
|
||||
script.sayAll(event, notify_user=False)
|
||||
return True
|
||||
except Exception as e:
|
||||
msg = f"SPEECH DBUS MANAGER: Error during Say All: {e}"
|
||||
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
||||
return False
|
||||
|
||||
# Key Echo Controls
|
||||
@dbus_service.getter
|
||||
def get_key_echo_enabled(self) -> bool:
|
||||
"""Returns whether echo of key presses is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableKeyEcho")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_key_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether echo of key presses is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable key echo to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableKeyEcho", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_character_echo_enabled(self) -> bool:
|
||||
"""Returns whether echo of inserted characters is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableEchoByCharacter")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_character_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether echo of inserted characters is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable character echo to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableEchoByCharacter", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_word_echo_enabled(self) -> bool:
|
||||
"""Returns whether word echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableEchoByWord")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_word_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether word echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable word echo to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableEchoByWord", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_sentence_echo_enabled(self) -> bool:
|
||||
"""Returns whether sentence echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableEchoBySentence")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_sentence_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether sentence echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable sentence echo to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableEchoBySentence", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_alphabetic_keys_enabled(self) -> bool:
|
||||
"""Returns whether alphabetic keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableAlphabeticKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_alphabetic_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether alphabetic keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable alphabetic keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableAlphabeticKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_numeric_keys_enabled(self) -> bool:
|
||||
"""Returns whether numeric keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableNumericKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_numeric_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether numeric keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable numeric keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableNumericKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_punctuation_keys_enabled(self) -> bool:
|
||||
"""Returns whether punctuation keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enablePunctuationKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_punctuation_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether punctuation keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable punctuation keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enablePunctuationKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_space_enabled(self) -> bool:
|
||||
"""Returns whether space key will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableSpace")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_space_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether space key will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable space to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableSpace", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_modifier_keys_enabled(self) -> bool:
|
||||
"""Returns whether modifier keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableModifierKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_modifier_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether modifier keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable modifier keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableModifierKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_function_keys_enabled(self) -> bool:
|
||||
"""Returns whether function keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableFunctionKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_function_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether function keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable function keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableFunctionKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_action_keys_enabled(self) -> bool:
|
||||
"""Returns whether action keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableActionKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_action_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether action keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable action keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableActionKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.getter
|
||||
def get_navigation_keys_enabled(self) -> bool:
|
||||
"""Returns whether navigation keys will be echoed when key echo is enabled."""
|
||||
|
||||
return self._settings_manager.getSetting("enableNavigationKeys")
|
||||
|
||||
@dbus_service.setter
|
||||
def set_navigation_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether navigation keys will be echoed when key echo is enabled."""
|
||||
|
||||
msg = f"SPEECH DBUS MANAGER: Setting enable navigation keys to {value}."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._settings_manager.setSetting("enableNavigationKeys", value)
|
||||
return True
|
||||
|
||||
@dbus_service.command
|
||||
def cycle_key_echo(self, script=None, event=None):
|
||||
"""Cycle through the key echo levels."""
|
||||
|
||||
tokens = ["SPEECH DBUS MANAGER: cycle_key_echo. Script:", script, "Event:", event]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
# Get current settings
|
||||
key = self._settings_manager.getSetting("enableKeyEcho")
|
||||
word = self._settings_manager.getSetting("enableEchoByWord")
|
||||
sentence = self._settings_manager.getSetting("enableEchoBySentence")
|
||||
|
||||
# Cycle through the combinations: none -> key -> word -> sentence -> all -> none
|
||||
if not key and not word and not sentence:
|
||||
# None -> Key only
|
||||
new_key, new_word, new_sentence = True, False, False
|
||||
brief = messages.KEY_ECHO_KEY_BRIEF
|
||||
full = messages.KEY_ECHO_KEY_FULL
|
||||
elif key and not word and not sentence:
|
||||
# Key -> Word
|
||||
new_key, new_word, new_sentence = False, True, False
|
||||
brief = messages.KEY_ECHO_WORD_BRIEF
|
||||
full = messages.KEY_ECHO_WORD_FULL
|
||||
elif not key and word and not sentence:
|
||||
# Word -> Sentence
|
||||
new_key, new_word, new_sentence = False, False, True
|
||||
brief = messages.KEY_ECHO_SENTENCE_BRIEF
|
||||
full = messages.KEY_ECHO_SENTENCE_FULL
|
||||
elif not key and not word and sentence:
|
||||
# Sentence -> All
|
||||
new_key, new_word, new_sentence = True, True, True
|
||||
brief = messages.KEY_ECHO_KEY_AND_WORD_BRIEF
|
||||
full = messages.KEY_ECHO_KEY_AND_WORD_FULL
|
||||
else:
|
||||
# All -> None
|
||||
new_key, new_word, new_sentence = False, False, False
|
||||
brief = messages.KEY_ECHO_NONE_BRIEF
|
||||
full = messages.KEY_ECHO_NONE_FULL
|
||||
|
||||
# Apply new settings
|
||||
self._settings_manager.setSetting("enableKeyEcho", new_key)
|
||||
self._settings_manager.setSetting("enableEchoByWord", new_word)
|
||||
self._settings_manager.setSetting("enableEchoBySentence", new_sentence)
|
||||
|
||||
if script is not None:
|
||||
script.presentMessage(full, brief)
|
||||
@@ -37,6 +37,7 @@ gi.require_version("Atspi", "2.0")
|
||||
from gi.repository import Atspi
|
||||
|
||||
from . import cmdnames
|
||||
from . import dbus_service
|
||||
from . import debug
|
||||
from . import guilabels
|
||||
from . import input_event
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env python3
|
||||
# Cthulhu
|
||||
#
|
||||
# Copyright 2005-2008 Sun Microsystems Inc.
|
||||
# Copyright 2011-2025 Igalia, S.L.
|
||||
# Copyright 2025 Stormux <storm_dragon@stormux.org>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||
# Boston MA 02110-1301 USA.
|
||||
|
||||
"""Provides typing echo support with D-Bus controls."""
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
|
||||
"Copyright (c) 2011-2025 Igalia, S.L."
|
||||
__license__ = "LGPL"
|
||||
|
||||
import string
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import braille
|
||||
from . import cmdnames
|
||||
from . import dbus_service
|
||||
from . import debug
|
||||
from . import input_event
|
||||
from . import keybindings
|
||||
from . import messages
|
||||
from . import settings
|
||||
from . import settings_manager
|
||||
from . import speech
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import default
|
||||
|
||||
_settings_manager = settings_manager.getManager()
|
||||
|
||||
class TypingEchoPresenter:
|
||||
"""Provides typing echo functionality with D-Bus remote control support."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the typing echo presenter."""
|
||||
debug.printMessage(debug.LEVEL_INFO, "TYPING ECHO PRESENTER: Initializing", True)
|
||||
|
||||
# D-Bus getters and setters for key echo settings
|
||||
@dbus_service.getter
|
||||
def get_key_echo_enabled(self) -> bool:
|
||||
"""Returns whether echo of key presses is enabled."""
|
||||
return _settings_manager.getSetting('enableKeyEcho')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_key_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether echo of key presses is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableKeyEcho', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting key echo: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_character_echo_enabled(self) -> bool:
|
||||
"""Returns whether echo of inserted characters is enabled."""
|
||||
return _settings_manager.getSetting('enableEchoByCharacter')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_character_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether echo of inserted characters is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableEchoByCharacter', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting character echo: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_word_echo_enabled(self) -> bool:
|
||||
"""Returns whether word echo is enabled."""
|
||||
return _settings_manager.getSetting('enableEchoByWord')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_word_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether word echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableEchoByWord', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting word echo: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_sentence_echo_enabled(self) -> bool:
|
||||
"""Returns whether sentence echo is enabled."""
|
||||
return _settings_manager.getSetting('enableEchoBySentence')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_sentence_echo_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether sentence echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableEchoBySentence', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting sentence echo: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_alphabetic_keys_enabled(self) -> bool:
|
||||
"""Returns whether alphabetic keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableAlphabeticKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_alphabetic_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether alphabetic keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableAlphabeticKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting alphabetic keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_numeric_keys_enabled(self) -> bool:
|
||||
"""Returns whether numeric keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableNumericKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_numeric_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether numeric keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableNumericKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting numeric keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_punctuation_keys_enabled(self) -> bool:
|
||||
"""Returns whether punctuation keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enablePunctuationKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_punctuation_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether punctuation keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enablePunctuationKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting punctuation keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_space_enabled(self) -> bool:
|
||||
"""Returns whether space key will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableSpace')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_space_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether space key will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableSpace', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting space key: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_modifier_keys_enabled(self) -> bool:
|
||||
"""Returns whether modifier keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableModifierKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_modifier_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether modifier keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableModifierKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting modifier keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_function_keys_enabled(self) -> bool:
|
||||
"""Returns whether function keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableFunctionKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_function_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether function keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableFunctionKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting function keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_action_keys_enabled(self) -> bool:
|
||||
"""Returns whether action keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableActionKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_action_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether action keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableActionKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting action keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_navigation_keys_enabled(self) -> bool:
|
||||
"""Returns whether navigation keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableNavigationKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_navigation_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether navigation keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableNavigationKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting navigation keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.getter
|
||||
def get_diacritical_keys_enabled(self) -> bool:
|
||||
"""Returns whether diacritical keys will be echoed when key echo is enabled."""
|
||||
return _settings_manager.getSetting('enableDiacriticalKeys')
|
||||
|
||||
@dbus_service.setter
|
||||
def set_diacritical_keys_enabled(self, value: bool) -> bool:
|
||||
"""Sets whether diacritical keys will be echoed when key echo is enabled."""
|
||||
try:
|
||||
_settings_manager.setSetting('enableDiacriticalKeys', value)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_WARNING, f"Error setting diacritical keys: {e}", True)
|
||||
return False
|
||||
|
||||
@dbus_service.command
|
||||
def cycle_key_echo(self, script: 'default.Script', event=None):
|
||||
"""Cycles through key echo modes."""
|
||||
if not _settings_manager.getSetting('enableKeyEcho'):
|
||||
_settings_manager.setSetting('enableKeyEcho', True)
|
||||
script.presentMessage(messages.KEY_ECHO_ENABLED)
|
||||
else:
|
||||
_settings_manager.setSetting('enableKeyEcho', False)
|
||||
script.presentMessage(messages.KEY_ECHO_DISABLED)
|
||||
return True
|
||||
|
||||
def should_echo_keyboard_event(self, event: input_event.KeyboardEvent) -> bool:
|
||||
"""Returns whether the given keyboard event should be echoed."""
|
||||
if not _settings_manager.getSetting('enableKeyEcho'):
|
||||
return False
|
||||
|
||||
if event.event_string in ["shift", "control", "alt", "meta"]:
|
||||
return _settings_manager.getSetting('enableModifierKeys')
|
||||
|
||||
if event.event_string.startswith("f") and event.event_string[1:].isdigit():
|
||||
return _settings_manager.getSetting('enableFunctionKeys')
|
||||
|
||||
if event.event_string in ["return", "enter", "tab", "escape", "backspace", "delete"]:
|
||||
return _settings_manager.getSetting('enableActionKeys')
|
||||
|
||||
if event.event_string in ["up", "down", "left", "right", "home", "end", "page_up", "page_down"]:
|
||||
return _settings_manager.getSetting('enableNavigationKeys')
|
||||
|
||||
if event.event_string == "space":
|
||||
return _settings_manager.getSetting('enableSpace')
|
||||
|
||||
if len(event.event_string) == 1:
|
||||
char = event.event_string
|
||||
if char.isalpha():
|
||||
return _settings_manager.getSetting('enableAlphabeticKeys')
|
||||
elif char.isdigit():
|
||||
return _settings_manager.getSetting('enableNumericKeys')
|
||||
elif char in string.punctuation:
|
||||
return _settings_manager.getSetting('enablePunctuationKeys')
|
||||
|
||||
return False
|
||||
|
||||
def is_character_echoable(self, event: input_event.KeyboardEvent) -> bool:
|
||||
"""Returns True if the script will echo this event as part of character echo."""
|
||||
if not _settings_manager.getSetting('enableEchoByCharacter'):
|
||||
return False
|
||||
|
||||
# Character echo is for printable characters being inserted
|
||||
if len(event.event_string) == 1 and event.event_string.isprintable():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def echo_keyboard_event(self, script: 'default.Script', event: input_event.KeyboardEvent) -> None:
|
||||
"""Presents the KeyboardEvent event."""
|
||||
if self.should_echo_keyboard_event(event):
|
||||
if event.event_string == "space":
|
||||
script.presentMessage(messages.SPACE)
|
||||
elif event.event_string == "tab":
|
||||
script.presentMessage(messages.TAB)
|
||||
elif event.event_string == "return" or event.event_string == "enter":
|
||||
script.presentMessage(messages.ENTER)
|
||||
elif event.event_string == "backspace":
|
||||
script.presentMessage(messages.BACKSPACE)
|
||||
elif event.event_string == "delete":
|
||||
script.presentMessage(messages.DELETE)
|
||||
else:
|
||||
# For simple characters and other keys, just speak the event string
|
||||
script.presentMessage(event.event_string)
|
||||
|
||||
# Global instance
|
||||
_manager = None
|
||||
|
||||
def getManager():
|
||||
"""Get the typing echo presenter manager."""
|
||||
global _manager
|
||||
if not _manager:
|
||||
_manager = TypingEchoPresenter()
|
||||
return _manager
|
||||
Executable
+477
@@ -0,0 +1,477 @@
|
||||
#!/usr/bin/python
|
||||
# generate_dbus_documentation.py
|
||||
#
|
||||
# Generate markdown documentation for Cthulhu's D-Bus remote controller.
|
||||
# Must be run while Cthulhu is active with the D-Bus service enabled.
|
||||
#
|
||||
# Copyright 2025 Igalia, S.L.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||
# Boston MA 02110-1301 USA.
|
||||
|
||||
"""Generate markdown documentation for Cthulhu's D-Bus remote controller."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from dasbus.connection import SessionMessageBus
|
||||
from dasbus.error import DBusError
|
||||
|
||||
|
||||
SERVICE_NAME = "org.stormux.Cthulhu.Service"
|
||||
SERVICE_PATH = "/org/stormux/Cthulhu/Service"
|
||||
|
||||
|
||||
def get_system_commands(proxy):
|
||||
"""Get system-level commands from the main service interface."""
|
||||
try:
|
||||
commands = proxy.ListCommands()
|
||||
return sorted(commands, key=lambda x: x[0])
|
||||
except DBusError as e:
|
||||
print(f"Error getting system commands: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
|
||||
def get_modules(proxy):
|
||||
"""Get list of registered modules."""
|
||||
try:
|
||||
modules = proxy.ListModules()
|
||||
return sorted(modules)
|
||||
except DBusError as e:
|
||||
print(f"Error getting modules: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
|
||||
def get_module_info(bus, module_name):
|
||||
"""Get all commands, parameterized commands, getters, and setters for a module."""
|
||||
object_path = f"{SERVICE_PATH}/{module_name}"
|
||||
|
||||
try:
|
||||
module_proxy = bus.get_proxy(SERVICE_NAME, object_path)
|
||||
|
||||
commands = module_proxy.ListCommands()
|
||||
parameterized_commands = module_proxy.ListParameterizedCommands()
|
||||
getters = module_proxy.ListRuntimeGetters()
|
||||
setters = module_proxy.ListRuntimeSetters()
|
||||
|
||||
return {
|
||||
"commands": sorted(commands, key=lambda x: x[0]),
|
||||
"parameterized_commands": sorted(parameterized_commands, key=lambda x: x[0]),
|
||||
"getters": sorted(getters, key=lambda x: x[0]),
|
||||
"setters": sorted(setters, key=lambda x: x[0])
|
||||
}
|
||||
except DBusError as e:
|
||||
print(f"Error getting info for module {module_name}: {e}", file=sys.stderr)
|
||||
return {
|
||||
"commands": [],
|
||||
"parameterized_commands": [],
|
||||
"getters": [],
|
||||
"setters": []
|
||||
}
|
||||
|
||||
|
||||
def format_system_commands(commands):
|
||||
"""Format system-level commands as markdown."""
|
||||
lines = []
|
||||
lines.append("## Service-Level Commands")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"These commands are available directly on the main service object "
|
||||
"at `/org/stormux/Cthulhu/Service`."
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
for name, description in commands:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _group_structural_navigator_commands(commands):
|
||||
"""Group structural navigator commands by object type."""
|
||||
groups = {}
|
||||
other = []
|
||||
|
||||
def normalize_obj_type(obj_type): # pylint: disable=too-many-return-statements
|
||||
"""Convert to a canonical form for grouping."""
|
||||
|
||||
# Group all heading commands together. Ditto for link commands.
|
||||
if obj_type.startswith("Heading"):
|
||||
return "Heading"
|
||||
if "Link" in obj_type:
|
||||
return "Link"
|
||||
|
||||
# Common plural patterns
|
||||
if obj_type.endswith("ies"): # Entries -> Entry
|
||||
return obj_type[:-3] + "y"
|
||||
if obj_type.endswith("xes"): # Checkboxes -> Checkbox
|
||||
return obj_type[:-2]
|
||||
if obj_type.endswith("shes") or obj_type.endswith("ches"): # Matches -> Match
|
||||
return obj_type[:-2]
|
||||
if obj_type.endswith("s") and not obj_type.endswith("ss"):
|
||||
return obj_type[:-1]
|
||||
return obj_type
|
||||
|
||||
for name, description in commands:
|
||||
# Extract the object type from command names like NextHeading, ListButtons, etc.
|
||||
if name.startswith("Next") or name.startswith("Previous"):
|
||||
prefix = "Next" if name.startswith("Next") else "Previous"
|
||||
obj_type = name[len(prefix):]
|
||||
normalized = normalize_obj_type(obj_type)
|
||||
if normalized not in groups:
|
||||
if normalized == "Heading":
|
||||
display = "Headings"
|
||||
elif not obj_type.endswith("s"):
|
||||
display = obj_type + "s"
|
||||
else:
|
||||
display = obj_type
|
||||
groups[normalized] = {"commands": [], "display_name": display}
|
||||
groups[normalized]["commands"].append((name, description))
|
||||
elif name.startswith("List"):
|
||||
obj_type = name[4:]
|
||||
normalized = normalize_obj_type(obj_type)
|
||||
if normalized not in groups:
|
||||
display = "Headings" if normalized == "Heading" else obj_type
|
||||
groups[normalized] = {"commands": [], "display_name": display}
|
||||
else:
|
||||
current_display = groups[normalized]["display_name"]
|
||||
if not current_display.endswith("s") or normalized == "Heading":
|
||||
new_display = "Headings" if normalized == "Heading" else obj_type
|
||||
groups[normalized]["display_name"] = new_display
|
||||
groups[normalized]["commands"].append((name, description))
|
||||
else:
|
||||
# Other commands like ContainerStart, CycleMode
|
||||
other.append((name, description))
|
||||
|
||||
return groups, other
|
||||
|
||||
# pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
|
||||
def format_module_commands(module_name, info):
|
||||
"""Format module-level commands as markdown."""
|
||||
|
||||
lines = []
|
||||
lines.append(f"### {module_name}")
|
||||
lines.append("")
|
||||
lines.append(f"**Object Path:** `/org/stormux/Cthulhu/Service/{module_name}`")
|
||||
lines.append("")
|
||||
|
||||
# Commands - special handling for certain modules
|
||||
if info["commands"]:
|
||||
lines.append("#### Commands")
|
||||
lines.append("")
|
||||
lines.append("**Method:** `org.stormux.Cthulhu.Module.ExecuteCommand`")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"**Parameters:** `CommandName` (string), "
|
||||
"[`NotifyUser`](README-REMOTE-CONTROLLER.md#user-notification-applicability) (boolean)"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
if module_name == "SpeechAndVerbosityManager":
|
||||
# Group related increase/decrease commands
|
||||
def sort_speech_commands(cmd_tuple):
|
||||
name, _ = cmd_tuple
|
||||
# Define groups and their order
|
||||
groups = {
|
||||
"Rate": 0, "Pitch": 1, "Volume": 2,
|
||||
}
|
||||
# Check if it's an Increase/Decrease command
|
||||
for group_name, group_order in groups.items():
|
||||
if group_name in name:
|
||||
if name.startswith("Increase"):
|
||||
return (group_order, 0, name)
|
||||
if name.startswith("Decrease"):
|
||||
return (group_order, 1, name)
|
||||
# Other commands go at the end, sorted alphabetically
|
||||
return (100, 0, name)
|
||||
|
||||
sorted_commands = sorted(info["commands"], key=sort_speech_commands)
|
||||
for name, description in sorted_commands:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
lines.append("")
|
||||
|
||||
elif module_name == "FlatReviewPresenter":
|
||||
# Group Go commands at the top
|
||||
def sort_flat_review_commands(cmd_tuple):
|
||||
name, _ = cmd_tuple
|
||||
if name.startswith("Go"):
|
||||
return (0, name)
|
||||
return (1, name)
|
||||
|
||||
sorted_commands = sorted(info["commands"], key=sort_flat_review_commands)
|
||||
for name, description in sorted_commands:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
lines.append("")
|
||||
|
||||
elif module_name == "CaretNavigator":
|
||||
# Group related navigation commands
|
||||
def sort_caret_commands(cmd_tuple):
|
||||
name, _ = cmd_tuple
|
||||
# Define groups for Character, Word, Line, File
|
||||
if "Character" in name:
|
||||
group = 0
|
||||
elif "Word" in name:
|
||||
group = 1
|
||||
elif "Line" in name:
|
||||
group = 2
|
||||
elif "File" in name:
|
||||
group = 3
|
||||
else:
|
||||
group = 100
|
||||
|
||||
# Within each group: Next, Previous, Start, End, Toggle
|
||||
if name.startswith("Next"):
|
||||
order = 0
|
||||
elif name.startswith("Previous"):
|
||||
order = 1
|
||||
elif name.startswith("Start"):
|
||||
order = 0
|
||||
elif name.startswith("End"):
|
||||
order = 1
|
||||
elif name.startswith("Toggle"):
|
||||
order = 2
|
||||
else:
|
||||
order = 99
|
||||
|
||||
return (group, order, name)
|
||||
|
||||
sorted_commands = sorted(info["commands"], key=sort_caret_commands)
|
||||
for name, description in sorted_commands:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
lines.append("")
|
||||
|
||||
elif module_name == "StructuralNavigator":
|
||||
groups, other = _group_structural_navigator_commands(info["commands"])
|
||||
|
||||
# Show grouped commands by object type first
|
||||
for obj_type in sorted(groups.keys()):
|
||||
cmds = groups[obj_type]
|
||||
display_name = cmds["display_name"]
|
||||
lines.append(f"##### {display_name}")
|
||||
lines.append("")
|
||||
|
||||
# Sort commands in a specific order: Next, Previous, List, grouped by variant
|
||||
def sort_key(cmd_tuple):
|
||||
name, _ = cmd_tuple
|
||||
# Extract base command and any suffix (like Level1, UnvisitedLink, etc.)
|
||||
if name.startswith("Next"):
|
||||
prefix_order = 0
|
||||
suffix = name[4:] # Remove "Next"
|
||||
elif name.startswith("Previous"):
|
||||
prefix_order = 1
|
||||
suffix = name[8:] # Remove "Previous"
|
||||
elif name.startswith("List"):
|
||||
prefix_order = 2
|
||||
suffix = name[4:] # Remove "List"
|
||||
else:
|
||||
prefix_order = 3
|
||||
suffix = name
|
||||
|
||||
# Extract level number or variant for proper ordering
|
||||
level_or_variant = 0
|
||||
|
||||
if "Level" in suffix:
|
||||
# For headings: extract level number
|
||||
try:
|
||||
level_or_variant = int(suffix.split("Level")[1])
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
elif "Unvisited" in suffix:
|
||||
# For links: Unvisited comes after base
|
||||
level_or_variant = 1
|
||||
elif "Visited" in suffix:
|
||||
# For links: Visited comes after Unvisited
|
||||
level_or_variant = 2
|
||||
|
||||
return (level_or_variant, prefix_order, name)
|
||||
|
||||
sorted_commands = sorted(cmds["commands"], key=sort_key)
|
||||
for name, desc in sorted_commands:
|
||||
lines.append(f"- **`{name}`:** {desc}")
|
||||
lines.append("")
|
||||
|
||||
# Show uncategorized commands at the end
|
||||
if other:
|
||||
lines.append("##### Other")
|
||||
lines.append("")
|
||||
for name, description in other:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
lines.append("")
|
||||
else:
|
||||
for name, description in info["commands"]:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
lines.append("")
|
||||
|
||||
# Parameterized Commands
|
||||
if info["parameterized_commands"]:
|
||||
lines.append("#### Parameterized Commands")
|
||||
lines.append("")
|
||||
lines.append("**Method:** `org.stormux.Cthulhu.Module.ExecuteParameterizedCommand`")
|
||||
lines.append("")
|
||||
for name, description, parameters in info["parameterized_commands"]:
|
||||
param_list = ", ".join([f"`{pname}` ({ptype})" for pname, ptype in parameters])
|
||||
if param_list:
|
||||
lines.append(f"- **`{name}`:** {description} Parameters: {param_list}")
|
||||
else:
|
||||
lines.append(f"- **`{name}`:** {description}")
|
||||
lines.append("")
|
||||
|
||||
# Runtime Settings (combine getters and setters)
|
||||
if info["getters"] or info["setters"]:
|
||||
lines.append("#### Settings")
|
||||
lines.append("")
|
||||
lines.append("**Methods:** `org.stormux.Cthulhu.Module.ExecuteRuntimeGetter` / `org.stormux.Cthulhu.Module.ExecuteRuntimeSetter`")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"**Parameters:** `PropertyName` (string), "
|
||||
"`Value` (variant, setter only)"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
# Build a merged dictionary of properties
|
||||
# Prefer setter descriptions as they may contain range/default info
|
||||
properties = {}
|
||||
for name, description in info["getters"]:
|
||||
properties[name] = {"description": description, "getter": True, "setter": False}
|
||||
for name, description in info["setters"]:
|
||||
if name in properties:
|
||||
properties[name]["setter"] = True
|
||||
properties[name]["description"] = description
|
||||
else:
|
||||
properties[name] = {"description": description, "getter": False, "setter": True}
|
||||
|
||||
# Output sorted properties with annotations
|
||||
for name in sorted(properties.keys()):
|
||||
prop = properties[name]
|
||||
description = prop["description"]
|
||||
annotation = ""
|
||||
|
||||
if prop["getter"] and not prop["setter"]:
|
||||
annotation = " (getter only)"
|
||||
elif prop["setter"] and not prop["getter"]:
|
||||
annotation = " (setter only)"
|
||||
else:
|
||||
# Both getter and setter - change "Returns" or "Sets" to "Gets/Sets"
|
||||
if description.startswith("Returns "):
|
||||
description = description.replace("Returns ", "Gets/Sets ", 1)
|
||||
elif description.startswith("Sets "):
|
||||
description = description.replace("Sets ", "Gets/Sets ", 1)
|
||||
|
||||
lines.append(f"- **`{name}`:** {description}{annotation}")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_documentation():
|
||||
"""Generate the complete documentation."""
|
||||
try:
|
||||
bus = SessionMessageBus()
|
||||
except DBusError as e:
|
||||
print(f"Error connecting to D-Bus: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
try:
|
||||
proxy = bus.get_proxy(SERVICE_NAME, SERVICE_PATH)
|
||||
except DBusError as e:
|
||||
print(f"Error connecting to Cthulhu service: {e}", file=sys.stderr)
|
||||
print("Make sure Cthulhu is running with the D-Bus service enabled.", file=sys.stderr)
|
||||
return None
|
||||
|
||||
system_commands = get_system_commands(proxy)
|
||||
modules = get_modules(proxy)
|
||||
module_infos = {}
|
||||
for module_name in modules:
|
||||
module_infos[module_name] = get_module_info(bus, module_name)
|
||||
|
||||
total_commands = len(system_commands)
|
||||
total_commands += sum(len(info["commands"]) for info in module_infos.values())
|
||||
total_commands += sum(len(info["parameterized_commands"]) for info in module_infos.values())
|
||||
total_getters = sum(len(info["getters"]) for info in module_infos.values())
|
||||
total_setters = sum(len(info["setters"]) for info in module_infos.values())
|
||||
|
||||
lines = []
|
||||
lines.append("# Cthulhu D-Bus Service Commands Reference")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"This document lists all commands ({total_commands}), "
|
||||
f"runtime getters ({total_getters}), and runtime setters ({total_setters}) available"
|
||||
)
|
||||
lines.append("via Cthulhu's D-Bus Remote Controller interface.")
|
||||
lines.append("")
|
||||
lines.append("The service can be accessed at:")
|
||||
lines.append("")
|
||||
lines.append("- **Service Name:** `org.stormux.Cthulhu.Service`")
|
||||
lines.append("- **Main Object Path:** `/org/stormux/Cthulhu/Service`")
|
||||
lines.append("- **Module Object Paths:** `/org/stormux/Cthulhu/Service/ModuleName`")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Additional information about using the remote controller can be found in "
|
||||
"[README-REMOTE-CONTROLLER.md](README-REMOTE-CONTROLLER.md)."
|
||||
)
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
# System commands
|
||||
lines.append(format_system_commands(system_commands))
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
# Module commands
|
||||
lines.append("## Modules")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Each module exposes commands, getters, and setters on its object "
|
||||
"at `/org/stormux/Cthulhu/Service/ModuleName`."
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
for module_name in modules:
|
||||
lines.append(format_module_commands(module_name, module_infos[module_name]))
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
|
||||
# Write to parent directory since script is in tools/
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(script_dir)
|
||||
output_file = os.path.join(parent_dir, "REMOTE-CONTROLLER-COMMANDS.md")
|
||||
|
||||
print("Generating D-Bus documentation...", file=sys.stderr)
|
||||
documentation = generate_documentation()
|
||||
|
||||
if documentation is None:
|
||||
print("Failed to generate documentation.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
try:
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write(documentation)
|
||||
print(f"Documentation written to {output_file}", file=sys.stderr)
|
||||
return 0
|
||||
except IOError as e:
|
||||
print(f"Error writing to {output_file}: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user