Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor streamdeck #2283

Merged
merged 23 commits into from
Jan 13, 2020
Merged

Refactor streamdeck #2283

merged 23 commits into from
Jan 13, 2020

Conversation

cmsj
Copy link
Member

@cmsj cmsj commented Jan 7, 2020

I'm going to merge this as-is, because I remain unconvinced about the other way of doing button inputs, and I want to have a release with support for the Stream Deck Mini :)

…hange the continuation bool to actually indicate the final page. Remove unconditional 180 degree rotation from the NSImage BMP extension. Fix NSImage flipping extension to actually do things the right way, instead of exactly the opposite of what was asked for. Fix up the deck properties to match the change in image behaviour
@latenitefilms
Copy link
Contributor

So, in theory, this should now work with all the Stream Deck models?

@cmsj
Copy link
Member Author

cmsj commented Jan 7, 2020

Just the Original and Mini. Original V2 and XL are not yet implemented. I'm unsure if it's better to try and get them right without being able to test, or just leave them out.

@latenitefilms
Copy link
Contributor

I'm not sure if I've got the Original or Original V2 - will check when I get home tonight.

I've just ordered an XL & Mini, so I can test those when those arrive later in the week. Now I just need to get some GitHub Sponsors to fund all these fancy toys, haha!

@cmsj
Copy link
Member Author

cmsj commented Jan 8, 2020

@latenitefilms Ok, since you've ordered hardware I'll add some speculative support for the XL. If we have that working, I think the differences for the Original V2 should be pretty minimal.

@cmsj
Copy link
Member Author

cmsj commented Jan 8, 2020

ok @latenitefilms - awaiting your feedback on the XL :)

@latenitefilms
Copy link
Contributor

I've just connected up the XL, Mini & Original. Unfortunately, the XL doesn't seem to be working at all yet.

I'm using this test code:

hs.console.clearConsole()
hs.streamdeck.init(function(connected, object)
    if connected then
        local horizontal, vertical = object:buttonLayout()
        print(string.format("Stream Deck Connected: %s", object))       
        print(string.format("   - Serial Number: %s", object:serialNumber()))
        print(string.format("   - Firmware Version: %s", object:firmwareVersion()))
        print(string.format("   - Button Layout: %s %s", horizontal, vertical))        
        
        local i = 1
        for _=1, vertical do
            for _=1, horizontal do
                local imageHolder = hs.canvas.new{x = 0, y = 0, h = 100, w = 100}
                imageHolder[1] = {
                    frame = { h = 100, w = 100, x = 0, y = 0 },
                    fillColor = { alpha = 0.5, green = 1.0  },
                    type = "rectangle",
                }
                imageHolder[2] = {
                    frame = { h = 100, w = 100, x = 0, y = 40 },
                    text = i,
                    textAlignment = "center",
                    textColor = { white = 1.0 },
                    textSize = 20,
                    type = "text",
                }
                local textIcon = imageHolder:imageFromCanvas()
                object:setButtonImage(i, textIcon)
                i = i + 1
            end
        end 
    else
        print(string.format("Stream Deck Disconnected: %s", object))
    end
end)

...and I get:

2020-01-10 11:00:17: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck (Original v1), serial: AL29G1A09928 (0x6040009817f8)
2020-01-10 11:00:17:    - Serial Number: AL29G1A09928
2020-01-10 11:00:17:    - Firmware Version: 1.0.170118��
2020-01-10 11:00:17:    - Button Layout: 5 3
2020-01-10 11:00:17: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck (Mini), serial: BL19H1A09914 (0x6040009f8cb8)
2020-01-10 11:00:17:    - Serial Number: BL19H1A09914
2020-01-10 11:00:17:    - Firmware Version: 2.02.001����
2020-01-10 11:00:17:    - Button Layout: 3 2

It looks like this:

IMG_5945

Notice that the Stream Deck Mini has 1 to 6 in what I would say is the correct order, but the original Stream Deck goes the opposite direction. I think it makes more sense to go left to right, top to bottom, like you do on the Mini.

I'll have a play and see if I can work out why the XL isn't being recognised.

break;

case USB_PID_STREAMDECK_ORIGINAL_V2:
case USB_PID_STREAMDECK_XL:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing:

            deck = [[HSStreamDeckDeviceXL alloc] initWithDevice:device manager:self];
            break;

@@ -13,6 +13,8 @@
@import LuaSkin;

#import "HSStreamDeckDevice.h"
#import "HSStreamDeckDeviceOriginal.h"
#import "HSStreamDeckDeviceMini.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing:

#import "HSStreamDeckDeviceXL.h"

@latenitefilms
Copy link
Contributor

It looks like there's just a bunch of XL-related code missing, noted above.

However, when I add this in, I do get a crash - a stack buffer overflow in deviceRead.

Logs below:

2020-01-10 11:16:06.987421+1100 Hammerspoon[5362:4425237] Unknown class 'SUUpdater', using 'NSObject' instead. Encountered in Interface Builder file at path /Users/chrishocking/Library/Developer/Xcode/DerivedData/Hammerspoon-aubeqwjzazfymhbkrrmpxikgeyja/Build/Products/Debug/Hammerspoon.app/Contents/Resources/MainMenu.nib.
2020-01-10 11:16:06.987936+1100 Hammerspoon[5362:4425237] Could not connect action, target class NSObject does not respond to -checkForUpdates:
2020-01-10 11:16:06.988154+1100 Hammerspoon[5362:4425237] Could not connect action, target class NSObject does not respond to -checkForUpdates:
2020-01-10 11:16:07.051704+1100 Hammerspoon[5362:4425237] Determined Spoons path will be: /Users/chrishocking/.hammerspoon/Spoons (exists: YES, isDir: YES)
2020-01-10 11:16:07.190675+1100 Hammerspoon[5362:4425237] createLuaState
-- Augmenting require paths
-- package.path:
      /Users/chrishocking/.hammerspoon/?.lua
      /Users/chrishocking/.hammerspoon/?/init.lua
      /Users/chrishocking/.hammerspoon/Spoons/?.spoon/init.lua
      /usr/local/share/lua/5.3/?.lua
      /usr/local/share/lua/5.3/?/init.lua
      /usr/local/lib/lua/5.3/?.lua
      /usr/local/lib/lua/5.3/?/init.lua
      ./?.lua
      ./?/init.lua
      /Users/chrishocking/Library/Developer/Xcode/DerivedData/Hammerspoon-aubeqwjzazfymhbkrrmpxikgeyja/Build/Products/Debug/Hammerspoon.app/Contents/Resources/extensions/?.lua
      /Users/chrishocking/Library/Developer/Xcode/DerivedData/Hammerspoon-aubeqwjzazfymhbkrrmpxikgeyja/Build/Products/Debug/Hammerspoon.app/Contents/Resources/extensions/?/init.lua
