Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Is it possible to send a URL to pythonista from my phone and make it open on Windows?
-
Hello everyone,,
I tried a few apps that say they sync ios with windows, but they don't work the way I want or they are very limited.https://showbox.bio/ https://tutuapp.uno/What I want to do is pass a URL from my iOS device to my windows PC and make it open automatically.
The steps I think it need to have are:
Get the URL
Pass it to Pythonista
Use a script to send this URL, as well as appropriate commands to my windows PC
Run the command, which will open a new tab, loading the URL
What I cannot do yet is to connect pythonista and windows. I think that I'll be able to ssh (now that windows 10 has native ssh). But I have little to no knowledge on these things and I don't even know how to start configuring a ssh server on my pc, let alone connect it using stash.
Do you think this is possible? If it is, could you help me understand how can I create an active server (maybe ssh, but it can also be by using powershell)
-
@Jhonmicky, see the instructions here for setting up SSH Remoting on Windows 10.
In Pythonista, you would only use stash if you want to send the URL manually every time; in a script, you can use the paramiko module.
-
@Jhonmicky, I have had the exact same need for an app I am currently developping.
You can use Desktop.py below as follows:
- Run the module on the PC
- On the iPhone :
from Desktop import announce_service # Will launch Firefox on the Desktop, and open 'your_url' announce_service(your_url, True) ... # (Optional) Will close the Firefox window announce_service(your_url, False)
Lots of things can be configured, please see in the source code. This module was developped for a specific application (MyDB, you will see it mentionned several times in the source), not as a general purpose module, you may have to tweak it a bit to suit your needs.
Note on implementation : what started out as a simple UDP broadcast function ended up as a full protocol with a fallback against routers that filter out UDP broadcast packets, and fences against lost or delayed packets, WiFi networks that are slow to authenticate, etc., as I tried it out in various environments (a tame Internet Box at home, enterprise LAN, hotel LAN, etc.)
This is still work in progress.
Hope this helps.
""" Desktop companion for MyDB Web UI. There are two components to this module: - service_daemon() runs on the desktop. It will automatically open a browser window when the user activates desktop mode on the iPhone, and close the browser window when the user exits desktop mode. This is done by listening for MyDB Web UI service announcements. - announce_service() is used by Web_UI.py, to announce that MyDB's Web UI service is available / unavailable. Revision history: 22-Jul-2019 TPO - Created this module 25-Jul-2019 TPO - Initial release """ import json import os import socket import subprocess import time import threading # When the following variable is set to True, both service_daemon() and # announce_service() will print debug information on stdout. DEBUG = True # Change the following 2 variables if using another browser than Firefox: START_BROWSER = [(r'"C:\Program Files (x86)\Mozilla Firefox\Firefox.exe" ' r'-new-window {ip}/'), (r'"C:\Program Files\Mozilla Firefox\Firefox.exe" ' r'-new-window {ip}/')] STOP_BROWSER = r'TASKKILL /IM firefox.exe /FI "WINDOWTITLE eq MyDB*"' ANNOUNCE_PORT = 50000 ACK_PORT = 50001 MAGIC = "XXMYDBXX" ANNOUNCE_COUNTER = 0 def debug(message: str) -> None: global DEBUG if DEBUG: print(message) def announce_service(ip: str, available: bool) -> bool: """ Announce that MyDB's Web UI service is available / unavailable. Broadcast the status of the MyDB Web UI service, so that the desktop daemon can open a browser window when the user activates desktop mode on the iPhone, and close the browser window when the user exits desktop mode. Arguments: - ip: string containing our IP address. - available: if True, announce that the service is now available. If False, announce that the service is now unavailable. Returns: True if the announcement has been received and ackowledged by the desktop daemon, False otherwise. Two broadcast modes are tried: - UDP broadcast is tried first (code is courtesy of goncalopp, https://stackoverflow.com/a/21090815) - If UDP broadcast fails, as can happen on LANs where broadcasting packets are filtered by the routers (think airport or hotel LAN), a brute force method is tried, by sending the announcement packet to 254 IP adresses, using values 1 - 255 for byte 4 of our own IP address (should actually use subnet mask, but this is a quick and dirty kludge !) TODO: document ACK mechanism + counter and session id """ def do_brute_force_broadcast(s: socket.socket, ip: str, port: int, data: bytes) -> None: ip_bytes_1_to_3 = ip[:ip.rfind('.')] + '.' for i in range(1, 255): print(i, sep=" ") s.sendto(data, (ip_bytes_1_to_3 + str(i), port)) print(".") global ACK_PORT, ANNOUNCE_PORT, MAGIC, ANNOUNCE_COUNTER SOCKET_TIMEOUT = 0.3 data = json.dumps({'magic': MAGIC, 'counter': ANNOUNCE_COUNTER, 'service available': available, 'IP': ip, 'Session id': time.time()}).encode('utf-8') snd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) snd.bind(('', ANNOUNCE_PORT)) rcv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) rcv.bind(('', ACK_PORT)) rcv.settimeout(SOCKET_TIMEOUT) ack = False debug(f"Counter = {ANNOUNCE_COUNTER}, announcing service is " f"{'ON' if available else 'OFF'}") for brute_force, retries in ((False, 3), (True, 8)): debug(f" Trying {'Brute force' if brute_force else 'UDP'} broadcast") snd.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, not brute_force) for retry in range(retries): debug(f" Retry # {retry}") if brute_force: brute_force_broadcast_thread = threading.Thread( target=do_brute_force_broadcast, args=(snd, ip, ANNOUNCE_PORT, data)) brute_force_broadcast_thread.start() else: snd.sendto(data, ('<broadcast>', ANNOUNCE_PORT)) while 1: try: data_bytes, addr = rcv.recvfrom(1024) except socket.timeout: debug(" Socket time out, going for next retry") break debug(f" Received {data_bytes}") try: data = json.loads(data_bytes.decode('utf-8')) except json.JSONDecodeError: debug(" Invalid JSON, ignoring") continue if (isinstance(data, dict) and data.get('magic') == MAGIC and data.get('counter') == ANNOUNCE_COUNTER and data.get('IP') == ip): debug(" ACK received") ack = True break print(" Invalid ACK, ignoring") if ack: break if brute_force: # Need to wait for broadcast_thread to be done before we proceed # to close the snd socket, or do_brute_force_broadcast() will fail # with "Errno 9: Bad file descriptor". brute_force_broadcast_thread.join() if ack: break if not ack: debug(" Both UDP and brute force broadcast methods failed, giving up") snd.close() rcv.close() ANNOUNCE_COUNTER += 1 return ack def service_daemon(): """ Automatically open / close web browser on desktop. service_daemon() runs on the desktop. It will automatically open a browser window when the user activates desktop mode on the iPhone, and close the browser window when the user exits desktop mode. This is done by listening for MyDB Web UI service announcements. """ global ACK_PORT, ANNOUNCE_PORT, MAGIC # Keep track of the counter value for last annoucement packet processed, in # order to ignore retry packets sent by announce_service(), which all have # the same counter value. last_counter = -1 # Web_UI sessions all start with a counter value of 0, so we need to keep # track of Web_UI sessions and reset last_counter every time a new session # is started (i.e. when the user activates desktop mode on the iPhone) current_session_id = -1 rcv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) rcv.bind(('', ANNOUNCE_PORT)) snd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) snd.bind(('', ACK_PORT)) debug(f"Listening on port {ANNOUNCE_PORT}") while 1: data_bytes, addr = rcv.recvfrom(1024) debug(f"Received packet from {addr}:\n '{data_bytes}'") try: data = json.loads(data_bytes.decode('utf-8')) except json.JSONDecodeError: debug(" Invalid JSON, ignoring") continue if (isinstance(data, dict) and data.get('magic') == MAGIC and 'counter' in data and 'IP' in data and 'service available' in data and 'Session id' in data): if (data['Session id'] == current_session_id and data['counter'] <= last_counter): debug(f" Ignoring MyDB announcement for counter = " f"{data['counter']}, already processed") continue current_session_id = data['Session id'] last_counter = data['counter'] debug(f" MyDB announcement: IP = {data['IP']}, " f"service {'ON' if data['service available'] else 'OFF'}, " f"counter = {data['counter']}") ack = json.dumps({'magic': MAGIC, 'counter': data['counter'], 'IP': data['IP']}).encode('utf-8') snd.sendto(ack, (data['IP'], ACK_PORT)) debug(f" ACK sent back to {data['IP']}:{ACK_PORT}") if data['service available']: debug(" Launching browser") for start_browser in START_BROWSER: try: subprocess.Popen(start_browser.format(ip=data['IP'])) except FileNotFoundError: continue break else: debug(" Closing browser") os.system(STOP_BROWSER) else: debug(" Not a MyDB announcement, ignoring") if __name__ == '__main__': service_daemon()
-
A few month ago someone asked a similiar question on reddit.
Assuming you can run python on both sides, you can use this example. For more details, please see the discussion in the reddit discussion linked above. This example does not use SSH, but thesocket
&webbrowser
modules instead. -
@Jhonmicky, can you be more precise on your use case ?
More specifically, does your Windows 10 PC have a fixed IP address ? If so, the method proposed by @bennr01 should work (and would be simpler). If your PC does not have a fixed IP address, then you need to establish a protocol between your iOS device and your windows PC, so that they can exchange IP addresses (and this is where the module I posted earlier would come in handy).
-
To dialog with my Mac, I use SSH (paramiko) and I connect to the computer name of the Mac which does not have a fixed ip (dhcp)