Xamarin MacOS app launch at login - xamarin

I'm trying to make my Xamarin MacOS App to run at login. Looking around I found only very old topics with no solutions.
Is there any solution available? Is feasible?
My App runs, by default with Administrator Privileges
Thanks

I wrote this article some time ago. It should still be valid https://shamsutdinov.net/2016/09/27/how-to-launch-at-login-your-xamarin-mac-sandboxed-application/
tl;dr;
Your app should run in the sandbox:
In your main app add this code
public class StartAtLoginOption
{
[DllImport("/System/Library/Frameworks/ServiceManagement.framework/ServiceManagement")]
static extern bool SMLoginItemSetEnabled(IntPtr aId, bool aEnabled);
public static bool StartAtLogin(bool value)
{
CoreFoundation.CFString id = new CoreFoundation.CFString("my.helper.app.bundle.id");
return SMLoginItemSetEnabled(id.Handle, value);
}
}
Create a helper app which only runs in background:
Start your main app from the helper app:
public override void DidFinishLaunching(NSNotification notification)
{
if (!NSWorkspace.SharedWorkspace.RunningApplications.Any(a => a.BundleIdentifier == "my.main.app.bundle.id"))
{
var path = new NSString(NSBundle.MainBundle.BundlePath)
.DeleteLastPathComponent()
.DeleteLastPathComponent()
.DeleteLastPathComponent()
.DeleteLastPathComponent();
var pathToExecutable = path + #"Contents/MacOS/LoginItemTestMain";
if (NSWorkspace.SharedWorkspace.LaunchApplication(pathToExecutable)) { }
else NSWorkspace.SharedWorkspace.LaunchApplication(path);
}
NSApplication.SharedApplication.Terminate(this);
}

This method works also for "non sandboxed" apps. I don't really like it but, for the moment, it's working:
public void SetAtLogin()
{
//Checking if the app is in the login items or not
var script = "tell application \"System Events\"\n get the name of every login item\n if login item \"AppNameTest\" exists then\n return true\n else\n return false\n end if\n end tell";
NSAppleScript appleScript = new NSAppleScript(script);
var errors = new NSDictionary();
NSAppleEventDescriptor result = appleScript.ExecuteAndReturnError(out errors);
var isLoginItem = result.BooleanValue;
if (!isLoginItem)
{
NSAppleScript login;
//AppleScript to add app to login items
script = "tell application \"System Events\"\n make new login item at end of login items with properties {name: \"AppNameTest\", path:\"/Applications/DayOne.app\", hidden:false}\n end tell";
login = new NSAppleScript(script);
var resul = login.ExecuteAndReturnError(out errors);
}
}

Related

URI start of MAUI Windows app creates a new instance. I need to have only one instance of app

I am able to start my Windows MAUI app using an URI, and I can get the URI itself. But, it appears that a NEW instance of the app is being created. This is not ideal for me -- if my app is already running, I want to use that instance.
I have done something like this for a Xamarin.Forms app. I override OnActivated in Application class.
Re: my MAUI app, I'm not even clear on whether the issue is how I've done the "protocol" in package.appxmanifest, or if it is how I respond to lifecycle events.
The default behaviour is to run multiple instances of your app. You can make the app single-instanced by defining a customized class with a Main method as suggested in this blog post:
[STAThread]
static async Task Main(string[] args)
{
WinRT.ComWrappersSupport.InitializeComWrappers();
bool isRedirect = await DecideRedirection();
if (!isRedirect)
{
Microsoft.UI.Xaml.Application.Start((p) =>
{
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new App();
});
}
return 0;
}
private static async Task DecideRedirection()
{
bool isRedirect = false;
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind;
AppInstance keyInstance = AppInstance.FindOrRegisterForKey("randomKey");
if (keyInstance.IsCurrent)
{
keyInstance.Activated += OnActivated;
}
else
{
isRedirect = true;
await keyInstance.RedirectActivationToAsync(args);
}
return isRedirect;
}
There is an open suggestion to simplify this process available on GitHub.

