rbtray icon indicating copy to clipboard operation
rbtray copied to clipboard

Allow multiple instances

Open cosmicdan opened this issue 5 years ago • 6 comments

Given the new info shared in this comment, it would be great if we could run multiple instances of RBTray - one for each user.

cosmicdan avatar Mar 20 '19 07:03 cosmicdan

Simply removing this check to see if it's already running wouldn't actually work, I tried it.

ghost avatar May 16 '19 15:05 ghost

Hi @cosmicdan,

As @Aidolii says, this is not as simple as it sounds. But theoretically, if it were implemented, how would you manage multiple running instances of RBTray? How would RBTray arbitrate between the instances when you performed one of the minimize steps? I'm wondering if it might be better to keep a single instance of RBTray but experiment with adjusting privileges on the fly? Still, even that might not work, so if you have any ideas on how to go against Microsoft's security model on this but keep a decent user experience, please let me know.

Thanks, Ben

benbuck avatar May 24 '19 05:05 benbuck

@benbuck I hope you can find some sort of acceptable workaround. rbtray's main use for me is traying command prompts. Some things like aria2 in server mode has to be ran as admin in order to use the 'falloc' file allocation method, and every utility I've come across for traying aria2 doesn't seem to be compatible with running it as admin.

Then of course, I have other things running in a command prompt that are not being ran as admin. But as it stands, I can only tray one or the other.