-- package.cpath:
      /Users/chrishocking/.hammerspoon/?.so
      /usr/local/lib/lua/5.3/?.so
      /usr/local/lib/lua/5.3/loadall.so
      ./?.so
      /Users/chrishocking/Library/Developer/Xcode/DerivedData/Hammerspoon-aubeqwjzazfymhbkrrmpxikgeyja/Build/Products/Debug/Hammerspoon.app/Contents/Resources/extensions/?.so
2020-01-10 11:16:07.313056+1100 Hammerspoon[5362:4425237] require: hs.webview.toolbar_internal
2020-01-10 11:16:07.324440+1100 Hammerspoon[5362:4425237] Path  given to -[NSWorkspace iconForFile:] is not a full path.
2020-01-10 11:16:07.325664+1100 Hammerspoon[5362:4425237] require: hs.image.internal
2020-01-10 11:16:07.338492+1100 Hammerspoon[5362:4425237] require: hs.drawing.color.internal
2020-01-10 11:16:07.339654+1100 Hammerspoon[5362:4425237] require: hs.drawing.color
2020-01-10 11:16:07.342026+1100 Hammerspoon[5362:4425237] require: hs.image
2020-01-10 11:16:07.342341+1100 Hammerspoon[5362:4425237] require: hs.webview.toolbar
2020-01-10 11:16:07.344348+1100 Hammerspoon[5362:4425237] require: hs.drawing.color
2020-01-10 11:16:07.356170+1100 Hammerspoon[5362:4425237] require: hs.styledtext.internal
2020-01-10 11:16:07.356433+1100 Hammerspoon[5362:4425237] require: hs.drawing.color
2020-01-10 11:16:07.356913+1100 Hammerspoon[5362:4425237] require: hs.styledtext
2020-01-10 11:16:07.365110+1100 Hammerspoon[5362:4425237] require: hs.console.internal
2020-01-10 11:16:07.365364+1100 Hammerspoon[5362:4425237] require: hs.webview.toolbar
2020-01-10 11:16:07.365566+1100 Hammerspoon[5362:4425237] require: hs.console
2020-01-10 11:16:07.365786+1100 Hammerspoon[5362:4425237] require: hs.image
2020-01-10 11:16:07.431513+1100 Hammerspoon[5362:4425237] Loaded from: /Users/chrishocking/Library/Developer/Xcode/DerivedData/Hammerspoon-aubeqwjzazfymhbkrrmpxikgeyja/Build/Products/Debug/Hammerspoon.app/Contents/Resources/extensions
2020-01-10 11:16:07.433554+1100 Hammerspoon[5362:4425237] Created Lua instance
2020-01-10 11:16:08.224637+1100 Hammerspoon[5362:4425237] Accessibility is: DISABLED
2020-01-10 11:16:08.277350+1100 Hammerspoon[5362:4425237] SUUpdater doesn't exist, disabling updates checkbox in Preferences
2020-01-10 11:16:08.493740+1100 Hammerspoon[5362:4425237] Metal API Validation Enabled
2020-01-10 11:16:08.531154+1100 Hammerspoon[5362:4425738] flock failed to lock maps file: errno = 35
2020-01-10 11:16:08.532568+1100 Hammerspoon[5362:4425738] flock failed to lock maps file: errno = 35
2020-01-10 11:16:08.595218+1100 Hammerspoon[5362:4425237] Accessibility is: DISABLED
2020-01-10 11:16:31.735711+1100 Hammerspoon[5362:4425237] require: hs.console
2020-01-10 11:16:31.737746+1100 Hammerspoon[5362:4425237] require: hs.image
2020-01-10 11:16:31.750144+1100 Hammerspoon[5362:4425237] require: hs.drawing.color
2020-01-10 11:16:31.765233+1100 Hammerspoon[5362:4425237] require: hs.canvas.internal
2020-01-10 11:16:31.772179+1100 Hammerspoon[5362:4425237] require: hs.canvas.matrix_internal
2020-01-10 11:16:31.772363+1100 Hammerspoon[5362:4425237] require: hs.canvas.matrix
2020-01-10 11:16:31.772491+1100 Hammerspoon[5362:4425237] require: hs.image
2020-01-10 11:16:31.772597+1100 Hammerspoon[5362:4425237] require: hs.styledtext
2020-01-10 11:16:31.772914+1100 Hammerspoon[5362:4425237] require: hs.canvas
2020-01-10 11:16:31.773034+1100 Hammerspoon[5362:4425237] require: hs.styledtext
2020-01-10 11:16:31.773350+1100 Hammerspoon[5362:4425237] require: hs.drawing.canvasWrapper
2020-01-10 11:16:31.773500+1100 Hammerspoon[5362:4425237] require: hs.drawing
2020-01-10 11:16:31.780064+1100 Hammerspoon[5362:4425237] require: hs.streamdeck.internal
2020-01-10 11:16:31.780297+1100 Hammerspoon[5362:4425237] require: hs.streamdeck
2020-01-10 11:16:31.855406+1100 Hammerspoon[5362:4425237] require: hs.canvas
=================================================================
==5362==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x0001049b6631 at pc 0x00010080af89 bp 0x7ffeefbf4c90 sp 0x7ffeefbf4428
READ of size 32 at 0x0001049b6631 thread T0
atos(5389,0x1000b95c0) malloc: enabling scribbling to detect mods to free blocks
2020-01-10 11:16:49.308602+1100 atos[5389:4426917] examining /Users/USER/Library/Developer/Xcode/DerivedData/Hammerspoon-aubeqwjzazfymhbkrrmpxikgeyja/Build/Products/Debug/Hammerspoon.app/Contents/MacOS/Hammerspoon [5362]
    #0 0x10080af88 in wrap_memmove (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x1df88)
    #1 0x7fff42523ae7 in -[_NSInlineData initWithBytes:length:] (Foundation:x86_64+0x23ae7)
    #2 0x7fff425051a6 in -[_NSPlaceholderData initWithBytes:length:copy:deallocator:] (Foundation:x86_64+0x51a6)
    #3 0x7fff42523aaf in -[NSData(NSData) initWithBytes:length:] (Foundation:x86_64+0x23aaf)
    #4 0x7fff42523ef4 in +[NSData(NSData) dataWithBytes:length:] (Foundation:x86_64+0x23ef4)
    #5 0x110231b00 in -[HSStreamDeckDevice deviceRead:reportID:] HSStreamDeckDevice.m:62
    #6 0x11022e117 in -[HSStreamDeckDeviceXL serialNumber] HSStreamDeckDeviceXL.m:59
    #7 0x11022bf30 in streamdeck_object_tostring internal.m:305
    #8 0x1018ad926 in luaD_precall ldo.c:451
    #9 0x1018b1b1d in luaD_call ldo.c:515
    #10 0x1018b1f63 in luaD_callnoyield ldo.c:526
    #11 0x101844573 in lua_callk lapi.c:925
    #12 0x101926b8d in luaL_callmeta lauxlib.c:793
    #13 0x101926e0a in luaL_tolstring lauxlib.c:811
    #14 0x1017dc13e in str_format lstrlib.c:1066
    #15 0x1018ad926 in luaD_precall ldo.c:451
    #16 0x1018928a7 in luaV_execute lvm.c:1134
    #17 0x1018b1b41 in luaD_call ldo.c:516
    #18 0x1018b1f63 in luaD_callnoyield ldo.c:526
    #19 0x10184765b in f_call lapi.c:943
    #20 0x10177a0ac in luai_objcttry lobjectivec_exceptions.m:84
    #21 0x1018a63a4 in luaD_rawrunprotected ldo.c:156
    #22 0x1018b7d12 in luaD_pcall ldo.c:746
    #23 0x101845e9f in lua_pcallk lapi.c:969
    #24 0x101743bb6 in -[LuaSkin protectedCallAndTraceback:nresults:] Skin.m:251
    #25 0x101744005 in -[LuaSkin protectedCallAndError:nargs:nresults:] Skin.m:262
    #26 0x1102445d5 in -[HSStreamDeckManager deviceDidConnect:] HSStreamDeckManager.m:162
    #27 0x110242dd9 in HIDconnect HSStreamDeckManager.m:26
    #28 0x7fff42beeda5 in __IOHIDManagerDeviceApplier (IOKit:x86_64+0x36da5)
    #29 0x7fff42bc33fc in __IOHIDManagerDeviceAdded (IOKit:x86_64+0xb3fc)
    #30 0x7fff42bbf6eb in IODispatchCalloutFromCFMessage (IOKit:x86_64+0x76eb)
    #31 0x7fff402cf46c in __CFMachPortPerform (CoreFoundation:x86_64h+0x5346c)
    #32 0x7fff402cf346 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ (CoreFoundation:x86_64h+0x53346)
    #33 0x7fff402cf2a4 in __CFRunLoopDoSource1 (CoreFoundation:x86_64h+0x532a4)
    #34 0x7fff402b729b in __CFRunLoopRun (CoreFoundation:x86_64h+0x3b29b)
    #35 0x7fff402b666d in CFRunLoopRunSpecific (CoreFoundation:x86_64h+0x3a66d)
    #36 0x7fff3f5151aa in RunCurrentEventLoopInMode (HIToolbox:x86_64+0xb1aa)
    #37 0x7fff3f514ee4 in ReceiveNextEventCommon (HIToolbox:x86_64+0xaee4)
    #38 0x7fff3f514c75 in _BlockUntilNextEventMatchingListInModeWithFilter (HIToolbox:x86_64+0xac75)
    #39 0x7fff3d8ad77c in _DPSNextEvent (AppKit:x86_64+0x1a77c)
    #40 0x7fff3d8ac46a in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] (AppKit:x86_64+0x1946a)
    #41 0x7fff3d8a6587 in -[NSApplication run] (AppKit:x86_64+0x13587)
    #42 0x7fff3d895ac7 in NSApplicationMain (AppKit:x86_64+0x2ac7)
    #43 0x100011713 in main MJAppDelegate.m:375
    #44 0x7fff6c2423d4 in start (libdyld.dylib:x86_64+0x163d4)

