Skip to content

Commit

Permalink
1.1.6 - tk-window + plgin + opacity loop
Browse files Browse the repository at this point in the history
  • Loading branch information
JimEverest committed Oct 16, 2024
1 parent 33ddfa3 commit f25a86e
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 96 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ venv/
*.egg-info/
dist/
build/
decoded/*

182 changes: 137 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,59 +185,137 @@ Fastshot is designed to seamlessly integrate into your workflow without altering
By providing powerful tools for capturing, annotating, and sharing screen content, Fastshot is an indispensable asset for anyone who requires efficient multitasking capabilities in their daily activities.



## Plugin Mechanism
Fastshot supports a plugin mechanism, allowing for functionality extensions to meet various business needs. You can develop custom plugins to enhance or modify the tool's capabilities.

1. Create a Plugin Class: Your plugin should be a Python class with the desired functionality. For example, an OCR plugin might look like this:
The plugin system in Fastshot is designed to be simple yet powerful, enabling developers to add custom functionalities without modifying the core application code. Plugins are Python modules that adhere to a specific interface, allowing the main application to load, manage, and execute them seamlessly.

### How the Plugin System Works
- **Plugin Discovery:** On startup, Fastshot scans the plugins directory for plugin modules.
- **Dynamic Loading:** The application dynamically imports each plugin using Python's importlib.
- **Metadata Extraction:** Each plugin provides metadata (e.g., name, ID, description) through a get_plugin_info() function.
- **Hotkey Registration:** Plugins specify default keyboard shortcuts and activation criteria (e.g., pressing the Alt key three times). The main application registers these hotkeys.
- **Execution:** When a plugin's activation criteria are met, the main application calls the plugin's run(app_context) function, passing the application context for interaction.

### Plugin Structure
A plugin can be a single Python file placed directly in the plugins directory or a package (folder with an __init__.py file) if it requires multiple modules or resources.

#### Plugin Metadata
Each plugin must define a get_plugin_info() function that returns a dictionary with the following keys:
- **name**: Human-readable name of the plugin.
- **id**: Unique identifier for the plugin.
- **description**: Brief description of the plugin's functionality.
- **author**: Author's name.
- **version**: Version of the plugin.
- **default_shortcut**: Default keyboard shortcut to activate the plugin (e.g., 'alt').
- **press_times**: Number of consecutive times the shortcut key must be pressed to activate the plugin.
- **enabled**: Boolean indicating whether the plugin is enabled by default.

#### Plugin Entry Point
Each plugin must implement a run(app_context) function, which is the entry point when the plugin is activated. The app_context parameter provides access to the main application and allows the plugin to interact with it.

### Developing a Plugin
Follow these steps to create a plugin for Fastshot.

#### Step 1: Create the Plugin File
Navigate to the plugins directory in the Fastshot application.
Create a new Python file for your plugin (e.g., my_plugin.py).

#### Step 2: Define Plugin Metadata
In your plugin file, define the get_plugin_info() function:
```python
from paddleocr import PaddleOCR
from PIL import Image
import win32clipboard
import tkinter as tk
def get_plugin_info():
"""Returns metadata about the plugin."""
return {
'name': 'My Plugin',
'id': 'my_plugin',
'description': 'A plugin that does something useful.',
'author': 'Your Name',
'version': '1.0',
'default_shortcut': 'alt',
'press_times': 3,
'enabled': True
}
```
Note: Adjust the default_shortcut and press_times to suit your plugin's activation method.

class PluginOCR:
def __init__(self):
self.ocr_engine = PaddleOCR(use_angle_cls=True, lang='en')

def ocr(self, image):
result = self.ocr_engine.ocr(image, cls=True)
ocr_text = "\n".join([line[1][0] for res in result for line in res])
self.copy_to_clipboard(ocr_text)
return ocr_text

def copy_to_clipboard(self, text):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(text, win32clipboard.CF_UNICODETEXT)
win32clipboard.CloseClipboard()

def show_message(self, message, parent):
label = tk.Label(parent, text=message, bg="yellow", fg="black", font=("Helvetica", 10))
label.pack(side="bottom", fill="x")
parent.after(3000, label.destroy)
#### Step 3: Implement the run Function
Implement the run(app_context) function, which contains the code to be executed when the plugin is activated:

```python
def run(app_context):
"""The main function that gets called when the plugin is activated."""
# Your plugin code here
print("My Plugin has been activated!")
```
2. Register the Plugin: In the SnipasteApp class, you can register your plugin by adding it to the plugin list.
Example: You might display a message box, manipulate application data, or perform any desired action.
#### Step 4: Use the Application Context (Optional)
If your plugin needs to interact with the main application, use the app_context parameter:

```python
class SnipasteApp:
def load_plugins(self):
plugin_modules = ['fastshot.plugin_ocr'] # Add your plugin module here
for module_name in plugin_modules:
module = importlib.import_module(module_name)
plugin_class = getattr(module, 'PluginOCR')
self.plugins[module_name] = plugin_class()
def run(app_context):
"""The main function that gets called when the plugin is activated."""
# Access application attributes or methods
app_context.some_method()
```
Note: Refer to the application documentation for available methods and attributes.
#### Step 5: Test the Plugin
Start the Fastshot application.
Activate the plugin by pressing the specified shortcut key the required number of times within one second.
Verify that the plugin behaves as expected.

### Example Plugin
Below is an example of a simple plugin that displays a "Hello, World!" message when activated.

3. Invoke the Plugin: You can invoke the plugin from your application code, such as from a menu item.
```python
def ocr(self):
plugin = self.app.plugins.get('fastshot.plugin_ocr')
if plugin:
img_path = 'temp.png'
self.img_label.zoomed_image.save(img_path)
result = plugin.ocr(img_path)
plugin.show_message("OCR result updated in clipboard", self.img_window)
# plugins/plugin_hello_world.py
import tkinter as tk
from tkinter import messagebox

def get_plugin_info():
"""Returns metadata about the plugin."""
return {
'name': 'Hello World Plugin',
'id': 'plugin_hello_world',
'description': 'Displays a Hello World message.',
'author': 'Your Name',
'version': '1.0',
'default_shortcut': 'alt',
'press_times': 3,
'enabled': True
}

def run(app_context):
"""The main function that gets called when the plugin is activated."""
root = tk.Tk()
root.withdraw()
messagebox.showinfo("Hello Plugin", "Hello, World!")
root.destroy()
```
By following these steps, you can create and integrate custom plugins to extend the functionality of Fastshot.
Activation: Press the Alt key three times within one second to activate this plugin.

### Plugin Configuration
Default Shortcuts: Plugins specify default shortcuts in their metadata.
User Configuration: In future versions, users will be able to modify plugin settings (e.g., shortcuts, enable/disable) through the application's web portal or configuration files.
Conflict Avoidance: Ensure your plugin's shortcut doesn't conflict with existing shortcuts.

### Best Practices
Unique IDs: Assign a unique id to your plugin to prevent conflicts.
Error Handling: Include try-except blocks in your plugin code to handle exceptions gracefully.
Minimal Impact: Ensure your plugin doesn't negatively impact the application's performance or stability.
Documentation: Comment your code and provide clear explanations of your plugin's functionality.
Security: Avoid executing untrusted code and be cautious with file and network operations.

### Security Considerations
Trust: Only use plugins from trusted sources to prevent security risks.
Sandboxing: Currently, plugins run with the same permissions as the main application. Be mindful of this when developing plugins.
Validation: Future versions may include security enhancements, such as plugin signing or sandboxing mechanisms.

### Contributing Plugins
Share Your Plugin: If you've developed a plugin that could benefit others, consider contributing it to the project.
Contribution Guidelines: Follow the project's contribution guidelines for submitting plugins.
Collaboration: Engage with the community to improve and expand plugin functionalities.




Expand All @@ -246,7 +324,7 @@ By following these steps, you can create and integrate custom plugins to extend
1. ~~tk window force trigger~~
2. ~~ppocr[Default]~~
3. ~~screenpen integration~~
4. hyder
4. ~~hyder~~
5. ~~transprent window~~
6. ~~fixed on top~~
7. pyinstaller
Expand All @@ -257,4 +335,18 @@ By following these steps, you can create and integrate custom plugins to extend
12. ~~Documents~~
13. ~~config-env~~
14. copy&paste image into the Ask Dialog
15. ~~Global Ask Dialog~~
15. ~~Global Ask Dialog~~AS

17. predefined prompt for ask
18. D-board name
19. OCR Packaging.




20. ~~透明度单向循环。(100-->90-->80-->70-->60-->50-->40-->30-->20-->10 --> 100 --> ......)~~
21. tk window force bring to front again
22. tk window trigger clean previous window.

16. 2nd color for screenpen + Highlighter

77 changes: 36 additions & 41 deletions fastshot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
from fastshot.window_control import HotkeyListener, load_config
from fastshot.ask_dialog import AskDialog


import importlib
import pkgutil
import time


#plugins
from fastshot.plugin_ocr import PluginOCR
# from fastshot.plugin_ask import PluginAsk
Expand All @@ -47,10 +53,11 @@ def __init__(self):
self.snipping_tool = SnippingTool(self.root, self.monitors, self.on_screenshot)
self.windows = []
self.plugins = {}

self.config = self.load_config()
self.print_config_info()
self.check_and_download_models()
# # self.load_plugins()
self.load_plugins()
self.plugins['fastshot.plugin_ocr']=PluginOCR()
# self.plugins['fastshot.plugin_ask']=PluginAsk()

Expand All @@ -70,6 +77,34 @@ def __init__(self):
# Start the Flask web app
self.start_flask_app()


def load_plugins(self):
plugins_dir = os.path.join(os.path.dirname(__file__), 'plugins')
sys.path.insert(0, plugins_dir)

for finder, name, ispkg in pkgutil.iter_modules([plugins_dir]):
try:
plugin_module = importlib.import_module(name)
plugin_info = plugin_module.get_plugin_info()
self.plugins[plugin_info['id']] = {
'module': plugin_module,
'info': plugin_info
}
print(f"Loaded plugin: {plugin_info['name']}")
except Exception as e:
print(f"Failed to load plugin {name}: {e}")

def setup_plugin_hotkeys(self):
for plugin_id, plugin_data in self.plugins.items():
plugin_info = plugin_data['info']
if plugin_info.get('enabled', True):
shortcut_key = plugin_info.get('default_shortcut')
press_times = int(plugin_info.get('press_times', 1))
self.listener.register_plugin_hotkey(
plugin_id, shortcut_key, press_times)



def start_flask_app(self):
def run_flask():
try:
Expand Down Expand Up @@ -148,31 +183,6 @@ def print_config_info(self):
value = self.config['Shortcuts'].get(key, '')
print(f"{desc}: {value}")

# def check_and_download_models(self):
# home_dir = os.path.expanduser('~') #C:\Users\xxxxxxx/
# paddleocr_dir = os.path.join(home_dir, '.paddleocr', 'whl')#C:\Users\xxxxxxx/.paddleocr/whl/
# model_dirs = [
# os.path.join(paddleocr_dir, 'det', 'ch', 'ch_PP-OCRv4_det_infer'),#C:\Users\xxxxxxx/.paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer/
# os.path.join(paddleocr_dir, 'rec', 'ch', 'ch_PP-OCRv4_rec_infer'),#C:\Users\xxxxxxx/.paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer/
# os.path.join(paddleocr_dir, 'cls', 'ch_ppocr_mobile_v2.0_cls_infer')#C:\Users\xxxxxxx/.paddleocr/whl/cls/ch_ppocr_mobile_v2.0_cls_infer/
# ]
# models_exist = all(os.path.exists(model_dir) for model_dir in model_dirs)
# if not models_exist:
# print("未找到 PaddleOCR 模型文件,正在下载...")
# download_url = self.config['Paths'].get('download_url')#C:\Users\xxxxxxx/.paddleocr.zip
# zip_path = os.path.join(home_dir, '.paddleocr.zip')
# try:
# urllib.request.urlretrieve(download_url, zip_path)
# print("下载完成,正在解压...")
# with zipfile.ZipFile(zip_path, 'r') as zip_ref:
# zip_ref.extractall(home_dir)
# print("模型文件解压完成。")
# os.remove(zip_path)
# except Exception as e:
# print(f"下载和解压模型文件失败: {e}")
# else:
# print("PaddleOCR 模型文件已存在。")

def check_and_download_models(self):
home_dir = os.path.expanduser('~') # C:\Users\xxxxxxx/
paddleocr_dir = os.path.join(home_dir, '.paddleocr', 'whl') # C:\Users\xxxxxxx/.paddleocr/whl/
Expand Down Expand Up @@ -210,17 +220,7 @@ def check_and_download_models(self):
print(f"下载和解压模型文件失败: {e}")
else:
print("PaddleOCR 模型文件已存在。")


# def load_plugins(self):
# plugin_modules = {
# 'fastshot.plugin_ocr': 'PluginOCR',
# 'fastshot.plugin_ask': 'PluginAsk'
# }
# for module_name, class_name in plugin_modules.items():
# module = importlib.import_module(module_name)
# plugin_class = getattr(module, class_name)
# self.plugins[module_name] = plugin_class()

def setup_hotkey_listener(self):
def on_activate_snip():
Expand All @@ -246,11 +246,6 @@ def for_canonical(f):
on_press=for_canonical(lambda key: on_escape() if key == keyboard.Key.esc else None))
self.listener_escape.start()

def start_screen_pen_listener(self):
# 启动 ScreenPen 的键盘监听器线程
keyboard_thread = threading.Thread(target=self.screen_pen.start_keyboard_listener)
keyboard_thread.daemon = True
keyboard_thread.start()

def on_screenshot(self, img):
window = ImageWindow(self, img, self.config)
Expand Down
25 changes: 25 additions & 0 deletions fastshot/plugins/plugin_hello_world.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# plugin_hello_world.py

import tkinter as tk
from tkinter import messagebox

def run(app_context):
"""The main function that gets called when the plugin is activated."""
# Display a Hello World message box
root = tk.Tk()
root.withdraw()
messagebox.showinfo("Hello Plugin", "Hello, World!")
root.destroy()

def get_plugin_info():
"""Returns metadata about the plugin."""
return {
'name': 'Hello World Plugin',
'id': 'plugin_hello_world',
'description': 'A sample plugin that shows a Hello World message.',
'author': 'Jim',
'version': '1.0',
'default_shortcut': 'esc',
'press_times': 4,
'enabled': True
}
Empty file.
2 changes: 1 addition & 1 deletion fastshot/screen_pen.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def set_window_to_draw(self):
# 移除 WS_EX_TRANSPARENT 样式
extended_style = extended_style & ~0x20
ctypes.windll.user32.SetWindowLongW(hwnd, -20, extended_style)
self.set_window_opacity(0.15) # 设置透明度为 15%
self.set_window_opacity(0.4) # 设置透明度为 15%
else:
print("Could not find window handle to set drawing mode.")

Expand Down
Loading

0 comments on commit f25a86e

Please sign in to comment.