Help with script error

If you have a question or need help, this is the place to be.
User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Sat Aug 03, 2019 12:08 pm

as far as the speed goes there is nothing i can do about that. it takes a while to enumerate all of the "windows" that are running. the lower the number of applications you have running the faster it is going to be.

A window is classified as a window classed item. so that can be a button or some text, maybe a choice control. just about every single element in a GUI is a window classed item. If you use the Find Window action and simply expand all of the items in the tree you will get a better understanding of what it is dealing with.

and to be honest 2 seconds is bloody fast actually. the Find Window action has been known to take in the 10's of seconds to locate a window.

As far as the errors are concerned i would need to see them in their entirety to know what is happening and I can fix it from there. also some of the 2 seconds for the OSD display might have been in the fading as well. I can adjust the speed of the fading pretty easily.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Sun Sep 29, 2019 11:14 pm

kgschlosser wrote:
Sat Aug 03, 2019 12:08 pm
As far as the errors are concerned i would need to see them in their entirety to know what is happening and I can fix it from there. also some of the 2 seconds for the OSD display might have been in the fading as well. I can adjust the speed of the fading pretty easily.
Ok after you have fixed the previous problem with my volume control my OSD using the new method will sometimes fail to appear or make video pause for a second or two. I noticed using this script when I re-enabled it that fade in helped fix this problem with sudden OSD displaying make the OSD not show a rectangular box or freezing video playback. The problem is I rather have immediate fade out and slightly faster fade in. How do I change the variables for the fade in and out?

User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Mon Sep 30, 2019 7:21 am

we can control the fad in speed. But it is set bloody fast now as it is. It is set to something like one step every hundredth of a second and there are only 255 steps. the fading should go pretty fast from start to finish.

Now what you might be interperting as part of the fade in is the actual time it takes for Windows to enumerate all of the open windows and for us to find the one we are looking for.

As a test. Close everything except for EG and MPC and run it.. I mean close as much as you can. even things in the system tray. when windows get enumerated over that also includes every button and bit of text you see inside of an application. see if the time it takes decreases. I want to know 100% where the slowdown is. I can add a bunch of timers to the code to time how long each step takes. this will help to isolate the problem area.

what I can do is this. because python scripts are persistent between runs. meaning that hwnd if you set it the first time it runs it will be available the second time it runs. I think I already do that in the script for some of the things. But what we can do is we can use that mechanism to "cache" the window handle. if we try the cached window handle and it fails then we tell it to do the search over again. you will end up with what appears as a linger fade in if you close the program and then reopen it and run the script. This would be because it has to do the search over.

Unfortunately when dealing with any window searching it is always going to slow the hell out of everything..

I want you to take a look at Task Monitor Plus. The reason I say this is because it is going to post events in EG when an application starts, stops and also when you activate it. The real magic comes in the payload. the payload is actually a set of functions you can use for manipulating the application window. you can get and set the following

size. minimize, maximize, restore, position, hidden.. You can also flash the taskbar icon.. but one of the things you can get is the window handle. By using this plugin you will remove the need to search for a window handle. Now this is a starting point you can get the main application handle from that. I also believe you can iterate over the child windows to get the handle of the volume slider. The point is if you get a close event for the app. you can set eg.globals.MPC_hwnd = None
and when the event comes in for it getting started then you can set it to the handle.

Now if you have it running and then you start EG. add the find window to the Autostart group to see if it can locate the bugger. and if it cannot then set the variable to None. otherwise set it to the handle. a 2-3 second delay on startup of EG should not be so much of an annoyance as every time the script runs.

I say we start out with adding timers to the script and see exactly where the time is being spent.
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Mon Sep 30, 2019 7:36 am

try this I added a whole snot load of timers. it is going to print out how long each step in the process takes,

Code: Select all

# this try/except routine is done because the information that is generated
# in a Python script is persistent between runs. Whats this means is that
# anything that is created in a python script when the script gets run a
# second, third, fourth... the objects created the first time are available to
# every run thereafter. So if you have any objects created that are static and
# do not change between runs then there is no need to make them over again.
# To test for this we simply use one of those object names that are static.
# if the object exists it will pass right on by. if it oes not it will
# generate an error that we catch in the except portion of thee code block.
# And that is where we will then create all of the static objects


# adjustable set between 0 and 255
MAX_TRANSPARENT = 90

import time

start = time.time()
try:
    user32
except NameError:
    import wx
    import datetime
    import time
    import threading
    import ctypes
    from ctypes.wintypes import (
        BOOL,
        LONG,
        UINT,
        POINT,
        RECT,
        HWND,
        LPARAM,
        DWORD
    )  # NOQA

    from eg.WinApi import (
        GetWindowText,
        GetTopLevelWindowList,
        GetProcessName
    )  # NOQA

    from eg.WinApi.Dynamic import (
        SetWindowPos,
        SWP_FRAMECHANGED,
        SWP_NOACTIVATE,
        SWP_NOOWNERZORDER,
        SWP_SHOWWINDOW,
    )


    HWND_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED

    GWL_STYLE = -16
    GWL_EXSTYLE = -20
    WS_BORDER = 0x00800000
    WS_DLGFRAME = 0x00400000
    WS_THICKFRAME = 0x00040000
    WS_EX_WINDOWEDGE = 0x00000100
    WS_POPUP = 0x80000000
    WS_EX_TOPMOST = 0x00000008

    PID = DWORD
    user32 = ctypes.windll.User32

    # DWORD GetWindowThreadProcessId(
    #   HWND    hWnd,
    #   LPDWORD lpdwProcessId
    # );
    _GetWindowThreadProcessId = user32.GetWindowThreadProcessId
    _GetWindowThreadProcessId.restype = DWORD

    # LONG GetWindowLongW(
    #   HWND hWnd,
    #   int  nIndex
    # );

    _GetWindowLong = user32.GetWindowLongW
    _GetWindowLong.restype = LONG

    # HWND GetActiveWindow(
    #
    # );
    _GetActiveWindow = user32.GetActiveWindow
    _GetActiveWindow.restype = HWND

    BAR_TEMPLATE = '[{bars}{remaining}] - {percent:.2f}%'
    ELAPSED_TOTAL_TEMPLATE = 'Elapsed: {elapsed} / Total: {duration}'
    OSD_TEMPLATE = '{title}\n \n{bar}\n{elapsed_total}'
    osd_frame = eg.plugins.EventGhost.actions['ShowOSD'].osdFrame
    _old_show_osd = osd_frame.ShowOSD
    _old_on_timeout = osd_frame.OnTimeout

    eg.globals.fade_in_event = threading.Event()
    eg.globals.fade_out_event = threading.Event()


stop = time.time()

print 'imports and setup', (stop - start) * 1000, 'milliseconds'

start = time.time()
# OK so I removed the euse of the eg.WindowMatcher.
# it is a horribly slow thing to use and since we are only looking to see
# if a process is running or not we use GetTopLevelWindowList which returns
# the handles for the base window class of an application. then we ask windows
# to get us the process id (pid) for one of the handles in the returned list.
# Then we use that pid to get the process name. and we match that process name
# up with the one we are looking for.

handles = GetTopLevelWindowList(False)

stop = time.time()

print 'GetTopLevelWindowList', (stop - start) * 1000, 'milliseconds'
start = time.time()
# OK so I added some code to locate which instance of mpc to use if more then
# one is running.
# I elected to do some crafty coding with this. What happens is after the the
# window handles are found I then query the Windows API to get thee styles of
# the window. i check for specific markers like if the window has a border and
# if the window is set to be the topmost window as these are things that get
# turned on/off when an application goes into a fullscreen mode. at the same
# time I am also checking for a copy of mpc that is the active window. I do
# this to cover my bases, if there is more then a single active copy and none
# of them are set to full screen it is going to choose the one that is active
# (has focus). if there is a fullscreen copy running and a second. and the
# second is active the fullscreen is going to be the one used.

for handle in handles[:]:
    pid = PID()
    _GetWindowThreadProcessId(HWND(handle), ctypes.byref(pid))

    process_name = GetProcessName(pid.value)

    # we check the process name of the window. if it does not match then we
    # remove it form the list.
    if process_name != 'mpc-hc64.exe':
        handles.remove(handle)

stop = time.time()

print '_GetWindowThreadProcessId', (stop - start) * 1000, 'milliseconds'

# if there are no windows found then we want to stop the macro from running and
# exit the script

if not handles:
    handle = None
    eg.StopMacro()
    eg.Exit()


handle = None
# I removed the use of eg.globals.WindowsState as a mechanism for checking if
# the app is in fullscreen mode. Because this is one of the things we are
# looking for when we pick the window to use we no longer need to depend on
# an outside source to determine the window state. So you can remove that from
# your tree if you want.
is_fullscreen = False


start = time.time()
for hwnd in handles:
    # getting the windows styles and extended styles.
    style = _GetWindowLong(HWND(hwnd), GWL_STYLE)
    style_ex = _GetWindowLong(HWND(hwnd), GWL_EXSTYLE)

    # checking the styles for specific markers.
    if (
        not style & WS_BORDER and
        not style & WS_DLGFRAME and
        not style & WS_THICKFRAME and
        not style_ex & WS_EX_WINDOWEDGE and
        style & WS_POPUP and
        style_ex & WS_EX_TOPMOST
    ):
        # if there are 2 copies of mpc running and both are fullscreen then
        # we are going to use the one that is active
        if is_fullscreen is False:
            handle = hwnd
            is_fullscreen = True
        elif _GetActiveWindow() == hwnd:
            handle = hwnd
            is_fullscreen = True

    # if there is no full screen one found already and this window is active
    # then we seet it into place
    elif handle is None and _GetActiveWindow() == hwnd:
        handle = hwnd
        is_fullscreen = False

stop = time.time()

print 'is fullscreen', (stop - start) * 1000, 'milliseconds'


# fallback if something does not work properly in the code.
if handle is None:
    handle = handles[0]

start = time.time()
window_text = GetWindowText(handle)
stop = time.time()
print 'GetWindowText', (stop - start) * 1000, 'milliseconds'

# I removed the use of re and did a simple trial and error using a for loop
# with an else statement.
# If i split a string on a value and that value is not n the string it is
# going to return a list with the original string at index 0. So the list
# only las a length of 1. if it did have the value in it the list would have a
# length greater then 1. and we use that to break the loop. This will cause
# the else to not get run.

start = time.time()
for item in (".mkv", ".mp4", ".avi", ".ogm"):
    window_text = window_text.split(item)
    if len(window_text) > 1:
        break
    window_text = window_text[0]
else:
    window_text = window_text.split('.')

stop = time.time()

print 'windows text evaluation', (stop - start) * 1000, 'milliseconds'


title = window_text[0]


start = time.time()
_elapsed, _remaining, _duration = eg.plugins.MediaPlayerClassic.GetTimes()
# print "elaps, rem, total =",elaps, rem, total

# I removed all of that math involved in trying to get the seconds in
# favor of using time.strptime and datetime.timedelta
# time.strptime will take a string formatted time representation and turn it
# into a python object for us. we simply have to provide details as to how the
# string is formatted. you can get more information on the identifiers here
# http://strftime.org/
# once we have that object which only does the simple
# conversion to hours minutes seconds we need to create another object that
# does the match for us and will return the total seconds

stop = time.time()
print 'eg.plugins.MediaPlayerClassic.GetTimes', (stop - start) * 1000, 'milliseconds'

start = time.time()
while _elapsed.count(':') < 2:
    _elapsed = '00:' + _elapsed
while _remaining.count(':') < 2:
    _remaining = '00:' + _remaining
while _duration.count(':') < 2:
    _duration = '00:' + _duration

elapsed = time.strptime(_elapsed, '%H:%M:%S')
elapsed = datetime.timedelta(
    hours=elapsed.tm_hour,
    minutes=elapsed.tm_min,
    seconds=elapsed.tm_sec
)

remaining = time.strptime(_remaining, '%H:%M:%S')
remaining = datetime.timedelta(
    hours=remaining.tm_hour,
    minutes=remaining.tm_min,
    seconds=remaining.tm_sec
)

