MrTanoshii / rusty-autoclicker

A pure Rust portable auto clicker built for Linux, macOS & Windows.
Creative Commons Zero v1.0 Universal
71 stars 12 forks source link

Add icon to binary file #9

Closed MrTanoshii closed 1 year ago

MrTanoshii commented 1 year ago

Problem

The app icon is currently implemented in the app itself but not on the binary file.

explorer_2y9u6T4fld

Potential Solutions

bryonye commented 1 year ago

Hi, I’m interested in having a go at this!

MrTanoshii commented 1 year ago

Hi, I’m interested in having a go at this!

Of course, you are very much welcome to do so! Thank you for the interest.

I had been looking into it lately too, I think winres will not work out after all as Rusty AutoClicker uses rust-version = "1.63" and there is a an open issue https://github.com/mxre/winres/issues/40 starting from 1.61

Another crate to look into is https://crates.io/crates/embed-resource (Windows). I haven't found any potential solutions for Linux and macOS.

Edit: Linux executables do not have icons

MrTanoshii commented 1 year ago

@bryonye Are you still interested in this?

MrTanoshii commented 1 year ago

https://github.com/emilk/egui/discussions/2026#discussioncomment-4025176

nozwock commented 1 year ago

@MrTanoshii Hey how's it going? I just looked up your profile and found this, Would you like me to submit a PR for this? If you haven't made the changes already that is.

Regarding macOS, I think I've read before on how to add an Icon to a macOS binary (will have to look that up again) but I don't really have an access to a macOS, so can't really test it out.

MrTanoshii commented 1 year ago

Yes, if you're up for it please do submit a PR. It will be very welcome!

I'm assigning this to you

nozwock commented 1 year ago

Alright, I'll submit it tomorrow. It's late here rn.

nozwock commented 1 year ago

Hey @MrTanoshii, I wanted to confirm something; Why do we've a lib crate in this package? Am I missing something? Because to me It makes not much sense to have a lib crate here.

The reason I'm mentioning this is because unfortunately winres doesn't work with packages having both a lib crate and bin crate, as mentioned here https://github.com/mxre/winres/issues/32#issuecomment-933035162 (seems to be due to cargo limitations).

Some possible solutions:

  1. Get rid of lib.rs and then use winres. (very straightforward to do, overkill?)
  2. Dividing the pkg into different pkgs (with cargo workspaces) and then make use of the lib pkg in the bin pkg. (overkill?)
  3. Just have something like rcedit apply the resources after the build process (can be automated for eg. with CI).
  4. Explore some other options like the embed-resource for instance.
nozwock commented 1 year ago

And coming to Icons, should I just use the existing icon-64x64.ico or rather make a new .ico of higher res (like 256x256 or 512x512)?

MrTanoshii commented 1 year ago

https://github.com/MrTanoshii/rusty-autoclicker/pull/25#discussion_r1015284486

Let's go with 256x256

ITachiLab commented 1 year ago

Hey, just to let you know that I successfully forced resources to be added to the binary crate in a project with both library and binary crate. You can check my workaround there: https://github.com/mxre/winres/issues/32#issuecomment-1304805763.

I'm not involved in this project, but removing lib.rs is slight overkill imho. Even in the official documentation authors of Rust suggest dividing application into binary and library crates.

nozwock commented 1 year ago

I'm not involved in this project, but removing lib.rs is slight overkill imho. Even in the official documentation authors of Rust suggest dividing application into binary and library crates.

Hi @ITachiLab, It's not that I wanted to remove lib.rs just for the sake of making winres work, but also that I didn't get the point of having a lib.rs here. We can just have functionalities divided into modules, can't we? What's wrong with that approach? Also, it just feels weird to me to have app.rs as part of a lib crate.

Actually I'm new to rust, so it'd be great if you could let me know your thoughts on this. I guess this is probably already have been discussed in some later chapter of the Rust book, which I'm currently going through.

Thanks for mentioning the workaround btw, I'll look into it.

nozwock commented 1 year ago

Oh @ITachiLab actually, I remember trying that workaround before I removed the lib crate. It didn't workout on my end unfortunately; using Rust 1.64 and this winres fork.

I guess I'll try it again, just to be sure.

Edit: tried again, didn't work (doesn't work with cdylib either, just gives different error)

Compiling rusty-autoclicker v2.1.0 (/home/nozwock/hub/dev/forks/rusty-autoclicker)
error: the linking modifiers `+bundle` and `+whole-archive` are not compatible with each other when generating rlibs

error: could not compile `rusty-autoclicker` due to previous error

Here's the build.rs

use std::io;
use winres::WindowsResource;

fn main() -> io::Result<()> {
    if std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" {
        let mut res = WindowsResource::new();
        let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
        match env.as_str() {
            "gnu" => {
                res.set_ar_path("x86_64-w64-mingw32-ar")
                    .set_windres_path("x86_64-w64-mingw32-windres");
            }
            "msvc" => {}
            _ => panic!("unsupported target-env: {}", env),
        };
        res.set_icon("assets/icon-256.ico");
        res.compile()?;
        println!("cargo:rustc-link-arg-bins=resource.lib");
    }
    Ok(())
}