Address 0x0001049b6631 is located in stack of thread T0 at offset 49 in frame
    #0 0x1102317ff in -[HSStreamDeckDevice deviceRead:reportID:] HSStreamDeckDevice.m:57

  This frame has 3 object(s):
    [32, 49) 'report' (line 58) <== Memory access at offset 49 overflows this variable
    [96, 104) 'reportLength' (line 59)
    [128, 136) 'data' (line 62)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x1df88) in wrap_memmove
Shadow bytes around the buggy address:
  0x100020936c70: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x100020936c80: f1 f1 f1 f1 00 f2 f2 f2 00 f2 f2 f2 00 f2 f2 f2
  0x100020936c90: 00 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x100020936ca0: f1 f1 f1 f1 f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2
  0x100020936cb0: f8 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100020936cc0: f1 f1 f1 f1 00 00[01]f2 f2 f2 f2 f2 00 f2 f2 f2
  0x100020936cd0: 00 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x100020936ce0: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x100020936cf0: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x100020936d00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x100020936d10: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
2020-01-10 11:16:51.058509+1100 Hammerspoon[5362:4425237] =================================================================
2020-01-10 11:16:51.058692+1100 Hammerspoon[5362:4425237] ==5362==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x0001049b6631 at pc 0x00010080af89 bp 0x7ffeefbf4c90 sp 0x7ffeefbf4428
2020-01-10 11:16:51.058775+1100 Hammerspoon[5362:4425237] READ of size 32 at 0x0001049b6631 thread T0
2020-01-10 11:16:51.058851+1100 Hammerspoon[5362:4425237]     #0 0x10080af88 in wrap_memmove (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x1df88)
2020-01-10 11:16:51.058926+1100 Hammerspoon[5362:4425237]     #1 0x7fff42523ae7 in -[_NSInlineData initWithBytes:length:] (Foundation:x86_64+0x23ae7)
2020-01-10 11:16:51.058981+1100 Hammerspoon[5362:4425237]     #2 0x7fff425051a6 in -[_NSPlaceholderData initWithBytes:length:copy:deallocator:] (Foundation:x86_64+0x51a6)
2020-01-10 11:16:51.059034+1100 Hammerspoon[5362:4425237]     #3 0x7fff42523aaf in -[NSData(NSData) initWithBytes:length:] (Foundation:x86_64+0x23aaf)
2020-01-10 11:16:51.059102+1100 Hammerspoon[5362:4425237]     #4 0x7fff42523ef4 in +[NSData(NSData) dataWithBytes:length:] (Foundation:x86_64+0x23ef4)
2020-01-10 11:16:51.059179+1100 Hammerspoon[5362:4425237]     #5 0x110231b00 in -[HSStreamDeckDevice deviceRead:reportID:] HSStreamDeckDevice.m:62
2020-01-10 11:16:51.059234+1100 Hammerspoon[5362:4425237]     #6 0x11022e117 in -[HSStreamDeckDeviceXL serialNumber] HSStreamDeckDeviceXL.m:59
2020-01-10 11:16:51.059299+1100 Hammerspoon[5362:4425237]     #7 0x11022bf30 in streamdeck_object_tostring internal.m:305
2020-01-10 11:16:51.059374+1100 Hammerspoon[5362:4425237]     #8 0x1018ad926 in luaD_precall ldo.c:451
2020-01-10 11:16:51.059421+1100 Hammerspoon[5362:4425237]     #9 0x1018b1b1d in luaD_call ldo.c:515
2020-01-10 11:16:51.059481+1100 Hammerspoon[5362:4425237]     #10 0x1018b1f63 in luaD_callnoyield ldo.c:526
2020-01-10 11:16:51.059539+1100 Hammerspoon[5362:4425237]     #11 0x101844573 in lua_callk lapi.c:925
2020-01-10 11:16:51.059604+1100 Hammerspoon[5362:4425237]     #12 0x101926b8d in luaL_callmeta lauxlib.c:793
2020-01-10 11:16:51.059658+1100 Hammerspoon[5362:4425237]     #13 0x101926e0a in luaL_tolstring lauxlib.c:811
2020-01-10 11:16:51.059711+1100 Hammerspoon[5362:4425237]     #14 0x1017dc13e in str_format lstrlib.c:1066
2020-01-10 11:16:51.059757+1100 Hammerspoon[5362:4425237]     #15 0x1018ad926 in luaD_precall ldo.c:451
2020-01-10 11:16:51.059819+1100 Hammerspoon[5362:4425237]     #16 0x1018928a7 in luaV_execute lvm.c:1134
2020-01-10 11:16:51.059876+1100 Hammerspoon[5362:4425237]     #17 0x1018b1b41 in luaD_call ldo.c:516
2020-01-10 11:16:51.059923+1100 Hammerspoon[5362:4425237]     #18 0x1018b1f63 in luaD_callnoyield ldo.c:526
2020-01-10 11:16:51.059975+1100 Hammerspoon[5362:4425237]     #19 0x10184765b in f_call lapi.c:943
2020-01-10 11:16:51.060026+1100 Hammerspoon[5362:4425237]     #20 0x10177a0ac in luai_objcttry lobjectivec_exceptions.m:84
2020-01-10 11:16:51.060074+1100 Hammerspoon[5362:4425237]     #21 0x1018a63a4 in luaD_rawrunprotected ldo.c:156
2020-01-10 11:16:51.060126+1100 Hammerspoon[5362:4425237]     #22 0x1018b7d12 in luaD_pcall ldo.c:746
2020-01-10 11:16:51.060175+1100 Hammerspoon[5362:4425237]     #23 0x101845e9f in lua_pcallk lapi.c:969
2020-01-10 11:16:51.060225+1100 Hammerspoon[5362:4425237]     #24 0x101743bb6 in -[LuaSkin protectedCallAndTraceback:nresults:] Skin.m:251
2020-01-10 11:16:51.060286+1100 Hammerspoon[5362:4425237]     #25 0x101744005 in -[LuaSkin protectedCallAndError:nargs:nresults:] Skin.m:262
2020-01-10 11:16:51.060338+1100 Hammerspoon[5362:4425237]     #26 0x1102445d5 in -[HSStreamDeckManager deviceDidConnect:] HSStreamDeckManager.m:162
2020-01-10 11:16:51.060400+1100 Hammerspoon[5362:4425237]     #27 0x110242dd9 in HIDconnect HSStreamDeckManager.m:26
2020-01-10 11:16:51.060449+1100 Hammerspoon[5362:4425237]     #28 0x7fff42beeda5 in __IOHIDManagerDeviceApplier (IOKit:x86_64+0x36da5)
2020-01-10 11:16:51.060505+1100 Hammerspoon[5362:4425237]     #29 0x7fff42bc33fc in __IOHIDManagerDeviceAdded (IOKit:x86_64+0xb3fc)
2020-01-10 11:16:51.060557+1100 Hammerspoon[5362:4425237]     #30 0x7fff42bbf6eb in IODispatchCalloutFromCFMessage (IOKit:x86_64+0x76eb)
2020-01-10 11:16:51.060609+1100 Hammerspoon[5362:4425237]     #31 0x7fff402cf46c in __CFMachPortPerform (CoreFoundation:x86_64h+0x5346c)
2020-01-10 11:16:51.060660+1100 Hammerspoon[5362:4425237]     #32 0x7fff402cf346 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ (CoreFoundation:x86_64h+0x53346)
2020-01-10 11:16:51.060712+1100 Hammerspoon[5362:4425237]     #33 0x7fff402cf2a4 in __CFRunLoopDoSource1 (CoreFoundation:x86_64h+0x532a4)
2020-01-10 11:16:51.060765+1100 Hammerspoon[5362:4425237]     #34 0x7fff402b729b in __CFRunLoopRun (CoreFoundation:x86_64h+0x3b29b)
2020-01-10 11:16:51.060815+1100 Hammerspoon[5362:4425237]     #35 0x7fff402b666d in CFRunLoopRunSpecific (CoreFoundation:x86_64h+0x3a66d)
2020-01-10 11:16:51.060867+1100 Hammerspoon[5362:4425237]     #36 0x7fff3f5151aa in RunCurrentEventLoopInMode (HIToolbox:x86_64+0xb1aa)
2020-01-10 11:16:51.060923+1100 Hammerspoon[5362:4425237]     #37 0x7fff3f514ee4 in ReceiveNextEventCommon (HIToolbox:x86_64+0xaee4)
2020-01-10 11:16:51.060977+1100 Hammerspoon[5362:4425237]     #38 0x7fff3f514c75 in _BlockUntilNextEventMatchingListInModeWithFilter (HIToolbox:x86_64+0xac75)
2020-01-10 11:16:51.061032+1100 Hammerspoon[5362:4425237]     #39 0x7fff3d8ad77c in _DPSNextEvent (AppKit:x86_64+0x1a77c)
2020-01-10 11:16:51.061082+1100 Hammerspoon[5362:4425237]     #40 0x7fff3d8ac46a in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] (AppKit:x86_64+0x1946a)
2020-01-10 11:16:51.061135+1100 Hammerspoon[5362:4425237]     #41 0x7fff3d8a6587 in -[NSApplication run] (AppKit:x86_64+0x13587)
2020-01-10 11:16:51.061185+1100 Hammerspoon[5362:4425237]     #42 0x7fff3d895ac7 in NSApplicationMain (AppKit:x86_64+0x2ac7)
2020-01-10 11:16:51.061243+1100 Hammerspoon[5362:4425237]     #43 0x100011713 in main MJAppDelegate.m:375
2020-01-10 11:16:51.061298+1100 Hammerspoon[5362:4425237]     #44 0x7fff6c2423d4 in start (libdyld.dylib:x86_64+0x163d4)
2020-01-10 11:16:51.061345+1100 Hammerspoon[5362:4425237] 
2020-01-10 11:16:51.061406+1100 Hammerspoon[5362:4425237] Address 0x0001049b6631 is located in stack of thread T0 at offset 49 in frame
2020-01-10 11:16:51.061454+1100 Hammerspoon[5362:4425237]     #0 0x1102317ff in -[HSStreamDeckDevice deviceRead:reportID:] HSStreamDeckDevice.m:57
2020-01-10 11:16:51.061509+1100 Hammerspoon[5362:4425237] 
2020-01-10 11:16:51.061566+1100 Hammerspoon[5362:4425237]   This frame has 3 object(s):
2020-01-10 11:16:51.061622+1100 Hammerspoon[5362:4425237]     [32, 49) 'report' (line 58) <== Memory access at offset 49 overflows this variable
2020-01-10 11:16:51.061684+1100 Hammerspoon[5362:4425237]     [96, 104) 'reportLength' (line 59)
2020-01-10 11:16:51.061730+1100 Hammerspoon[5362:4425237]     [128, 136) 'data' (line 62)
2020-01-10 11:16:51.061780+1100 Hammerspoon[5362:4425237] HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
2020-01-10 11:16:51.061833+1100 Hammerspoon[5362:4425237]       (longjmp and C++ exceptions *are* supported)
2020-01-10 11:16:51.061897+1100 Hammerspoon[5362:4425237] SUMMARY: AddressSanitizer: stack-buffer-overflow (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x1df88) in wrap_memmove
2020-01-10 11:16:51.061948+1100 Hammerspoon[5362:4425237] Shadow bytes around the buggy address:
2020-01-10 11:16:51.061993+1100 Hammerspoon[5362:4425237]   0x100020936c70: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
2020-01-10 11:16:51.062049+1100 Hammerspoon[5362:4425237]   0x100020936c80: f1 f1 f1 f1 00 f2 f2 f2 00 f2 f2 f2 00 f2 f2 f2
2020-01-10 11:16:51.062122+1100 Hammerspoon[5362:4425237]   0x100020936c90: 00 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
2020-01-10 11:16:51.062185+1100 Hammerspoon[5362:4425237]   0x100020936ca0: f1 f1 f1 f1 f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2
2020-01-10 11:16:51.062248+1100 Hammerspoon[5362:4425237]   0x100020936cb0: f8 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
2020-01-10 11:16:51.062304+1100 Hammerspoon[5362:4425237] =>0x100020936cc0: f1 f1 f1 f1 00 00[01]f2 f2 f2 f2 f2 00 f2 f2 f2
2020-01-10 11:16:51.062375+1100 Hammerspoon[5362:4425237]   0x100020936cd0: 00 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
2020-01-10 11:16:51.062430+1100 Hammerspoon[5362:4425237]   0x100020936ce0: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
2020-01-10 11:16:51.062483+1100 Hammerspoon[5362:4425237]   0x100020936cf0: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
2020-01-10 11:16:51.062531+1100 Hammerspoon[5362:4425237]   0x100020936d00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
2020-01-10 11:16:51.062589+1100 Hammerspoon[5362:4425237]   0x100020936d10: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
2020-01-10 11:16:51.062639+1100 Hammerspoon[5362:4425237] Shadow byte legend (one shadow byte represents 8 application bytes):
2020-01-10 11:16:51.062694+1100 Hammerspoon[5362:4425237]   Addressable:           00
2020-01-10 11:16:51.062742+1100 Hammerspoon[5362:4425237]   Partially addressable: 01 02 03 04 05 06 07
2020-01-10 11:16:51.062797+1100 Hammerspoon[5362:4425237]   Heap left redzone:       fa
2020-01-10 11:16:51.062844+1100 Hammerspoon[5362:4425237]   Freed heap region:       fd
2020-01-10 11:16:51.062918+1100 Hammerspoon[5362:4425237]   Stack left redzone:      f1
2020-01-10 11:16:51.062972+1100 Hammerspoon[5362:4425237]   Stack mid redzone:       f2
2020-01-10 11:16:51.063036+1100 Hammerspoon[5362:4425237]   Stack right redzone:     f3
2020-01-10 11:16:51.063095+1100 Hammerspoon[5362:4425237]   Stack after return:      f5
2020-01-10 11:16:51.063150+1100 Hammerspoon[5362:4425237]   Stack use after scope:   f8
2020-01-10 11:16:51.063228+1100 Hammerspoon[5362:4425237]   Global redzone:          f9
2020-01-10 11:16:51.063297+1100 Hammerspoon[5362:4425237]   Global init order:       f6
2020-01-10 11:16:51.063359+1100 Hammerspoon[5362:4425237]   Poisoned by user:        f7
2020-01-10 11:16:51.063407+1100 Hammerspoon[5362:4425237]   Container overflow:      fc
2020-01-10 11:16:51.063454+1100 Hammerspoon[5362:4425237]   Array cookie:            ac
2020-01-10 11:16:51.063505+1100 Hammerspoon[5362:4425237]   Intra object redzone:    bb
2020-01-10 11:16:51.063554+1100 Hammerspoon[5362:4425237]   ASan internal:           fe
2020-01-10 11:16:51.063617+1100 Hammerspoon[5362:4425237]   Left alloca redzone:     ca
2020-01-10 11:16:51.063720+1100 Hammerspoon[5362:4425237]   Right alloca redzone:    cb
2020-01-10 11:16:51.063808+1100 Hammerspoon[5362:4425237]   Shadow gap:              cc
==5362==ABORTING
AddressSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
(lldb) 

