OpenMacroBoard / StreamDeckSharp

A simple .NET wrapper for Stream Deck
MIT License
356 stars 47 forks source link

Support for multiple StreamDecks? #9

Closed evilC closed 5 years ago

evilC commented 5 years ago

Hi, I currently use the Slion/SharpLibStreamDeck library for my AutoHotStreamDeck library.
I am wondering if your library could possibly support multiple StreamDecks?

wischi-chr commented 5 years ago

It does.

You can open as many devices as you like and use them at the same time. You can use StreamDeck.EnumerateDevices to get all connected devices. This method will return an enumerable of IDeviceReferenceHandle and you can retrieve a device handle (to control the deck) with the .Open() method

Here is a quick example (not very pretty) to demonstrate how to use multiple devices at once.

using OpenMacroBoard.SDK;
using StreamDeckSharp;
using System;
using System.Linq;
using System.Threading;

namespace StreamDeckMultipleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //get all devices (and open them)
            var allConnectedDevices = StreamDeck
                                        .EnumerateDevices()
                                        .Select(x => x.Open())
                                        .ToList();

            //generate a random color key
            var rnd = new Random();
            var color = new byte[3];
            rnd.NextBytes(color);
            var rndKey = KeyBitmap.Create.FromRgb(color[0], color[1], color[2]);

            //use all opened devices (at the same time)
            foreach(var d in allConnectedDevices)
            {
                d.SetKeyBitmap(rndKey);
            }

            //do other stuff with open devices...
            Thread.Sleep(5000);

            //Close all devices
            foreach (var d in allConnectedDevices)
                d.Dispose();
        }
    }
}

Let me know if you need more help ;-)

evilC commented 5 years ago

Cheers, will look into porting over to your version then.
FYI, your README seems wrong.
var deck = StreamDeck.FromHID() / deck.KeyPressed += KeyHandler

evilC commented 5 years ago

OK, so acquiring the device does not seem a problem, but I am having trouble working out how to port over some of the other functionality:

BTW, if you want to avoid back-and-forth on your issue tracker, I can be found in the HidWizards Discord

evilC commented 5 years ago

PS, I just noticed your OpenMacroBoard comments. I am part of a project called UCR.
The main reason I bought an SD was purely to investigate how I might integrate devices such as this into UCR - AutoHotStreamDeck is just a side project, to get a bit of experience with the device.
If you are at all interested, you would be welcome joining us in trying to integrate support for Macro Boards into UCR!

wischi-chr commented 5 years ago

Regarding build errors. You can create KeyBitmaps with the KeyBitmap.Create factory. A few examples:

KeyBitmap.Create.FromWpfElement(width, height, canvas);

KeyBitmap.Create.FromGraphics(width, height, g =>
{
    g.DrawEllipse(...);
    g.DrawLine(...);
    g.DrawString(...);
});

KeyBitmap.Create.FromRgb(255, 0, 0);

You can get the number of keys for an IMarcoBoard with device.Keys.Count