Windows Application Driver, error "Could not find any recognizable digits." when connecting to session (driver)

I know how to launch a windows application using the filepath to launch it and that works (working example below). I am writing tests and they work too but my question is this: If the application is running already, how do I create my "session" (often called "driver") for the currently running application?
I have read this article that explains how you would connect a new session to Cortana which is already running. It's a great example but my app is an exe that has been launched and is not part of windows and I'm getting the error "Could not find any recognizable digits.".
What am I doing wrong?
WORKING CODE THAT LAUNCHES THE APP AND CREATES THE "session":
private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
protected static WindowsDriver<RemoteWebElement> session;
public static void Setup(TestContext context)
{
// Launch app and populate session
if (session == null)
{
// Create a new sessio
DesiredCapabilities appCapabilities = new DesiredCapabilities();
appCapabilities.SetCapability("app", filepath /*The exeecutable's filepath on c drive*/);
//LaunchWPF app and wpf session
session = new WindowsDriver<RemoteWebElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities);
session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
}
}
PROBLEM CODE :
[TestMethod()]
public void Common_CreateSession_ForAlreadyRunningmyApp()
{
string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
IntPtr myAppTopLevelWindowHandle = new IntPtr();
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.Contains("MyApp.Client.Shell"))
{
myAppTopLevelWindowHandle = clsProcess.Handle;
}
}
DesiredCapabilities appCapabilities = new DesiredCapabilities();
appCapabilities.SetCapability("appTopLevelWindow", myAppTopLevelWindowHandle);
//Create session for app that's already running (THIS LINE FAILS, ERROR: : 'Could not find any recognizable digits.')
session = new WindowsDriver<RemoteWebElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities);
session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
}
}
There's now an answer on github here. You can see on github I have made 3 tweaks to the answer given by moonkey124, 2 of them were obvious (my aplication name and a little sleep command), 1 of them was to adapt the answer to a WPF application under test...

What is the right way to verify if local Storage variable exists? - XAMARIN

Hi I'm doing an app that has a login, and it has the option to keep you login even if you turn off the app.
What is the problem? This is what I'm doing this in App.cs:
var statusLog = Application.Current.Properties["logStatus"].ToString();
if (statusLog == "F")
{
Application.Current.MainPage = new NavigationPage(new LoginPage());
}
else
{
var userStore = (Application.Current.Properties["user"].ToString());
Task.Run(() => lp.GetTokenLogin()).Wait();
MainPage = new NavigationPage(new ConfirmarViatura(userStore));
}
It works fine, but their is one situation that it do not work, that is if I run the app for the first time in the device, it gives me a exception that the local variable "logStatus" do not exists.
I understand it do not exists, but how can i do this verification?
I can't do this :
if (Application.Current.Properties["logStatus"].Equals(null))
{
Application.Current.Properties["logStatus"] = "F";
}
This do not work because the variable dosen't even exists. Any ideas?
You can check it this way:
if (Application.Current.Properties.ContainsKey("logStatus"))
{
var statusLog = Application.Current.Properties["logStatus"] as string;
// rest of your code
}
You can check first if it exists, then do your logic and assigning.
if (App.Current.Properties.Exists("logStatus"))

Xamarin Auth account store