@latenitefilms
Copy link
Contributor

What I'm seeing in Xcode:

Screen Shot 2020-01-10 at 11 32 20 am

@latenitefilms
Copy link
Contributor

FWIW, I tried using the below test code using the Stream Deck extension in the #2102 pull request:

hs.console.clearConsole()
hs.streamdeck.init(function(connected, object)
    if connected then
        print(string.format("Stream Deck Connected: %s", object))       
        print(string.format("   - Serial Number: %s", object:serialNumber()))
        print(string.format("   - Firmware Version: %s", object:firmwareVersion()))
        for i=1, 32 do
            local imageHolder = hs.canvas.new{x = 0, y = 0, h = 100, w = 100}
            imageHolder[1] = {
                frame = { h = 100, w = 100, x = 0, y = 0 },
                fillColor = { alpha = 0.5, green = 1.0  },
                type = "rectangle",
            }
            imageHolder[2] = {
                frame = { h = 100, w = 100, x = 0, y = 40 },
                text = i,
                textAlignment = "center",
                textColor = { white = 1.0 },
                textSize = 20,
                type = "text",
            }
            local textIcon = imageHolder:imageFromCanvas()
            object:setButtonImage(i, textIcon)
            i = i + 1
        end
    else
        print(string.format("Stream Deck Disconnected: %s", object))
    end
end)

