c# Xamarin Change CutterBehavior of UIKit.UIPrintInteractionController.ChooseCutterBehavior - c#

I have a probably simple question for every pro coder:
I would like to write an iOS app with xamarin in C# that prints html to a receipt printer. For that I would like to adapt the default Cutterbeavior of the receipt printer.
i have this Code which works perfect for me:
var printer = UIPrintInteractionController.SharedPrintController;
printer.ShowsPageRange = true;
printer.ShowsNumberOfCopies = true;
printer.ShowsPaperSelectionForLoadedPapers = true;
var page = #"<!DOCTYPE html><style></style><html><head><meta charset='UTF-8'><title>Title</title></head><body>Text to print</body></html>";
var textFormatter = new UIMarkupTextPrintFormatter(page);
printer.PrintFormatter = textFormatter;
printer.Present(true, (handler, completed, err) =>
{
if (!completed && err != null)
{
Console.WriteLine("error");
}
});
and now i would like to customize this property:
printer.ChooseCutterBehavior
Unfortunately, the documenation does not help me any further:
https://developer.xamarin.com/api/property/UIKit.UIPrintInteractionController.ChooseCutterBehavior/
How can I set the property ChooseCutterBehavior? Unfortunately, I can not continue at this point and I am grateful for any hint!
Thanks!

In your IUIPrintInteractionControllerDelegate implementation, add the optional printInteractionController:chooseCutterBehavior: method.
This method may be called by the printer driver and will give the available (UIPrinterCutterBehavior) behaviors that this printer supports. You return one of the supported behaviors based upon your requirements.
public class MyPrintInteractionControllerDelegate : NSObject, IUIPrintInteractionControllerDelegate
{
[Export("printInteractionController:chooseCutterBehavior:")]
public UIPrinterCutterBehavior ChooseCutterBehavior(UIPrintInteractionController printInteractionController, NSNumber[] availableBehaviors)
{
// What does this printer support?
foreach (var whatsAvailable in availableBehaviors)
{
var nameofEnum = Enum.GetName(typeof(UIPrinterCutterBehavior), whatsAvailable.Int16Value);
Console.WriteLine($"AvailableBehavior : {nameofEnum}");
}
// If the printer supports CutAfterEachJob, use it, otherwise use the whatever the printer default is.
if (availableBehaviors.Contains((int)UIPrinterCutterBehavior.CutAfterEachJob))
return UIPrinterCutterBehavior.CutAfterEachJob;
else
return UIPrinterCutterBehavior.PrinterDefault;
}
//~~~ other delegate methods ~~~
}
Now assign the delegate (IUIPrintInteractionControllerDelegate) to your UIPrintInteractionController.SharedPrintController instance:
var printer = UIPrintInteractionController.SharedPrintController;
printer.Delegate = new MyPrintInteractionControllerDelegate();

Related

How to get separate arguments from single arguments string like ProcessStartInfo.Arguments?

