From f25a86e5fae8de123e2300e8a736d24cd33e02db Mon Sep 17 00:00:00 2001 From: jimeverest Date: Wed, 16 Oct 2024 10:18:07 +0800 Subject: [PATCH] 1.1.6 - tk-window + plgin + opacity loop --- .gitignore | 2 + README.md | 182 +++++++++++++++++++------ fastshot/main.py | 77 +++++------ fastshot/plugins/plugin_hello_world.py | 25 ++++ fastshot/plugins/utils/__init__.py | 0 fastshot/screen_pen.py | 2 +- fastshot/snipping_tool.py | 38 +++++- fastshot/window_control.py | 95 ++++++++++++- setup.py | 2 +- test.ipynb | 0 10 files changed, 327 insertions(+), 96 deletions(-) create mode 100644 fastshot/plugins/plugin_hello_world.py create mode 100644 fastshot/plugins/utils/__init__.py create mode 100644 test.ipynb diff --git a/.gitignore b/.gitignore index 42dea21..8704dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ venv/ *.egg-info/ dist/ build/ +decoded/* + diff --git a/README.md b/README.md index 677f22d..8a7abdb 100644 --- a/README.md +++ b/README.md @@ -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. + @@ -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 @@ -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 + diff --git a/fastshot/main.py b/fastshot/main.py index e283bf3..c36625e 100644 --- a/fastshot/main.py +++ b/fastshot/main.py @@ -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 @@ -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() @@ -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: @@ -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/ @@ -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(): @@ -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) diff --git a/fastshot/plugins/plugin_hello_world.py b/fastshot/plugins/plugin_hello_world.py new file mode 100644 index 0000000..b63e1c6 --- /dev/null +++ b/fastshot/plugins/plugin_hello_world.py @@ -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 + } diff --git a/fastshot/plugins/utils/__init__.py b/fastshot/plugins/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastshot/screen_pen.py b/fastshot/screen_pen.py index 0d51f7f..0059d1d 100644 --- a/fastshot/screen_pen.py +++ b/fastshot/screen_pen.py @@ -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.") diff --git a/fastshot/snipping_tool.py b/fastshot/snipping_tool.py index 241dbd8..ad3e46c 100644 --- a/fastshot/snipping_tool.py +++ b/fastshot/snipping_tool.py @@ -3,6 +3,8 @@ import mss import mss.tools import io +import win32gui +import win32con class SnippingTool: def __init__(self, root, monitors, on_screenshot): @@ -14,6 +16,9 @@ def __init__(self, root, monitors, on_screenshot): self.rects = [] def start_snipping(self): + # Clear any existing overlays + self.exit_snipping() + self.snipping = True self.overlays = [] self.canvases = [] @@ -21,9 +26,11 @@ def start_snipping(self): for monitor in self.monitors: overlay = tk.Toplevel(self.root) + overlay.title("overlay_snipping") overlay.geometry(f"{monitor.width}x{monitor.height}+{monitor.x}+{monitor.y}") overlay.configure(bg='blue') overlay.attributes('-alpha', 0.3) + overlay.attributes('-topmost', True) # Ensure the window is always on top overlay.bind('', self.exit_snipping) canvas = tk.Canvas(overlay, cursor="cross") @@ -36,15 +43,35 @@ def start_snipping(self): self.canvases.append(canvas) self.rects.append(None) + # Bring the overlay window to the front + self.bring_window_to_front(overlay) + self.root.update_idletasks() self.root.update() self.start_x = self.start_y = self.end_x = self.end_y = 0 + def bring_window_to_front(self, window): + # Get the window handle (HWND) + hwnd = int(window.frame(), 16) # Convert hex string to integer + # Use win32gui to bring the window to front + win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, + win32con.SWP_NOMOVE | win32con.SWP_NOSIZE) + win32gui.SetForegroundWindow(hwnd) + win32gui.BringWindowToTop(hwnd) + # Force the window to be the active window + win32gui.SetActiveWindow(hwnd) + def exit_snipping(self, event=None): self.snipping = False for overlay in self.overlays: - overlay.destroy() + try: + overlay.destroy() + except Exception as e: + print(f"Error destroying overlay: {e}") + self.overlays = [] + self.canvases = [] + self.rects = [] def on_mouse_down(self, event): self.start_x = event.x_root @@ -81,10 +108,10 @@ def take_screenshot(self): with mss.mss() as sct: monitor = { - "top": y1, - "left": x1, - "width": x2 - x1, - "height": y2 - y1 + "top": int(y1), + "left": int(x1), + "width": int(x2 - x1), + "height": int(y2 - y1) } screenshot = sct.grab(monitor) img = mss.tools.to_png(screenshot.rgb, screenshot.size) @@ -95,4 +122,3 @@ def take_screenshot(self): img = Image.open(io.BytesIO(img)) img = img.convert('RGB') self.on_screenshot(img) - diff --git a/fastshot/window_control.py b/fastshot/window_control.py index d59e2fa..5483963 100644 --- a/fastshot/window_control.py +++ b/fastshot/window_control.py @@ -89,6 +89,9 @@ def toggle_always_on_top(): class HotkeyListener: def __init__(self, config, root, app): + self.plugin_shortcuts = {} + self.plugin_key_counts = {} + self.plugin_last_press_times = {} self.config = config self.root = root # Tkinter root window self.app = app # Reference to main application @@ -97,6 +100,27 @@ def __init__(self, config, root, app): self.ctrl_press_count = 0 self.ctrl_last_release_time = 0.0 + def register_plugin_hotkeys(self): + for plugin_id, plugin_data in self.app.plugins.items(): + try: + plugin_info = plugin_data['info'] + if plugin_info.get('enabled', True): + key_str = plugin_info.get('default_shortcut') + press_times = int(plugin_info.get('press_times', 1)) + self.register_plugin_hotkey(plugin_id, key_str, press_times) + print(f"Registered plugin hotkey: {plugin_info['name']}, key: {key_str}, press_times: {press_times}") + except Exception as e: + print(f"Error registering plugin hotkey for {plugin_id}: {e}") + continue + + def register_plugin_hotkey(self, plugin_id, key_str, press_times): + self.plugin_shortcuts[key_str] = { + 'plugin_id': plugin_id, + 'press_times': press_times + } + self.plugin_key_counts[key_str] = 0 + self.plugin_last_press_times[key_str] = 0 + def load_hotkeys(self): shortcuts = self.config['Shortcuts'] @@ -128,12 +152,38 @@ def load_hotkeys(self): self.ask_dialog_time_window = float(shortcuts.get('hotkey_ask_dialog_time_window', '1.0')) def start(self): + print("Starting HotkeyListener") self.listener = keyboard.Listener( on_press=self.on_press, on_release=self.on_release) + self.register_plugin_hotkeys() # Add this line self.listener.start() + + def get_key_char(self, key): + if isinstance(key, keyboard.Key): + # Handle special keys + if key == keyboard.Key.alt or key == keyboard.Key.alt_l or key == keyboard.Key.alt_r: + return 'alt' + elif key == keyboard.Key.ctrl or key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r: + return 'ctrl' + elif key == keyboard.Key.shift or key == keyboard.Key.shift_l or key == keyboard.Key.shift_r: + return 'shift' + elif key == keyboard.Key.cmd: + return 'cmd' + # Add other special keys as needed + else: + return str(key).lower().replace('key.', '') + else: + try: + return key.char.lower() + except AttributeError: + return str(key).lower().replace('key.', '') + def on_press(self, key): + print(f"Key pressed: {key}") + # Existing code... + # --------------------------------------- self.hotkey_topmost_on.press(self.listener.canonical(key)) self.hotkey_topmost_off.press(self.listener.canonical(key)) self.hotkey_opacity_down.press(self.listener.canonical(key)) @@ -141,13 +191,16 @@ def on_press(self, key): self.hotkey_snip.press(self.listener.canonical(key)) # Handle Ctrl key presses + # --------------------------------------- if key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r: pass # Do nothing on press else: # Any other key resets the count self.ctrl_press_count = 0 + def on_release(self, key): + print(f"Key released: {key}") self.hotkey_topmost_on.release(self.listener.canonical(key)) self.hotkey_topmost_off.release(self.listener.canonical(key)) self.hotkey_opacity_down.release(self.listener.canonical(key)) @@ -174,6 +227,36 @@ def on_release(self, key): # Any other key resets the count self.ctrl_press_count = 0 + # --------------------------------------- + # Handle plugin hotkeys + key_char = self.get_key_char(key) + print(f"Key pressed: {key_char}") # Debug statement + if key_char in self.plugin_shortcuts: + current_time = time.time() + last_press_time = self.plugin_last_press_times.get(key_char, 0) + if current_time - last_press_time > 1.0: + self.plugin_key_counts[key_char] = 1 + else: + self.plugin_key_counts[key_char] += 1 + + self.plugin_last_press_times[key_char] = current_time + + if self.plugin_key_counts[key_char] >= self.plugin_shortcuts[key_char]['press_times']: + plugin_id = self.plugin_shortcuts[key_char]['plugin_id'] + self.activate_plugin(plugin_id) + self.plugin_key_counts[key_char] = 0 + + def activate_plugin(self, plugin_id): + plugin_data = self.app.plugins.get(plugin_id) + if plugin_data: + plugin_module = plugin_data['module'] + try: + plugin_module.run(self.app) + print(f"Activated plugin: {plugin_data['info']['name']}") + except Exception as e: + print(f"Error activating plugin {plugin_id}: {e}") + + def toggle_topmost_on(self): toggle_always_on_top() @@ -184,7 +267,11 @@ def decrease_opacity(self): hwnd = get_foreground_window() if hwnd: current_opacity = get_window_opacity(hwnd) - new_opacity = max(0.1, current_opacity - 0.1) + if current_opacity > 0.1: + new_opacity = current_opacity - 0.1 + else: + new_opacity = 1.0 # Reset to 100% opacity + new_opacity = round(new_opacity, 1) # Ensure precision set_window_opacity(hwnd, new_opacity) print(f"Window opacity decreased to {new_opacity * 100:.0f}%") @@ -192,7 +279,11 @@ def increase_opacity(self): hwnd = get_foreground_window() if hwnd: current_opacity = get_window_opacity(hwnd) - new_opacity = min(1.0, current_opacity + 0.1) + if current_opacity < 1.0: + new_opacity = current_opacity + 0.1 + else: + new_opacity = 0.1 # Reset to 10% opacity + new_opacity = round(new_opacity, 1) # Ensure precision set_window_opacity(hwnd, new_opacity) print(f"Window opacity increased to {new_opacity * 100:.0f}%") diff --git a/setup.py b/setup.py index 0ae20f8..6662c75 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='fastshot', - version='1.1.5', + version='1.1.6', packages=find_packages(), include_package_data=True, install_requires=[ diff --git a/test.ipynb b/test.ipynb new file mode 100644 index 0000000..e69de29