It gives me:

2020-01-10 16:48:38: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck, serial: �1.00.006��� (0x604001704178)
2020-01-10 16:48:38:    - Serial Number: �1.00.006���
2020-01-10 16:48:38:    - Firmware Version: 5I1A04271���
2020-01-10 16:48:38: -- Loading extension: canvas

However, the screen looks like this:

IMG_5949

…eading code to react properly to differently sized requests
@cmsj
Copy link
Member Author

cmsj commented Jan 10, 2020

@latenitefilms apologies for missing out the XL initialisation, that was very stupid of me! I've just pushed that code and also (I hope) fixed deviceRead.

@cmsj
Copy link
Member Author

cmsj commented Jan 10, 2020

As for the button directions thing, yes it's super annoying that the Original numbers right-to-left. I'm not sure how much I want to do to hide that. We probably ought to, but let's figure out the basic hardware support first and then see what we can do about the buttons.

@latenitefilms
Copy link
Contributor

Awesome, thanks!

This fixes the serialNumber & firmwareVersion, but setButtonImage is currently not working. The Stream Deck is still just showing the startup logo.

Here's my test code:

hs.console.clearConsole()
hs.streamdeck.init(function(connected, object)
    if connected then
        local horizontal, vertical = object:buttonLayout()
        print(string.format("Stream Deck Connected: %s", object))       
        print(string.format("   - Serial Number: %s", object:serialNumber()))
        print(string.format("   - Firmware Version: %s", object:firmwareVersion()))
        print(string.format("   - Button Layout: %s %s", horizontal, vertical))        
        local i = 1
        for _=1, vertical do
            for _=1, horizontal do
                local imageHolder = hs.canvas.new{x = 0, y = 0, h = 100, w = 100}
                imageHolder[1] = {
                    frame = { h = 100, w = 100, x = 0, y = 0 },
                    fillColor = { alpha = 0.5, green = 1.0  },
                    type = "rectangle",
                }
                imageHolder[2] = {
                    frame = { h = 100, w = 100, x = 0, y = 40 },
                    text = i,
                    textAlignment = "center",
                    textColor = { white = 1.0 },
                    textSize = 20,
                    type = "text",
                }
                local textIcon = imageHolder:imageFromCanvas()
                print(string.format("Applying Image to Button: %s", i))
                object:setButtonImage(i, textIcon)
                i = i + 1
            end
        end         
        object:buttonCallback(function(obj, num, pressed)
            if pressed then
                log.df("Button #%s Pressed (%s)", num, obj)
            else                
                log.df("Button #%s Released (%s)", num, obj)
            end            
        end)
    else
        print(string.format("Stream Deck Disconnected: %s", object))
    end
end)