The only way I know of to allow a program to possibly do this (and I'm not 100% sure it would work) is the uiAccess attribute, which has some special programming considerations in this context. Documentation on it uiAccess attribute is mainly located here, in an article talking about a security policy to allow it: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-account-control-allow-uiaccess-applications-to-prompt-for-elevation-without-using-the-secure-desktop

You could require users enable this policy to use a special version of the program, or you could go with a scripted method that signs that exe file with the machine it's being ran on in order to not require this policy. AutoHotkey does it for the UI Access enabled version of their program. Here is the AutoHotkey script it runs, which could provide some hints as to do it yourself if this is the way you choose to go:

EnableUIAccess("ExeFileToBeSigned.exe")
return

EnableUIAccess(filename)
{
    hStore := DllCall("Crypt32\CertOpenStore", "ptr", 10 ; STORE_PROV_SYSTEM_W
        , "uint", 0, "ptr", 0, "uint", 0x20000 ; SYSTEM_STORE_LOCAL_MACHINE
        , "wstr", "Root", "ptr")
    if !hStore
        throw
    p := DllCall("Crypt32\CertFindCertificateInStore", "ptr", hStore
        , "uint", 0x10001 ; X509_ASN_ENCODING|PKCS_7_ASN_ENCODING
        , "uint", 0, "uint", 0x80007 ; FIND_SUBJECT_STR
        , "wstr", "AutoHotkey", "ptr", 0, "ptr")
    if p
        cert := new CertContext(p)
    else
        cert := EnableUIAccess_CreateCert("AutoHotkey", hStore)
    EnableUIAccess_SetManifest(filename)
    EnableUIAccess_SignFile(filename, cert, "AutoHotkey")
}
EnableUIAccess_SetManifest(file)
{
    xml := ComObjCreate("Msxml2.DOMDocument")
    xml.async := false
    xml.setProperty("SelectionLanguage", "XPath")
    xml.setProperty("SelectionNamespaces"
        , "xmlns:v1='urn:schemas-microsoft-com:asm.v1' "
        . "xmlns:v3='urn:schemas-microsoft-com:asm.v3'")
    if !xml.load("res://" file "/#24/#1") ; Load current manifest
        throw
    node := xml.selectSingleNode("/v1:assembly/v3:trustInfo/v3:security"
                    . "/v3:requestedPrivileges/v3:requestedExecutionLevel")
    if !node ; Not AutoHotkey v1.1?
        throw
    node.setAttribute("uiAccess", "true")
    xml := RTrim(xml.xml, "`r`n")
    VarSetCapacity(data, data_size := StrPut(xml, "utf-8") - 1)
    StrPut(xml, &data, "utf-8")
    if !(hupd := DllCall("BeginUpdateResource", "str", file, "int", false))
        throw
    r := DllCall("UpdateResource", "ptr", hupd, "ptr", 24, "ptr", 1
                    , "ushort", 1033, "ptr", &data, "uint", data_size)
    if !DllCall("EndUpdateResource", "ptr", hupd, "int", !r) && r
        throw
}
EnableUIAccess_CreateCert(CertName, hStore)
{
    if !DllCall("Advapi32\CryptAcquireContext", "ptr*", hProv
            , "str", CertName, "ptr", 0, "uint", 1, "uint", 8) ; PROV_RSA_FULL=1, CRYPT_NEWKEYSET=8
        throw
    prov := new CryptContext(hProv)
    if !DllCall("Advapi32\CryptGenKey", "ptr", hProv
            , "uint", 2, "uint", 0x4000001, "ptr*", hKey) ; AT_SIGNATURE=2, EXPORTABLE=..01
        throw
    key := new CryptKey(hKey)
    Loop 2
    {
        if A_Index = 1
            pbName := cbName := 0
        else
            VarSetCapacity(bName, cbName), pbName := &bName
        if !DllCall("Crypt32\CertStrToName", "uint", 1, "str", "CN=" CertName
            , "uint", 3, "ptr", 0, "ptr", pbName, "uint*", cbName, "ptr", 0) ; X509_ASN_ENCODING=1, CERT_X500_NAME_STR=3
            throw
    }
    VarSetCapacity(cnb, 2*A_PtrSize), NumPut(pbName, NumPut(cbName, cnb))
    VarSetCapacity(endTime, 16)
    DllCall("GetSystemTime", "ptr", &endTime)
    NumPut(NumGet(endTime, "ushort") + 10, endTime, "ushort") ; += 10 years
    if !hCert := DllCall("Crypt32\CertCreateSelfSignCertificate"
        , "ptr", hProv, "ptr", &cnb, "uint", 0, "ptr", 0
        , "ptr", 0, "ptr", 0, "ptr", &endTime, "ptr", 0, "ptr")
        throw
    cert := new CertContext(hCert)
    if !DllCall("Crypt32\CertAddCertificateContextToStore", "ptr", hStore
        , "ptr", hCert, "uint", 1, "ptr", 0) ; STORE_ADD_NEW=1
        throw
    return cert
}
class CryptContext {
    __New(p) {
        this.p := p
    }
    __Delete() {
        DllCall("Advapi32\CryptReleaseContext", "ptr", this.p, "uint", 0)
    }
}
class CertContext extends CryptContext {
    __Delete() {
        DllCall("Crypt32\CertFreeCertificateContext", "ptr", this.p)
    }
}
class CryptKey extends CryptContext {
    __Delete() {
        DllCall("Advapi32\CryptDestroyKey", "ptr", this.p)
    }
}
EnableUIAccess_SignFile(File, CertCtx, Name)
{
    VarSetCapacity(wfile, 2 * StrPut(File, "utf-16")), StrPut(File, &wfile, "utf-16")
    VarSetCapacity(wname, 2 * StrPut(Name, "utf-16")), StrPut(Name, &wname, "utf-16")
    cert_ptr := IsObject(CertCtx) ? CertCtx.p : CertCtx
    EnableUIAccess_Struct(file_info, "ptr", A_PtrSize*3 ; SIGNER_FILE_INFO
        , "ptr", &wfile)
    VarSetCapacity(dwIndex, 4, 0) ; DWORD
    EnableUIAccess_Struct(subject_info, "ptr", A_PtrSize*4 ; SIGNER_SUBJECT_INFO
        , "ptr", &dwIndex, "ptr", SIGNER_SUBJECT_FILE:=1, "ptr", &file_info)
    EnableUIAccess_Struct(cert_store_info, "ptr", A_PtrSize*4 ; SIGNER_CERT_STORE_INFO
        , "ptr", cert_ptr, "ptr", SIGNER_CERT_POLICY_CHAIN:=2)
    EnableUIAccess_Struct(cert_info, "uint", 8+A_PtrSize*2 ; SIGNER_CERT
        , "uint", SIGNER_CERT_STORE:=2, "ptr", &cert_store_info)
    EnableUIAccess_Struct(authcode_attr, "uint", 8+A_PtrSize*3 ; SIGNER_ATTR_AUTHCODE
        , "int", false, "ptr", true, "ptr", &wname)
    EnableUIAccess_Struct(sig_info, "uint", 8+A_PtrSize*4 ; SIGNER_SIGNATURE_INFO
        , "uint", CALG_SHA1:=0x8004, "ptr", SIGNER_AUTHCODE_ATTR:=1
        , "ptr", &authcode_attr)
    hr := DllCall("MSSign32\SignerSign"
        , "ptr", &subject_info, "ptr", &cert_info, "ptr", &sig_info
        , "ptr", 0, "ptr", 0, "ptr", 0, "ptr", 0, "uint")
    if (hr != 0)
        throw hr
}
EnableUIAccess_Struct(ByRef struct, arg*)
{
    VarSetCapacity(struct, arg[2], 0), p := &struct
    Loop % arg.Length()//2
        p := NumPut(arg[2], p+0, arg[1]), arg.RemoveAt(1, 2)
    return &struct
}

ghost avatar Jun 12 '19 16:06 ghost

Honestly, I have no technical expertise to provide here, I work in Java - haven't touched C(++) for years. I tried to learn about how RBTray works and I've realized that I really just don't like the right-click-minimize way of doing things. RBTray has to hook the mouse and that causes issues.

I predict that, if you were to consider changing to a Shift-Click Minimize instead of Right-click, many problems could be solved as it's less intrusive and fragile. But... it'd probably be a bit of rework. Still, it's not hard - you can just hook for EVENT_SYSTEM_MINIMIZESTART and/or EVENT_SYSTEM_MINIMIZEEND, check if SHIFT is held down via GetAsyncKeyState (then minimize to tray the usual way if so). Doing it this way, if the call you do to SetWinEventHook is made from an elevated context, then it will hook every window - not just the other elevated-windows (well, it does for other events, iirc). Also, the lack of a mouse hook should completely remove every performance issue/bug with games (that I have seen reported here, and also encountered myself in other games).

If you've no interest in that though, that's fine and perfectly understandable. I could pretty easily replicate all this myself in a tiny Java app via JNR. (Or maybe I will just for fun, the world needs to know about modular Java applications haha).

cosmicdan avatar Jul 25 '19 06:07 cosmicdan

I hacked on it for a bit and was able to get a user-level instance running alongside an admin-level instance by changing one of the instances to RBTray2 and using MBUTTON instead of RBUTTON.

garfieldbanks avatar Dec 26 '19 10:12 garfieldbanks

Why won't removing (or rather modifying) the check to see if it's already running and using two different instances (e.g. RBTray.exe and RBTray_Admin.exe) not work? Instead of checking if it's running by the current method, which detects it regardless of the name, check either if the exe the user is attempting to run is currently running or if it's currently running with or without admin privileges, depending on how the user is trying to run it, which would then allow one, and only one, of each admin and non-admin instances to run.

Then whenever the user right-clicks on the minimize button in a window, both instances would intercept the click, but only one would actually be able to succeed, whereas the other would just do nothing, as is currently the case.

vertigo220 avatar Jul 30 '22 02:07 vertigo220