Sorry wiki and readme is pretty outdated at the moment (see issue #8)

PS: If you want to integrate StreamDecks into UCR I would recommend to link OpenMacroBoard, this way UCR would automatically support other LCD boards that implement IMacroBoard.

If you have team members without a real stream deck you can use a "virtual board/streamdeck" as described here: OpenMacroBoard-Core / Getting started for development.

evilC commented 5 years ago

So I can use one real SD plus one virtual MacroBoard to test my multiple device code?
Otherwise, seems like that covers all my questions except how to discover the resolution of the screens for a given device.
I am quite busy over the next couple of days, so it may be a while before I get to work on it again.

wischi-chr commented 5 years ago

So I can use one real SD plus one virtual MacroBoard to test my multiple device code? Yes. You could even use multiple virtual boards if you want.

On every device (IMacroBoard) you can use the Keys Collection to get the exact layout and dimensions. OpenMacroBoard even supports "weird" (non-grid) layouts like keyboards.

evilC commented 5 years ago

Thanks for that, with those pointers I managed to port pretty much everything over - https://github.com/evilC/AutoHotStreamDeck/tree/feature/openmacroboard
However, my demo AHK script tries to work out how many columns and rows there are, and I don't see a way to work that out without examining rects etc.
I also don't see a way of getting eg VID/PID of the device, so I can't make a lookup table.
SharpLibStreamDeck had RowCount and ColumnCount properties - I know these maybe don't make sense in a non-grid scenario, but by going more generic we are maybe losing out on simplicity?
WRT the virtualboard, it does not quite seem to do what I want in this case - when I run the demo code in a console app, then start my main app and call EnumerateDevices, I still only see my real SD. I was hoping to see the virtual device also.
If it was possible to have one process spawn a virtualboard that another process could read it as if it were a real one, that could also potentially open up a bunch of interesting use-cases. I do work with disabled people, and a virtualboard could potentially act as a virtual on-screen keyboard on steroids, without the need for a dedicated API.
People who only used their eyes to look at an icon and blink could use an on screen virtualboard along with something like a Tobii Eye Tracker to click the buttons, people who can only operate a limited number of buttons can use a physical MacroBoard.
If we could allow both of those via one unified API, that would be really cool.
I dunno how you could get a virtual board to be visible to EnumerateDevices - one way of course would be HID emulation, which as it happens, I know just the man - Nefarius has a generic virtual USB bus that could do it, but maybe there's a simpler way? I suppose UCR could spawn the virtualboards also, but that would add some extra complexity (In terms of configuring number and size of buttons etc).

wischi-chr commented 5 years ago

tries to work out how many columns and rows there are There is no (general) method of determining how many rows and colums a device has (at the moment) because the library also allows non-grid-layouts (like keyboards for example)

I also don't see a way of getting eg VID/PID of the device The library is also agnostic about how a device is connected. VID and PID are USB specific. For example the virtual stream deck does not have a VID or PID because it's not a USB device. I think about implementing some other form of identification (GUID like key derived from device specific paramters)

but by going more generic we are maybe losing out on simplicity? That's true - I'm open for suggestions (that don't sacrifice non-grid layout support)

I was hoping to see the virtual device also. EnumerateDevices is a method of the StreamDeckSharp package and only enumerates StreamDeck hardware. You can use BoardFactory.SpawnVirtualBoard() from the OpenMacroBoard.VirtualBoard package. Atm there is no common enumerator, but I already thought about implementing some kind of Provider Interface and a common enumeration method that invokes all providers.

If it was possible to have one process spawn a virtualboard that another process could read it as if it were a real one, that could also potentially open up a bunch of interesting use-cases That was the original idea for the virtual board but at the end I was a bit to lazy ;-) (but still on my todo list)

I dunno how you could get a virtual board to be visible to EnumerateDevices I wouldn't make it visible to EnumerateDevices because that's StreamDeck specific. I would write an IBoardProvider with a method that allows to enumerate devices. All Providers (StreamDeckSharp, VirtualBoard, etc.) implement IBoardProvider.

The OpenMacroBoard core will look for IBoardProviders on startup (get all types that implement IBoardProvider via reflection) and provide a method to enumerate boards that would list all devices from different providers.

evilC commented 5 years ago

It looks like the ctors for the KeyPositionCollection accept xCount and yCount params. Why not expose these as properties?
I get that IKeyPositionCollection is intended to be able to represent both grid and non-grid layouts, but hiding useful information that is being passed in is counter-productive. Could you not have IGridKeyPositionCollection and INonGridKeyPositionCollection or something?
I bodged it for now and just assumed a 6-button device was a 3x2 mini and a 15-button devices was a 5x3 SD

wischi-chr commented 5 years ago

@evilC I'm working on that atm. Should be done by the weekend.

evilC commented 5 years ago

Awesome, thank you!

wischi-chr commented 5 years ago

Just updated StreamDeckSharp to v0.3.0 (https://www.nuget.org/packages/streamdecksharp/) and the Keys property now returns a GridKeyPositionCollection.

Quick Example:

using (var deck = StreamDeck.OpenDevice())
{
    Console.WriteLine($"Number of Keys horizontally : {deck.Keys.KeyCountX}");
    Console.WriteLine($"Number of Keys vertically   : {deck.Keys.KeyCountY}");

    // there are also Properties for
    //      - distance between keys (x and y)
    //      - key size (x and y)
}

Let me know what you think.

evilC commented 5 years ago

I released some test builds yesterday with support for multiple SDs.
I had to make some changes - targetting IStreamDeckBoard instead of IMacroBoard, but I am unsure if this could actually be done via the generic library.
I am waiting for feedback as to whether it all works or not, as I cannot test myself.

wischi-chr commented 5 years ago

You could use a VirtualBoard for testing and cast Keys to IGridKeyPositionCollection.

evilC commented 5 years ago

I can test the KeyCount props, they work fine.
What I am unable to test is multiple StreamDecks connected at once, and whether StreamDeck.EnumerateDevices() and associated code works OK.
I seem to remember we discussed this before, and VirtualBoards are not visible to EnumerateDevices?

wischi-chr commented 5 years ago

You can spawn multiple VirtualBoards if you want. I plan on writing a unified provider interface but I'm not sure when that will be ready.

evilC commented 5 years ago

No rush, the people requesting it seem to have gone quiet anyway