Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7922910bd8 | |||
| 664d652e9e | |||
| 10971e6a02 | |||
| 17f4bc0c5f | |||
| 44c21e68d8 |
21
README.md
21
README.md
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.9.0
|
||||
1.0.0
|
||||
|
||||
Reference in New Issue
Block a user