Let's say I have a shell command line. A single line of text, exactly as you would type that in shell. Splitting command from arguments is trivial. It's always a space after the command, in every shell in the world. I know, the command theoretically can contain spaces, but let's assume it's a normal command for a while.
When I just pass the arguments as string to the ProcessStartInfo.Arguments string - it will work. But this is not what I want.
I want the arguments to be separated.
Each one. When an argument is like #"C:\Program Files" - it should be a one argument.
Getting that manually is just tricky as (s)hell. Because it's done differently for different OSes and shells. However - .NET does it somehow internally and I need to get to it not to reinvent the wheel.
What I REALLY want to do is to build an argument list first like this:
new[] { "dir", #"'C:\Program Files'" }
And then like this:
new[] { "cmd", "/C", #"dir 'C:\Program Files'" }
So it goes both ways - I extract arguments from a string, then I build an arguments string from separate arguments. All according for the OS-specific rules. My code should work on both Linux and Windows.
The catch is, it has to work multi-platform, so instead of "cmd /C" it could be "bash -c" on Linux, you get the idea.
The main point is to make the .NET to do all the quoting / unqouting itself, and AFAIK it does it properly on both Windows and Linux.
I really did search the Google and Stack Overflow for this - it seems like there's nothing.
I tried to get ArgumentList from ProcessStartInfo after settings Arguments property. It doesn't work. Setting one property doesn't set the other one. You can actually set both and get the exception when trying to start the process.
I bet the problem is far from being trivial. Any suggestions?
UPDATE
I've done more research based on digging in .NET sources and GitHub discussions. It looks really bad (or really good depending on a point of view). It seems like there's nothing like this. So I basically need one method to quote one argument for the target OS, and one method to parse a command line into unquoted parts. For parsing I'll go standard, using FSM (finite state machine algorithm), quoting is trivial, it's just adding OS / shell specific quote symbols.
Anyway, if it's somewhere in .NET just hiding and giggling, please let me know ;)
So it seems like it was not there yet, so I made a little braindead simple hack for it:
Pre-requisite:
public class SpaceDelimitedStringParser {
public char Quote { get; set; } = '"';
public IEnumerable<string> Split(string input) {
bool isQuoting = false;
for (int i = 0, s = 0, n = input.Length; i < n; i++) {
var c = input[i];
var isWhiteSpace = c is ' ' or '\t';
var isQuote = c == Quote;
var isBreak = i == n - 1 || isWhiteSpace && !isQuoting;
if (isBreak) {
yield return input[s..i];
s = i + 1;
continue;
}
if (isWhiteSpace && !isQuoting) continue;
if (isQuote) {
if (!isQuoting) {
s = i + 1;
isQuoting = true;
}
else {
yield return input[s..i];
s = i + 1;
isQuoting = false;
}
}
}
}
public string Join(IEnumerable<string> items)
=> String.Join(' ', items.Select(i => i.Contains(' ') || i.Contains('\t') ? (Quote + i + Quote) : i));
}
It just splits and joins space delimited strings using a quoting character: double quoute. I tested that is treated similarily on Windows and Linux.
Then another nice helper class:
public class ShellStartInfo {
public ShellStartInfo(string command, bool direct = false) {
var (shell, exec) =
OS.IsLinux ? ("bash", "-c") :
OS.IsWindows ? ("cmd", "/C") :
throw new PlatformNotSupportedException();
var arguments = new[] { exec, command };
StartInfo = new ProcessStartInfo(shell, new SpaceDelimitedStringParser().Join(arguments));
if (!direct) {
StartInfo.RedirectStandardInput = true;
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardError = true;
StartInfo.StandardOutputEncoding = Encoding.UTF8;
StartInfo.StandardErrorEncoding = Encoding.UTF8;
}
}
public static implicit operator ProcessStartInfo(ShellStartInfo startInfo) => startInfo.StartInfo;
private readonly ProcessStartInfo StartInfo;
}
And another little helper:
public static class OS {
public static bool IsLinux => _IsLinux ?? (_IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)).Value;
public static bool IsWindows => _IsWindows ?? (_IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)).Value;
private static bool? _IsLinux;
private static bool? _IsWindows;
}
And we can test it:
var command =
OS.IsLinux ? #"cat 'File name with spaces.txt'" :
OS.IsWindows ? #"type ""File name with spaces.txt""" :
throw new PlatformNotSupportedException();
var process = Process.Start(new ShellStartInfo(command));
if (process is null) throw new InvalidOperationException();
await process.WaitForExitAsync();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
if (output.Length > 0) Console.WriteLine($"OUTPUT:\n{output}\nEND.");
if (error.Length > 0) Console.WriteLine($"ERROR:\n{error}\nEND.");
Works on Windows, works on Linux. Done and done. The split function is unnecessary here. I thought it can be useful to pass arguments as array somewhere, but well.
Passing arguments through ArgumentList property of ProcessStartInfo class is a little broken. It just doesn't work as inteneded, especially when I pass the arguments as one shell execute argument. Why? IDK, works when passed like in the example.
BTW, my ShellStartInfo class also sets some other properties of internal ProcessStartInfo that makes it useful to read the command's output.
In my remote shell it just works, the local terminal behaves as the remote one. It's not full blown Putty clone, it's just a tiny tool to have a little piece of "ssh" to a client device that doesn't even have external IP, open SSH port or may even not accept TCP connections. In fact - the client is a small IoT device that runs Linux.

