Skip to content

Fix Alt-tabbing while using hand tool locks the hand tool (fix #325)#5601

Closed
Gasparoken wants to merge 1 commit into
aseprite:mainfrom
Gasparoken:fix-alt-tab
Closed

Fix Alt-tabbing while using hand tool locks the hand tool (fix #325)#5601
Gasparoken wants to merge 1 commit into
aseprite:mainfrom
Gasparoken:fix-alt-tab

Conversation

@Gasparoken

@Gasparoken Gasparoken commented Dec 16, 2025

Copy link
Copy Markdown
Member

fix #325

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/ui/editor/editor.cpp Outdated
(m_proj.scaleX() <= 1.0 && m_proj.scaleY() <= 1.0)));
}

void Editor::cancelOperation()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: method 'cancelOperation' can be made static [readability-convert-member-functions-to-static]

src/app/ui/editor/editor.h:114:

-   void cancelOperation();
+   static void cancelOperation();

Comment thread src/ui/manager.cpp Outdated

static CancelActionsCallback cancelActionsCallback = nullptr;

void Manager::setCancelActionsCallback(CancelActionsCallback callback)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: method 'setCancelActionsCallback' can be made static [readability-convert-member-functions-to-static]

src/ui/manager.h:146:

-   void setCancelActionsCallback(CancelActionsCallback callback);
+   static void setCancelActionsCallback(CancelActionsCallback callback);

Comment thread src/ui/manager.cpp Outdated
cancelActionsCallback = callback;
}

void Manager::cancelActions()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: method 'cancelActions' can be made static [readability-convert-member-functions-to-static]

src/ui/manager.h:147:

-   void cancelActions();
+   static void cancelActions();

@dacap

dacap commented Dec 17, 2025

Copy link
Copy Markdown
Member

@Gasparoken please give a proper title to the PR

@Gasparoken Gasparoken changed the title Draft Fix Alt-tabbing while using hand tool locks the hand tool (fix #325) Dec 17, 2025
@Gasparoken

Gasparoken commented Dec 17, 2025

Copy link
Copy Markdown
Member Author

@Gasparoken please give a proper title to the PR

Ok, no problem, by the way: @dacap If you're available, could you check if the approach is correct? I'm still testing on Windows and trying to address the hand tool issue with the mouse middle button.

@Gasparoken Gasparoken marked this pull request as ready for review December 18, 2025 19:09
@Gasparoken Gasparoken requested a review from dacap as a code owner December 18, 2025 19:09
Comment thread src/ui/manager.cpp Outdated
enqueueMessage(msg);
break;
}
case os::Event::CancelActions: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about introducing this new os.Event in laf as is...because it doesn't seem to map well to an actual OS event. Should this event be fired when the program's window is not the main/focused/active window any more?

Comment thread src/ui/manager.cpp Outdated
// when the os::Display is resized by the user.
static bool auto_window_adjustment = true;

static CancelActionsCallback cancelActionsCallback = nullptr;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should avoid using a static callback for this.