duration = time.strptime(_duration, '%H:%M:%S')
duration = datetime.timedelta(
    hours=duration.tm_hour,
    minutes=duration.tm_min,
    seconds=duration.tm_sec
)

stop = time.time()
print 'time evaluation', (stop - start) * 1000, 'milliseconds'

# heere we do a test to see if elapsed has any seconds to it. if iit does
# that means the video is playing and to generate the osd
if elapsed.total_seconds():
    def fade_in():
        beg = time.time()
        for i in range(MAX_TRANSPARENT):
            if eg.globals.fade_in_event.isSet():
                break
            osd_frame.SetTransparent(i)
            eg.globals.fade_in_event.wait(0.01)

        osd_frame.ShowOSD = _old_show_osd
        end = time.time()

        print 'fade in', (end - beg) * 1000, 'milliseconds'


    def fade_out():
        beg = time.time()

        for i in range(MAX_TRANSPARENT, 0, -1):
            if eg.globals.fade_out_event.isSet():
                break
            osd_frame.SetTransparent(i)
            eg.globals.fade_out_event.wait(0.01)

        _old_on_timeout()
        osd_frame.OnTimeout = _old_on_timeout
        end = time.time()

        print 'fade out', (end - beg) * 1000, 'milliseconds'

    def show_osd(*args, **kwargs):
        args = list(args)
        args[7] += MAX_TRANSPARENT * 0.01

        eg.globals.fade_out_event.set()
        eg.globals.fade_in_event.clear()

        osd_frame.SetTransparent(0)
        _old_show_osd(*args, **kwargs)

        fade_in()

    def on_timeout():
        eg.globals.fade_in_event.set()
        eg.globals.fade_out_event.clear()
        fade_out()


    osd_frame.ShowOSD = show_osd
    osd_frame.OnTimeout = on_timeout

    # instead of having to calls to ShowOSD we can set the parameters we want
    # to pass to ShowOSD into a variable you name the variable the same for
    # Fullscreen and not Fullscreen and set the parameters for each variation
    # then when we call ShowOSD the proper set of parameters will be passed
    if is_fullscreen:
        osd_args = (
            u'0;-82;0;0;0;700;255;0;0;0;3;2;1;34;Verdana',
            (0, 255, 0),
            (0, 0, 0),
            5,
            (0, 0),
            1,
            3.0,
            None
        )
    else:
        osd_args = (
            u'0;-030;0;0;0;700;255;0;0;0;3;2;1;34;Verdana',
            (0, 255, 0),
            (0, 0, 0),
            5,
            (0, 0),
            2,
            3.0,
            None
        )
        
    start = time.time()

    # I trimmed down the percent math. iif you want to change an int to a
    # float you do not need to do 1.0 * int. this will work but the pythonic
    # way is to wrap that int with float()
    percent = (
        (
            float(elapsed.total_seconds()) /
            float(duration.total_seconds())
        ) * 100.0
    )
    # I removed the conversion of the percent form a float to an int because
    # i wanted the percent as a float for use further on down the line

    bars = '/' * int(round(percent))
    remaining = '.' * (100 - len(bars))

    # I created string templates for the OSD. i split the osd into 3 sections
    # I will explain as to why they are split into 3 sections further down
    # I added the percent that has elapsed to the bar line as a 2 decimal place
    # float

    bar = BAR_TEMPLATE.format(
        bars=bars,
        remaining=remaining,
        percent=percent
    )

    elapsed_total = ELAPSED_TOTAL_TEMPLATE.format(
        elapsed=_elapsed,
        duration=_duration
    )

    # OK so this is an odd thing. wx is thee GUI interface we use to generate
    # all of the graphics, windows and controls in EG. because of how text
    # gets displayed when using a GUI the characters defined in a font are
    # not of equal size, so a "G" does not have the same width as an "!"
    # I saw that you were displaying the OSD n the center of the screen with
    # what looked like center justification. because of that font issue ths
    # is extremely difficult to do. Portions of the wx library allow us to call
    # a function called GetTextExtent this function rill return the width and
    # height in pixels of a string passed into it. we have to set the font we
    # want to use then call the function. in order to get the center
    # justification as close as we can and the space is the only thing we can
    # really do it with we also want to know the width of a space.

    stop = time.time()
    print 'building the bar', (stop - start) * 1000, 'milliseconds'

    start = time.time()
    frame = wx.Frame(None, -1)
    font = wx.FontFromNativeInfoString(osd_args[0])
    frame.SetFont(font)
    bar_len = frame.GetTextExtent(bar)[0]
    title_len = frame.GetTextExtent(title)[0]
    elapsed_total_len = frame.GetTextExtent(elapsed_total)[0]
    space_len = frame.GetTextExtent(' ')[0]

    # so here we do the checking to see which line is longer then the other to
    # adjust the one that is shorter
    if bar_len < title_len:
        # this is the number of spaces we are going to need. so we find out the
        # pixel difference divide that by the number of pixels a space is.
        # this is going to return the total number of spaces needed to make the
        # line the same length. Now remember center justification. so half of
        # those spaces would need to be on thee back end of the line. Those
        # we do not need to add to the line. so wee divide the total number of
        # spaces by 2 to get the number of spaces we need to add to the front
        # of the line

        space_count = (
            int(round(float(title_len - bar_len) / float(space_len))) / 2
        )
        bar = ' ' * space_count + bar

    elif title_len < bar_len:
        space_count = (
            int(round(float(bar_len - title_len) / float(space_len))) / 2
        )
        title = ' ' * space_count + title

    bar_len = frame.GetTextExtent(bar)[0]

    frame.Destroy()

    if elapsed_total_len < bar_len:
        space_count = (
            int(round(
                float(bar_len - elapsed_total_len) / float(space_len))) / 2
        )
        elapsed_total = ' ' * space_count + elapsed_total

    osd = OSD_TEMPLATE.format(
        title=title,
        bar=bar,
        elapsed_total=elapsed_total
    )
    # and here is the single call to ShowOSD. we prefiix that variable we made
    # earlier with a * which expands the variable into the parameters that
    # needs to be passed
    stop = time.time()
    print 'calculating bar position on the screen', (stop - start) * 1000, 'milliseconds'

    eg.plugins.EventGhost.ShowOSD(osd, *osd_args)

IF YOU GET ANY ERRORS I NEED THE TRACEBACK DATA THAT GETS PRINTED IN THE LOG!!! otherwise it makes it very hard to know where the problem is.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Wed Oct 02, 2019 5:07 am

Thanks it seems to be behaving well so for the OSD but for some reason it's affecting the show OSD for volume control and fading that in also which makes it difficult to control volume.

BTW I changed font type from Verdant to Consolas so that it's a fixed font and the bar no longer resizes based on how many indicators there is.

My volume control OSD is pretty simple so not sure why the fade in and out is triggering for it after we changed the Show OSD code.

Code: Select all