C# - UWP AppInfo throws NotImplementedException

I'm developing a C# WinForms app, using the UWP API. I'm attempting to read notifications programatically, and I have succeeded so far. However, whenever I call AppInfo from the UserNotification class, I get a NotImplementedException, no matter what property I read from AppInfo.
Does anyone have any suggestions?
I have only been able to find 1 answer to this question and it's not very useful, and also a few years old. This is a major roadblock in my project, any help is massively appreciated!
Thank you in advance.
EDIT
Here's my code.
try {
this.source = notification.AppInfo.DisplayInfo.DisplayName;
} catch(NotImplementedException e) {
this.source = "Unspecified";
}
NotificationBinding binding = notification.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);
if (binding != null) {
Console.WriteLine(binding.GetTextElements()[1]);
this.title = binding.GetTextElements().FirstOrDefault()?.Text;
this.body = string.Join("\n", binding.GetTextElements().Skip(1).Select(t => t.Text));
}
Init();
I'm using the code from the examples in the docs.
UWP AppInfo throws NotImplementedException
Based on the exception message, it looks like notification.AppInfo.DisplayInfo has not
been implemented for WinForm platform. For this scenario, we have a workaround for getting AppInfo with [AppDiagnosticInfo][1] api. Please refer the following code
var list = await AppDiagnosticInfo.RequestInfoAsync();
var currentPackage = list.Where(o => o.AppInfo.PackageFamilyName == Package.Current.Id.FamilyName).FirstOrDefault();
if (currentPackage != null)
{
AppInfo currentAppInfo = currentPackage.AppInfo;
var display = currentAppInfo.DisplayInfo;
}
private async Task<AppDisplayInfo> GetDisplayInfo()
{
var list = await AppDiagnosticInfo.RequestInfoAsync();
var currentPackage = list.FirstOrDefault(o => o.AppInfo.PackageFamilyName == Package.Current.Id.FamilyName);
if (currentPackage != null)
{
var currentAppInfo = currentPackage.AppInfo;
var display = currentAppInfo.DisplayInfo;
return display;
}
return null;
}

Call Monitor using C# TAPI