Comment thread src/ui/manager.cpp Outdated
break;
}
case os::Event::CancelActions: {
cancelActions();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should follow the same principles as for the other events: translating the os Event into a Message. Then you will be able to handle that Message in the Editor's onProcessMessage method and call your Editor::cancelOperation() method.

@Gasparoken Gasparoken force-pushed the fix-alt-tab branch 2 times, most recently from 1b55755 to b9223cc Compare December 22, 2025 20:55

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/ui/manager.cpp

case os::Event::AppLostFocus: {
if (mouse_widget) {
auto msg = new AppLostFocusMessage();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: 'auto msg' can be declared as 'auto *msg' [readability-qualified-auto]

Suggested change
auto msg = new AppLostFocusMessage();
auto *msg = new AppLostFocusMessage();

…te#325)

AppLostFocusMessage was added to tell the Editor to disable the
'quick hand tool' when the application loses focus via Alt+tab.
@dacap

dacap commented Mar 5, 2026

Copy link
Copy Markdown
Member

I'm testing this one with https://github.com/gasparoken/laf/tree/fix-alt-tab (which is different from https://github.com/aseprite/laf/tree/fix-alt-tab).

Probably I'll cherry-pick https://github.com/gasparoken/laf/tree/fix-alt-tab and rename the events to AppEnter/AppLeave. I always wanted a couple of WindowEnter/WindowLeave events, but probably those would be different e.g. in a multiple-windows scenario a window could lose focus, but another window of the same app be focused (so there is a WindowLeave for the old window, WindowEnter for the new one, but no AppEnter/Leave events).

I'll try to make these events work on laf, and then go back to this PR.

@dacap

dacap commented Mar 5, 2026

Copy link
Copy Markdown
Member

Merged the laf patch in aseprite/laf@c0a8427 but then implemented AppEnter/AppLeave/WindowEnter/WindowLeave in all platforms: aseprite/laf@592136b

I'll continue with this PR tomorrow.

@dacap dacap left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving some comments about the code and how I'm going to rebase this PR with these patches. There is no need for action from your side.

Comment on lines +2107 to +2118
ui::MouseMessage* msg = new ui::MouseMessage(ui::MessageType::kMouseUpMessage,
os::PointerType::Mouse,
ui::MouseButton::kButtonLeft,
os::KeyModifiers::kKeyNoneModifier,
manager->getMouse()->mousePosInDisplay());
m_state->onMouseUp(this, msg);
ui::MouseMessage* msg2 = new ui::MouseMessage(ui::MessageType::kMouseUpMessage,
os::PointerType::Mouse,
ui::MouseButton::kButtonMiddle,
os::KeyModifiers::kKeyNoneModifier,
manager->getMouse()->mousePosInDisplay());
m_state->onMouseUp(this, msg2);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both new ui::MouseMessage(...) generate memory leaks as they are not queued in the manager, I'm going to replace both of them with:

Suggested change
ui::MouseMessage* msg = new ui::MouseMessage(ui::MessageType::kMouseUpMessage,
os::PointerType::Mouse,
ui::MouseButton::kButtonLeft,
os::KeyModifiers::kKeyNoneModifier,
manager->getMouse()->mousePosInDisplay());
m_state->onMouseUp(this, msg);
ui::MouseMessage* msg2 = new ui::MouseMessage(ui::MessageType::kMouseUpMessage,
os::PointerType::Mouse,
ui::MouseButton::kButtonMiddle,
os::KeyModifiers::kKeyNoneModifier,
manager->getMouse()->mousePosInDisplay());
m_state->onMouseUp(this, msg2);
ui::MouseMessage msg(ui::MessageType::kMouseUpMessage,
os::PointerType::Mouse,
ui::MouseButton::kButtonLeft,
os::KeyModifiers::kKeyNoneModifier,
manager->getMouse()->mousePosInDisplay());
m_state->onMouseUp(this, &msg);
ui::MouseMessage msg2(ui::MessageType::kMouseUpMessage,
os::PointerType::Mouse,
ui::MouseButton::kButtonMiddle,
os::KeyModifiers::kKeyNoneModifier,
manager->getMouse()->mousePosInDisplay());
m_state->onMouseUp(this, &msg2);

Another important thing, after merging this I'll try to refactor the ui-lib in some way automatically or to handle this in a more generic way (e.g. the Timeline has the same problem)

Comment thread src/ui/manager.cpp
break;
}

case os::Event::AppLostFocus: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to use the WindowLeave event instead of the app one (both messages should be received, but I have to test on all platforms yet):

Suggested change
case os::Event::AppLostFocus: {
case os::Event::WindowLeave: {

Comment thread src/ui/manager.cpp

case os::Event::AppLostFocus: {
if (mouse_widget) {
auto msg = new AppLostFocusMessage();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need for a new Message sub-class if the message doesn't contain any extra information, I'm going to replace this with:

Suggested change
auto msg = new AppLostFocusMessage();
auto* msg = new Message(kWinLeaveMessage);

The kWinLeaveMessage name is temptative, but probably we could add something like kMouseLostMessage or see if we can just send a kMouseLeaveMessage.

Comment thread src/ui/manager.cpp
case os::Event::AppLostFocus: {
if (mouse_widget) {
auto msg = new AppLostFocusMessage();
msg->setRecipient(mouse_widget);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After furthering testing, this AppLostFocus (or another one like WindowLeave message) cannot be received by the mouse_widget only (we should setPropagateToParent(true)), but the message should be sent to the display window.

For this specific fix, I'll try another approach, probably a MouseLeave message when os::Event::WindowLeave is received (instead of creating a new AppLostFocusMessage), something to solve every case of all widgets that have the mouse captured.

@dacap dacap closed this Mar 6, 2026
dacap added a commit to dacap/aseprite that referenced this pull request Mar 6, 2026
dacap added a commit to dacap/aseprite that referenced this pull request Mar 6, 2026
, aseprite#5601)

Switching to another app will generate a kMouseUpMessage without
button (kButtonNone) for the capture_widget so it can release the
mouse and go back to its idle state.
dacap added a commit to dacap/aseprite that referenced this pull request Mar 9, 2026
, aseprite#5601)

Switching to another app will generate a kMouseUpMessage without
button (kButtonNone) for the capture_widget so it can release the
mouse and go back to its idle state.
dacap added a commit to dacap/aseprite that referenced this pull request Apr 1, 2026
, aseprite#5601)

Switching to another app will generate a kMouseUpMessage without
button (kButtonNone) for the capture_widget so it can release the
mouse and go back to its idle state.
jjolmo pushed a commit to jjolmo/aseprite that referenced this pull request Apr 4, 2026
On XWayland, FocusOut events can be sent spuriously by the compositor
(e.g. when focus moves between Aseprite's own windows in multi-display
mode). Using WindowLeave to release capture caused popup windows like
the color picker to close prematurely.

Change to AppLeave which only fires when focus leaves the application
entirely (e.g. Alt+Tab to another app), preserving the original fix
for aseprite#325 and aseprite#5601.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alt-tabbing while using hand tool locks mouse in other windows.

4 participants