5 Commits
v0.9.5 ... main

Author SHA1 Message Date
7922910bd8 Update README and version for v1.0.0 release
- Add relay server documentation and setup instructions
- Add relay_client.py to project structure
- Add aiohttp to core dependencies
- Update version.txt to 1.0.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 14:06:52 -08:00
664d652e9e Fix Copied! visibility and remove URL hover effect
- Use visible gray (#888888) for "Copied!" text
- Remove hover color change from URL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 11:48:45 -08:00
10971e6a02 Remove version from title bar, improve copy feedback
- Remove version number from window title (still shown in About)
- Show "Copied!" over URL text with dimmed style instead of status bar message

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 11:44:31 -08:00
17f4bc0c5f Make URL clickable to copy instead of separate button
Cleaner interface - URL text now highlights on hover and copies to
clipboard when clicked.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 11:33:14 -08:00
44c21e68d8 Release v1.0.0 with UI improvements and bug fixes
- Fix window restore when clicking Show from system tray or double-clicking
- Add Copy button for web interface URL
- Fix image loading when app starts with Windows startup
- Remove debug print statements
- Bump version to 1.0.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 11:26:29 -08:00
4 changed files with 85 additions and 16 deletions

View File

@@ -20,6 +20,7 @@ A cross-platform macro management application with desktop and web interfaces. C
- **System Tray**: Minimize to tray, always accessible
### Additional Features
- **Relay Server Support**: Access your macros securely over HTTPS from anywhere
- **QR Code Generation**: Quickly connect mobile devices
- **Real-time Sync**: WebSocket updates across all connected devices
- **Offline Support**: PWA caches for offline macro viewing
@@ -39,6 +40,7 @@ A cross-platform macro management application with desktop and web interfaces. C
- pystray (System tray)
- netifaces (Network detection)
- qrcode (QR code generation)
- aiohttp (Relay server client)
## Installation
@@ -136,6 +138,24 @@ The web interface provides full macro management:
- **Show**: Restore window
- **Quit**: Exit application
### Relay Server (Remote Access)
Access your macros from outside your local network using a relay server:
1. Click **Settings** (gear icon) in the toolbar
2. Check **Enable Relay Server**
3. Enter your relay server URL and password
4. Click **Save**
Once connected, a relay URL will appear in the toolbar. Use this URL from any device with internet access. The relay provides:
- Secure HTTPS connection
- Full macro execution and management
- PWA installation support
- Wake lock and fullscreen mode
> [!NOTE]
> You need access to a relay server. See [MP-Relay](https://repo.anhonesthost.net/MacroPad/MP-Relay) for self-hosting instructions.
## Command Types Reference
| Type | Description | Parameters |
@@ -213,6 +233,7 @@ MP-Server/
├── config.py # Configuration constants
├── macro_manager.py # Macro storage and execution
├── web_server.py # FastAPI web server
├── relay_client.py # Relay server WebSocket client
├── pyproject.toml # Dependencies and build config
├── gui/ # PySide6 desktop interface
│ ├── main_window.py

View File

@@ -1,6 +1,6 @@
# Configuration and constants for MacroPad Server
VERSION = "0.9.5"
VERSION = "1.0.0"
DEFAULT_PORT = 40000
SETTINGS_FILE = "settings.json"

View File

@@ -40,7 +40,7 @@ class MacroButton(QPushButton):
edit_requested = Signal(str)
delete_requested = Signal(str)
def __init__(self, macro_id: str, macro: dict, parent=None):
def __init__(self, macro_id: str, macro: dict, app_dir: str, parent=None):
super().__init__(parent)
self.macro_id = macro_id
self.macro = macro
@@ -75,7 +75,9 @@ class MacroButton(QPushButton):
image_label.setAlignment(Qt.AlignCenter)
if macro.get("image_path"):
pixmap = QPixmap(macro["image_path"])
# Resolve relative image path against app directory
image_path = os.path.join(app_dir, macro["image_path"])
pixmap = QPixmap(image_path)
if not pixmap.isNull():
pixmap = pixmap.scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(pixmap)
@@ -174,7 +176,7 @@ class MainWindow(QMainWindow):
def setup_ui(self):
"""Setup the main UI components."""
self.setWindowTitle(f"MacroPad Server v{VERSION}")
self.setWindowTitle("MacroPad Server")
self.setMinimumSize(600, 400)
self.setStyleSheet(f"background-color: {THEME['bg_color']};")
@@ -210,9 +212,18 @@ class MainWindow(QMainWindow):
toolbar_layout.addStretch()
# IP address label
self.ip_label = QLabel()
self.ip_label.setStyleSheet(f"color: {THEME['fg_color']};")
# Clickable IP address label (click to copy)
self.ip_label = QPushButton()
self.ip_label.setCursor(Qt.PointingHandCursor)
self.ip_label.setStyleSheet(f"""
QPushButton {{
background-color: transparent;
color: {THEME['fg_color']};
border: none;
padding: 4px 8px;
}}
""")
self.ip_label.clicked.connect(self.copy_url_to_clipboard)
self.update_ip_label()
toolbar_layout.addWidget(self.ip_label)
@@ -358,7 +369,7 @@ class MainWindow(QMainWindow):
# Tray menu
tray_menu = QMenu()
show_action = tray_menu.addAction("Show")
show_action.triggered.connect(self.show)
show_action.triggered.connect(self.restore_window)
quit_action = tray_menu.addAction("Quit")
quit_action.triggered.connect(self.close)
@@ -369,8 +380,15 @@ class MainWindow(QMainWindow):
def on_tray_activated(self, reason):
"""Handle tray icon activation."""
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
self.show()
self.activateWindow()
self.restore_window()
def restore_window(self):
"""Restore and bring window to front."""
# Clear minimized state and show
self.setWindowState(Qt.WindowNoState)
self.show()
self.raise_()
self.activateWindow()
def start_server(self):
"""Start the web server in a background thread."""
@@ -422,7 +440,6 @@ class MainWindow(QMainWindow):
# Check if relay is connected and has a session ID
relay_connected = self.relay_client and self.relay_client.is_connected()
session_id = self.settings_manager.get_relay_session_id()
print(f"[DEBUG] update_ip_label: relay_connected={relay_connected}, session_id={session_id}")
if relay_connected and session_id:
relay_url = self.settings_manager.get_relay_url()
@@ -430,7 +447,6 @@ class MainWindow(QMainWindow):
base_url = relay_url.replace('wss://', 'https://').replace('ws://', 'http://')
base_url = base_url.replace('/desktop', '').rstrip('/')
full_url = f"{base_url}/{session_id}"
print(f"[DEBUG] Setting relay URL: {full_url}")
self.ip_label.setText(full_url)
return
@@ -449,6 +465,40 @@ class MainWindow(QMainWindow):
pass
self.ip_label.setText(f"http://localhost:{DEFAULT_PORT}")
def copy_url_to_clipboard(self):
"""Copy the web interface URL to clipboard."""
url = self.ip_label.text()
if url == "Copied!":
return # Already showing copied feedback
clipboard = QApplication.clipboard()
clipboard.setText(url)
# Show "Copied!" feedback with dimmed style
self.ip_label.setText("Copied!")
self.ip_label.setStyleSheet("""
QPushButton {
background-color: transparent;
color: #888888;
border: none;
padding: 4px 8px;
}
""")
# Restore original URL after delay
QTimer.singleShot(1500, self._restore_url_label)
def _restore_url_label(self):
"""Restore URL label after copy feedback."""
self.ip_label.setStyleSheet(f"""
QPushButton {{
background-color: transparent;
color: {THEME['fg_color']};
border: none;
padding: 4px 8px;
}}
""")
self.update_ip_label()
def refresh_tabs(self):
"""Refresh the tab widget."""
self.tab_widget.blockSignals(True)
@@ -499,7 +549,7 @@ class MainWindow(QMainWindow):
# Add macro buttons
cols = max(1, (self.width() - 40) // 130)
for i, (macro_id, macro) in enumerate(filtered):
btn = MacroButton(macro_id, macro)
btn = MacroButton(macro_id, macro, self.app_dir)
btn.clicked.connect(lambda checked, mid=macro_id: self.execute_macro(mid))
btn.edit_requested.connect(self.edit_macro)
btn.delete_requested.connect(self.delete_macro)
@@ -675,12 +725,10 @@ class MainWindow(QMainWindow):
def on_relay_session_id(self, session_id: str):
"""Handle receiving session ID from relay (called from background thread)."""
print(f"[DEBUG] on_relay_session_id called with: {session_id}")
self.relay_session_received.emit(session_id)
def _handle_relay_session(self, session_id: str):
"""Handle relay session on main thread."""
print(f"[DEBUG] _handle_relay_session on main thread: {session_id}")
self.settings_manager.set_relay_session_id(session_id)
self.update_ip_label()

View File

@@ -1 +1 @@
0.9.0
1.0.0