I need to create a program to monitor activity of phone call.And get information about the calls, like number and Name.
I'm not strong with TAPI code and C#, so hope that anybody can help me, I'm desperate.
I have this code where I try to detect the available devices and obtain information from those devices when a call comes in:
using System;
using TAPI3Lib;
using JulMar.Atapi;
namespace ConsoleApp1
{
class Program
{
private void tapi_ITTAPIEventNotification_Event(TAPI_EVENT TapiEvent, object pEvent)
{
try
{
ITCallNotificationEvent cn = pEvent as ITCallNotificationEvent;
if(cn.Call.CallState == CALL_STATE.CS_CONNECTED)
{
string calledidname = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNAME);
Console.WriteLine("Called ID Name " + calledidname);
string callednumber = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNUMBER);
Console.WriteLine("Called Number " + callednumber);
string calleridname = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNAME);
Console.WriteLine("Caller ID Name " + calleridname);
string callernumber = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNUMBER);
Console.WriteLine("Caller Number " + callernumber);
}
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
static void Main(string[] args)
{
TapiManager mgr = new TapiManager("ConsoleApp1");
mgr.Initialize();
foreach(TapiLine line in mgr.Lines)
{
foreach (string s in line.Capabilities.AvailableDeviceClasses)
Console.WriteLine("{0} - {1}", line.Name, s);
}
}
}
}
But when I run it, just see the available devices but don't see any information about calls. I'm used to programming in java so I guess I should send to call the method that gets the call information in the main, but I do not know how to do that and in any code I've seen they do.
So, hope that you can help me to understand how TAPI works and what can I do to do my code work.
Okay, first, you want to stick to one Version of TAPI. In your using statements, youre importing a TAPI 2.x managed libaray and a TAPI 3.x managed library.
using TAPI3Lib; // this is a TAPI 3.x library
using JulMar.Atapi; // this is a TAPI 2.x library
If you're choosing to go with TAPI 3.x, you should start by creating a new class, which handles different kinds of TAPI events. To do so, it needs to implement the ITTAPIEventNotification interface:
public class CallNotification : ITTAPIEventNotification
{
public void Event(TAPI_EVENT TapiEvent, object pEvent)
{
if(pEvent == null)
throw new ArgumentNullException(nameof(pEvent));
switch (TapiEvent)
{
case TAPI_EVENT.TE_CALLNOTIFICATION:
// This event will be raised every time a new call is created on an monitored line-
// You can use CALLINFO_LONG.CIL_ORIGIN to see weather it's an inbound call, or an
// outbound call.
break;
case TAPI_EVENT.TE_CALLSTATE:
// This event will be raised every time the state of a call on one of your monitored
// Lines changes.
// If you'd want to read information about a call, you can do it here:
ITCallStateEvent callStateEvent = (ITCallStateEvent)pEvent;
ITCallInfo call = callStateEvent.Call;
string calledidname = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNAME);
Console.WriteLine("Called ID Name " + calledidname);
string callednumber = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNUMBER);
Console.WriteLine("Called Number " + callednumber);
string calleridname = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNAME);
Console.WriteLine("Caller ID Name " + calleridname);
string callernumber = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNUMBER);
Console.WriteLine("Caller Number " + callernumber);
break;
}
// Since you're working with COM objects, you should release any used references.
Marshal.ReleaseComObject(pEvent);
}
}
In order to use this class, you need to create a new instance of TAPI3Lib.TAPIClass and call its Initialize method. After that, you can attach your newly create CallNotification class as an event handler. You could also specify which types of events you want your handler to receive. Notice that you wont receive any event notifications at this point, because you haven't told TAPIClass which lines it should monitor:
CallNotification callevent = new CallNotification();
TAPIClass tapi = new TAPIClass();
tapi.Initialize();
tapi.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION | TAPI_EVENT.TE_CALLSTATE);
tapi.ITTAPIEventNotification_Event_Event += new ITTAPIEventNotification_EventEventHandler(callevent.Event);
In order to tell TAPIClass which lines it should monitor, you need to do two things. ask for all lines registered to you IPBX and determine, weather its a line you have the rights to monitor (this is an IPBX configuration):
public List<ITAddress> EnumerateLines(TAPIClass tapi)
{
List<ITAddress> addresses = new List<ITAddress>();
ITAddress address;
uint arg = 0;
ITAddressCapabilities addressCapabilities;
int callfeatures;
int linefeatures;
bool hasCallFeaturesDial;
bool hasLineFeaturesMakeCall;
IEnumAddress ea = tapi.EnumerateAddresses();
do
{
ea.Next(1, out address, ref arg);
if (address != null)
{
addressCapabilities = (ITAddressCapabilities)address;
callfeatures = addressCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_CALLFEATURES1);
linefeatures = addressCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_LINEFEATURES);
hasCallFeaturesDial = (callfeatures1 & (int)0x00000040) != 0; //Contains LineCallFeatures Dial; see Tapi.h for details
hasLineFeaturesMakeCall = (linefeatures & (int)0x00000008) != 0; //Contains LineFeatures MakeCall; see Tapi.h for details
// this is basically saying "Am I allowed to dial numbers and create calls on this specific line?"
if(hasCallFeaturesDial && hasLineFeaturesMakeCall)
addresses.Add(address);
}
} while (address != null);
return addresses;
}
public void RegisterLines(TAPIClass tapi, IEnumerable<ITAddress> addresses)
{
if (tapi == null)
throw new ArgumentNullException(nameof(tapi));
if (addresses == null)
throw new ArgumentNullException(nameof(addresses));
foreach (ITAddress address in addresses)
{
tapi.RegisterCallNotifications(address, true, true, TapiConstants.TAPIMEDIATYPE_AUDIO, 2);
}
}
so Your initialization would look like this:
CallNotification callevent = new CallNotification();
TAPIClass tapi = new TAPIClass();
tapi.Initialize();
IEnumerable<ITAddress> addresses = this.EnumerateLines(tapi);
this.RegisterLines(tapi, addresses);
tapi.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION | TAPI_EVENT.TE_CALLSTATE);
tapi.ITTAPIEventNotification_Event_Event += callevent.Event;
Once you run your program, and it's done executing above code, you will get notifications from incoming and outgoing calls when their call state changes.
I hope you could follow this post. If you have any questions, just ask =)
The line is TapiLine, you have to use TapiCall.