Hmm, I was using x86_64-pc-windows-gnu target, maybe this would work fine with x86_64-pc-windows-msvc but I don't have a windows machine right now to test it.

ITachiLab commented 1 year ago

Hmm, I was using x86_64-pc-windows-gnu target, maybe this would work fine with x86_64-pc-windows-msvc but I don't have a windows machine right now to test it.

From what I can see in winres source code here: https://github.com/mxre/winres/blob/master/lib.rs#L597, when using GNU tools for compiling, winres creates libresource.a instead of resource.lib, which is understandable because .lib format is used only by Windows.

With that being said, you can try using one of these for the GNU tools:

println!("cargo:rustc-link-arg-bins=libresource.a");
println!("cargo:rustc-link-arg-bins=-lresource");

Actually I'm new to rust

No worries, me too 😅.

nozwock commented 1 year ago

winres creates libresource.a instead of resource.lib, which is understandable because .lib format is used only by Windows.

Didn't thought of that.

println!("cargo:rustc-link-arg-bins=-lresource");

This works (with original winres & Rust 1.65), https://github.com/mxre/winres/issues/40#issuecomment-1276785306. Don't know what the windows equivalent for this would be, since I don't know anything about linking flags.

#[cfg(unix)]
{
    println!("cargo:rustc-link-arg=-Wl,--whole-archive");
    println!("cargo:rustc-link-arg=-Wl,-Bstatic");
    println!("cargo:rustc-link-arg-bins=-lresource");
    println!("cargo:rustc-link-arg=-Wl,--no-whole-archive");
}
ITachiLab commented 1 year ago

For Windows and MSVC, having just println!("cargo:rustc-link-arg-bins=resource.lib"); is enough. I'm currently developing a Windows application under Windows and already had gone through the same problem of including resources. https://github.com/ITachiLab/rectangular/blob/master/build.rs#L8

Apparently, MSVC expects resources to appear in linker command line and knows how to deal with them without additional flags.

nozwock commented 1 year ago

For Windows and MSVC, having just println!("cargo:rustc-link-arg-bins=resource.lib"); is enough. I'm currently developing a Windows application under Windows and already had gone through the same problem of including resources. https://github.com/ITachiLab/rectangular/blob/master/build.rs#L8

I see, got it, Thanks!

btw @ITachiLab, I've looked up your project rectangular and it looks like it's completely related to the windows platform. Sorry for being a bother and this is getting off topic but I was hoping maybe you could help me with this Rust windows related issue (https://github.com/nozwock/ventoy-toybox/issues/1) of mine.

TL;DR I need to launch an .exe as a subprocess with elevated privileges (using UAC prompt) & process pwd set to parent directory of the .exe.

While searching I came across this post "how do I trigger a new process to be run as admin?". The API version being used there seems to be quite old 0.10 and also is unsafe necessary for this case? Any advice on how to approach this?

ITachiLab commented 1 year ago

No worries, we both learn by solving issues ^^.

The API version being used there seems to be quite old 0.10

It's waaaay outdated. The latest version of windows crate is 0.43.0, this is the version I'm using for Rectangular.

So, in order to run an executable as an administrator, you indeed must use ShellExecuteA function from Windows API. When working with Rust and Windows API, always browse two things simultaneously, the first and foremost is a documentation for Windows API itself, in our case this will be a page dedicated to ShellExecuteA: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea; the second is a documentation for the analogous function in windows crate: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/Shell/fn.ShellExecuteA.html. The crate for Windows stuff is developed by guys from Microsoft, so it's always in sync with Windows API. The crate documentation is rather laconic, but the most important thing is the search bar which you will be using for determining in which module the function lives, and information what features you must include to use the function.

Putting this all together, Cargo.toml:

[dependencies.windows]
version = "0.43.0"
features = [
    "Win32_UI_Shell",
    "Win32_Foundation",
    "Win32_UI_WindowsAndMessaging"
]

main.rs:

use windows::{
    s, Win32::UI::Shell::ShellExecuteA, Win32::Foundation::GetLastError,
    Win32::UI::WindowsAndMessaging::SW_NORMAL
};

fn main() {
    unsafe {
        let result = ShellExecuteA(
            None,
            s!("runas"),
            s!("C:\\Users\\Itachi\\AppData\\Local\\Programs\\Python\\Python38-32\\python.exe"),
            s!("-c \"import os; os.mkdir('test');\""),
            s!("C:\\Users\\Administrator"),
            SW_NORMAL
        );

        println!("{}", GetLastError().0);
    }

    assert!(result.0 > 32);
}

What I'm doing here is running Python as an Administrator, setting C:\Users\Administrator as the working directory, and I'm passing a simple script to the interpreter; the script simply creates a 'test' directory within the working directory. I ran this program successfully, UAC asked for permissions and the directory has been created in Administrator's home directory, which means it truly has started with correct permissions.

nozwock commented 1 year ago

@ITachiLab Thanks a lot! I got it to work through your examples.

https://github.com/nozwock/ventoy-toybox/pull/6