Here's what's in the Console:

2020-01-11 09:58:19: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck (XL), serial: 5I1A04271�������������������� �� (0x604000bcea78)
2020-01-11 09:58:19:    - Serial Number: 5I1A04271�������������������� ��
2020-01-11 09:58:19:    - Firmware Version: �1.00.006�������������������� ��
2020-01-11 09:58:19:    - Button Layout: 8 4
2020-01-11 09:58:19: Applying Image to Button: 1
2020-01-11 09:58:19: Applying Image to Button: 2
2020-01-11 09:58:19: Applying Image to Button: 3
2020-01-11 09:58:19: Applying Image to Button: 4
2020-01-11 09:58:19: Applying Image to Button: 5
2020-01-11 09:58:19: Applying Image to Button: 6
2020-01-11 09:58:19: Applying Image to Button: 7
2020-01-11 09:58:19: Applying Image to Button: 8
2020-01-11 09:58:19: Applying Image to Button: 9
2020-01-11 09:58:19: Applying Image to Button: 10
2020-01-11 09:58:19: Applying Image to Button: 11
2020-01-11 09:58:19: Applying Image to Button: 12
2020-01-11 09:58:19: Applying Image to Button: 13
2020-01-11 09:58:19: Applying Image to Button: 14
2020-01-11 09:58:19: Applying Image to Button: 15
2020-01-11 09:58:19: Applying Image to Button: 16
2020-01-11 09:58:19: Applying Image to Button: 17
2020-01-11 09:58:19: Applying Image to Button: 18
2020-01-11 09:58:19: Applying Image to Button: 19
2020-01-11 09:58:19: Applying Image to Button: 20
2020-01-11 09:58:19: Applying Image to Button: 21
2020-01-11 09:58:19: Applying Image to Button: 22
2020-01-11 09:58:19: Applying Image to Button: 23
2020-01-11 09:58:19: Applying Image to Button: 24
2020-01-11 09:58:19: Applying Image to Button: 25
2020-01-11 09:58:19: Applying Image to Button: 26
2020-01-11 09:58:19: Applying Image to Button: 27
2020-01-11 09:58:19: Applying Image to Button: 28
2020-01-11 09:58:19: Applying Image to Button: 29
2020-01-11 09:58:19: Applying Image to Button: 30
2020-01-11 09:58:19: Applying Image to Button: 31
2020-01-11 09:58:19: Applying Image to Button: 32