I'm trying to implement Xamairn Auth with my app. I've installed the nuget package from https://www.nuget.org/packages/Xamarin.Auth.
Following their example I have the following code in the shared project.
public void SaveCredentials (string userName, string password)
{
if (!string.IsNullOrWhiteSpace (userName) && !string.IsNullOrWhiteSpace (password)) {
Account account = new Account {
Username = userName
};
account.Properties.Add ("Password", password);
AccountStore.Create ().Save (account, App.AppName);
}
}
When run on android, it saves the username and password but I'm getting the following message in the console:
"This version is insecure, because of default password.
Please use version with supplied password for AccountStore.
AccountStore.Create(Contex, string) or AccountStore.Create(string);"
I tried passing a parameter to the AccountStore.Create() method but it doesn't seem to take one. Something like this:
#if ANDROID
_accountStore = AccountStore.Create(Application.Context);
#else
_accountStore = AccountStore.Create();
#endif
Do I need to write android specific code to extend the create method.
I understand why you deleted the non-answer, I thought that would show interest in the question. I guess I should have upvoted the question instead. Anyways, here's the answer I found.
You can't use the PCL version for android. It doesn't have an option to add a password. I used the android specific version. Will call it using dependency service.
Here's an example:
Account account = null;
try
{
//account = AccountStore.Create(Application.ApplicationContext, "System.Char[]").FindAccountsForService("My APP").FirstOrDefault();
var aStore = AccountStore.Create(Application.ApplicationContext, "myownpassword");
// save test
account = aStore.FindAccountsForService(Constants.AppName).FirstOrDefault();
if (account == null)
account = new Account();
account.Username = "bobbafett";
account.Properties["pswd"] = "haha";
aStore.Save(account, Constants.AppName);
// delete test, doesn't seem to work, account is still found
var accts = aStore.FindAccountsForService(Constants.AppName);
int howMany = accts.ToList().Count;
foreach (var acct in accts)
{
aStore.Delete(acct, Constants.AppName);
}
account = aStore.FindAccountsForService(Constants.AppName).FirstOrDefault();
}
catch (Java.IO.IOException ex)
{
// This part is not invoked anymore once I use the suggested password.
int i1 = 123;
}
I was able to get it to work by implementing a getAccountStore method in android which has an option to add a password, then use DependencyService to call it.
public AccountStore GetAccountStore()
{
try
{
var acctStore = AccountStore.Create(Application.Context, "somePassword");
return acctStore;
}
catch (Java.IO.IOException ex)
{
throw ex;
}
}
Then in your pcl project call it as such:
if (Device.RuntimePlatform == Device.Android)
_accountStore = DependencyService.Get<IAccountStoreHelper>().GetAccountStore();
else
_accountStore = AccountStore.Create();

Fail to attach windows service with Skype4COM to Skype Client

I tried to create a windows service which will allow to interact with Skype Client.
I'm using SKYPE4COM.DLL lib.
When I create a simple console or win32 aplication all works ok (I have the Skype request for this application and it works well). But when I try to run this application as a service,
I have an error
Service cannot be started. System.Runtime.InteropServices.COMException (0x80040201): Wait timeout.
at SKYPE4COMLib.SkypeClass.Attach(Int32 Protocol, Boolean Wait)
at Commander.Commander.OnStart(String[] args)
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)
And I have no notification about process connecting to Skype.
Can you give me an advice how to attach service to Skype client or maybe I need to change my Skype settings?
I think it is not possible due to Windows User Id security restrictions. You have to run your application under the same user as Skype otherwise it won't be able to attach.
I had the same issue.
Resolved it by converting it to Windows Application and using it as System Tray App:
[STAThread]
static void Main()
{
Log.Info("starting app");
//facade that contains all code for my app
var facade = new MyAppFacade();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (ProcessIcon icon = new ProcessIcon(facade))
{
icon.Display();
Application.Run();
}
}
public class ProcessIcon : IDisposable
{
private readonly MyAppFacade facade;
private NotifyIcon ni;
public ProcessIcon(MyAppFacade facade)
{
this.facade = facade;
this.ni = new NotifyIcon();
}
public void Display()
{
ni.Icon = Resources.Resources.TrayIcon;
ni.Text = "Skype soccer";
ni.Visible = true;
// Attach a context menu.
ni.ContextMenuStrip = new ContextMenuStrip();
var start = new ToolStripMenuItem("Start");
start.Click += (sender, args) => facade.Start();
ni.ContextMenuStrip.Items.Add(start);
var stop = new ToolStripMenuItem("Stop");
stop.Click += (sender, args) => facade.Stop();
ni.ContextMenuStrip.Items.Add(stop);
var exit = new ToolStripMenuItem("Exit");
exit.Click += (sender, args) => Application.Exit();
ni.ContextMenuStrip.Items.Add(exit);
}
public void Dispose()
{
ni.Dispose();
}
}

Resources