# this is going to seem like a goofy bit of code. what this does is makes sure 
# that eg.globals.WindowsState exists. and if it doesn\'t then set it to \'Normal\'
eg.globals.WindowsState = getattr(eg.globals, \'WindowsState\', \'Normal\')

# variable fs = fontsize as a string

from eg.WinApi.Dynamic import SendMessage
TBM_GETPOS = 1024

del eg.lastFoundWindows[:]
eg.plugins.Window.FindWindow(u\'mpc-hc64.exe\', None, u\'MediaPlayerClassicW\', u\'\', u\'msctls_trackbar32\', 1, True, 0.0, 0)

# this is going to simply stop the macro from running and exit the script if FindWindow did not find anything.
if not eg.lastFoundWindows:
   eg.StopMacro()
   eg.Exit()

# get the handle from the storage container.
hwnd = eg.lastFoundWindows[0]
eg.globals.WindowsState = getattr(eg.globals, \'WindowsState\', \'Normal\')
print "Current Window State == " + eg.globals.WindowsState

if eg.globals.WindowsState != "Fullscreen":
  fs = \'64\'
  mon = 2
  top = 1000
else:
   fs = \'128\'
   mon = 1
   top = 1800

volume = SendMessage(hwnd, TBM_GETPOS, 0, 0)
osd = "Volume: %i%%"

if volume == 1 :
   volume = 0
eg.plugins.EventGhost.ShowOSD(osd % volume, u\'0;-\' + fs + \';0;0;0;700;0;0;0;238;3;2;1;66;Arial\', (255, 255, 255), (0, 0, 0), 5, (0, top), mon, 3.0, True)
Was planning to change the window match to be the way the new code is just have not messed with this part of the script yet.

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Thu Oct 03, 2019 3:58 am

The if is_fullscreen: portion does not work and it always does the else statement. Other then that the code is working great so far.

Code: Select all

# instead of having to calls to ShowOSD we can set the parameters we want
    # to pass to ShowOSD into a variable you name the variable the same for
    # Fullscreen and not Fullscreen and set the parameters for each variation
    # then when we call ShowOSD the proper set of parameters will be passed
    #if is_fullscreen:
    if eg.globals.WindowsState == "Fullscreen":
        osd_args = (
            u'0;-060;0;0;0;700;255;0;0;0;3;2;1;34;Consolas',
            (0, 255, 255),
            (0, 0, 0),
            4,
            (0, 0),
            1,
            3.0,
            None
        )
    else:
        osd_args = (
            u'0;-030;0;0;0;700;255;0;0;0;3;2;1;34;Consolas',
            (0, 255, 255),
            (0, 0, 0),
            4,
            (0, 0),
            2,
            3.0,
            None
        )

User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Sat Oct 05, 2019 7:51 am

this is wrong

Code: Select all

eg.globals.WindowsState = getattr(eg.globals, \'WindowsState\', \'Normal\')
it should be

Code: Select all

eg.globals.WindowsState = getattr(eg.globals, 'WindowsState', 'Normal')

the "\" is used for escaping characters in a string..
so if you ran this code.

Code: Select all

print 'It's a nice day'
you would get an error.

is a string is wrapped in single quotes and there are other single quotes in the string the single quotes in the string need to be escaped.

Code: Select all

print 'It\'s a nice day'
the same applies if a string is wrapped in double quotes as well and there are other double quotes in the string.

If you have single quotes in the string but no double quotes then you can wrap the string in double quotes and then you will not have to escape the single quotes. the same thing applies if you have doubles in the string and no singles.. wrap the string in singles.

Code: Select all

print "it's a nice day"

Now in order for me to know what is going on with the OSD script I need you to run the one I posted earlier. and I need the information that gets printed out in the log pasted here. without that I am not going to be able to help. The other thing is you MUST leave the script as i posted it. do not edit it in any way. run it like it is. if there is an error in it post the entire error to me and I will fix it.

I am trying to determine where the slowdown is that is making the fade in run longer then the fade out.. I do not think that actual fading process is what is running longer. I believe there is some processing time before the OSD even gets shown that is padding the length of time until it is fully seen.

as I stated before the fade in and out change a single level every .001 seconds so 255 * .001 = .225 or a 1/4 of a second. Now I know there is some processing time that is consumed by the code to do the actual level change.. so it ends up being closer to 3/4 of a second. I need to verify that and also verify the duration other components are taking to run.

If you do not use the code that I tested and know works and you modify it before using it I am not going to be able to help because I do not know what you did to it. You posted code for one script that was for checking the full screen and that is the script i altered and gave back.. then there is a script for creating a volume OSD and that I also edited and got working correctly. and now You are showing me a 3rd script one that I had originally made a while back for creating a volume OSD, and it had been altered. so which script are you using to show the OSD? the first or the second?

Did you modify the script for the full screen at all? and if so. did you even try running it before modifying it?

There is a select all at the top of a code block. if you click that and then copy the selected text in the code block and paste it directly into a python script it will get pasted exactly as you see it in the code block with all characters being correct and no added escaping.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Sat Oct 05, 2019 2:05 pm

Ok I re-activated the fullscreen script below for the Windows State.

Code: Select all

import ctypes
from ctypes.wintypes import LONG, HWND

user32 = ctypes.windll.User32

# Retrieves the extended window styles.
GWL_EXSTYLE = -20

# Retrieves a handle to the application instance.
GWL_HINSTANCE = -6

# Retrieves a handle to the parent window, if any.
GWL_HWNDPARENT = -8

# Retrieves the identifier of the window.
GWL_ID = -12

# Retrieves the window styles.
GWL_STYLE = -16

# Retrieves the user data associated with the window. This data is intended
# for use by the application that created the window. Its value is initially
# zero.
GWL_USERDATA = -21

# Retrieves the address of the window procedure, or a handle representing the
# address of the window procedure. You must use the CallWindowProc function to
# call the window procedure.
GWL_WNDPROC = -4


# ***** Wnidow Styles

# The window has a thin-line border.
WS_BORDER = 0x00800000

# The window has a title bar (includes the WS_BORDER style).
WS_CAPTION = 0x00C00000

# The window is a child window. A window with this style cannot have a menu
# bar. This style cannot be used with the WS_POPUP style.
WS_CHILD = 0x40000000

# Same as the WS_CHILD style.
WS_CHILDWINDOW = 0x40000000

# Excludes the area occupied by child windows when drawing occurs within the
# parent window. This style is used when creating the parent window.
WS_CLIPCHILDREN = 0x02000000

# Clips child windows relative to each other; that is, when a particular child
# window receives a WM_PAINT message, the WS_CLIPSIBLINGS style clips all
# other overlapping child windows out of the region of the child window to be
# updated. If WS_CLIPSIBLINGS is not specified and child windows overlap,
# it is possible, when drawing within the client area of a child window, to
# draw within the client area of a neighboring child window.
WS_CLIPSIBLINGS = 0x04000000

# The window is initially disabled. A disabled window cannot receive input
# from the user. To change this after a window has been created, use the
# EnableWindow function.
WS_DISABLED = 0x08000000

# The window has a border of a style typically used with dialog boxes. A
# window with this style cannot have a title bar.
WS_DLGFRAME = 0x00400000

# The window is the first control of a group of controls. The group consists
# of this first control and all controls defined after it, up to the next
# control with the WS_GROUP style. The first control in each group usually
# has the WS_TABSTOP style so that the user can move from group to group.
# The user can subsequently change the keyboard focus from one control in
# the group to the next control in the group by using the direction keys.
# You can turn this style on and off to change dialog box navigation. To
# change this style after a window has been created, use the SetWindowLong
# function.
WS_GROUP = 0x00020000

# The window has a horizontal scroll bar.
WS_HSCROLL = 0x00100000

# The window is initially minimized. Same as the WS_MINIMIZE style.
WS_ICONIC = 0x20000000

# The window is initially maximized.
WS_MAXIMIZE = 0x01000000

# The window has a maximize button. Cannot be combined with the
# WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified.
WS_MAXIMIZEBOX = 0x00010000

# The window is initially minimized. Same as the WS_ICONIC style.
WS_MINIMIZE = 0x20000000

# The window has a minimize button. Cannot be combined with the
# WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified.
WS_MINIMIZEBOX = 0x00020000

# The window is an overlapped window. An overlapped window has a title bar
# and a border. Same as the WS_TILED style.
WS_OVERLAPPED = 0x00000000

# The windows is a pop-up window. This style cannot be used with the
# WS_CHILD style.
WS_POPUP = 0x80000000

# The window has a sizing border. Same as the WS_THICKFRAME style.
WS_SIZEBOX = 0x00040000

# The window has a window menu on its title bar. The WS_CAPTION style must
# also be specified.
WS_SYSMENU = 0x00080000

# The window is a control that can receive the keyboard focus when the user
# presses the TAB key. Pressing the TAB key changes the keyboard focus to
# the next control with the WS_TABSTOP style. You can turn this style on
# and off to change dialog box navigation. To change this style after a
# window has been created, use the SetWindowLong function. For user-created
# windows and modeless dialogs to work with tab stops, alter the message
# loop to call the IsDialogMessage function.
WS_TABSTOP = 0x00010000

# The window has a sizing border. Same as the WS_SIZEBOX style.
WS_THICKFRAME = 0x00040000

# The window is an overlapped window. An overlapped window has a title bar
# and a border. Same as the WS_OVERLAPPED style.
WS_TILED = 0x00000000

# The window is an overlapped window. Same as the WS_OVERLAPPEDWINDOW style.
WS_TILEDWINDOW = (
    WS_OVERLAPPED |
    WS_CAPTION |
    WS_SYSMENU |
    WS_THICKFRAME |
    WS_MINIMIZEBOX |
    WS_MAXIMIZEBOX
)

# The window is an overlapped window. Same as the WS_TILEDWINDOW style.
WS_OVERLAPPEDWINDOW = (
    WS_OVERLAPPED |
    WS_CAPTION |
    WS_SYSMENU |
    WS_THICKFRAME |
    WS_MINIMIZEBOX |
    WS_MAXIMIZEBOX
)

# The window is a pop-up window. The WS_CAPTION and WS_POPUPWINDOW styles
# must be combined to make the window menu visible.
WS_POPUPWINDOW = (
    WS_POPUP |
    WS_BORDER |
    WS_SYSMENU
)

# The window is initially visible. This style can be turned on and off by
# using the ShowWindow or SetWindowPos function.
WS_VISIBLE = 0x10000000

# The window has a vertical scroll bar.
WS_VSCROLL = 0x00200000

# ******* Extended Styles

# The window accepts drag-drop files.
WS_EX_ACCEPTFILES = 0x00000010

# Forces a top-level window onto the taskbar when the window is visible.
WS_EX_APPWINDOW = 0x00040000

# The window has a border with a sunken edge.
WS_EX_CLIENTEDGE = 0x00000200

# Paints all descendants of a window in bottom-to-top painting order using
# double-buffering. For more information, see Remarks. This cannot be used if
# the window has a class style of either CS_OWNDC or CS_CLASSDC.
# Windows 2000: This style is not supported.
WS_EX_COMPOSITED = 0x02000000

# The title bar of the window includes a question mark. When the user clicks
# the question mark, the cursor changes to a question mark with a pointer.
# If the user then clicks a child window, the child receives a WM_HELP
# message. The child window should pass the message to the parent window
# procedure, which should call the WinHelp function using the HELP_WM_HELP
# command. The Help application displays a pop-up window that typically
# contains help for the child window. WS_EX_CONTEXTHELP cannot be used with
# the WS_MAXIMIZEBOX or WS_MINIMIZEBOX styles.
WS_EX_CONTEXTHELP = 0x00000400

# The window itself contains child windows that should take part in dialog
# box navigation. If this style is specified, the dialog manager recurses
# into children of this window when performing navigation operations such as
# handling the TAB key, an arrow key, or a keyboard mnemonic.
WS_EX_CONTROLPARENT = 0x00010000

# The window has a double border; the window can, optionally, be created with
# a title bar by specifying the WS_CAPTION style in the dwStyle parameter.
WS_EX_DLGMODALFRAME = 0x00000001

# The window is a layered window. This style cannot be used if the window has
# a class style of either CS_OWNDC or CS_CLASSDC. Windows 8: The WS_EX_LAYERED
# style is supported for top-level windows and child windows. Previous Windows
# versions support WS_EX_LAYERED only for top-level windows.
WS_EX_LAYERED = 0x00080000

# If the shell language is Hebrew, Arabic, or another language that supports
# reading order alignment, the horizontal origin of the window is on the right
# edge. Increasing horizontal values advance to the left.
WS_EX_LAYOUTRTL = 0x00400000

# The window has generic left-aligned properties. This is the default.
WS_EX_LEFT = 0x00000000

# If the shell language is Hebrew, Arabic, or another language that supports
# reading order alignment, the vertical scroll bar (if present) is to the left
# of the client area. For other languages, the style is ignored.
WS_EX_LEFTSCROLLBAR = 0x00004000

# The window text is displayed using left-to-right reading-order properties.
# This is the default.
WS_EX_LTRREADING = 0x00000000

# The window is a MDI child window.
WS_EX_MDICHILD = 0x00000040

# A top-level window created with this style does not become the foreground
# window when the user clicks it. The system does not bring this window to the
# foreground when the user minimizes or closes the foreground window. The
# window should not be activated through programmatic access or via keyboard
# navigation by accessible technology, such as Narrator. To activate the
# window, use the SetActiveWindow or SetForegroundWindow function. The window
# does not appear on the taskbar by default. To force the window to appear on
# the taskbar, use the WS_EX_APPWINDOW style.
WS_EX_NOACTIVATE = 0x08000000

# The window does not pass its window layout to its child windows.
WS_EX_NOINHERITLAYOUT = 0x00100000

# The child window created with this style does not send the WM_PARENTNOTIFY
# message to its parent window when it is created or destroyed.
WS_EX_NOPARENTNOTIFY = 0x00000004

# The window does not render to a redirection surface. This is for windows
# that do not have visible content or that use mechanisms other than surfaces
# to provide their visual.
WS_EX_NOREDIRECTIONBITMAP = 0x00200000

# The window has generic "right-aligned" properties. This depends on the
# window class. This style has an effect only if the shell language is Hebrew,
# Arabic, or another language that supports reading-order alignment;
# otherwise, the style is ignored. Using the WS_EX_RIGHT style for static or
# edit controls has the same effect as using the SS_RIGHT or ES_RIGHT style,
# respectively. Using this style with button controls has the same effect as
# using BS_RIGHT and BS_RIGHTBUTTON styles.
WS_EX_RIGHT = 0x00001000

# The vertical scroll bar (if present) is to the right of the client area.
# This is the default.
WS_EX_RIGHTSCROLLBAR = 0x00000000

# If the shell language is Hebrew, Arabic, or another language that supports
# reading-order alignment, the window text is displayed using right-to-left
# reading-order properties. For other languages, the style is ignored.
WS_EX_RTLREADING = 0x00002000

# The window has a three-dimensional border style intended to be used for
# items that do not accept user input.
WS_EX_STATICEDGE = 0x00020000

# The window is intended to be used as a floating toolbar. A tool window has
# a title bar that is shorter than a normal title bar, and the window title is
# drawn using a smaller font. A tool window does not appear in the taskbar or
# in the dialog that appears when the user presses ALT+TAB. If a tool window
# has a system menu, its icon is not displayed on the title bar. However, you
# can display the system menu by right-clicking or by typing ALT+SPACE.
WS_EX_TOOLWINDOW = 0x00000080

# The window should be placed above all non-topmost windows and should stay
# above them, even when the window is deactivated. To add or remove this
# style, use the SetWindowPos function.
WS_EX_TOPMOST = 0x00000008

# The window should not be painted until siblings beneath the window (that
# were created by the same thread) have been painted. The window appears
# transparent because the bits of underlying sibling windows have already
# been painted. To achieve transparency without these restrictions, use the
# SetWindowRgn function.
WS_EX_TRANSPARENT = 0x00000020

# The window has a border with a raised edge.
WS_EX_WINDOWEDGE = 0x00000100

# The window is an overlapped window.
WS_EX_OVERLAPPEDWINDOW = (
    WS_EX_WINDOWEDGE |
    WS_EX_CLIENTEDGE
)

# The window is palette window, which is a modeless dialog box that presents
# an array of commands.
WS_EX_PALETTEWINDOW = (
    WS_EX_WINDOWEDGE |
    WS_EX_TOOLWINDOW |
    WS_EX_TOPMOST
)

# LONG GetWindowLongW(
#   HWND hWnd,
#   int  nIndex
# );

_GetWindowLong = user32.GetWindowLongW
_GetWindowLong.restype = LONG


del eg.lastFoundWindows[:]
eg.plugins.Window.FindWindow(u'mpc-hc64.exe', None, u'MediaPlayerClassicW', u'', u'msctls_trackbar32', 1, True, 0.0, 0)
if not eg.lastFoundWindows:
   eg.StopMacro()
   eg.Exit()

# get the handle from the storage container.
hwnd = eg.lastFoundWindows[0]

style = _GetWindowLong(HWND(hwnd), GWL_STYLE)
style_ex = _GetWindowLong(HWND(hwnd), GWL_EXSTYLE)

if style == style | WS_ICONIC:
    eg.globals.WindowsState = 'Minimized'
    
elif style == style | WS_MAXIMIZE:
    eg.globals.WindowsState = 'Maximized'

elif (
    style == style | WS_BORDER and
    style == style | WS_DLGFRAME and
    style == style | WS_THICKFRAME and 
    style_ex == style_ex | WS_EX_WINDOWEDGE
):
    eg.globals.WindowsState = 'Normal'

else:
    eg.globals.WindowsState = 'Fullscreen'

Now it can not detect the fullscreen with this script so it is always showing on the monitor and not on the Tv output.

1. Does not display on the Fullscreen Tv
2. Causes the OSD to appear twice
3. Affects the Show Volume OSD as it will fade in and out

DEBUG LOG
---> Welcome to EventGhost <---
DXusbPCR.E-mail
Display OSD
Jump to "Windows State" and return
Windows State
Find Window: mpc-hc64.exe
New Script
DEBUG: 0 ActionThread: FindWindow.__del__()
Windows State = Fullscreen
New Style
imports and setup 0.0 milliseconds
GetTopLevelWindowList 0.0 milliseconds
_GetWindowThreadProcessId 110.000133514 milliseconds
is fullscreen 0.0 milliseconds
GetWindowText 0.0 milliseconds
windows text evaluation 0.0 milliseconds
eg.plugins.MediaPlayerClassic.GetTimes 0.0 milliseconds
time evaluation 0.999927520752 milliseconds
building the bar 0.0 milliseconds
calculating bar position on the screen 2.00009346008 milliseconds
DEBUG: 1 MainThread: OSDFrame.ShowOSD(osdText=' Sounan Desuka - 12 [HorribleSubs]\n \n[////////////////////////////////////////////////////////////////////................................] - 67.73%\n Elapsed: 00:08:28 / Total: 00:12:30', fontInfo=u'0;-030;0;0;0;700;255;0;0;0;3;2;1;34;Consolas', textColour=(0, 255, 255), outlineColour=(0, 0, 0), alignment=4, offset=(0, 0), displayNumber=2, timeout=6.45, event=12924, skin=None)
DEBUG: 1 MainThread: OSDFrame.OnPaint(dummyEvent=<wx.PaintEvent>)
fade in 918.999910355 milliseconds
DEBUG: 1 MainThread: OSDFrame.OnPaint(dummyEvent=<wx.PaintEvent>)
DEBUG: 0 Thread-230: OSDFrame.OnTimeout()
fade out 3453.00006866 milliseconds

User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Sat Oct 05, 2019 4:56 pm

OK now You said that the OSD takes longer to show then it does to disappear yes?
If you can would you please copy all of the macros dealing with the detection of the fullscreen as well as the showing of the osd and place them into a single folder.. then right click on the folder and click on copy..

paste it into a forum post.. please be sure to add the code tags so we can keep the formatting. I need to see the order in which the macros are getting run and how.
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Sat Oct 05, 2019 6:42 pm

Ok here you go.

Trigger Show OSD Action

Code: Select all

eg.plugins.EventGhost.NewJumpIf(XmlIdLink(110), 2, True)
----> Jump to the "Windows State" Event

Code: Select all

import ctypes
from ctypes.wintypes import LONG, HWND

user32 = ctypes.windll.User32

# Retrieves the extended window styles.
GWL_EXSTYLE = -20

# Retrieves a handle to the application instance.
GWL_HINSTANCE = -6

# Retrieves a handle to the parent window, if any.
GWL_HWNDPARENT = -8

# Retrieves the identifier of the window.
GWL_ID = -12

# Retrieves the window styles.
GWL_STYLE = -16

# Retrieves the user data associated with the window. This data is intended
# for use by the application that created the window. Its value is initially
# zero.
GWL_USERDATA = -21

# Retrieves the address of the window procedure, or a handle representing the
# address of the window procedure. You must use the CallWindowProc function to
# call the window procedure.
GWL_WNDPROC = -4


# ***** Wnidow Styles

# The window has a thin-line border.
WS_BORDER = 0x00800000

# The window has a title bar (includes the WS_BORDER style).
WS_CAPTION = 0x00C00000

# The window is a child window. A window with this style cannot have a menu
# bar. This style cannot be used with the WS_POPUP style.
WS_CHILD = 0x40000000

# Same as the WS_CHILD style.
WS_CHILDWINDOW = 0x40000000

# Excludes the area occupied by child windows when drawing occurs within the
# parent window. This style is used when creating the parent window.
WS_CLIPCHILDREN = 0x02000000

# Clips child windows relative to each other; that is, when a particular child
# window receives a WM_PAINT message, the WS_CLIPSIBLINGS style clips all
# other overlapping child windows out of the region of the child window to be
# updated. If WS_CLIPSIBLINGS is not specified and child windows overlap,
# it is possible, when drawing within the client area of a child window, to
# draw within the client area of a neighboring child window.
WS_CLIPSIBLINGS = 0x04000000

# The window is initially disabled. A disabled window cannot receive input
# from the user. To change this after a window has been created, use the
# EnableWindow function.
WS_DISABLED = 0x08000000

# The window has a border of a style typically used with dialog boxes. A
# window with this style cannot have a title bar.
WS_DLGFRAME = 0x00400000

# The window is the first control of a group of controls. The group consists
# of this first control and all controls defined after it, up to the next
# control with the WS_GROUP style. The first control in each group usually
# has the WS_TABSTOP style so that the user can move from group to group.
# The user can subsequently change the keyboard focus from one control in
# the group to the next control in the group by using the direction keys.
# You can turn this style on and off to change dialog box navigation. To
# change this style after a window has been created, use the SetWindowLong
# function.
WS_GROUP = 0x00020000

# The window has a horizontal scroll bar.
WS_HSCROLL = 0x00100000

# The window is initially minimized. Same as the WS_MINIMIZE style.
WS_ICONIC = 0x20000000

# The window is initially maximized.
WS_MAXIMIZE = 0x01000000

# The window has a maximize button. Cannot be combined with the
# WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified.
WS_MAXIMIZEBOX = 0x00010000

# The window is initially minimized. Same as the WS_ICONIC style.
WS_MINIMIZE = 0x20000000

# The window has a minimize button. Cannot be combined with the
# WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified.
WS_MINIMIZEBOX = 0x00020000

# The window is an overlapped window. An overlapped window has a title bar
# and a border. Same as the WS_TILED style.
WS_OVERLAPPED = 0x00000000

# The windows is a pop-up window. This style cannot be used with the
# WS_CHILD style.
WS_POPUP = 0x80000000

# The window has a sizing border. Same as the WS_THICKFRAME style.
WS_SIZEBOX = 0x00040000

# The window has a window menu on its title bar. The WS_CAPTION style must
# also be specified.
WS_SYSMENU = 0x00080000

# The window is a control that can receive the keyboard focus when the user
# presses the TAB key. Pressing the TAB key changes the keyboard focus to
# the next control with the WS_TABSTOP style. You can turn this style on
# and off to change dialog box navigation. To change this style after a
# window has been created, use the SetWindowLong function. For user-created
# windows and modeless dialogs to work with tab stops, alter the message
# loop to call the IsDialogMessage function.
WS_TABSTOP = 0x00010000

# The window has a sizing border. Same as the WS_SIZEBOX style.
WS_THICKFRAME = 0x00040000

# The window is an overlapped window. An overlapped window has a title bar
# and a border. Same as the WS_OVERLAPPED style.
WS_TILED = 0x00000000

# The window is an overlapped window. Same as the WS_OVERLAPPEDWINDOW style.
WS_TILEDWINDOW = (
    WS_OVERLAPPED |
    WS_CAPTION |
    WS_SYSMENU |
    WS_THICKFRAME |
    WS_MINIMIZEBOX |
    WS_MAXIMIZEBOX
)

# The window is an overlapped window. Same as the WS_TILEDWINDOW style.
WS_OVERLAPPEDWINDOW = (
    WS_OVERLAPPED |
    WS_CAPTION |
    WS_SYSMENU |
    WS_THICKFRAME |
    WS_MINIMIZEBOX |
    WS_MAXIMIZEBOX
)

# The window is a pop-up window. The WS_CAPTION and WS_POPUPWINDOW styles
# must be combined to make the window menu visible.
WS_POPUPWINDOW = (
    WS_POPUP |
    WS_BORDER |
    WS_SYSMENU
)

# The window is initially visible. This style can be turned on and off by
# using the ShowWindow or SetWindowPos function.
WS_VISIBLE = 0x10000000

# The window has a vertical scroll bar.
WS_VSCROLL = 0x00200000

# ******* Extended Styles

# The window accepts drag-drop files.
WS_EX_ACCEPTFILES = 0x00000010

# Forces a top-level window onto the taskbar when the window is visible.
WS_EX_APPWINDOW = 0x00040000

# The window has a border with a sunken edge.
WS_EX_CLIENTEDGE = 0x00000200

# Paints all descendants of a window in bottom-to-top painting order using
# double-buffering. For more information, see Remarks. This cannot be used if
# the window has a class style of either CS_OWNDC or CS_CLASSDC.
# Windows 2000: This style is not supported.
WS_EX_COMPOSITED = 0x02000000

# The title bar of the window includes a question mark. When the user clicks
# the question mark, the cursor changes to a question mark with a pointer.
# If the user then clicks a child window, the child receives a WM_HELP
# message. The child window should pass the message to the parent window
# procedure, which should call the WinHelp function using the HELP_WM_HELP
# command. The Help application displays a pop-up window that typically
# contains help for the child window. WS_EX_CONTEXTHELP cannot be used with
# the WS_MAXIMIZEBOX or WS_MINIMIZEBOX styles.
WS_EX_CONTEXTHELP = 0x00000400

# The window itself contains child windows that should take part in dialog
# box navigation. If this style is specified, the dialog manager recurses
# into children of this window when performing navigation operations such as
# handling the TAB key, an arrow key, or a keyboard mnemonic.
WS_EX_CONTROLPARENT = 0x00010000

# The window has a double border; the window can, optionally, be created with
# a title bar by specifying the WS_CAPTION style in the dwStyle parameter.
WS_EX_DLGMODALFRAME = 0x00000001

# The window is a layered window. This style cannot be used if the window has
# a class style of either CS_OWNDC or CS_CLASSDC. Windows 8: The WS_EX_LAYERED
# style is supported for top-level windows and child windows. Previous Windows
# versions support WS_EX_LAYERED only for top-level windows.
WS_EX_LAYERED = 0x00080000

# If the shell language is Hebrew, Arabic, or another language that supports
# reading order alignment, the horizontal origin of the window is on the right
# edge. Increasing horizontal values advance to the left.
WS_EX_LAYOUTRTL = 0x00400000

# The window has generic left-aligned properties. This is the default.
WS_EX_LEFT = 0x00000000

# If the shell language is Hebrew, Arabic, or another language that supports
# reading order alignment, the vertical scroll bar (if present) is to the left
# of the client area. For other languages, the style is ignored.
WS_EX_LEFTSCROLLBAR = 0x00004000

# The window text is displayed using left-to-right reading-order properties.
# This is the default.
WS_EX_LTRREADING = 0x00000000

# The window is a MDI child window.
WS_EX_MDICHILD = 0x00000040

# A top-level window created with this style does not become the foreground
# window when the user clicks it. The system does not bring this window to the
# foreground when the user minimizes or closes the foreground window. The
# window should not be activated through programmatic access or via keyboard
# navigation by accessible technology, such as Narrator. To activate the
# window, use the SetActiveWindow or SetForegroundWindow function. The window
# does not appear on the taskbar by default. To force the window to appear on
# the taskbar, use the WS_EX_APPWINDOW style.
WS_EX_NOACTIVATE = 0x08000000

# The window does not pass its window layout to its child windows.
WS_EX_NOINHERITLAYOUT = 0x00100000

# The child window created with this style does not send the WM_PARENTNOTIFY
# message to its parent window when it is created or destroyed.
WS_EX_NOPARENTNOTIFY = 0x00000004

# The window does not render to a redirection surface. This is for windows
# that do not have visible content or that use mechanisms other than surfaces
# to provide their visual.
WS_EX_NOREDIRECTIONBITMAP = 0x00200000

# The window has generic "right-aligned" properties. This depends on the
# window class. This style has an effect only if the shell language is Hebrew,
# Arabic, or another language that supports reading-order alignment;
# otherwise, the style is ignored. Using the WS_EX_RIGHT style for static or
# edit controls has the same effect as using the SS_RIGHT or ES_RIGHT style,
# respectively. Using this style with button controls has the same effect as
# using BS_RIGHT and BS_RIGHTBUTTON styles.
WS_EX_RIGHT = 0x00001000

# The vertical scroll bar (if present) is to the right of the client area.
# This is the default.
WS_EX_RIGHTSCROLLBAR = 0x00000000

# If the shell language is Hebrew, Arabic, or another language that supports
# reading-order alignment, the window text is displayed using right-to-left
# reading-order properties. For other languages, the style is ignored.
WS_EX_RTLREADING = 0x00002000

# The window has a three-dimensional border style intended to be used for
# items that do not accept user input.
WS_EX_STATICEDGE = 0x00020000

# The window is intended to be used as a floating toolbar. A tool window has
# a title bar that is shorter than a normal title bar, and the window title is
# drawn using a smaller font. A tool window does not appear in the taskbar or
# in the dialog that appears when the user presses ALT+TAB. If a tool window
# has a system menu, its icon is not displayed on the title bar. However, you
# can display the system menu by right-clicking or by typing ALT+SPACE.
WS_EX_TOOLWINDOW = 0x00000080

# The window should be placed above all non-topmost windows and should stay
# above them, even when the window is deactivated. To add or remove this
# style, use the SetWindowPos function.
WS_EX_TOPMOST = 0x00000008

# The window should not be painted until siblings beneath the window (that
# were created by the same thread) have been painted. The window appears
# transparent because the bits of underlying sibling windows have already
# been painted. To achieve transparency without these restrictions, use the
# SetWindowRgn function.
WS_EX_TRANSPARENT = 0x00000020

# The window has a border with a raised edge.
WS_EX_WINDOWEDGE = 0x00000100

# The window is an overlapped window.
WS_EX_OVERLAPPEDWINDOW = (
    WS_EX_WINDOWEDGE |
    WS_EX_CLIENTEDGE
)

# The window is palette window, which is a modeless dialog box that presents
# an array of commands.
WS_EX_PALETTEWINDOW = (
    WS_EX_WINDOWEDGE |
    WS_EX_TOOLWINDOW |
    WS_EX_TOPMOST
)

# LONG GetWindowLongW(
#   HWND hWnd,
#   int  nIndex
# );

_GetWindowLong = user32.GetWindowLongW
_GetWindowLong.restype = LONG


del eg.lastFoundWindows[:]
eg.plugins.Window.FindWindow(u\'mpc-hc64.exe\', None, u\'MediaPlayerClassicW\', u\'\', u\'msctls_trackbar32\', 1, True, 0.0, 0)
if not eg.lastFoundWindows:
   eg.StopMacro()
   eg.Exit()

# get the handle from the storage container.
hwnd = eg.lastFoundWindows[0]

style = _GetWindowLong(HWND(hwnd), GWL_STYLE)
style_ex = _GetWindowLong(HWND(hwnd), GWL_EXSTYLE)

if style == style | WS_ICONIC:
    eg.globals.WindowsState = \'Minimized\'
    
elif style == style | WS_MAXIMIZE:
    eg.globals.WindowsState = \'Maximized\'

elif (
    style == style | WS_BORDER and
    style == style | WS_DLGFRAME and
    style == style | WS_THICKFRAME and 
    style_ex == style_ex | WS_EX_WINDOWEDGE
):
    eg.globals.WindowsState = \'Normal\'
    print "Windows State = " + eg.globals.WindowsState
else:
    eg.globals.WindowsState = \'Fullscreen\'
    print "Windows State = " + eg.globals.WindowsState


----> Returns back to the "Show OSD" Event

Code: Select all

# this try/except routine is done because the information that is generated
# in a Python script is persistent between runs. Whats this means is that
# anything that is created in a python script when the script gets run a
# second, third, fourth... the objects created the first time are available to
# every run thereafter. So if you have any objects created that are static and
# do not change between runs then there is no need to make them over again.
# To test for this we simply use one of those object names that are static.
# if the object exists it will pass right on by. if it oes not it will
# generate an error that we catch in the except portion of thee code block.
# And that is where we will then create all of the static objects


# adjustable set between 0 and 255
MAX_TRANSPARENT = 90

import time

start = time.time()
try:
    user32
except NameError:
    import wx
    import datetime
    import time
    import threading
    import ctypes
    from ctypes.wintypes import (
        BOOL,
        LONG,
        UINT,
        POINT,
        RECT,
        HWND,
        LPARAM,
        DWORD
    )  # NOQA

    from eg.WinApi import (
        GetWindowText,
        GetTopLevelWindowList,
        GetProcessName
    )  # NOQA

    from eg.WinApi.Dynamic import (
        SetWindowPos,
        SWP_FRAMECHANGED,
        SWP_NOACTIVATE,
        SWP_NOOWNERZORDER,
        SWP_SHOWWINDOW,
    )


    HWND_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED

    GWL_STYLE = -16
    GWL_EXSTYLE = -20
    WS_BORDER = 0x00800000
    WS_DLGFRAME = 0x00400000
    WS_THICKFRAME = 0x00040000
    WS_EX_WINDOWEDGE = 0x00000100
    WS_POPUP = 0x80000000
    WS_EX_TOPMOST = 0x00000008

    PID = DWORD
    user32 = ctypes.windll.User32

    # DWORD GetWindowThreadProcessId(
    #   HWND    hWnd,
    #   LPDWORD lpdwProcessId
    # );
    _GetWindowThreadProcessId = user32.GetWindowThreadProcessId
    _GetWindowThreadProcessId.restype = DWORD

    # LONG GetWindowLongW(
    #   HWND hWnd,
    #   int  nIndex
    # );

    _GetWindowLong = user32.GetWindowLongW
    _GetWindowLong.restype = LONG

    # HWND GetActiveWindow(
    #
    # );
    _GetActiveWindow = user32.GetActiveWindow
    _GetActiveWindow.restype = HWND

    BAR_TEMPLATE = \'[{bars}{remaining}] - {percent:.2f}%\'
    ELAPSED_TOTAL_TEMPLATE = \'Elapsed: {elapsed} / Total: {duration}\'
    OSD_TEMPLATE = \'{title}\
 \
{bar}\
{elapsed_total}\'
    osd_frame = eg.plugins.EventGhost.actions[\'ShowOSD\'].osdFrame
    _old_show_osd = osd_frame.ShowOSD
    _old_on_timeout = osd_frame.OnTimeout

    eg.globals.fade_in_event = threading.Event()
    eg.globals.fade_out_event = threading.Event()


stop = time.time()

print \'imports and setup\', (stop - start) * 1000, \'milliseconds\'

start = time.time()
# OK so I removed the euse of the eg.WindowMatcher.
# it is a horribly slow thing to use and since we are only looking to see
# if a process is running or not we use GetTopLevelWindowList which returns
# the handles for the base window class of an application. then we ask windows
# to get us the process id (pid) for one of the handles in the returned list.
# Then we use that pid to get the process name. and we match that process name
# up with the one we are looking for.

handles = GetTopLevelWindowList(False)

stop = time.time()

print \'GetTopLevelWindowList\', (stop - start) * 1000, \'milliseconds\'
start = time.time()
# OK so I added some code to locate which instance of mpc to use if more then
# one is running.
# I elected to do some crafty coding with this. What happens is after the the
# window handles are found I then query the Windows API to get thee styles of
# the window. i check for specific markers like if the window has a border and
# if the window is set to be the topmost window as these are things that get
# turned on/off when an application goes into a fullscreen mode. at the same
# time I am also checking for a copy of mpc that is the active window. I do
# this to cover my bases, if there is more then a single active copy and none
# of them are set to full screen it is going to choose the one that is active
# (has focus). if there is a fullscreen copy running and a second. and the
# second is active the fullscreen is going to be the one used.

for handle in handles[:]:
    pid = PID()
    _GetWindowThreadProcessId(HWND(handle), ctypes.byref(pid))

    process_name = GetProcessName(pid.value)

    # we check the process name of the window. if it does not match then we
    # remove it form the list.
    if process_name != \'mpc-hc64.exe\':
        handles.remove(handle)

stop = time.time()

print \'_GetWindowThreadProcessId\', (stop - start) * 1000, \'milliseconds\'

# if there are no windows found then we want to stop the macro from running and
# exit the script

if not handles:
    handle = None
    eg.StopMacro()
    eg.Exit()


handle = None
# I removed the use of eg.globals.WindowsState as a mechanism for checking if
# the app is in fullscreen mode. Because this is one of the things we are
# looking for when we pick the window to use we no longer need to depend on
# an outside source to determine the window state. So you can remove that from
# your tree if you want.
is_fullscreen = False


start = time.time()
for hwnd in handles:
    # getting the windows styles and extended styles.
    style = _GetWindowLong(HWND(hwnd), GWL_STYLE)
    style_ex = _GetWindowLong(HWND(hwnd), GWL_EXSTYLE)

    # checking the styles for specific markers.
    if (
        not style &amp; WS_BORDER and
        not style &amp; WS_DLGFRAME and
        not style &amp; WS_THICKFRAME and
        not style_ex &amp; WS_EX_WINDOWEDGE and
        style &amp; WS_POPUP and
        style_ex &amp; WS_EX_TOPMOST
    ):
        # if there are 2 copies of mpc running and both are fullscreen then
        # we are going to use the one that is active
        if is_fullscreen is False:
            handle = hwnd
            is_fullscreen = True
        elif _GetActiveWindow() == hwnd:
            handle = hwnd
            is_fullscreen = True

    # if there is no full screen one found already and this window is active
    # then we seet it into place
    elif handle is None and _GetActiveWindow() == hwnd:
        handle = hwnd
        is_fullscreen = False

stop = time.time()

print \'is fullscreen\', (stop - start) * 1000, \'milliseconds\'


# fallback if something does not work properly in the code.
if handle is None:
    handle = handles[0]

start = time.time()
window_text = GetWindowText(handle)
stop = time.time()
print \'GetWindowText\', (stop - start) * 1000, \'milliseconds\'

# I removed the use of re and did a simple trial and error using a for loop
# with an else statement.
# If i split a string on a value and that value is not n the string it is
# going to return a list with the original string at index 0. So the list
# only las a length of 1. if it did have the value in it the list would have a
# length greater then 1. and we use that to break the loop. This will cause
# the else to not get run.

start = time.time()
for item in (".mkv", ".mp4", ".avi", ".ogm"):
    window_text = window_text.split(item)
    if len(window_text) &gt; 1:
        break
    window_text = window_text[0]
else:
    window_text = window_text.split(\'.\')

stop = time.time()

print \'windows text evaluation\', (stop - start) * 1000, \'milliseconds\'


title = window_text[0]


start = time.time()
_elapsed, _remaining, _duration = eg.plugins.MediaPlayerClassic.GetTimes()
# print "elaps, rem, total =",elaps, rem, total

# I removed all of that math involved in trying to get the seconds in
# favor of using time.strptime and datetime.timedelta
# time.strptime will take a string formatted time representation and turn it
# into a python object for us. we simply have to provide details as to how the
# string is formatted. you can get more information on the identifiers here
# http://strftime.org/
# once we have that object which only does the simple
# conversion to hours minutes seconds we need to create another object that
# does the match for us and will return the total seconds

stop = time.time()
print \'eg.plugins.MediaPlayerClassic.GetTimes\', (stop - start) * 1000, \'milliseconds\'

start = time.time()
while _elapsed.count(\':\') &lt; 2:
    _elapsed = \'00:\' + _elapsed
while _remaining.count(\':\') &lt; 2:
    _remaining = \'00:\' + _remaining
while _duration.count(\':\') &lt; 2:
    _duration = \'00:\' + _duration

elapsed = time.strptime(_elapsed, \'%H:%M:%S\')
elapsed = datetime.timedelta(
    hours=elapsed.tm_hour,
    minutes=elapsed.tm_min,
    seconds=elapsed.tm_sec
)

remaining = time.strptime(_remaining, \'%H:%M:%S\')
remaining = datetime.timedelta(
    hours=remaining.tm_hour,
    minutes=remaining.tm_min,
    seconds=remaining.tm_sec
)

duration = time.strptime(_duration, \'%H:%M:%S\')
duration = datetime.timedelta(
    hours=duration.tm_hour,
    minutes=duration.tm_min,
    seconds=duration.tm_sec
)

stop = time.time()
print \'time evaluation\', (stop - start) * 1000, \'milliseconds\'

# heere we do a test to see if elapsed has any seconds to it. if iit does
# that means the video is playing and to generate the osd
if elapsed.total_seconds():
    def fade_in():
        beg = time.time()
        for i in range(MAX_TRANSPARENT):
            if eg.globals.fade_in_event.isSet():
                break
            osd_frame.SetTransparent(i)
            eg.globals.fade_in_event.wait(0.01)

        osd_frame.ShowOSD = _old_show_osd
        end = time.time()

        print \'fade in\', (end - beg) * 1000, \'milliseconds\'


    def fade_out():
        beg = time.time()

        for i in range(MAX_TRANSPARENT, 0, -1):
            if eg.globals.fade_out_event.isSet():
                break
            osd_frame.SetTransparent(i)
            eg.globals.fade_out_event.wait(0.01)

        _old_on_timeout()
        osd_frame.OnTimeout = _old_on_timeout
        end = time.time()

        print \'fade out\', (end - beg) * 1000, \'milliseconds\'

    def show_osd(*args, **kwargs):
        args = list(args)
        args[7] += MAX_TRANSPARENT * 0.01

        eg.globals.fade_out_event.set()
        eg.globals.fade_in_event.clear()

        osd_frame.SetTransparent(0)
        _old_show_osd(*args, **kwargs)

        fade_in()

    def on_timeout():
        eg.globals.fade_in_event.set()
        eg.globals.fade_out_event.clear()
        fade_out()


    osd_frame.ShowOSD = show_osd
    osd_frame.OnTimeout = on_timeout

    # instead of having to calls to ShowOSD we can set the parameters we want
    # to pass to ShowOSD into a variable you name the variable the same for
    # Fullscreen and not Fullscreen and set the parameters for each variation
    # then when we call ShowOSD the proper set of parameters will be passed
    if is_fullscreen:
        osd_args = (
            u\'0;-82;0;0;0;700;255;0;0;0;3;2;1;34;Consolas\',
            (0, 255, 255),
            (0, 0, 0),
            4,
            (0, 0),
            1,
            3.0,
            None
        )
    else:
        osd_args = (
            u\'0;-030;0;0;0;700;255;0;0;0;3;2;1;34;Consolas\',
            (0, 255, 255),
            (0, 0, 0),
            4,
            (0, 0),
            2,
            3.0,
            None
        )
        
    start = time.time()

    # I trimmed down the percent math. iif you want to change an int to a
    # float you do not need to do 1.0 * int. this will work but the pythonic
    # way is to wrap that int with float()
    percent = (
        (
            float(elapsed.total_seconds()) /
            float(duration.total_seconds())
        ) * 100.0
    )
    # I removed the conversion of the percent form a float to an int because
    # i wanted the percent as a float for use further on down the line

    bars = \'/\' * int(round(percent))
    remaining = \'.\' * (100 - len(bars))

    # I created string templates for the OSD. i split the osd into 3 sections
    # I will explain as to why they are split into 3 sections further down
    # I added the percent that has elapsed to the bar line as a 2 decimal place
    # float

    bar = BAR_TEMPLATE.format(
        bars=bars,
        remaining=remaining,
        percent=percent
    )

    elapsed_total = ELAPSED_TOTAL_TEMPLATE.format(
        elapsed=_elapsed,
        duration=_duration
    )

    # OK so this is an odd thing. wx is thee GUI interface we use to generate
    # all of the graphics, windows and controls in EG. because of how text
    # gets displayed when using a GUI the characters defined in a font are
    # not of equal size, so a "G" does not have the same width as an "!"
    # I saw that you were displaying the OSD n the center of the screen with
    # what looked like center justification. because of that font issue ths
    # is extremely difficult to do. Portions of the wx library allow us to call
    # a function called GetTextExtent this function rill return the width and
    # height in pixels of a string passed into it. we have to set the font we
    # want to use then call the function. in order to get the center
    # justification as close as we can and the space is the only thing we can
    # really do it with we also want to know the width of a space.

    stop = time.time()
    print \'building the bar\', (stop - start) * 1000, \'milliseconds\'

    start = time.time()
    frame = wx.Frame(None, -1)
    font = wx.FontFromNativeInfoString(osd_args[0])
    frame.SetFont(font)
    bar_len = frame.GetTextExtent(bar)[0]
    title_len = frame.GetTextExtent(title)[0]
    elapsed_total_len = frame.GetTextExtent(elapsed_total)[0]
    space_len = frame.GetTextExtent(\' \')[0]

    # so here we do the checking to see which line is longer then the other to
    # adjust the one that is shorter
    if bar_len &lt; title_len:
        # this is the number of spaces we are going to need. so we find out the
        # pixel difference divide that by the number of pixels a space is.
        # this is going to return the total number of spaces needed to make the
        # line the same length. Now remember center justification. so half of
        # those spaces would need to be on thee back end of the line. Those
        # we do not need to add to the line. so wee divide the total number of
        # spaces by 2 to get the number of spaces we need to add to the front
        # of the line

        space_count = (
            int(round(float(title_len - bar_len) / float(space_len))) / 2
        )
        bar = \' \' * space_count + bar

    elif title_len &lt; bar_len:
        space_count = (
            int(round(float(bar_len - title_len) / float(space_len))) / 2
        )
        title = \' \' * space_count + title

    bar_len = frame.GetTextExtent(bar)[0]

    frame.Destroy()

    if elapsed_total_len &lt; bar_len:
        space_count = (
            int(round(
                float(bar_len - elapsed_total_len) / float(space_len))) / 2
        )
        elapsed_total = \' \' * space_count + elapsed_total

    osd = OSD_TEMPLATE.format(
        title=title,
        bar=bar,
        elapsed_total=elapsed_total
    )
    # and here is the single call to ShowOSD. we prefiix that variable we made
    # earlier with a * which expands the variable into the parameters that
    # needs to be passed
    stop = time.time()
    print \'calculating bar position on the screen\', (stop - start) * 1000, \'milliseconds\'

    eg.plugins.EventGhost.ShowOSD(osd, *osd_args)

User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Sun Oct 06, 2019 6:16 am

OK so take all of those scripts and remove them from your tree. Save them to a text file. They have to be removed as they may cause some issues with the new script. I am fairly certain you are no longer going to need them but save them any way.

Here is a new script.. DO NOT EDIT THIS SCRIPT.. I have not tested this script at all. So I would like to have you run it as is so i can work out the kinks in it. This is a complex bugger But you are only going to have to run it a single time. So Place it in you Autostart group. THIS IS IMPORTANT! move the MPC plugin to the top of the Autostart Group and move this script to directly after it. You need to keep this script right after the MPC plugin and you need to also keep it at the top of Autostart. This is because of how EG works when loading the tree and we want to ensure that components that need to get overridden do so before anything else can use them.


READ THE COMMENTS AT THE TOP OF THE SCRIPT

The comments at the top explain what this script is going to do and also how to use it. There are a plethora of new variables you can set so please read the comments. they will explain most of them. others you can figure out by the name.

If you happen to find a bug and get a traceback you need to paste the entire error to me. If it is a simple issue and you took care of it yourself then post the script letting me know what you changed and why. I want to make sure that we are both working on the same code.

Lets get this thing 100% bug free.. and then you can modify it to your hearts content.. It is going to be far easier this way. If there is a change you want to make afterwords and are unsure of how to go about it.. ask me.. I will tell you if it can be done and where to make the change.

There is no more hunting and pecking for window styles to see if the thing is maximized or full screen. that code has been removed.. It is explained in the comments.

I know this is not going to run out of the box , and it is going to have some errors. So what you may want to do is create a new eg save file. install the MPC plugin and add this script to that tree. then you can simply change files when i have made an update and if it is not working load the other save file and you are back to your original tree.. This is going to be easier then having to delete and make Python Script actions for your old scripts,

Disabling the scripts is not going to stop them from being compiled. They cannot be in the same tree as this script.

Code: Select all



# ********************** READ ME VERY IMPORTANT **************************

# OK this is a redesign of the entire script you had.. Everything is now handled in this single script.
# This script needs to be placed directly after the MPC plugin in the Autostart group.
# This is a run once and forget it script. You can tell the script to show one of 2 OSD's if you add one or both of the 
# line below to a python script you will be able to display the OSD. If and only if MPC is running.

# This script sorts out what screen to show the OSD on. If you have more then a single copy of MPC running then it is 
# going to use the one that has focus. If there aren't any that have focus then it is going to choose one at random.
# the size of the text that is getting displayed is going to depend on the width of the monitor that it is showing on.
# It is going to provide the largest text it is capable of displaying.

# This script is gong to provide automatic volume OSD display if you change the volume in MPC, You do not need to set 
# up a macro to tell it to display the volume when you press a button on a remote. It is going to do it all by it's 
# self. If the OSD is already active it is going to refresh the current displayed OSD with the new volume level and 
# reset the timer that closes it.

# There is also going to be an automatic display of the metadata OSD as well. This is going to occur if you change 
# the media. and the same applies as above for the rest.


# Here are the commands you can use if you want to show the OSD manually. 
# eg.globals.OSD.show_volume()
# eg.globals.OSD.show_metadata()

# you can also display either of the OSD's or both of them whenever you want. This script is also going to check and
# see if the EG OSD is being displayed and if one is being displayed and conflicts with the monitor and position of 
# either of the OSD's in this script the conflicting OSD will be closed to allow the EG one to be displayed.

# If there is an EG OSD that is being displayed that conflicts with the showing of either os the OSD's in this script 
# the EG OSD is going to get closed.

# So now you have the ability to have multiple OSD's being displayed at the same time so long as the OSD's do not have 
# the same monitor and OSD position. 

import eg

# this is a global transparency. It is different then the 4th number in the colors. 
# this sets the transparency for everything.
MAX_TRANSPARENT = 255

# These variable control how fast the fade in and out speed is.. the lower the number the faster it is.
# the number is how long to wait between fade steps. The number of fade steps is the MAX_TRANSPARENT variable
# example:
#    255 * 0.01 = 2.25 seconds the fade in or out is going to run.

# there is a single exception to this. If an OSD is already showing the fade out is going to be sped up
# by a factor of 3.
# example:
#    (255 * 0.01) / 3 =  0.85 seconds

FADE_IN_SPEED = 0.01
FADE_OUT_SPEED = 0.01

# font settings
# '0;  -30; 0; 0; 0;    700;   255;          0;              0; 0; 3; 2; 1;     34;   Verdana'
#  ?; size; ?; ?; ?; weight; italics; underlined; strike through; ?; ?; ?; ?; family; face name
# weight: 0 - 1000 number  with 400 being normal
# italics: 0 - 255 number with 0 being none
# underlined: 1 is yes and 0 is no
# strike through: 1 is yes and 0 is no
# family:is the font family. It is tied to the face name
# face name: name of the font

FONT_FACE_NAME = 'Verdana'
FONT_FAMILY = 34
FONT_STRIKE_THROUGH = 0
FONT_UNDERLINE = 0
FONT_ITALIC = 0
FONT_WEIGHT = 400

# next is the text position
# 0 = Top Left
# 1 = Top Right
# 2 = Bottom Left
# 3 = Bottom Right
# 4 = Center
# 5 = Bottom Center
# 6 = Top Center
# 7 = Left Center
# 8 = Right Center

METADATA_TEXT_POSITION = 6
VOLUME_TEXT_POSITION = 5

# these are the characters that are used for the volume bar and for the duration bar
VOLUME_LEVEL_CHARACTER = '|'
VOLUME_EMPTY_CHARACTER = '.'

# how long to keep the OSD's on the screen for
VOLUME_OSD_TIMEOUT = 3.0
METADATA_OSD_TIMEOUT = 5.0

# this is a monitor override. the OSD is going to display on the screen that MPC is being displayed on
# if you leave this set to None
MONITOR = None

# text colors (red, green, blue, transparency) 0 - 255 for each
OSD_TEXT_COLOR = (0, 255, 0, 255)
OSD_OUTLINE_COLOR =  (0, 0, 0, 255)

# border around the osd
OSD_BORDER_COLOR = (0, 255, 0)
OSD_BORDER_WIDTH = 3
OSD_BRODER_CORNER_RADIUS = 3

# osd background color
OSD_BACKGROUND_COLOR = (0, 0, 0, 120)

import wx
import datetime
import time
import threading
import ctypes
    
from eg.WinApi.Utils import GetMonitorDimensions
    
from eg.WinApi.Dynamic import (
    SetWindowPos,
    SWP_FRAMECHANGED,
    SWP_HIDEWINDOW,
    SWP_NOACTIVATE,
    SWP_NOOWNERZORDER,
    SWP_SHOWWINDOW,
    SendMessage
)
from ctypes.wintypes import (
    BOOL,
    LONG,
    UINT,
    POINT,
    RECT,
    HWND,
    DWORD
)  # NOQA
from eg.WinApi import (
    GetWindowText,
    GetTopLevelWindowList,
    GetProcessName
)  # NOQA

HWND_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED

GWL_STYLE = -16
GWL_EXSTYLE = -20

WS_BORDER = 0x00800000
WS_DLGFRAME = 0x00400000
WS_THICKFRAME = 0x00040000
WS_EX_WINDOWEDGE = 0x00000100
WS_POPUP = 0x80000000
WS_EX_TOPMOST = 0x00000008

PID = DWORD
TBM_GETPOS = 1024

FONT_INFO_TEMPLATE = '0;{{size}};0;0;0;{weight};{italic};{underline};{strike};0;3;2;1;{family};{face}'
FONT_INFO_TEMPLATE = FONT_INFO_TEMPLATE.format(
    weight=FONT_WEIGHT,
    italic=FONT_ITALIC,
    underline=FONT_UNDERLINE,
    strike=FONT_STRIKE_THROUGH,
    family=FONT_FAMILY,
    face=FONT_FACE_NAME
)



ELAPSED_TOTAL_TEMPLATE = '[{bars}{remaining}] - Elapsed: {elapsed}  Remaining: {duration}'
OSD_TEMPLATE = '{title}\n{elapsed_total}'
VOLUME_TEMPLATE = 'Volume: {bars}{remaining} - {percent:.2f}%'


user32 = ctypes.windll.User32


class tagWINDOWPLACEMENT(ctypes.Structure):
    _fields_ = [
        ('length', UINT),
        ('flags', UINT),
        ('showCmd', UINT),
        ('ptMinPosition', POINT),
        ('ptMaxPosition', POINT),
        ('rcNormalPosition', RECT),
        ('rcDevice', RECT)
    ]


WINDOWPLACEMENT = tagWINDOWPLACEMENT

# BOOL GetWindowPlacement(
#   HWND            hWnd,
#   WINDOWPLACEMENT *lpwndpl
# );
_GetWindowPlacement = user32.GetWindowPlacement
_GetWindowPlacement.restype = BOOL

# DWORD GetWindowThreadProcessId(
#   HWND    hWnd,
#   LPDWORD lpdwProcessId
# );
_GetWindowThreadProcessId = user32.GetWindowThreadProcessId
_GetWindowThreadProcessId.restype = DWORD

# LONG GetWindowLongW(
#   HWND hWnd,
#   int  nIndex
# );

_GetWindowLong = user32.GetWindowLongW
_GetWindowLong.restype = LONG

# HWND GetActiveWindow(
#
# );
_GetActiveWindow = user32.GetActiveWindow
_GetActiveWindow.restype = HWND


from eg.CorePluginModule.EventGhost import ShowOSD

_show_osd = ShowOSD.OSDFrame.ShowOSD


volume_osd_conflict = []
metadata_osd_conflict = []

def show_osd(
    self,
    osdText="",
    fontInfo=None,
    textColour=(255, 255, 255),
    outlineColour=(0, 0, 0),
    alignment=0,
    offset=(0, 0),
    displayNumber=0,
    timeout=3.0,
    event=None,
    skin=None,
):
    
    if self in volume_osd_conflict:
        volume_osd_conflict.remove(self)
        
    if self in metadata_osd_conflict:
        metadata_osd_conflict.remove(self)
    
    if alignment == VOLUME_TEXT_POSITION and displayNumber == volume_osd.monitor:
        volume_osd.timer.cancel()
        volume_osd_conflict.append(self)
        
    if alignment == METADATA_TEXT_POSITION and displayNumber == metadata_osd.monitor:
        metadata_osd.timer.cancel()
        metadata_osd_conflict.append(self)
        
    _show_osd(
        self, 
        osdText, 
        fontInfo, 
        textColour, 
        outlineColour, 
        alignment, 
        offset, 
        displayNumber, 
        timeout, 
        event, 
        skin
    )
        
ShowOSD.OSDFrame.ShowOSD = show_osd

class OSDFrame(wx.Frame):
    """
    A shaped frame to display the OSD.
    """

    @eg.LogIt
    def __init__(self, parent):
        wx.Frame.__init__(
            self,
            parent,
            -1,
            "OSD Window",
            size=(0, 0),
            style=(
                    wx.FRAME_SHAPED |
                    wx.NO_BORDER |
                    wx.FRAME_NO_TASKBAR |
                    wx.STAY_ON_TOP
            )
        )
        self.fade_in_event = threading.Event()
        self.fade_out_event = threading.Event()

        self.hwnd = self.GetHandle()
        self.bitmap = wx.EmptyBitmap(0, 0)
        
        self.is_shown = False

        # we need a timer to possibly cancel it

        def dummy_func(_):
            pass

        self.timer = Timer(0.0, dummy_func)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, dummyEvent=None):
        pass

    def OnPaint(self, dummyEvent=None):
        wx.BufferedPaintDC(self, self.bitmap)

    def OnTimeout(self, canceled):
        if canceled:
            self.fade_out(FADE_OUT_SPEED / 3)
        else:
            self.fade_out(FADE_OUT_SPEED)

    def fade_in(self, x, y, width, height):
        self.is_shown = True
        SetWindowPos(self.hwnd, 0, x, y, width, height, HWND_FLAGS | SWP_SHOWWINDOW)

        for i in range(MAX_TRANSPARENT):
            if self.fade_in_event.is_set():
                break
            self.SetTransparent(i)
            self.fade_in_event.wait(FADE_IN_SPEED)

    def fade_out(self, duration):
        for i in range(MAX_TRANSPARENT, 0, -1):
            if self.fade_out_event.is_set():
                break
            self.SetTransparent(i)
            self.fade_out_event.wait(duration)

        self.is_shown = False
        SetWindowPos(self.hwnd, 0, 0, 0, 0, 0, HWND_FLAGS | SWP_HIDEWINDOW)

    def ShowOSD(
            self,
            osdText,
            alignment,
            monitor,
            timeout
    ): 
        self.timer.pause()
        if osdText.strip() == "":
            self.bitmap = wx.EmptyBitmap(0, 0)
            SetWindowPos(self.hwnd, 0, 0, 0, 0, 0, HWND_FLAGS | SWP_HIDEWINDOW)
            return

        memoryDC = wx.MemoryDC()
        gc = wx.GCDC(memoryDC)

        forbiddenColours = (OSD_TEXT_COLOR, OSD_OUTLINE_COLOR)
        maskColour = (255, 0, 255)
        if maskColour in forbiddenColours:
            maskColour = (0, 0, 2)
            if maskColour in forbiddenColours:
                maskColour = (0, 0, 3)
        maskBrush = wx.Brush(maskColour, wx.SOLID)
        gc.SetBackground(maskBrush)

        font = self.GetFont()
        gc.SetFont(font)

        textLines = osdText.splitlines()
        sizes = [memoryDC.GetTextExtent(line or " ") for line in textLines]
        textWidths, textHeights = zip(*sizes)
        textWidth = max(textWidths)
        textHeight = sum(textHeights)

        if OSD_OUTLINE_COLOR is None:
            width, height = textWidth, textHeight
            bitmap = wx.EmptyBitmapRGBA(width, height)
            memoryDC.SelectObject(bitmap)

            gc.Clear()

            gc.SetTextForeground(wx.Colour(*OSD_TEXT_COLOR))
            DrawTextLines(gc, textLines, textHeights)

            memoryDC.SelectObject(wx.NullBitmap)
            bitmap.SetMask(wx.Mask(bitmap, maskColour))

            gc.SetBackground(wx.Brush(wx.Colour(*OSD_TEXT_COLOR), wx.SOLID))
            memoryDC.SelectObject(bitmap)
            memoryDC.Clear()
            memoryDC.SelectObject(wx.NullBitmap)
        else:
            width, height = textWidth + 5, textHeight + 5
            outlineBitmap = wx.EmptyBitmapRGBA(width, height, 1)
            outlineDC = wx.MemoryDC()
            outline_gc = wx.GCDC(outlineDC)
            outline_gc.SetFont(font)
            outlineDC.SelectObject(outlineBitmap)
            outline_gc.Clear()
            outline_gc.SetBackgroundMode(wx.SOLID)
            DrawTextLines(outline_gc, textLines, textHeights)

            outlineDC.SelectObject(wx.NullBitmap)
            outlineBitmap.SetMask(wx.Mask(outlineBitmap))
            outlineDC.SelectObject(outlineBitmap)

            bitmap = wx.EmptyBitmapRGBA(width, height)
            gc.SetTextForeground(wx.Colour(OSD_OUTLINE_COLOR))
            memoryDC.SelectObject(bitmap)
            gc.Clear()

            Blit = gc.Blit
            logicalFunc = wx.COPY
            for x in xrange(5):
                for y in xrange(5):
                    Blit(
                        x, y, width, height, outline_gc, 0, 0, logicalFunc, True
                    )

            outlineDC.SelectObject(wx.NullBitmap)
            gc.SetTextForeground(OSD_TEXT_COLOR)
            DrawTextLines(gc, textLines, textHeights, 2, 2)
            memoryDC.SelectObject(wx.NullBitmap)
            bitmap.SetMask(wx.Mask(bitmap, maskColour))

            outline_gc.Destroy()
            del outline_gc
            outlineDC.Destroy()
            del outlineDC

        monitorDimensions = GetMonitorDimensions()
        try:
            displayRect = monitorDimensions[monitor]
        except IndexError:
            displayRect = monitorDimensions[0]

        xFunc, yFunc = ALIGNMENT_FUNCS[alignment]

        x = displayRect.x + xFunc((displayRect.width - width), 0)
        y = displayRect.y + yFunc((displayRect.height - height), 0)

        if OSD_BACKGROUND_COLOR is not None:
            gc.SetBrush(wx.Brush(wx.Colour(*OSD_BACKGROUND_COLOR)))
        else:
            gc.SetBrush(wx.TRANSPARENT_BRUSH)

        if OSD_BORDER_COLOR is not None and OSD_BORDER_WIDTH:
            gc.SetPen(wx.Pen(wx.Colour(*OSD_BORDER_COLOR), OSD_BORDER_WIDTH))

            last_bitmap = wx.EmptyBitmapRGBA(width, height)
            memoryDC.SelectObject(last_bitmap)

            if OSD_BRODER_CORNER_RADIUS:
                gc.DrawRoundedRectangle(0, 0, width, height, OSD_BRODER_CORNER_RADIUS)
            else:
                gc.DrawRectangle(0, 0, width, height)

            gc.DrawBitmap(bitmap, 0, 0)

            memoryDC.SelectObject(wx.NullBitmap)
            last_bitmap.SetMask(wx.Mask(last_bitmap))

            bitmap = last_bitmap

        elif OSD_BACKGROUND_COLOR:
            gc.SetPen(wx.TRANSPARENT_PEN)

            last_bitmap = wx.EmptyBitmapRGBA(width, height)
            memoryDC.SelectObject(last_bitmap)

            if OSD_BRODER_CORNER_RADIUS:
                gc.DrawRoundedRectangle(0, 0, width, height, OSD_BRODER_CORNER_RADIUS)
            else:
                gc.DrawRectangle(0, 0, width, height)

            gc.DrawBitmap(bitmap, 0, 0)

            memoryDC.SelectObject(wx.NullBitmap)
            last_bitmap.SetMask(wx.Mask(last_bitmap))

            bitmap = last_bitmap

        region = wx.RegionFromBitmap(bitmap)
        self.SetShape(region)
        self.bitmap = bitmap

        deviceContext = wx.ClientDC(self)
        device_gc = wx.GCDC(deviceContext)

        device_gc.DrawBitmap(self.bitmap, 0, 0, False)

        device_gc.Destroy()
        del device_gc
        
        if self.is_shown:
            self.timer.restart() 
        
        else:
            self.fade_in(x, y, width, height)

            if timeout > 0.0:
                self.timer = Timer(timeout, self.OnTimeout)
                self.timer.start()


class Timer(threading.Thread):
    def __init__(self, duration, func):
        self.event = threading.Event()
        self.pause_event = threading.Event()        
        self.duration = duration
        self.func = func
        
        threading.Thread.__init__(self)
        
    def pause(self):
        self.pause_event.set()
        
    def run_func(self, cancel):

        event = threading.Event()

        def func_wrapper(c):
            self.func(c)
            event.set()

        wx.CallAfter(func_wrapper, cancel)
        event.wait()

    def run(self):
        while not self.event.is_set():
            self.event.wait(self.duration)
            
            if not self.event.is_set():            
                if self.pause_event.is_set():
                    self.pause_event.clear()
                    self.pause_event.wait()
                    self.pause_event.clear()
                else:
                    self.run_func(False)
                    self.event.set()
                
    def restart(self):
        self.pause_event.set()
        
    def cancel(self):
        if not self.event.is_set():
            self.event.set()
            self.pause_event.set()
            self.run_func(True)


def AlignLeft(width, offset):
    return offset


def AlignCenter(width, offset):
    return (width / 2) + offset


def AlignRight(width, offset):
    return width - offset


ALIGNMENT_FUNCS = (
    (AlignLeft, AlignLeft),  # Top Left
    (AlignRight, AlignLeft),  # Top Right
    (AlignLeft, AlignRight),  # Bottom Left
    (AlignRight, AlignRight),  # Bottom Right
    (AlignCenter, AlignCenter),  # Screen Center
    (AlignCenter, AlignRight),  # Bottom Center
    (AlignCenter, AlignLeft),  # Top Center
    (AlignLeft, AlignCenter),  # Left Center
    (AlignRight, AlignCenter),  # Right Center
)


def DrawTextLines(deviceContext, textLines, textHeights, xOffset=0, yOffset=0):
    for i, textLine in enumerate(textLines):
        deviceContext.DrawText(textLine, xOffset, yOffset)
        yOffset += textHeights[i]


class MetaDataOSDFrame(OSDFrame):

    def __init__(self):
        OSDFrame.__init__(self, None)
        self.title = ''
        self.monitor = None

    def update_title(self, mpc_handle, monitor):
        self.monitor = monitor
        
        if mpc_handle is None:
            self.title = ''
            return

        window_text = GetWindowText(mpc_handle)

        for item in (".mkv", ".mp4", ".avi", ".ogm"):
            window_text = window_text.split(item)
            if len(window_text) > 1:
                break
            window_text = window_text[0]
        else:
            window_text = window_text.split('.')

        title = window_text[0]

        if title != self.title:
            self.title = title
            self.build_osd(monitor)

    def build_osd(self, monitor):
        for osd_frame in metadata_osd_conflict:
            try:
                osd_frame.timer.cancel()
                osd_frame.OnTimeout()
            except:
                pass

        _elapsed, _remaining, _duration = eg.plugins.MediaPlayerClassic.GetTimes()

        while _elapsed.count(':') < 2:
            _elapsed = '00:' + _elapsed
        while _remaining.count(':') < 2:
            _remaining = '00:' + _remaining
        while _duration.count(':') < 2:
            _duration = '00:' + _duration

        elapsed = time.strptime(_elapsed, '%H:%M:%S')
        elapsed = datetime.timedelta(
            hours=elapsed.tm_hour,
            minutes=elapsed.tm_min,
            seconds=elapsed.tm_sec
        )

        remaining = time.strptime(_remaining, '%H:%M:%S')
        remaining = datetime.timedelta(
            hours=remaining.tm_hour,
            minutes=remaining.tm_min,
            seconds=remaining.tm_sec
        )

        duration = time.strptime(_duration, '%H:%M:%S')
        duration = datetime.timedelta(
            hours=duration.tm_hour,
            minutes=duration.tm_min,
            seconds=duration.tm_sec
        )

        percent = (
                (float(elapsed.total_seconds()) / float(duration.total_seconds())) * 100.0
        )
        # I removed the conversion of the percent form a float to an int because
        # i wanted the percent as a float for use further on down the line

        bars = VOLUME_LEVEL_CHARACTER * int(round(percent))
        remaining = VOLUME_EMPTY_CHARACTER * (100 - len(bars))

        # I created string templates for the OSD. i split the osd into 3 sections
        # I will explain as to why they are split into 3 sections further down
        # I added the percent that has elapsed to the bar line as a 2 decimal place
        # float

        elapsed_total = ELAPSED_TOTAL_TEMPLATE.format(
            bars=bars,
            remaining=remaining,
            elapsed=_elapsed,
            duration=_duration
        )

        osd = OSD_TEMPLATE.format(
            title=self.title,
            elapsed_total=elapsed_total
        )

        tmp_osd = ELAPSED_TOTAL_TEMPLATE.format(
            bars=VOLUME_LEVEL_CHARACTER * 100,
            remaining='',
            elapsed=elapsed,
            duration=duration
        )

        mon_w = GetMonitorDimensions()[monitor][2]
        font = wx.FontFromNativeInfoString(FONT_INFO_TEMPLATE.format(size=-128))

        while self.GetFullTextExtent(tmp_osd)[0] > mon_w:
            font.SetPointSize(font.GetPointSize() + 1)

        self.SetFont(font)

        wx.CallAfter(
            self.ShowOSD,
            osd,
            METADATA_TEXT_POSITION,
            monitor,
            METADATA_OSD_TIMEOUT
        )


class VolumeOSDFrame(OSDFrame):

    def __init__(self):

        OSDFrame.__init__(self, None)
        self.volume = 0.0
        self.monitor = None

    def update_volume(self, mpc_handle, monitor):
        self.monitor = monitor
        
        if mpc_handle is None:
            self.volume = 0.0
            return

        volume = SendMessage(mpc_handle, TBM_GETPOS, 0, 0)

        if volume != self.volume:
            self.volume = volume

            self.build_osd(monitor)

    def build_osd(self, monitor):
        for osd_frame in volume_osd_conflict:
            try:
                osd_frame.timer.cancel()
                osd_frame.OnTimeout()
            except:
                pass
        
        bars = VOLUME_LEVEL_CHARACTER * int(self.volume)
        remaining = VOLUME_EMPTY_CHARACTER * (100 - int(self.volume))

        osd = VOLUME_TEMPLATE.format(bars=bars, remaining=remaining, percent=float(self.volume))

        tmp_osd = VOLUME_TEMPLATE.format(
            bars=VOLUME_LEVEL_CHARACTER * 100,
            remaining='',
            percent=100.0
        )

        mon_w = GetMonitorDimensions()[monitor][2]
        font = wx.FontFromNativeInfoString(FONT_INFO_TEMPLATE.format(size=-128))

        while self.GetFullTextExtent(tmp_osd)[0] > mon_w:
            font.SetPointSize(font.GetPointSize() + 1)

        self.SetFont(font)

        wx.CallAfter(
            self.ShowOSD,
            osd,
            VOLUME_TEXT_POSITION,
            monitor,
            VOLUME_OSD_TIMEOUT
        )

metadata_osd = None
volume_osd = None

thread_event = threading.Event()
show_volume = False
show_metadata = False

show_event = threading.Event()


def thread_loop():
    global show_volume
    global show_metadata

    while not thread_event.is_set():
        hwnd = None
        handles = GetTopLevelWindowList(False)

        for handle in handles[:]:
            pid = PID()
            _GetWindowThreadProcessId(HWND(handle), ctypes.byref(pid))

            process_name = GetProcessName(pid.value)

            # we check the process name of the window. if it does not match then we
            # remove it form the list.
            if process_name != 'mpc-hc64.exe':
                continue

            if _GetActiveWindow() == handle:
                hwnd = handle
                break
            else:
                hwnd = handle
        else:
            if hwnd is None:
                show_volume = False
                show_metadata = False
                metadata_osd.update_title(None, None)
                volume_osd.update_volume(None, None)
                thread_event.wait(0.2)
                continue

        placement = WINDOWPLACEMENT()

        if not _GetWindowPlacement(HWND(hwnd), ctypes.byref(placement)):
            raise ctypes.WinError()

        position = placement.rcNormalPosition

        if MONITOR is None:
            for mon_num, (start_x, start_y, mon_width, mon_height) in enumerate(GetMonitorDimensions()):

                end_x = start_x + mon_width
                end_y = start_y + mon_height

                if (
                        start_x < position.x < end_x and
                        start_y < position.y < end_y
                ):
                    break
            else:
                mon_num = 0
        else:
            mon_num = MONITOR

        metadata_osd.update_title(hwnd, mon_num)
        volume_osd.update_volume(hwnd, mon_num)

        if show_volume or show_metadata:
            if show_volume:
                volume_osd.build_osd(mon_num)

            if show_metadata:
                metadata_osd.build_osd(mon_num)
                
            show_event.set()

        thread_event.wait(0.2)


thread = threading.Thread(target=thread_loop)
thread.daemon = True
thread.start()


wait_event = threading.Event()

def MakeOSD():
    global metadata_osd
    global volume_osd

    metadata_osd = MetaDataOSDFrame()
    volume_osd = VolumeOSDFrame()

    def CloseOSD():
        thread_event.set()
        thread.join()
        volume_osd.timer.cancel()
        volume_osd.Close()

        metadata_osd.timer.cancel()
        metadata_osd.Close()

    eg.app.onExitFuncs.append(CloseOSD)

    wait_event.set()

wx.CallAfter(MakeOSD)
wait_event.wait()


class OSD:
    @staticmethod
    def show_volume():
        global show_volume
        show_event.clear()
        show_volume = True
        show_event.wait()
        show_volume = False
        
    @staticmethod
    def show_metadata():
        global show_metadata
        show_event.clear()
        show_metadata = True
        show_event.wait()
        show_metadata = False


eg.globals.OSD = OSD

If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Mon Oct 07, 2019 5:30 am

Ok done.. Created a new script and deleted all extra features on it for now so no other apps controlled by the script as well as deleted the code for the screen toggles right now like maximum and minimize. Then I restarted and got this error from the new script code.
---> Welcome to EventGhost <---
Autostart
Plugin: Media Player Classic
New_Script
Traceback (most recent call last):
Python script "0", line 224, in <module>
_show_osd = ShowOSD.OSDFrame.ShowOSD
AttributeError: type object 'ShowOSD' has no attribute 'OSDFrame'

Plugin: Task Monitor
Plugin: Keyboard
Plugin: Taskbar
Plugin: On Screen Explorer
Plugin: Deal Extreme USB PC Remote
I have also attached this cleaned script as it is now.

User avatar
kgschlosser
Site Admin
Posts: 5508
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Help with script error

Post by kgschlosser » Wed Oct 09, 2019 5:30 am

I am working on the script. I have it running I am just hammering out an issue with the alpha
If you like the work I have been doing then feel free to Image

molitar
Experienced User
Posts: 212
Joined: Fri Sep 11, 2009 6:44 am

Re: Help with script error

Post by molitar » Wed Oct 09, 2019 5:43 am

Ok great thanks for the update.. I really appreciate it.

Post Reply