in app purchase issue in windows store app

I have created an app with in app purchase and tested it with CurrentAppSimulator and works fine but when i create the package for app it fails
This is my code for which i am creating package
public async System.Threading.Tasks.Task InAppInit()
{
var listing = await CurrentApp.LoadListingInformationAsync();
// Delux Unlock - Durable
var unlockFeatureDelux = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "deluxe" && p.Value.ProductType == ProductType.Durable);
isDeluxPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureDelux.Value.ProductId].IsActive;
deluxProductID = unlockFeatureDelux.Value.ProductId;
// Standard Unlock - Durable
var unlockFeatureStandard = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "standard" && p.Value.ProductType == ProductType.Durable);
isStarndardPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureStandard.Value.ProductId].IsActive;
standardProductID = unlockFeatureStandard.Value.ProductId;
}
I am calling this method OnLaunched in App.xaml.cs
Based on our discussion here is what I would do:
The LoadListingInformationAsync method uses internet and if the user does not have an internet connection then it will throw an exception. So I suggest to wrap this whole stuff into a try/ctach block
As we see the ProductListings does not contain any item. I don't know how this list is populated, but as long as your app is not in the store I would not be surprised when that list is empty (Maybe someone can help out here... but i did not find anything regarding this in the docs). So for this I would just simply check if the feature you need is in the list... With this your package will pass the test you mentioned and you can upload it. If the list is also empty when the package is installed via the store, then something with the IAP setting is wrong (but that is related to the store..)
And a general comment: Obviously this code is not complete... You need some code for purchasing the IAPs and here we only get the Ids.. (But I think you only pasted the relevant part anyway.)
So all this in code:
public async System.Threading.Tasks.Task InAppInit()
{
try
{
var listing = await CurrentApp.LoadListingInformationAsync();
if (listing.ProductListings.ContainsKey("deluxe"))
{
// Delux Unlock - Durable
var unlockFeatureDelux = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "deluxe" && p.Value.ProductType == ProductType.Durable);
isDeluxPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureDelux.Value.ProductId].IsActive;
deluxProductID = unlockFeatureDelux.Value.ProductId;
}
else
{
//There is no deluxe IAP defined... so something with your IAP stuff is wrong...
}
if (listing.ProductListings.ContainsKey("standard"))
{
// Standard Unlock - Durable
var unlockFeatureStandard = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "standard" && p.Value.ProductType == ProductType.Durable);
isStarndardPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureStandard.Value.ProductId].IsActive;
standardProductID = unlockFeatureStandard.Value.ProductId;
}
else
{
//same as for Delux
}
}
catch
{
//Show this on the UI...
}
}

Disable GeckoFX confirm messages

I am using Gecko Web browser version 21.0.1 and .net Framework 4.0 in my windows application.
When I navigate to certain web pages I get Pop up confirm message:
This web page is being redirected to a new location. Would you like to
resend the form data you have typed to the new location?
How can I disable this kind of messages?
So far I have tried the following settings, but they didn't help:
GeckoPreferences.User["security.warn_viewing_mixed"] = false;
GeckoPreferences.User["plugin.state.flash"] = 0;
GeckoPreferences.User["browser.cache.disk.enable"] = false;
GeckoPreferences.User["browser.cache.memory.enable"] = false;
You could try providing you own nsIPromptService2 / nsIPrompt implementation.
Run this early on program start up (Although after XPCom.Initalize)
PromptFactory.PromptServiceCreator = () => new FilteredPromptService();
Where FilteredPromptService is defined something like this:
internal class FilteredPromptService : nsIPromptService2, nsIPrompt
{
private static PromptService _promptService = new PromptService();
public void Alert(nsIDOMWindow aParent, string aDialogTitle, string aText)
{
if(/*want default behaviour */)
{
_promptService.Alert(aDialogTitle, aText);
}
// Else do nothing
}
// TODO: implement other methods in similar fashion. (returning appropriate return values)
}
You will also need to make sure that error pages are not enabled:
GeckoPreferences.User["browser.xul.error_pages.enabled"] = false;

Categories