May this offset is relevant?

@cmsj
Copy link
Member Author

cmsj commented Jan 10, 2020

Hmm. It's really hard to know where the problem is - it's probably not that offset, that looks to be useful for reacting to button press events. It's either something to do with the JPEG image encoding code, or deviceWriteImage.

@cmsj
Copy link
Member Author

cmsj commented Jan 10, 2020

@latenitefilms I've ordered an XL, it's probably the only way we'll get this figured out :)

@cmsj
Copy link
Member Author

cmsj commented Jan 11, 2020

@latenitefilms I also forgot to actually include the JPEG rendering code the XL/v2 need 🙄
It's rendering images, but there's something wrong with them. I'll keep poking later.

…ating it as if it starts at 1, so an offset is needed in the image writing
@latenitefilms
Copy link
Contributor

Woohoo! You're a GENIUS! It's getting close now!

Using this test code:

hs.console.clearConsole()
hs.streamdeck.init(function(connected, object)
    if connected then
        local horizontal, vertical = object:buttonLayout()
        print(string.format("Stream Deck Connected: %s", object))       
        print(string.format("   - Serial Number: %s", object:serialNumber()))
        print(string.format("   - Firmware Version: %s", object:firmwareVersion()))
        print(string.format("   - Button Layout: %s %s", horizontal, vertical))        
        local i = 1
        for _=1, vertical do
            for _=1, horizontal do
                local imageHolder = hs.canvas.new{x = 0, y = 0, h = 100, w = 100}
                imageHolder[1] = {
                    frame = { h = 100, w = 100, x = 0, y = 0 },
                    fillColor = { alpha = 1, red = 1.0  },
                    type = "rectangle",
                }
                imageHolder[2] = {
                    frame = { h = 100, w = 100, x = 0, y = 40 },
                    text = i,
                    textAlignment = "center",
                    textColor = { white = 1.0 },
                    textSize = 20,
                    type = "text",
                }
                local textIcon = imageHolder:imageFromCanvas()
                --print(string.format("Applying Image to Button: %s", i))
                object:setButtonImage(i, textIcon)
                i = i + 1
            end
        end         
        object:buttonCallback(function(obj, num, pressed)
            if pressed then
                print(string.format("Button #%s Pressed (%s)", num, obj))
            else                
                --print(string.format("Button #%s Released (%s)", num, obj))
            end            
        end)
    else
        print(string.format("Stream Deck Disconnected: %s", object))
    end
end)

I get:

2020-01-12 20:42:01: -- Loading extension: streamdeck

2020-01-12 20:42:02: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck (XL), serial: 5I1A04271����������������������� (0x604000f78438)
2020-01-12 20:42:02:    - Serial Number: 5I1A04271�����������������������
2020-01-12 20:42:02:    - Firmware Version: �1.00.006�����������������������
2020-01-12 20:42:02:    - Button Layout: 8 4
2020-01-12 20:42:02: -- Loading extension: canvas
2020-01-12 20:42:02: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck (Original v1), serial: AL29G1A09928 (0x604001366978)
2020-01-12 20:42:02:    - Serial Number: AL29G1A09928
2020-01-12 20:42:02:    - Firmware Version: 1.0.170118��
2020-01-12 20:42:02:    - Button Layout: 5 3
2020-01-12 20:42:02: Stream Deck Connected: hs.streamdeck: Elgato Stream Deck (Mini), serial: BL19H1A09914 (0x604001c7d478)
2020-01-12 20:42:02:    - Serial Number: BL19H1A09914
2020-01-12 20:42:02:    - Firmware Version: 2.02.001����
2020-01-12 20:42:02:    - Button Layout: 3 2

IMG_5965

However, when I pressed button #1 on the Stream Deck XL I get:

2020-01-12 20:43:54: Button #186 Pressed (hs.streamdeck: Elgato Stream Deck (XL), serial: 5I1A04271�������������������� �� (0x604001f33e38))

When I pressed button #32 on the Stream Deck XL I get:

2020-01-12 20:44:46: Button #217 Pressed (hs.streamdeck: Elgato Stream Deck (XL), serial: 5I1A04271�������������������� �� (0x604002145b78))

When I press button 1 on the Stream Deck Mini I get:

2020-01-12 20:45:38: Button #62 Pressed (hs.streamdeck: Elgato Stream Deck (Mini), serial: BL19H1A09914 (0x6040024208b8))

When I press button #6 on the Stream Deck Mini I get:

2020-01-12 20:45:59: Button #67 Pressed (hs.streamdeck: Elgato Stream Deck (Mini), serial: BL19H1A09914 (0x6040025614b8))

The original Stream Deck works as expected.

FWIW, I vote to make button 1 on the top left on the original Stream Deck.

Thanks again for all your help and support @cmsj - absolute legend!

@cmsj
Copy link
Member Author

cmsj commented Jan 12, 2020

Ok, with that last commit, I believe I've fully fixed the button drawing on the XL 😁

@cmsj
Copy link
Member Author

cmsj commented Jan 12, 2020

Not sure what to do about the buttons - those HID values appear to either be deck specific, or more likely, are semi-random. Right now button 1 on my XL is identifying as 208.

@latenitefilms
Copy link
Contributor

This is the line that needs to change to fix the buttons IDs:

int button = IOHIDElementGetCookie(element) - 84;

For example, to fix the Stream Deck XL, I used:

int button = IOHIDElementGetCookie(element) - 84 - 185;

@cmsj
Copy link
Member Author

cmsj commented Jan 12, 2020

@latenitefilms I don't believe it's possible to do that because the numbers are not universal - like I said, button 1 on my XL is currently identifying as 208, so subtracting 185 wouldn't yield 1.

@latenitefilms
Copy link
Contributor

@cmsj - Sorry, posted before I saw your message. That's weird.

On another note, we could do something similar to this for the original Stream Deck button order.

@latenitefilms
Copy link
Contributor

@cmsj - In regards to button order, maybe have a look at this code and see if it sparks any ideas?

