I am trying to make a script which will help me track how long I spend on what on my computer. This script should track when I start, stop, and how long I spend on each "task". After some searching I have found a terminal utility called xdotool which will return the current focused window and it's title when ran like so: xdotool getwindowfocus getwindowna me. For example. when focusing on this window it returns:
linux - Monitering time spent on computer w/ Python and xorg-server - Stack Overflow — Firefox Developer Edition
which is exactly what I want. My first idea was to detect when the focused window is changed and then get the time that this happens at, however I could not find any results, so I have resorted to a while loop that runs this command every 5 seconds, but that is quite hack-y and has proven troublesome, I would highly prefer the on-focus-change method, but here is my code as of now:
#!/usr/bin/env python3
from subprocess import run
from time import time, sleep
log = []
prevwindow = ""
while True:
currentwindow = run(['xdotool', 'getwindowfocus', 'getwindowname'],
capture_output=True, text=True).stdout
if currentwindow != prevwindow:
for entry in log:
if currentwindow in entry:
pass # Calculate time spent
print(f"{time()}:\t{currentwindow}")
log.append((time(), currentwindow))
prevwindow = currentwindow
sleep(5)
I am on Arch linux with dwm should that matter
See this gist. Just put your logging mechanism inside the handle_change function and it should work, as tested on an Arch Linux - dwm system.
For archival purposes, here I include the code. All credit goes to Stephan Sokolow (sskolow) on GitHub.
from contextlib import contextmanager
from typing import Any, Dict, Optional, Tuple, Union # noqa
from Xlib import X
from Xlib.display import Display
from Xlib.error import XError
from Xlib.xobject.drawable import Window
from Xlib.protocol.rq import Event
# Connect to the X server and get the root window
disp = Display()
root = disp.screen().root
# Prepare the property names we use so they can be fed into X11 APIs
NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
NET_WM_NAME = disp.intern_atom('_NET_WM_NAME') # UTF-8
WM_NAME = disp.intern_atom('WM_NAME') # Legacy encoding
last_seen = {'xid': None, 'title': None} # type: Dict[str, Any]
#contextmanager
def window_obj(win_id: Optional[int]) -> Window:
"""Simplify dealing with BadWindow (make it either valid or None)"""
window_obj = None
if win_id:
try:
window_obj = disp.create_resource_object('window', win_id)
except XError:
pass
yield window_obj
def get_active_window() -> Tuple[Optional[int], bool]:
"""Return a (window_obj, focus_has_changed) tuple for the active window."""
response = root.get_full_property(NET_ACTIVE_WINDOW,
X.AnyPropertyType)
if not response:
return None, False
win_id = response.value[0]
focus_changed = (win_id != last_seen['xid'])
if focus_changed:
with window_obj(last_seen['xid']) as old_win:
if old_win:
old_win.change_attributes(event_mask=X.NoEventMask)
last_seen['xid'] = win_id
with window_obj(win_id) as new_win:
if new_win:
new_win.change_attributes(event_mask=X.PropertyChangeMask)
return win_id, focus_changed
def _get_window_name_inner(win_obj: Window) -> str:
"""Simplify dealing with _NET_WM_NAME (UTF-8) vs. WM_NAME (legacy)"""
for atom in (NET_WM_NAME, WM_NAME):
try:
window_name = win_obj.get_full_property(atom, 0)
except UnicodeDecodeError: # Apparently a Debian distro package bug
title = "<could not decode characters>"
else:
if window_name:
win_name = window_name.value # type: Union[str, bytes]
if isinstance(win_name, bytes):
# Apparently COMPOUND_TEXT is so arcane that this is how
# tools like xprop deal with receiving it these days
win_name = win_name.decode('latin1', 'replace')
return win_name
else:
title = "<unnamed window>"
return "{} (XID: {})".format(title, win_obj.id)
def get_window_name(win_id: Optional[int]) -> Tuple[Optional[str], bool]:
"""Look up the window name for a given X11 window ID"""
if not win_id:
last_seen['title'] = None
return last_seen['title'], True
title_changed = False
with window_obj(win_id) as wobj:
if wobj:
try:
win_title = _get_window_name_inner(wobj)
except XError:
pass
else:
title_changed = (win_title != last_seen['title'])
last_seen['title'] = win_title
return last_seen['title'], title_changed
def handle_xevent(event: Event):
"""Handler for X events which ignores anything but focus/title change"""
if event.type != X.PropertyNotify:
return
changed = False
if event.atom == NET_ACTIVE_WINDOW:
if get_active_window()[1]:
get_window_name(last_seen['xid']) # Rely on the side-effects
changed = True
elif event.atom in (NET_WM_NAME, WM_NAME):
changed = changed or get_window_name(last_seen['xid'])[1]
if changed:
handle_change(last_seen)
def handle_change(new_state: dict):
"""Replace this with whatever you want to actually do"""
print(new_state)
if __name__ == '__main__':
# Listen for _NET_ACTIVE_WINDOW changes
root.change_attributes(event_mask=X.PropertyChangeMask)
# Prime last_seen with whatever window was active when we started this
get_window_name(get_active_window()[0])
handle_change(last_seen)
while True: # next_event() sleeps until we get an event
handle_xevent(disp.next_event())
Related
I am trying to develop a listener for the Windows event log. It should wait until anything new is added and when this happens it should catch the new event and pass it as an object so I can create a handler. I have found some things online but nothing has worked so far. I am using win32evtlog and win32event.
The code I have so far is this:
import win32evtlog
import win32event
server = 'localhost' # name of the target computer to get event logs
logtype = 'Application' # 'Application' # 'Security'
hand = win32evtlog.OpenEventLog(server,logtype)
flags = win32evtlog.EVENTLOG_BACKWARDS_READ|win32evtlog.EVENTLOG_SEQUENTIAL_READ
total = win32evtlog.GetNumberOfEventLogRecords(hand)
print(total)
h_evt = win32event.CreateEvent(None, 1, 0, "evt0")
for x in range(10):
notify = win32evtlog.NotifyChangeEventLog(hand, h_evt)
wait_result = win32event.WaitForSingleObject(h_evt, win32event.INFINITE)
print("notify", notify)
The output after I run it and force one event to happen is this:
865
notify None
After this it gets stuck and does not catch any other events.
Thank you in advance for any help
I noticed 2 things. First, you create a manual reset event, but never reset it. Second, you should only need to call win32evtlog.NotifyChangeEventLog once.
import win32evtlog
import win32event
import win32api
server = 'localhost' # name of the target computer to get event logs
logtype = 'MyAppSource'
hand = win32evtlog.OpenEventLog(server, logtype)
flags = win32evtlog.EVENTLOG_FORWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ
total = win32evtlog.GetNumberOfEventLogRecords(hand)
print(total)
total += 1
h_evt = win32event.CreateEvent(None, False, False, "evt0")
notify = win32evtlog.NotifyChangeEventLog(hand, h_evt)
for x in range(10):
wait_result = win32event.WaitForSingleObject(h_evt, win32event.INFINITE)
readlog = win32evtlog.ReadEventLog(hand, flags, total)
for event in readlog:
print(f"{event.TimeGenerated.Format()} : [{event.RecordNumber}] : {event.SourceName}")
total += len(readlog)
win32evtlog.CloseEventLog(hand)
win32api.CloseHandle(h_evt)
You can change MyAppSource to your source name. For example, on my computer I have:
If I want to monitor "Dell", for example: logtype = 'Dell'
The method above will only work for first level sources. If you want to go to a deeper level, use win32evtlog.EvtSubscribe(). Here, I use it with a callback - borrowing the xml code from the answer linked to in the comments.
import win32evtlog
import xml.etree.ElementTree as ET
channel = 'Microsoft-Windows-Windows Defender/Operational'
def on_event(action, context, event_handle):
if action == win32evtlog.EvtSubscribeActionDeliver:
xml = ET.fromstring(win32evtlog.EvtRender(event_handle, win32evtlog.EvtRenderEventXml))
# xml namespace, root element has a xmlns definition, so we have to use the namespace
ns = '{http://schemas.microsoft.com/win/2004/08/events/event}'
event_id = xml.find(f'.//{ns}EventID').text
level = xml.find(f'.//{ns}Level').text
channel = xml.find(f'.//{ns}Channel').text
execution = xml.find(f'.//{ns}Execution')
process_id = execution.get('ProcessID')
thread_id = execution.get('ThreadID')
time_created = xml.find(f'.//{ns}TimeCreated').get('SystemTime')
print(f'Time: {time_created}, Level: {level} Event Id: {event_id}, Channel: {channel}, Process Id: {process_id}, Thread Id: {thread_id}')
print(xml.find(f'.//{ns}Data').text)
print()
handle = win32evtlog.EvtSubscribe(
channel,
win32evtlog.EvtSubscribeToFutureEvents,
None,
Callback = on_event)
# Wait for user to hit enter...
input()
win32evtlog.CloseEventLog(handle)
I was trying to craft a response to a question about streaming audio from a HTTP server, then play it with PyGame. I had the code mostly complete, but hit an error where the PyGame music functions tried to seek() on the urllib.HTTPResponse object.
According to the urlib docs, the urllib.HTTPResponse object (since v3.5) is an io.BufferedIOBase. I expected this would make the stream seek()able, however it does not.
Is there a way to wrap the io.BufferedIOBase such that it is smart enough to buffer enough data to handle the seek operation?
import pygame
import urllib.request
import io
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
# background colour
SKY_BLUE = (161, 255, 254)
### Begin the streaming of a file
### Return the urlib.HTTPResponse, a file-like-object
def openURL( url ):
result = None
try:
http_response = urllib.request.urlopen( url )
print( "streamHTTP() - Fetching URL [%s]" % ( http_response.geturl() ) )
print( "streamHTTP() - Response Status [%d] / [%s]" % ( http_response.status, http_response.reason ) )
result = http_response
except:
print( "streamHTTP() - Error Fetching URL [%s]" % ( url ) )
return result
### MAIN
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
pygame.display.set_caption("Music Streamer")
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
if ( pygame.mixer.music.get_busy() ):
print("busy")
else:
print("play")
remote_music = openURL( 'http://127.0.0.1/example.wav' )
if ( remote_music != None and remote_music.status == 200 ):
pygame.mixer.music.load( io.BufferedReader( remote_music ) )
pygame.mixer.music.play()
# Re-draw the screen
window.fill( SKY_BLUE )
# Update the window, but not more than 60fps
pygame.display.flip()
clock.tick_busy_loop( 60 )
pygame.quit()
When this code runs, and Up is pushed, it fails with the error:
streamHTTP() - Fetching URL [http://127.0.0.1/example.wav]
streamHTTP() - Response Status [200] / [OK]
io.UnsupportedOperation: seek
io.UnsupportedOperation: File or stream is not seekable.
io.UnsupportedOperation: seek
io.UnsupportedOperation: File or stream is not seekable.
Traceback (most recent call last):
File "./sound_stream.py", line 57, in <module>
pygame.mixer.music.load( io.BufferedReader( remote_music ) )
pygame.error: Unknown WAVE format
I also tried re-opening the the io stream, and various other re-implementations of the same sort of thing.
Seeking seeking
According to the urlib docs, the urllib.HTTPResponse object (since v3.5) is an io.BufferedIOBase. I expected this would make the stream seek()able, however it does not.
That's correct. The io.BufferedIOBase interface doesn't guarantee the I/O object is seekable. For HTTPResponse objects, IOBase.seekable() returns False:
>>> import urllib.request
>>> response = urllib.request.urlopen("http://httpbin.org/get")
>>> response
<http.client.HTTPResponse object at 0x110870ca0>
>>> response.seekable()
False
That's because the BufferedIOBase implementation offered by HTTPResponse is wrapping a socket object, and sockets are not seekable either.
You can't wrap an BufferedIOBase object in a BufferedReader object and add seeking support. The Buffered* wrapper objects can only wrap RawIOBase types, and they rely on the wrapped object to provide seeking support. You would have to emulate seeking at raw I/O level, see below.
You can still provide the same functionality at a higher level, but take into account that seeking on remote data is a lot more involved; this isn't a simple change a simple OS variable that represents a file position on disk operation. For larger remote file data, seeking without backing the whole file on disk locally could be as sophisticated as using HTTP range requests and local (in memory or on-disk) buffers to balance sound play-back performance and minimising local data storage. Doing this correctly for a wide range of use-cases can be a lot of effort, so is certainly not part of the Python standard library.
If your sound files are small
If your HTTP-sourced sound files are small enough (a few MB at most) then just read the whole response into an in-memory io.BytesIO() file object. I really do not think it is worth making this more complicated than that, because the moment you have enough data to make that worth pursuing your files are large enough to take up too much memory!
So this would be more than enough if your sound files are smaller (no more than a few MB):
from io import BytesIO
import urllib.error
import urllib.request
def open_url(url):
try:
http_response = urllib.request.urlopen(url)
print(f"streamHTTP() - Fetching URL [{http_response.geturl()}]")
print(f"streamHTTP() - Response Status [{http_response.status}] / [{http_response.reason}]")
except urllib.error.URLError:
print("streamHTTP() - Error Fetching URL [{url}]")
return
if http_response.status != 200:
print("streamHTTP() - Error Fetching URL [{url}]")
return
return BytesIO(http_response.read())
This doesn't require writing a wrapper object, and because BytesIO is a native implementation, once the data is fully copied over, access to the data is faster than any Python-code wrapper could ever give you.
Note that this returns a BytesIO file object, so you no longer need to test for the response status:
remote_music = open_url('http://127.0.0.1/example.wav')
if remote_music is not None:
pygame.mixer.music.load(remote_music)
pygame.mixer.music.play()
If they are more than a few MB
Once you go beyond a few megabytes, you could try pre-loading the data into a local file object. You can make this more sophisticated by using a thread to have shutil.copyfileobj() copy most of the data into that file in the background and give the file to PyGame after loading just an initial amount of data.
By using an actual file object, you can actually help performance here, as PyGame will try to minimize interjecting itself between the SDL mixer and the file data. If there is an actual file on disk with a file number (the OS-level identifier for a stream, something that the SDL mixer library can make use of), then PyGame will operate directly on that and so minimize blocking the GIL (which in turn will help the Python portions of your game perform better!). And if you pass in a filename (just a string), then PyGame gets out of the way entirely and leaves all file operations over to the SDL library.
Here's such an implementation; this should, on normal Python interpreter exit, clean up the downloaded files automatically. It returns a filename for PyGame to work on, and finalizing downloading the data is done in a thread after the initial few KB has been buffered. It will avoid loading the same URL more than once, and I've made it thread-safe:
import shutil
import urllib.error
import urllib.request
from tempfile import NamedTemporaryFile
from threading import Lock, Thread
INITIAL_BUFFER = 1024 * 8 # 8kb initial file read to start URL-backed files
_url_files_lock = Lock()
# stores open NamedTemporaryFile objects, keeping them 'alive'
# removing entries from here causes the file data to be deleted.
_url_files = {}
def open_url(url):
with _url_files_lock:
if url in _url_files:
return _url_files[url].name
try:
http_response = urllib.request.urlopen(url)
print(f"streamHTTP() - Fetching URL [{http_response.geturl()}]")
print(f"streamHTTP() - Response Status [{http_response.status}] / [{http_response.reason}]")
except urllib.error.URLError:
print("streamHTTP() - Error Fetching URL [{url}]")
return
if http_response.status != 200:
print("streamHTTP() - Error Fetching URL [{url}]")
return
fileobj = NamedTemporaryFile()
content_length = http_response.getheader("Content-Length")
if content_length is not None:
try:
content_length = int(content_length)
except ValueError:
content_length = None
if content_length:
# create sparse file of full length
fileobj.seek(content_length - 1)
fileobj.write(b"\0")
fileobj.seek(0)
fileobj.write(http_response.read(INITIAL_BUFFER))
with _url_files_lock:
if url in _url_files:
# another thread raced us to this point, we lost, return their
# result after cleaning up here
fileobj.close()
http_response.close()
return _url_files[url].name
# store the file object for this URL; this keeps the file
# open and so readable if you have the filename.
_url_files[url] = fileobj
def copy_response_remainder():
# copies file data from response to disk, for all data past INITIAL_BUFFER
with http_response:
shutil.copyfileobj(http_response, fileobj)
t = Thread(daemon=True, target=copy_response_remainder)
t.start()
return fileobj.name
Like the BytesIO() solution, the above returns either None or a value ready for passing to pass to pygame.mixer.music.load().
The above will probably not work if you try to immediately set an advanced playing position in your sound files, as later data may not yet have been copied into the file. It's a trade-off.
Seeking and finding third party libraries
If you need to have full seeking support on remote URLs and don't want to use on-disk space for them and don't want to have to worry about their size, you don't need to re-invent the HTTP-as-seekable-file wheel here. You could use an existing project that offers the same functionality. I found two that offer io.BufferedIOBase-based implementations:
smart_open
httpio
Both use HTTP Range requests to implement seeking support. Just use httpio.open(URL) or smart_open.open(URL) and pass that directly to pygame.mixer.music.load(); if the URL can't be opened, you can catch that by handling the IOError exception:
from smart_open import open as url_open # or from httpio import open
try:
remote_music = url_open('http://127.0.0.1/example.wav')
except IOError:
pass
else:
pygame.mixer.music.load(remote_music)
pygame.mixer.music.play()
smart_open uses an in-memory buffer to satisfy reads of a fixed size, but creates a new HTTP Range request for every call to seek that changes the current file position, so performance may vary. Since the SDL mixer executes a few seeks on audio files to determine their type, I expect this to be a little slower.
httpio can buffer blocks of data and so might handle seeks better, but from a brief glance at the source code, when actually setting a buffer size the cached blocks are never evicted from memory again so you'd end up with the whole file in memory, eventually.
Implementing seeking ourselves, via io.RawIOBase
And finally, because I'm not able to find efficient HTTP-Range-backed I/O implementations, I wrote my own. The following implements the io.RawIOBase interface, specifically so you can then wrap the object in a io.BufferedIOReader() and so delegate caching to a caching buffer that will be managed correctly when seeking:
import io
from copy import deepcopy
from functools import wraps
from typing import cast, overload, Callable, Optional, Tuple, TypeVar, Union
from urllib.request import urlopen, Request
T = TypeVar("T")
#overload
def _check_closed(_f: T) -> T: ...
#overload
def _check_closed(*, connect: bool, default: Union[bytes, int]) -> Callable[[T], T]: ...
def _check_closed(
_f: Optional[T] = None,
*,
connect: bool = False,
default: Optional[Union[bytes, int]] = None,
) -> Union[T, Callable[[T], T]]:
def decorator(f: T) -> T:
#wraps(cast(Callable, f))
def wrapper(self, *args, **kwargs):
if self.closed:
raise ValueError("I/O operation on closed file.")
if connect and self._fp is None or self._fp.closed:
self._connect()
if self._fp is None:
# outside the seekable range, exit early
return default
try:
return f(self, *args, **kwargs)
except Exception:
self.close()
raise
finally:
if self._range_end and self._pos >= self._range_end:
self._fp.close()
del self._fp
return cast(T, wrapper)
if _f is not None:
return decorator(_f)
return decorator
def _parse_content_range(
content_range: str
) -> Tuple[Optional[int], Optional[int], Optional[int]]:
"""Parse a Content-Range header into a (start, end, length) tuple"""
units, *range_spec = content_range.split(None, 1)
if units != "bytes" or not range_spec:
return (None, None, None)
start_end, _, size = range_spec[0].partition("/")
try:
length: Optional[int] = int(size)
except ValueError:
length = None
start_val, has_start_end, end_val = start_end.partition("-")
start = end = None
if has_start_end:
try:
start, end = int(start_val), int(end_val)
except ValueError:
pass
return (start, end, length)
class HTTPRawIO(io.RawIOBase):
"""Wrap a HTTP socket to handle seeking via HTTP Range"""
url: str
closed: bool = False
_pos: int = 0
_size: Optional[int] = None
_range_end: Optional[int] = None
_fp: Optional[io.RawIOBase] = None
def __init__(self, url_or_request: Union[Request, str]) -> None:
if isinstance(url_or_request, str):
self._request = Request(url_or_request)
else:
# copy request objects to avoid sharing state
self._request = deepcopy(url_or_request)
self.url = self._request.full_url
self._connect(initial=True)
def readable(self) -> bool:
return True
def seekable(self) -> bool:
return True
def close(self) -> None:
if self.closed:
return
if self._fp:
self._fp.close()
del self._fp
self.closed = True
#_check_closed
def tell(self) -> int:
return self._pos
def _connect(self, initial: bool = False) -> None:
if self._fp is not None:
self._fp.close()
if self._size is not None and self._pos >= self._size:
# can't read past the end
return
request = self._request
request.add_unredirected_header("Range", f"bytes={self._pos}-")
response = urlopen(request)
self.url = response.geturl() # could have been redirected
if response.status not in (200, 206):
raise OSError(
f"Failed to open {self.url}: "
f"{response.status} ({response.reason})"
)
if initial:
# verify that the server supports range requests. Capture the
# content length if available
if response.getheader("Accept-Ranges") != "bytes":
raise OSError(
f"Resource doesn't support range requests: {self.url}"
)
try:
length = int(response.getheader("Content-Length", ""))
if length >= 0:
self._size = length
except ValueError:
pass
# validate the range we are being served
start, end, length = _parse_content_range(
response.getheader("Content-Range", "")
)
if self._size is None:
self._size = length
if (start is not None and start != self._pos) or (
length is not None and length != self._size
):
# non-sensical range response
raise OSError(
f"Resource at {self.url} served invalid range: pos is "
f"{self._pos}, range {start}-{end}/{length}"
)
if self._size and end is not None and end + 1 < self._size:
# incomplete range, not reaching all the way to the end
self._range_end = end
else:
self._range_end = None
fp = cast(io.BufferedIOBase, response.fp) # typeshed doesn't name fp
self._fp = fp.detach() # assume responsibility for the raw socket IO
#_check_closed
def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
relative_to = {
io.SEEK_SET: 0,
io.SEEK_CUR: self._pos,
io.SEEK_END: self._size,
}.get(whence)
if relative_to is None:
if whence == io.SEEK_END:
raise IOError(
f"Can't seek from end on unsized resource {self.url}"
)
raise ValueError(f"whence value {whence} unsupported")
if -offset > relative_to: # can't seek to a point before the start
raise OSError(22, "Invalid argument")
self._pos = relative_to + offset
# there is no point in optimising an existing connection
# by reading from it if seeking forward below some threshold.
# Use a BufferedIOReader to avoid seeking by small amounts or by 0
if self._fp:
self._fp.close()
del self._fp
return self._pos
# all read* methods delegate to the SocketIO object (itself a RawIO
# implementation).
#_check_closed(connect=True, default=b"")
def read(self, size: int = -1) -> Optional[bytes]:
assert self._fp is not None # show type checkers we already checked
res = self._fp.read(size)
if res is not None:
self._pos += len(res)
return res
#_check_closed(connect=True, default=b"")
def readall(self) -> bytes:
assert self._fp is not None # show type checkers we already checked
res = self._fp.readall()
self._pos += len(res)
return res
#_check_closed(connect=True, default=0)
def readinto(self, buffer: bytearray) -> Optional[int]:
assert self._fp is not None # show type checkers we already checked
n = self._fp.readinto(buffer)
self._pos += n or 0
return n
Remember that this is a RawIOBase object, which you really want to wrap in a BufferReader(). Doing so in open_url() looks like this:
def open_url(url, *args, **kwargs):
return io.BufferedReader(HTTPRawIO(url), *args, **kwargs)
This gives you fully buffered I/O, with full support seeking, over a remote URL, and the BufferedReader implementation will minimise resetting the HTTP connection when seeking. I've found that using this with the PyGame mixer, only single HTTP connection is made, as all the test seeks are within the default 8KB buffer.
If your fine with using the requests module (which supports streaming) instead of urllib, you could use a wrapper like this:
class ResponseStream(object):
def __init__(self, request_iterator):
self._bytes = BytesIO()
self._iterator = request_iterator
def _load_all(self):
self._bytes.seek(0, SEEK_END)
for chunk in self._iterator:
self._bytes.write(chunk)
def _load_until(self, goal_position):
current_position = self._bytes.seek(0, SEEK_END)
while current_position < goal_position:
try:
current_position = self._bytes.write(next(self._iterator))
except StopIteration:
break
def tell(self):
return self._bytes.tell()
def read(self, size=None):
left_off_at = self._bytes.tell()
if size is None:
self._load_all()
else:
goal_position = left_off_at + size
self._load_until(goal_position)
self._bytes.seek(left_off_at)
return self._bytes.read(size)
def seek(self, position, whence=SEEK_SET):
if whence == SEEK_END:
self._load_all()
else:
self._bytes.seek(position, whence)
Then I guess you can do something like this:
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
SKY_BLUE = (161, 255, 254)
URL = 'http://localhost:8000/example.wav'
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
pygame.display.set_caption("Music Streamer")
clock = pygame.time.Clock()
done = False
font = pygame.font.SysFont(None, 32)
state = 0
def play_music():
response = requests.get(URL, stream=True)
if (response.status_code == 200):
stream = ResponseStream(response.iter_content(64))
pygame.mixer.music.load(stream)
pygame.mixer.music.play()
else:
state = 0
while not done:
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
if event.type == pygame.KEYDOWN and state == 0:
Thread(target=play_music).start()
state = 1
window.fill( SKY_BLUE )
window.blit(font.render(str(pygame.time.get_ticks()), True, (0,0,0)), (32, 32))
pygame.display.flip()
clock.tick_busy_loop( 60 )
pygame.quit()
using a Thread to start streaming.
I'm not sure this works 100%, but give it a try.
I'd like to be able to list the files in the shell:appsfolder in a python script but need the full path to do this using os.list. Is there a way to get the full path (or does anyone know it)? Alternatively, is there a different way I can list these files? Can I "cd" to it?
The idea behind the script is to automate the shortcut creation of all the Windows Store apps (identified by the fact they have a "long name" property I think) and extract those shortcuts to a folder where the program Launchy can detect them. I don't like having to manually go through the process of creating the shortcut (and renaming it to remove the " - shortcut) every time I download or remove an app so I thought I'd automate it.
Here's a function that hopefully does what you want in terms of creating shortcuts for the Windows Store apps that are listed in the "Applications" virtual folder (i.e. FOLDERID_AppsFolder). To classify Windows Store apps, it looks for an exclamation point in the Application User Model ID since the AUMID should be of the form "PackageFamily!ApplicationID" (see Automate Launching UWP Apps). For reliability it cross-checks each package family with the user's registered package families.
import os
import ctypes
import pywintypes
import pythoncom
import winerror
try:
import winreg
except ImportError:
# Python 2
import _winreg as winreg
bytes = lambda x: str(buffer(x))
from ctypes import wintypes
from win32com.shell import shell, shellcon
from win32com.propsys import propsys, pscon
# KNOWNFOLDERID
# https://msdn.microsoft.com/en-us/library/dd378457
# win32com defines most of these, except the ones added in Windows 8.
FOLDERID_AppsFolder = pywintypes.IID('{1e87508d-89c2-42f0-8a7e-645a0f50ca58}')
# win32com is missing SHGetKnownFolderIDList, so use ctypes.
_ole32 = ctypes.OleDLL('ole32')
_shell32 = ctypes.OleDLL('shell32')
_REFKNOWNFOLDERID = ctypes.c_char_p
_PPITEMIDLIST = ctypes.POINTER(ctypes.c_void_p)
_ole32.CoTaskMemFree.restype = None
_ole32.CoTaskMemFree.argtypes = (wintypes.LPVOID,)
_shell32.SHGetKnownFolderIDList.argtypes = (
_REFKNOWNFOLDERID, # rfid
wintypes.DWORD, # dwFlags
wintypes.HANDLE, # hToken
_PPITEMIDLIST) # ppidl
def get_known_folder_id_list(folder_id, htoken=None):
if isinstance(folder_id, pywintypes.IIDType):
folder_id = bytes(folder_id)
pidl = ctypes.c_void_p()
try:
_shell32.SHGetKnownFolderIDList(folder_id, 0, htoken,
ctypes.byref(pidl))
return shell.AddressAsPIDL(pidl.value)
except WindowsError as e:
if e.winerror & 0x80070000 == 0x80070000:
# It's a WinAPI error, so re-raise it, letting Python
# raise a specific exception such as FileNotFoundError.
raise ctypes.WinError(e.winerror & 0x0000FFFF)
raise
finally:
if pidl:
_ole32.CoTaskMemFree(pidl)
def enum_known_folder(folder_id, htoken=None):
id_list = get_known_folder_id_list(folder_id, htoken)
folder_shell_item = shell.SHCreateShellItem(None, None, id_list)
items_enum = folder_shell_item.BindToHandler(None,
shell.BHID_EnumItems, shell.IID_IEnumShellItems)
for item in items_enum:
yield item
def list_known_folder(folder_id, htoken=None):
result = []
for item in enum_known_folder(folder_id, htoken):
result.append(item.GetDisplayName(shellcon.SIGDN_NORMALDISPLAY))
result.sort(key=lambda x: x.upper())
return result
def create_shortcut(shell_item, shortcut_path):
id_list = shell.SHGetIDListFromObject(shell_item)
shortcut = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
shortcut.SetIDList(id_list)
persist = shortcut.QueryInterface(pythoncom.IID_IPersistFile)
persist.Save(shortcut_path, 0)
def get_package_families():
families = set()
subkey = (r'Software\Classes\Local Settings\Software\Microsoft'
r'\Windows\CurrentVersion\AppModel\Repository\Families')
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, subkey) as hkey:
index = 0
while True:
try:
families.add(winreg.EnumKey(hkey, index))
except OSError as e:
if e.winerror != winerror.ERROR_NO_MORE_ITEMS:
raise
break
index += 1
return families
def update_app_shortcuts(target_dir):
package_families = get_package_families()
for item in enum_known_folder(FOLDERID_AppsFolder):
try:
property_store = item.BindToHandler(None,
shell.BHID_PropertyStore, propsys.IID_IPropertyStore)
app_user_model_id = property_store.GetValue(
pscon.PKEY_AppUserModel_ID).ToString()
except pywintypes.error:
continue
# AUID template: Packagefamily!ApplicationID
if '!' not in app_user_model_id:
continue
package_family, app_id = app_user_model_id.rsplit('!', 1)
if package_family not in package_families:
continue
name = item.GetDisplayName(shellcon.SIGDN_NORMALDISPLAY)
shortcut_path = os.path.join(target_dir, '%s.lnk' % name)
create_shortcut(item, shortcut_path)
print('{}: {}'.format(name, app_user_model_id))
example
if __name__ == '__main__':
desktop = shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, 0, 0)
target_dir = os.path.join(desktop, 'Windows Store Apps')
if not os.path.exists(target_dir):
os.mkdir(target_dir)
update_app_shortcuts(target_dir)
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I need to make a program that drives a DYMO LabelManager PnP label printing device. DYMO provides a SDK for this purpose, but after some desperate trying, I'd say the SDK is useless. Then I found a program which is just what I need, written by a guy named S.Bronner. But the problem is that his program is made for Python in UNIX, and I would need it to work in Windows with python. So I'm asking, is there anyone who could examine this code and convert it to work in windows for me? My Python skills are not good enough to accomplish this. Here is the code which should be converted:
#!/usr/bin/env python
DEV_CLASS = 3
DEV_VENDOR = 0x0922
DEV_PRODUCT = 0x1001
DEV_NODE = None
DEV_NAME = 'Dymo LabelManager PnP'
FONT_FILENAME = '/usr/share/fonts/truetype/ttf-bitstream-vera/Vera.ttf'
FONT_SIZERATIO = 7./8
import Image
import ImageDraw
import ImageFont
import array
import fcntl
import os
import re
import struct
import subprocess
import sys
import termios
import textwrap
class DymoLabeler:
"""
Create and work with a Dymo LabelManager PnP object.
This class contains both mid-level and high-level functions. In general,
the high-level functions should be used. However, special purpose usage
may require the mid-level functions. That is why they are provided.
However, they should be well understood before use. Look at the
high-level functions for help. Each function is marked in its docstring
with 'HLF' or 'MLF' in parentheses.
"""
def __init__(self, dev):
"""Initialize the LabelManager object. (HLF)"""
self.maxBytesPerLine = 8 # 64 pixels on a 12mm-tape
self.ESC = 0x1b
self.SYN = 0x16
self.cmd = []
self.rsp = False
self.bpl = None
self.dtb = 0
if not os.access(dev, os.R_OK | os.W_OK): return False
self.dev = open(dev, 'r+')
def sendCommand(self):
"""Send the already built command to the LabelManager. (MLF)"""
if len(self.cmd) == 0: return
cmdBin = array.array('B', self.cmd)
cmdBin.tofile(self.dev)
self.cmd = []
if not self.rsp: return
self.rsp = False
rspBin = self.dev.read(8)
rsp = array.array('B', rspBin).tolist()
return rsp
def resetCommand(self):
"""Remove a partially built command. (MLF)"""
self.cmd = []
self.rsp = False
def buildCommand(self, cmd):
"""Add the next instruction to the command. (MLF)"""
self.cmd += cmd
def statusRequest(self):
"""Set instruction to get the device's status. (MLF)"""
cmd = [self.ESC, ord('A')]
self.buildCommand(cmd)
self.rsp = True
def dotTab(self, value):
"""Set the bias text height, in bytes. (MLF)"""
if value < 0 or value > self.maxBytesPerLine: raise ValueError
cmd = [self.ESC, ord('B'), value]
self.buildCommand(cmd)
self.dtb = value
self.bpl = None
def tapeColor(self, value):
"""Set the tape color. (MLF)"""
if value < 0: raise ValueError
cmd = [self.ESC, ord('C'), value]
self.buildCommand(cmd)
def bytesPerLine(self, value):
"""Set the number of bytes sent in the following lines. (MLF)"""
if value < 0 or value + self.dtb > self.maxBytesPerLine: raise ValueError
if value == self.bpl: return
cmd = [self.ESC, ord('D'), value]
self.buildCommand(cmd)
self.bpl = value
def cut(self):
"""Set instruction to trigger cutting of the tape. (MLF)"""
cmd = [self.ESC, ord('E')]
self.buildCommand(cmd)
def line(self, value):
"""Set next printed line. (MLF)"""
self.bytesPerLine(len(value))
cmd = [self.SYN] + value
self.buildCommand(cmd)
def chainMark(self):
"""Set Chain Mark. (MLF)"""
self.dotTab(0)
self.bytesPerLine(self.maxBytesPerLine)
self.line([0x99] * self.maxBytesPerLine)
def skipLines(self, value):
"""Set number of lines of white to print. (MLF)"""
if value <= 0: raise ValueError
self.bytesPerLine(0)
cmd = [self.SYN] * value
self.buildCommand(cmd)
def initLabel(self):
"""Set the label initialization sequence. (MLF)"""
cmd = [0x00] * 8
self.buildCommand(cmd)
def getStatus(self):
"""Ask for and return the device's status. (HLF)"""
self.statusRequest()
rsp = self.sendCommand()
print rsp
def printLabel(self, lines, dotTab):
"""Print the label described by lines. (HLF)"""
self.initLabel
self.tapeColor(0)
self.dotTab(dotTab)
for line in lines:
self.line(line)
self.skipLines(56) # advance printed matter past cutter
self.skipLines(56) # add symmetric margin
self.statusRequest()
rsp = self.sendCommand()
print rsp
def die(message=None):
if message: print >> sys.stderr, message
sys.exit(1)
def pprint(par, fd=sys.stdout):
rows, columns = struct.unpack('HH', fcntl.ioctl(sys.stderr, termios.TIOCGWINSZ, struct.pack('HH', 0, 0)))
print >> fd, textwrap.fill(par, columns)
def getDeviceFile(classID, vendorID, productID):
# find file containing the device's major and minor numbers
searchdir = '/sys/bus/hid/devices'
pattern = '^%04d:%04X:%04X.[0-9A-F]{4}$' % (classID, vendorID, productID)
deviceCandidates = os.listdir(searchdir)
foundpath = None
for devname in deviceCandidates:
if re.match(pattern, devname):
foundpath = os.path.join(searchdir, devname)
break
if not foundpath: return
searchdir = os.path.join(foundpath, 'hidraw')
devname = os.listdir(searchdir)[0]
foundpath = os.path.join(searchdir, devname)
filepath = os.path.join(foundpath, 'dev')
# get the major and minor numbers
f = open(filepath, 'r')
devnums = [int(n) for n in f.readline().strip().split(':')]
f.close()
devnum = os.makedev(devnums[0], devnums[1])
# check if a symlink with the major and minor numbers is available
filepath = '/dev/char/%d:%d' % (devnums[0], devnums[1])
if os.path.exists(filepath):
return os.path.realpath(filepath)
# check if the relevant sysfs path component matches a file name in
# /dev, that has the proper major and minor numbers
filepath = os.path.join('/dev', devname)
if os.stat(filepath).st_rdev == devnum:
return filepath
# search for a device file with the proper major and minor numbers
for dirpath, dirnames, filenames in os.walk('/dev'):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
if os.stat(filepath).st_rdev == devnum:
return filepath
def access_error(dev):
pprint('You do not have sufficient access to the device file %s:' % dev, sys.stderr)
subprocess.call(['ls', '-l', dev], stdout=sys.stderr)
print >> sys.stderr
pprint('You probably want to add a rule in /etc/udev/rules.d along the following lines:', sys.stderr)
print >> sys.stderr, ' SUBSYSTEM=="hidraw", \\'
print >> sys.stderr, ' ACTION=="add", \\'
print >> sys.stderr, ' DEVPATH=="/devices/pci[0-9]*/usb[0-9]*/0003:0922:1001.*/hidraw/hidraw0", \\'
print >> sys.stderr, ' GROUP="plugdev"'
print >> sys.stderr
pprint('Following that, turn off your device and back on again to activate the new permissions.', sys.stderr)
# get device file name
if not DEV_NODE:
dev = getDeviceFile(DEV_CLASS, DEV_VENDOR, DEV_PRODUCT)
else:
dev = DEV_NODE
if not dev: die("The device '%s' could not be found on this system." % DEV_NAME)
# create dymo labeler object
lm = DymoLabeler(dev)
if not lm: die(access_error(dev))
# check for any text specified on the command line
labeltext = [arg.decode(sys.stdin.encoding) for arg in sys.argv[1:]]
if len(labeltext) == 0: die("No label text was specified.")
# create an empty label image
labelheight = lm.maxBytesPerLine * 8
lineheight = float(labelheight) / len(labeltext)
fontsize = int(round(lineheight * FONT_SIZERATIO))
font = ImageFont.truetype(FONT_FILENAME, fontsize)
labelwidth = max(font.getsize(line)[0] for line in labeltext)
labelbitmap = Image.new('1', (labelwidth, labelheight))
# write the text into the empty image
labeldraw = ImageDraw.Draw(labelbitmap)
for i, line in enumerate(labeltext):
lineposition = int(round(i * lineheight))
labeldraw.text((0, lineposition), line, font=font, fill=255)
del labeldraw
# convert the image to the proper matrix for the dymo labeler object
labelrotated = labelbitmap.transpose(Image.ROTATE_270)
labelstream = labelrotated.tostring()
labelstreamrowlength = labelheight/8 + (1 if labelheight%8 != 0 else 0)
if len(labelstream)/labelstreamrowlength != labelwidth: die('An internal problem was encountered while processing the label bitmap!')
labelrows = [labelstream[i:i+labelstreamrowlength] for i in range(0, len(labelstream), labelstreamrowlength)]
labelmatrix = [array.array('B', labelrow).tolist() for labelrow in labelrows]
# optimize the matrix for the dymo label printer
dottab = 0
while max(line[0] for line in labelmatrix) == 0:
labelmatrix = [line[1:] for line in labelmatrix]
dottab += 1
for line in labelmatrix:
while len(line) > 0 and line[-1] == 0:
del line[-1]
# print the label
lm.printLabel(labelmatrix, dottab)
FONT_FILENAME = '/usr/share/fonts/truetype/ttf-bitstream-vera/Vera.ttf'
// should be changed to path to the font on your system
won't work because of filesystem differences.
searchdir = '/sys/bus/hid/devices'
// take a look at "pywinusb" library (?)
won't work either, you have to get the devices in a different way. Not sure from where though. The same problem is
filepath = '/dev/char/%d:%d' % (devnums[0], devnums[1])
this isn't accessible in Windows and you have to do in a different way.
Besides that everything else looks OS independent. If you have any errors after fixing previous 3 problems, then edit them into your question please.
Hello,
I am a novice to this (please answer like so for me). All of the code should run easily after fixing my (second) question. It actually runs great on my machine, but probably not yours yet. I have tried to comment everywhere for you to make it easier for someone to read. This runs on an Ubuntu 12.10 machine, but you don't need to be running Linux to help my issues!
SOLVED 1. Code Review: As you go through the code, I would appreciate any input on how to condense or do things in a better, more appropriate way. The rest of my questions are really just the things I already know should be worked on. But if there's something else you find in my coding style, et al, please be candid. Upvotes to all good comments.
SOLVED: 2. Relative Icon Path: At the following:
/home/mh00h/workspace/issindicator/src/International_Space_Station_clip_art.svg
This is an absolute path to the same folder as this script. I don't want that,, I want this script to work on anybody's machine. I tried these:
$HOME/workspace...
(nothing in front) International_Space_Station_clip_art.svg
./International_Space_Station_clip_art.svg
but those didn't work. The image above is what I am trying to use (yes, I know I have an SVG instead of png listed, imgur limitation). Here is the documentation. It talks about an ""icon-theme-path"... maybe that would do it somehow? Or perhaps there is a standard directory all programmers are expected to store icons?
3. Concentrate my datetime functions: Really, I was fortunate to get this to work at all. My way is roundabout, but as far as I can tell, it works. I'm pretty confident that there is a better way to fix that mess though!! You'll find a bunch of datetime stuff at the bottom of the script.
SOLVED: 4. Appindicator3 Hook: I would love to have GTK refresh only when the menu has been called instead of running every second regardless. This was partially answered here, but I don't really understand how to implement "realize." (Hopefully this is the right place to be asking this?)
Thank you!
#!/usr/bin/env python
import json, urllib2, time, math, datetime, os, webbrowser
from dateutil import tz
#indicator
from gi.repository import Gtk, GObject
from gi.repository import AppIndicator3 as appindicator
class indicator():
def __init__(self):
#######Set this to "False" if IssIndicator should hide it's icon during normal runtime (default = True)
self.isiconhidden = True
#
#create indicator
self.ind = appindicator.Indicator.new (
"issindicator",
"/home/mh00h/workspace/issindicator/src/International_Space_Station_clip_art.svg",
#"indicator-messages",
appindicator.IndicatorCategory.APPLICATION_STATUS)
if self.isiconhidden == True:
self.ind.set_status (appindicator.IndicatorStatus.PASSIVE)
else:
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
#this is used to keep track of the gtk refresh period
self.refreshvalue = False
#dropdown menu
#current pass menu items
self.menu = Gtk.Menu()
self.curpass = Gtk.MenuItem("not refreshed")
self.curpass.connect("activate", self.checkiss)
self.menu.append(self.curpass)
self.curpassdur = Gtk.MenuItem(" ")
self.menu.append(self.curpassdur)
self.curpassrise = Gtk.MenuItem(" ")
self.menu.append(self.curpassrise)
self.curpassset = Gtk.MenuItem(" ")
self.menu.append(self.curpassset)
self.sep1 = Gtk.SeparatorMenuItem()
self.menu.append(self.sep1)
#future pass items
self.futpass = Gtk.MenuItem(" ")
self.futpass.connect("activate", self.onurl)
self.menu.append(self.futpass)
self.sep2 = Gtk.SeparatorMenuItem()
self.menu.append(self.sep2)
#Options items
self.aboutmenu = Gtk.MenuItem("About")
self.aboutmenu.connect("activate", self.onabout)
self.menu.append(self.aboutmenu)
self.quit = Gtk.MenuItem("Quit")
self.quit.connect("activate", self.quitnow)
self.menu.append(self.quit)
self.curpass.show()
self.sep1.show()
self.futpass.show()
self.sep2.show()
self.aboutmenu.show()
self.quit.show()
self.ind.set_menu(self.menu)
#get iss data at first run
self.updatecache()
self.checkiss()
Gtk.main()
#functions
def hideicon(self, w=None):
self.ind.set_status (appindicator.IndicatorStatus.PASSIVE)
def showicon(self, w=None):
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
def quitnow(self, w=None):
Gtk.main_quit()
#open browser for more tracking info
def onurl(self, w=None):
webbrowser.open("http://www.n2yo.com/passes/")
def onabout(self,widget):
widget.set_sensitive(False)
ad=Gtk.AboutDialog()
ad.set_name("aboutdialog")
ad.set_version("0.1")
ad.set_copyright('Copyrignt (c) 2013 mh00h')
ad.set_comments('Indicating ISS Zarya')
ad.set_license(''+
'This program is free software: you can redistribute it and/or modify it\n'+
'under the terms of the GNU General Public License as published by the\n'+
'Free Software Foundation, either version 3 of the License, or (at your option)\n'+
'any later version.\n\n'+
'This program is distributed in the hope that it will be useful, but\n'+
'WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n'+
'or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for\n'+
'more details.\n\n'+
'You should have received a copy of the GNU General Public License along with\n'+
'this program. If not, see <http://www.gnu.org/licenses/>.')
ad.set_website('https://launchpad.net/~mh00h/+archive/issindicator')
ad.set_website_label('ISSIndicator Homepage')
ad.set_authors(['mh00h <abcd#abcd.com'])
ad.run()
ad.destroy()
widget.set_sensitive(True)
#how often to run checkiss
def setrefresh(self, r):
#this clause is required to keep script from hanging
if r != self.refreshvalue:
self.refreshvalue = r
try:
self.reftime = GObject.source_remove(True)
except:
pass
try:
self.reftime = GObject.timeout_add(r, self.checkiss)
except:
pass
#
def updatecache(self, w=None):
#this will show in the menu until the update process completes
self.passingstatus = 'not updated yet'
#get ISS data from api
self.ip = urllib2.urlopen("http://api.exip.org/?call=ip").read()
self.geoip = json.load(urllib2.urlopen("http://freegeoip.net/json/"+self.ip))
self.data = json.load(urllib2.urlopen("http://api.open-notify.org/iss/?lat="+str(self.geoip["latitude"])+"&lon="+str(self.geoip["longitude"])+"&alt=280&n=47"))
self.data = {"message": "success", "request": {"latitude": 45.0, "passes": 3, "altitude": 280, "longitude": -81.0, "datetime": 1361502063},
"response": [{"duration": 542, "risetime": time.time()+10}, {"duration": 642, "risetime": 1361560774}, {"duration": 593, "risetime": 1361566621}]}
def checkiss(self, w=None):
#so as to not overload api servers, this runs as a separate process
#this updates the timers
self.n = 0
self.passingstatus = "ISS Zarya is below the horizon"
#check if we've gone through cached iss passings and update api if needed
try:
#ignore errors in case internet is not accessible
#have a buffer of 5 passes remaining before updating cache
#at 2 passes left, stop the program to prevent the rest of the program from throwing codes
if time.time() > self.data['response'][len(self.data['response'])-5]['risetime']:
self.updatecache
except:
if time.time() > self.data['response'][len(self.data['response'])-2]['risetime']:
os.system("notify-send 'ISS Indicator tried multiple times to update its satellite cache but has run out of its cached track.' 'This may be due to a bad internet connection. The application will now quit.'")
Gtk.main_quit()
#get current time
current_utc = datetime.datetime.utcnow()
current_utc = current_utc.replace(tzinfo=tz.gettz('UTC'))
#iterate over all iss passes
for k in self.data['response']:
duration = self.data['response'][self.n]['duration']
risetime = self.data['response'][self.n]['risetime']
settime = risetime + duration
#if this iteration matches with the current time, do...
if risetime <= time.time() <= settime:
#make the countdown values for the current pass tick
#rise time calculations and date conversions to string format
currisetime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][self.n]['risetime'])
currisetime_utc = currisetime_utc.replace(tzinfo=tz.gettz('UTC'))
currisetime_tz = currisetime_utc.astimezone(tz.tzlocal())
currisetime_tzstr = str("%02d" % (currisetime_tz.hour))+':'+str("%02d" % (currisetime_tz.minute))+':'+str("%02d" % (currisetime_tz.second))
#set time calculations and durations
cursettime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][self.n]['risetime']+self.data['response'][self.n]['duration'])
cursettime_utc = cursettime_utc.replace(tzinfo=tz.gettz('UTC'))
cursettime_tz = cursettime_utc.astimezone(tz.tzlocal())
curremainingtimeleft = cursettime_utc - current_utc
curduration = cursettime_utc - currisetime_utc
z= curremainingtimeleft.seconds
zhours = z/60/60
zminutes = z/60-zhours*60
zseconds = z-zhours*60*60-zminutes*60
curremainingtimeleftstr = str(zhours)+':'+str("%02d" % (zminutes))+':'+str("%02d" % (zseconds))
z= curduration.seconds
zhours = z/60/60
zminutes = z/60-zhours*60
zseconds = z-zhours*60*60-zminutes*60
curdurationstr = str(zhours)+':'+str("%02d" % (zminutes))+':'+str("%02d" % (zseconds))
cursettime_tzstr = str("%02d" % (cursettime_tz.hour))+':'+str("%02d" % (cursettime_tz.minute))+':'+str("%02d" % (cursettime_tz.second))
#since the ISS is presently overhead, show the icon and update GTK menuitems to show timers on the ISS pass
self.showicon()
self.passingstatus = "ISS Zarya is above the horizon!"
self.curpass.get_child().set_text(self.passingstatus)
self.curpassdur.get_child().set_text("Duration: "+curdurationstr+" ("+curremainingtimeleftstr+" remaining)")
self.curpassdur.show()
self.curpassrise.get_child().set_text("Rise time: "+currisetime_tzstr)
self.curpassrise.show()
self.curpassset.get_child().set_text("Set time: "+cursettime_tzstr)
self.curpassset.show()
break
else:
#if this iteration of ISS passes does not match with current time, then increase self.n
self.n += 1
#regardless of results show the next pass time
#if the ISS is overhead, use the next dictionary key for data
if self.n != len(self.data['response']):
nextrisetime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][self.n+1]['risetime'])
else:
#if the ISS is not overhead, use the first key in the dictionary
nextrisetime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][0]['risetime'])
#calculate the next rise time and make timers
nextrisetime_utc = nextrisetime_utc.replace(tzinfo=tz.gettz('UTC'))
nextrisetime_tz = nextrisetime_utc.astimezone(tz.tzlocal())
remainingtimeleft = nextrisetime_utc - current_utc
z= remainingtimeleft.seconds
zhours = z/60/60
zminutes = z/60-zhours*60
zseconds = z-zhours*60*60-zminutes*60
remainingtimeleftstr = str(zhours)+':'+str("%02d" % (zminutes))+':'+str("%02d" % (zseconds))
nextrisetime_tzstr = str("%02d" % (nextrisetime_tz.hour))+':'+str("%02d" % (nextrisetime_tz.minute))+':'+str("%02d" % (nextrisetime_tz.second))
#update GTK menuitem
self.futpass.get_child().set_text("Next Pass: "+nextrisetime_tzstr+" ("+remainingtimeleftstr+")")
#if the ISS is not above the horizon, refresh GTK only once its time for the icon to be visible
if self.passingstatus != "ISS Zarya is above the horizon!":
self.setrefresh(remainingtimeleft.seconds*1000+100)
#self.setrefresh(1000)
self.curpass.get_child().set_text(self.passingstatus)
self.curpassdur.hide()
self.curpassrise.hide()
self.curpassset.hide()
if self.isiconhidden == True:
self.hideicon()
else:
#otherwise, refresh once a second to show the timers ticking in the menu
#test if the menu is active instead of always running like in this example
####MISSING CODE HERE##### DONT KNOW HOW TO DO IT, SO JUST SETTING TO 1 SEC
self.setrefresh(1000)
#for when setrefresh calls this function
return True
if __name__ == '__main__':
issindicator = indicator()