Certain GStreamer pipelines not considered to be a bin? - c#

I can run a very simple launch pipeline from the command line thus:
gst-launch-1.0 videotestsrc ! ximagesink
and, from gst-inspect-1.0, the ximagesink appears to support the GstVideoOverlay interface so that I can bind it to a specific Gtk widget.
However, when trying to do that from within some code I happened to find lying around on the net, it seems that the pipeline is not considered to be a bin (and hence, the widget ID is not being given to it).
The code to do it is as follows, first to create the pipeline and set it up to capture bus messages:
Gst.Element playbin = Gst.Parse.Launch("videotestsrc ! ximagesink");
Gst.Bus bus = playbin.Bus;
bus.AddSignalWatch();
bus.Message += MsgCallback;
Then to actually process the bus messages:
private void MsgCallback(object o, MessageArgs args) {
// Only care about window ID binding requests.
Gst.Message msg = args.Message;
if (! Gst.Video.Global.IsVideoOverlayPrepareWindowHandleMessage(msg))
return;
// Get source of message.
Gst.Element src = msg.Src as Gst.Element;
if (src == null)
return;
// Find element supporting interface and notify it to bind.
Gst.Element ov = null;
if (src is Gst.Bin) {
ov = ((Gst.Bin) src).GetByInterface(VideoOverlayAdapter.GType);
VideoOverlayAdapter ad = new VideoOverlayAdapter(ov.Handle);
ad.WindowHandle = windowXId;
}
}
Now, for some reason, the src is Gst.Bin is false, meaning that the windowXId (the widget ID I've previously set) is never being communicated to GStreamer.
However, if I provide a playbin pipeline (playbin uri=XYZZY videosink='videoconvert ! videoflip method=none ! videoconvert ! autovideosink', if you're interested), it works fine.
As far as I can tell from the documentation for Gst.Parse.Launch(), it should give me a pipeline which is a special case of a bin, as per here (after fixing the atrocious grammar):
Returns a new element on success, NULL on failure. If more than one top level element is specified by the pipeline description , all elements are put into a GstPipeline, which is then returned.
I'm pretty certain that videotestsrc and ximagesink are two separate top-level elements but, when I add the following code, after the check for src being null:
Console.WriteLine("is bin " + (src is Gst.Bin));
Console.WriteLine("is element " + (src is Gst.Element));
Console.WriteLine("is pipeline " + (src is Gst.Pipeline));
Console.WriteLine(type is " + src.GetType());
I see:
is bin False
is element True
is pipeline False
type is Gst.Element
for the problematic videotestsrc pipeline and the following for the good playbin one:
is bin True
is element True
is pipeline False
type is Gst.Bin
So everything points to the problematic one giving an element rather than a bin, despite what the documentation states.
What am I missing here? What is the difference between the two pipelines that would cause different behaviour?

Okay, it turns out that the Gst.Parse.Launch function is actually returning a pipeline. This can be seen if you copy those checking statements immediately after the bin creation:
Gst.Element playbin = Gst.Parse.Launch("videotestsrc ! ximagesink");
Console.WriteLine("is bin " + (playbin is Gst.Bin));
Console.WriteLine("is element " + (playbin is Gst.Element));
Console.WriteLine("is pipeline " + (playbin is Gst.Pipeline));
Console.WriteLine(type is " + playbin.GetType());
and you see:
is bin True
is element True
is pipeline True
type is Gst.Pipeline
It's just that, when we get the message in the callback, the source seems to be set to an element instead. I haven't actually figured out why the message source is coming through as an element (or even which element it is) but I at least have a workaround, thanks partly to my advanced intelligence but mostly to a sneaky look at the way Banshee does it :-)
Turns out that the callback can simply access the bin directly rather than trying to get it out of the source. This works in my case since I only have the one video stream (bin) per instance. If you have multiples, it may be a little more difficult.
To achieve this, you first make playbin a member variable (if it isn't already), and you can then modify the callback thus:
private void MsgCallback(object o, MessageArgs args) {
// Only care about window ID binding requests.
Gst.Message msg = args.Message;
if (! Gst.Video.Global.IsVideoOverlayPrepareWindowHandleMessage(msg))
return;
// Get instance bin, interface element, then notify it to bind.
Gst.Bin src = (Gst.Bin)(this.playbin);
if (src == null) return;
Gst.Element ov = src.GetByInterface(VideoOverlayAdapter.GType);
if (ov == null) return;
VideoOverlayAdapter ad = new VideoOverlayAdapter(ov.Handle);
if (ad == null) return;
ad.WindowHandle = windowXId;
}

Related

UIAutomation failing to find child nodes

Trying to automate IIS (INetMgr) trying to use UIAutomation, i'm fixing mixed results. I'm able to get some if the screen elements good fine, others, even immediate children nodes, can't get either with a Find[First|All] or try in a treewalker (content|control|raw), just can't get the node(s) needed. Any suggestion what to use for driving the UI to automate it?
Window 10/11 desktop environment.
Here is a C# Console app that dumps (max 2 levels) of InetMgr's items from the "Connections" pane.
This must be started as Administrator otherwise it will fail (not immediately). In general UIA clients must run at same UAC level as automated apps.
To determine what to get from the tree or if something can be done, before any coding, we can use the Inspect tool from Windows SDK or the more recent Accessibility Insights.
Also, I use Windows' UIAutomationClient COM object, not the old one from Windows XP era as it misses lots of stuff.
The code iterates all tree items recursively and expand them if they are not expanded using the ExpandCollapse Control Pattern because InetMgr's tree has a lazy loading behavior as it can potentially contains hundreds of thousands of items (mapped to disk folders at some points).
class Program
{
// add a COM reference to UIAutomationClient (don't use .NET legacy UIA .dll)
private static readonly CUIAutomation8 _automation = new CUIAutomation8(); // using UIAutomationClient;
static void Main()
{
var process = Process.GetProcessesByName("InetMgr").FirstOrDefault();
if (process == null)
{
Console.WriteLine("InetMgr not started.");
return;
}
var inetmgr = _automation.ElementFromHandle(process.MainWindowHandle);
if (inetmgr == null)
return;
// note: set "Embed Interop Type" to false in UIAutomationClient reference node or redefine all UIA_* used constants manually
// also: you *must* this program as administrator to see this panel
var connections = inetmgr.FindFirst(TreeScope.TreeScope_Subtree,
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_AutomationIdPropertyId, "_hierarchyPanel"));
if (connections == null)
return;
var treeRoot = connections.FindFirst(TreeScope.TreeScope_Subtree,
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_TreeItemControlTypeId));
Dump(0, treeRoot);
}
static void Dump(int indent, IUIAutomationElement element)
{
var s = new string(' ', indent);
Console.WriteLine(s + "name: " + element.CurrentName);
// get expand/collapse pattern. All treeitem support that
var expandPattern = (IUIAutomationExpandCollapsePattern)element.GetCurrentPattern(UIA_PatternIds.UIA_ExpandCollapsePatternId);
if (expandPattern != null && expandPattern.CurrentExpandCollapseState != ExpandCollapseState.ExpandCollapseState_Expanded)
{
try
{
expandPattern.Expand();
}
catch
{
// cannot be expanded
}
}
// tree can be huge,only dump 2 levels max
if (indent > 2)
return;
var children = element.FindAll(TreeScope.TreeScope_Children, _automation.CreateTrueCondition());
for (var i = 0; i < children.Length; i++)
{
Dump(indent + 1, children.GetElement(i));
}
}
}

Programmatically switch NLog targets to/from async

I have been trying to programmatically flip the NLog async switch on and off for a long time. I am getting nowhere. I have code that appears to do what I want, but after making the changes, I get no logging at all. Here's what I've found so far:
When the NLog config file contains async=true, then at runtime, I will have two targets (LogManager.Configuration.AllTargets) for each target in the config file. One is the original target, renamed with "_wrapped" at the end. The other is the original (now renamed) target wrapped in an AsyncTargetWrapper, and given the name of my original target.
Before doing anything (to turn on or off async), I first clear all of the LoggingRules.
To turn off async, I get the wrapped target from the AsyncTargetWrapper, rename it to the original name, remove the wrapper target (which seems to remove the original too), then re-add the original target (which has now been given its original name again). Then I create a LoggingRule and set the appropriate logging levels. Finally, I call LogManager.ReconfigExistingLoggers(). At this point, something is broken, as I will get no logging whatsoever.
I follow a similar process when turning async on, except this time, I rename the original target, create an AsyncTargetWrapper for it, then add the new wrapper target. Again, I create a LoggingRule for the new target and set the logging levels. At this point, something is broken, as I will get no logging whatsoever.
The code is below:
LogManager.Configuration.LoggingRules.Clear();
if (async) // turning async ON
{
foreach (var target in LogManager.Configuration.AllTargets)
{
if (!(target is AsyncTargetWrapper))
{
string targetName = target.Name;
// rename the synchronous target to indicate that it is going to be wrapped by another target (an asynchronous wrapper)
target.Name = $"{target.Name}_wrapped";
// create an asynchronous target wrpper and give it the name of the synchronous target's original (non-wrapped) name
AsyncTargetWrapper asyncTarget = new AsyncTargetWrapper(target) { Name = targetName };
LogManager.Configuration.AddTarget(asyncTarget);
LoggingRule asyncRule = new LoggingRule("*", asyncTarget);
if (_minLevel != null && _maxLevel != null)
{
asyncRule.EnableLoggingForLevels(_minLevel, _maxLevel);
}
else
{
foreach (var level in LogLevel.AllLevels.Except(new List<LogLevel>() { LogLevel.Off }))
{
if (Logger.IsEnabled(level)) asyncRule.EnableLoggingForLevel(level);
}
}
}
}
}
else // turning async OFF
{
foreach (var target in LogManager.Configuration.AllTargets)
{
if (target is AsyncTargetWrapper)
{
// get the wrapped (synchronous) target from the wrapper
Target syncTarget = ((AsyncTargetWrapper)target).WrappedTarget;
// rename the synchronous target (remove "_wrapped" from the end of its name)
syncTarget.Name = target.Name.Replace("_wrapped", "");
// remove the wrapper target
LogManager.Configuration.RemoveTarget(target.Name);
LogManager.Configuration.AddTarget(syncTarget);
// create a rule for the wrapped (synchronous) target
LoggingRule syncRule = new LoggingRule("*", syncTarget);
// set the logging level for the synchronous target
if (_minLevel != null && _maxLevel != null)
{
syncRule.EnableLoggingForLevels(_minLevel, _maxLevel);
}
else
{
foreach (var level in LogLevel.AllLevels.Except(new List<LogLevel>() { LogLevel.Off }))
{
if (Logger.IsEnabled(level)) syncRule.EnableLoggingForLevel(level);
}
}
}
}
}
LogManager.Configuration.Reload();
LogManager.ReconfigExistingLoggers();
I don't think those last two lines are needed, but I tried them because nothing else was working.
There must be a correct way to flip the async flag, but I don't know what it is. Can anyone tell me how to do it?
I found the problem. I had a working implementation in the past, so this was all very confusing for me. There were problems with my old implementation, so I decided to refactor it, but made a mistake in the new implementation. I left out a critical line of code! New logging rules must be added to the configuration after they are created:
LogManager.Configuration.LoggingRules.Add(syncRule);
Now it works exactly as I want it to. Whew!

Removing a command from DiscordBot CommandService

I am trying to figure out how to remove a command from the discord bot after it has been created. Here is how I create the command:
_commandService.CreateCommand("create").Parameter("message", ParameterType.Multiple).Do(async e =>
{
var message = e.Args.Skip(1).Aggregate("", (current, t) => current + (t + " "));;
_commandService.CreateCommand("hello").Do(async cc =>
{
await e.User.SendMessage(customCommand.Message);
});
});
The _commandService object is of type Discord.Commands.CommandService
Now, I want to be able to run:
_commandService.CreateCommand("delete").Parameter("message", ParameterType.Multiple).Do(async e =>
{
_commandService.DeleteCommand("hello");
});
However, no such method exists, nor am I able to access the commands inside _commandService object as everything is read only get;
Does anyone know how I can delete the command without having to restart the bot?
It's possible, but as of discord.net 1.0 you need to use the Modules system to do it. Unfortunately, it greatly complicates things. Hopefully they'll add a proper DeleteCommand(string commandName) in a future update.
Why you need to do this (this section not needed if you don't care about the discord.net source): The
class CommandMap (it stores the commands, unsurprisingly) exposes a method RemoveCommand that does what you're looking to do. The only reference to an object of this class in the source is in the private method RemoveModuleInternal in CommandService. This is exposed in one of two public methods: RemoveModuleAsync(ModuleInfo module) or RemoveModuleAsync<T>(). There is no other way to affect commands as of the 1.0 release.
How to do it: Get the ModuleInfo object for your module first. Preferably, the module you create will only contain the command you want to delete for reasons that should be obvious pretty soon. When you use CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc) (or one of the other methods used to add modules) you'll get the ModuleInfo object back. This does mean you need to use a ModuleBuilder instead of the simple commandService.CreateCommand method you use. Read up on how to do that here... if the process still confuses you, it's an excellent topic for another question.
You need to keep track of the ModuleInfo object that CreateModuleAsync returns in some manner (the method I would use is below) and then your second command becomes:
// private ModuleInfo createInfo
_commandService.CreateCommand("delete").Parameter("message", ParameterType.Multiple).Do(async e =>
{
if (createInfo != null)
{
await _commandService.DeleteModuleAsync(createInfo);
}
});
Do note that the entire module instance is getting deleted... that's why your "create" command should be the only thing in it.
An alternate solution (although significantly less elegant) if this whole Module business seems too complicated would be to store a boolean and simply toggle it to simulate the deletion of the command. That is:
// bool createNotDeleted = true;
_commandService.CreateCommand("create").Parameter("message", ParameterType.Multiple).Do(async e =>
{
if (createNotDeleted)
{
var message = e.Args.Skip(1).Aggregate("", (current, t) => current + (t + " "));;
_commandService.CreateCommand("hello").Do(async cc =>
{
await e.User.SendMessage(customCommand.Message);
});
}
else
{
// error handling
}
});
and
_commandService.CreateCommand("delete").Parameter("message", ParameterType.Multiple).Do(async e =>
{
if (createNotDeleted)
{
createNotDeleted = false
// return something indicating command is deleted
}
else
{
// error handling
}
});

Multi-threaded C# Selenium WebDriver automation with Uris not known beforehand

I need to perform some simultaneous webdrivers manipulation, but I am uncertain as to how to do this.
What I am asking here is:
What is the correct way to achieve this ?
What is the reason for the exception I am getting (revealed below)
After some research I ended up with:
1. The way I see people doing this (and the one I ended up using after playing with the API, before searching) is to loop over the window handles my WebDriver has at hand, and perform a switch to and out of the window handle I want to process, closing it when I am finished.
2. Selenium Grid does not seem like an option fore me - am I wrong or it is intended for parallel processing ? Since am running everything in a single computer, it will be of no use for me.
In trying the 1st option, I have the following scenario (a code sample is available below, I skipped stuff that is not relevant/repeat itself (where ever I added 3 dots:
I have a html page, with several submit buttons, stacked.
Clicking each of them will open a new browser/tab (interestingly enough, using ChromeDriver opens tabs, while FirefoxDriver opens separate windows for each.)
As a side note: I can't determine the uris of each submit beforehand (they must be determined by javascript, and at this point, let's just assume I want to handle everything knowing nothing about the client code.
Now, after looping over all the submit buttons, and issuing webElement.Click() on the corresponding elements, the tabs/windows open. The code flows to create a list of tasks to be executed, one for each new tab/window.
The problem is: since all tasks all depend upon the same instance of webdriver to switch to the window handles, seems I will need to add resource sharing locks/control. I am uncertain as whether I am correct, since I saw no mention of locks/resource access control in searching for multi-threaded web driver examples.
On the other hand, if I am able to determine the tabs/windows uris beforehand, I would be able to skip all the automation steps needed to reach this point, and then creating a webDriver instance for each thread, via Navigate().GoToUrl() would be straightforward. But this looks like a deadlock! I don't see webDriver's API providing any access to the newly opened tab/window without performing a switch. And I only want to switch if I do not have to repeat all the automation steps that lead me to the current window !
...
In any case, I keep getting the exception:
Element belongs to a different frame than the current one - switch to its containing frame to use it
at
IWebElement element = cell.FindElement
inside the ToDictionary() block.
I obviously checked that all my selectors are returning results, in chrome's console.
foreach (WebElement resultSet in resultSets)
resultSet.Click();
foreach(string windowHandle in webDriver.WindowHandles.Skip(1))
{
dataCollectionTasks.Add(Task.Factory.StartNew<List<DataTable>>(obj =>
{
List<DataTable> collectedData = new List<DataTable>();
string window = obj as string;
if (window != null)
{
webDriver.SwitchTo().Window(windowHandle);
List<WebElement> dataSets = webDriver.FindElements(By.JQuerySelector(utils.GetAppSetting("selectors.ResultSetData"))).ToList();
DataTable data = null;
for (int i = 0; i < dataSets.Count; i += 2)
{
data = new DataTable();
data.Columns.Add("Col1", typeof(string));
data.Columns.Add("Col2", typeof(string));
data.Columns.Add("Col3", typeof(string));
///...
//data set header
if (i % 2 != 0)
{
IWebElement headerElement = dataSets[i].FindElement(OpenQA.Selenium.By.CssSelector(utils.GetAppSetting("selectors.ResultSetDataHeader")));
data.TableName = string.Join(" ", headerElement.Text.Split().Take(3));
}
//data set records
else
{
Dictionary<string, string> cells = dataSets[i]
.FindElements(OpenQA.Selenium.By.CssSelector(utils.GetAppSetting("selectors.ResultSetDataCell")))
.ToDictionary(
cell =>
{
IWebElement element = cell.FindElement(OpenQA.Selenium.By.CssSelector(utils.GetAppSetting("selectors.ResultSetDataHeaderColumn")));
return element == null ? string.Empty : element.Text;
},
cell =>
{
return cell == null ? string.Empty : cell.Text;
});
string col1Value, col2Value, col3Value; //...
cells.TryGetValue("Col1", out col1Value);
cells.TryGetValue("Col2", out col2Value);
cells.TryGetValue("Col3", out col3Value);
//...
data.Rows.Add(col1Value, col2Value, col3Value /*...*/);
}
}
collectedData.Add(data);
}
webDriver.SwitchTo().Window(mainWindow);
webDriver.Close();
return collectedData;
}, windowHandle));
} //foreach
Task.WaitAll(dataCollectionTasks.ToArray());
foreach (Task<List<DataTable>> dataCollectionTask in dataCollectionTasks)
{
results.AddRange(dataCollectionTask.Result);
}
return results;

Out Of Context Variables In Visual Studio 2010 Debugger

I am having a very odd problem with local variables being out of context in the Visual Studio 2010 debugger for a C# console application targeting .NET 4.0. I've searched for other similar questions on SO, but while some have the same symptoms, none seem to apply directly to this problem (they all appear to have other root causes).
The problem is that for some variables (but not all) I do not get a tooltip with their value, they do not appear in the Locals window, and I get "The name 'xyz' does not exist in the current context" if I add them to the Watch window. It appears to affect some variables but not others, and I can't figure out a pattern (it doesn't seem to be based on member vs. local, class vs. struct, or any other differentiator). I've restarted my computer and Visual Studio, verified I'm in a clean Debug build, made sure the debugging frame is correct, made sure to refresh the variables in the watch screen, and attempted various spells and incantations.
I've included a screenshoot below (bigger version at http://i.stack.imgur.com/JTFBT.png).
Any thoughts?
EDIT:
Adding some additional information:
The problem is repeatable. The exact same variables either work or don't work, even after completely shutting down and restarting Visual Studio. This leads me to believe there's actually something systematic going wrong rather than just memory corruption or something.
I've also discovered that it appears to be related to the try-catch block. If I position the breakpoint outside the try statement I can see any of the in-scope variables normally. As soon as the execution point enters the try statement all the variables outside the try block become inaccessible and I can only access the ones inside the try statement. It's almost as though the debugger is treating the try block as a separate method (though you can see the code/compiler still does have access to in-scope variables). Has anyone seen this behavior before?
ANOTHER EDIT:
I (partially) take back what I said about the try-catch being suspect - it appears that in this portion of the code the debugger exhibits this odd taking stuff out of context for any enclosing block. For example, if I set a breakpoint directly inside the foreach statement in the screenshot I can see the "port" variable value on each iteration, but none of the variables outside the foreach statement (which disappear as soon as I enter the foreach block). Then as soon as you enter the try block, the "port" variable suddenly goes away. This is getting really weird.
Also, as requested, the code for the entire method is below.
private void ConfigureAnnouncerSockets(XDocument configDocument)
{
XElement socketsElement = configDocument.XPathSelectElement("/Configuration/Network/AnnouncerSockets");
bool useDefault = true;
if (socketsElement != null)
{
//Use the default announcers? (they will be added at the end)
XAttribute defaultAttribute = socketsElement.Attribute("useDefault");
if (defaultAttribute != null)
{
useDefault = Convert.ToBoolean(defaultAttribute);
}
//Get the default frequency
int defaultFrequency = Announcer.DefaultFrequency;
XAttribute frequencyAttribute = socketsElement.Attribute("frequency");
if (frequencyAttribute != null)
{
defaultFrequency = Convert.ToInt32(frequencyAttribute.Value);
}
//Get all sockets
foreach (XElement socketElement in socketsElement.XPathSelectElements("./Socket"))
{
//Get the address
IPAddress address = IPAddress.Broadcast;
string addressAttribute = (string)socketElement.Attribute("address");
if(!GetAddress(addressAttribute, ref address, true))
{
Intelliplex.Log.Warn("Invalid announcer socket address: " + addressAttribute);
continue;
}
//Get the local address
IPAddress localAddress = null;
string localAddressAttribute = (string)socketElement.Attribute("localAddress");
if(!GetAddress(localAddressAttribute, ref localAddress, false))
{
Intelliplex.Log.Warn("Invalid announcer socket local address: " + localAddressAttribute);
continue;
}
//Get the port(s)
List<int> ports = new List<int>();
string[] ranges = ((string)socketElement.Attribute("port")).Split(new[] { ',' });
foreach (string range in ranges)
{
string[] portPair = range.Split(new[] { '-' });
int firstPort = Convert.ToInt32(portPair[0]);
int lastPort = portPair.Length > 1 ? Convert.ToInt32(portPair[1]) : firstPort;
do
{
ports.Add(firstPort);
} while (++firstPort <= lastPort);
}
//Get the local port
int localPort = socketElement.Attribute("localPort") != null
? Convert.ToInt32((string)socketElement.Attribute("localPort")) : 0;
//Get the frequency
int frequency = socketElement.Attribute("frequency") != null
? Convert.ToInt32((string)socketElement.Attribute("frequency")) : defaultFrequency;
//Create the socket(s) and add it/them to the manager
foreach (int port in ports)
{
try
{
IPEndPoint endPoint = new IPEndPoint(address, port);
IPEndPoint localEndPoint = localAddress == null
? new IPEndPoint(IPAddress.Any, 0) : new IPEndPoint(localAddress, localPort);
Announcer socket = new Announcer(frequency, endPoint, localEndPoint);
AnnouncerSockets.Add(socket);
}
catch (Exception ex)
{
Intelliplex.Log.Warn("Could not add announcer socket: " + ex.Message);
}
}
}
}
//Add default announcement sockets?
if (useDefault)
{
ConfigureDefaultAnnouncerSockets();
}
}
So it turns out this is related to a bug in PostSharp. I had been using PostSharp but removed all aspects from my code and ensured that none were applied. I also verified with Reflector that the methods were intact in the assembly. However, it appears simply referencing PostSharp triggers some kind of manipulation of the debugging symbols that causes this problem. A (little) more information can be found here:
http://www.sharpcrafters.com/forum/Topic5794-21-1.aspx#bm7927
Also, in the release notes for the latest PostSharp hotfix states one of the fixed issues in hotfix 2.1.5.6 is "Debugging symbols: local variable symbols lost in implicit iterators."
When I installed the latest and greatest PostSharp the problem went away and the universe returned to normal. Hopefully this question/answer will help anyone else using PostSharp who stumbles on this odd behavior before the next official PostSharp release. Make sure you're on hotfix 2.1.5.6 or greater (given the severity of the bug, this probably should have been an actual release).
Thanks for all the help everyone.

Categories