https://github.com/Hammerspoon/hammerspoon/pull/2102/files#diff-aa78f02549f5b46cdaf02e368a6a834dR39

@cmsj
Copy link
Member Author

cmsj commented Jan 12, 2020

@latenitefilms yeah we're going to have to switch to using IOHIDReports, which is a bit of a shame, but I think it should be doable. My current plan is to cache the state of the buttons in the device object and when we get a report, see which buttons have changed, and call the user's callback accordingly, then update the cache. It ought to perform the same from the user's point of view, and means we'll have consistend 1-n button numbering across everything (and yes, I'll reverse the order on the Original decks, which will break existing configs, but it'll be better in the long run).
Probably don't have time to work on those bits tonight, I just really wanted to sort out the image rendering code for the XL :)

@latenitefilms
Copy link
Contributor

Ah, so that's what you were talking about regarding IOHIDReports elsewhere - all makes sense now. No rush! Thanks for all your amazing work!!

@cmsj
Copy link
Member Author

cmsj commented Jan 13, 2020

Run out of time for tonight, but this is getting close to making the button numbers correspond to what they should be. It's still buggy though - the last button won't work, lots of the XL buttons (oddly) don't respond at all, and there are several arrays that are still zero-indexed and need to move to being one-indexed.

(In short, don't bother testing this until I've had a chance to fix it up tomorrow!)

@latenitefilms
Copy link
Contributor

latenitefilms commented Jan 13, 2020

@cmsj - Whilst I think of it, a few potential feature requests/fixes before you wrap up this branch:

  • Is it worth making hs.streamdeck.init() private, and just have it called automatically once hs.streamdeck is first required/loaded? If a user is going to require hs.streamdeck in the first place, they're going to want to initialise it anyway?

  • Could you cache the serialNumber when the device first connects, so that it is still available to the hs.streamdeck object when the device disconnects? Knowing the serialNumber on disconnection will just make it easier to manage multiple Stream Deck devices in Lua land (i.e. saving them to a table to avoid garbage collection). You could also cache the firmwareVersion for consistency, but not quite as useful.

  • The reset() documentation has a typo in the return section.

@latenitefilms
Copy link
Contributor

FWIW - My latest test code:

hs.console.clearConsole()
streamDecks = {}
hs.streamdeck.init(function(connected, object)
    if connected then
        local horizontal, vertical = object:buttonLayout()
        print(string.format("Stream Deck Connected: %s", object))       
        print(string.format("   - Serial Number: %s", object:serialNumber()))
        print(string.format("   - Firmware Version: %s", object:firmwareVersion()))
        print(string.format("   - Button Layout: %s %s", horizontal, vertical))        
        streamDecks[object:serialNumber()] = object
        local sd = streamDecks[object:serialNumber()]
        local i = 1
        for _=1, vertical do
            for _=1, horizontal do
                local imageHolder = hs.canvas.new{x = 0, y = 0, h = 100, w = 100}
                imageHolder[1] = {
                    frame = { h = 100, w = 100, x = 0, y = 0 },
                    fillColor = { alpha = 1, red = 1.0  },
                    type = "rectangle",
                }
                imageHolder[2] = {
                    frame = { h = 100, w = 100, x = 0, y = 40 },
                    text = i,
                    textAlignment = "center",
                    textColor = { white = 1.0 },
                    textSize = 20,
                    type = "text",
                }
                local textIcon = imageHolder:imageFromCanvas()
                --print(string.format("Applying Image to Button: %s", i))
                sd:setButtonImage(i, textIcon)
                i = i + 1
            end
        end         
        sd:buttonCallback(function(obj, num, pressed)
            if pressed then
                print(string.format("Button #%s Pressed (%s)", num, obj))
            else                
                --print(string.format("Button #%s Released (%s)", num, obj))
            end            
        end)
    else
        local horizontal, vertical = object:buttonLayout()
        print(string.format("Stream Deck Disconnected: %s", object))
        print(string.format("   - Serial Number: %s", object:serialNumber()))
        print(string.format("   - Firmware Version: %s", object:firmwareVersion()))
        print(string.format("   - Button Layout: %s %s", horizontal, vertical))                
    end
end)

@cmsj
Copy link
Member Author

cmsj commented Jan 13, 2020

I'm not going to make init() run automatically, because if we do that, decks already connected will be discovered before a discovery callback has been set. I've made the other two changes though.

Buttons are now working and numbered correctly on Original v1 and Mini decks, but for some reason I don't yet understand, on the XL, most of the buttons don't report anything, only the last row. I wonder if this also affected @octplane's PR...

@octplane
Copy link

Amazing work! I confess I've been running my own HS build which works for my Mini for too long. I'm also using the stock application because I'm too lazy to configure HS properly for my deck... This merge will create the occasion to work on my universal menu navigation system...

@cmsj
Copy link
Member Author

cmsj commented Jan 13, 2020

@octplane as soon as this is finished and working, I'll merge it and cut a HS release - I want it in the release builds for me :D

@cmsj
Copy link
Member Author

cmsj commented Jan 13, 2020

Hmm, ok, this is weird, now my XL deck is registering button presses properly, even though I don't think I've changed anything. Seems like I might be about ready to declare victory - @latenitefilms could you give it a test on your decks and see if everything is working?

@latenitefilms
Copy link
Contributor

Amazing work @cmsj ! All seems to work correctly as intended with the Original, XL and Mini connected!

The only remaining question I have is around garbage collection.

It seems when you "reload" Hammerspoon, the callback is still being triggered? Is there some garbage collection missing from Objective-C land?

Also, when you quit Hammerspoon, whatever's on the Stream Deck display "sticks". Should the Stream Deck's be "reset" during garbage collection?

@cmsj
Copy link
Member Author

cmsj commented Jan 13, 2020

Hmm, I'm not able to reproduce the reload bug you describe - if I comment out my call to hs.streamdeck.init() and reload Hammerspoon, pressing buttons does nothing as I would expect.

I did think about resetting the deck unconditionally, but it occurred to me that people might want to render something pretty to it and have it stay like that, but I realise that's quite a niche requirement, but it'd certainly be prettier than looking at the default Elgato logo screen. The easy workaround of course is to put a call to :reset() in hs.shutdownCallback.

I'm happy to discuss those things further, but for now neither of them is a regression caused by this PR, so I'm going to go ahead and merge and then release. Thanks for reporting your testing findings!

@cmsj cmsj merged commit b2df9ac into Hammerspoon:master Jan 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants