Skip to content

Commit

Permalink
Add history_index to open_application
Browse files Browse the repository at this point in the history
  • Loading branch information
tekezo committed Dec 2, 2024
1 parent cda1136 commit fef2648
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ class components_manager final : public pqrs::dispatcher::extra::dispatcher_clie
if (auto m = pqrs::osx::frontmost_application_monitor::monitor::get_shared_monitor().lock()) {
m->frontmost_application_changed.connect([this](auto&& application_ptr) {
if (application_ptr) {
if (software_function_handler_) {
software_function_handler_->add_frontmost_application_history(*application_ptr);
}

//
// Notify the grabber of frontmost application changed.
//

if (application_ptr->get_bundle_identifier() == "org.pqrs.Karabiner.EventViewer" ||
application_ptr->get_bundle_identifier() == "org.pqrs.Karabiner-EventViewer") {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "logger.hpp"
#include <deque>
#include <pqrs/osx/accessibility.hpp>
#include <pqrs/osx/cg_display.hpp>
#include <pqrs/osx/cg_event.hpp>
Expand Down Expand Up @@ -34,6 +35,54 @@ class software_function_handler final : public pqrs::dispatcher::extra::dispatch
}
}

void add_frontmost_application_history(const pqrs::osx::frontmost_application_monitor::application& application) {
//
// Remove any identical entries that already exist.
//
// If the same application appears multiple times in the history,
// it causes inconvenient behavior when switching between the same application repeatedly or after an application is closed.
// To address this, duplicates should be eliminated from the history.
//
// For example, consider the following application switching sequence:
//
// 1. Mail
// 2. Terminal
// 3. Safari
// 4. Terminal
//
// As a user, we would expect the app two steps back to be Mail, not Terminal.
//
// Similarly, if Terminal is closed in this state, the focus will return to Safari.
// If duplicates are not removed, the history will look like this:
//
// 1. Mail
// 2. Terminal
// 3. Safari
// 4. Terminal
// 5. Safari
//
// In this scenario, if we try to switch to the previous application,
// Terminal (which is already closed) will be excluded, and the next app in the history, "3. Safari", will be selected.
// But as a user, it would feel more natural for Mail to be selected instead.
//
// By removing duplicates from the history, such behavior can be achieved,
// resulting in a smoother and more intuitive user experience.
//

std::erase(frontmost_application_history_, application);

//
// Add to history
//

frontmost_application_history_.push_front(application);

size_t max_size = 20;
while (frontmost_application_history_.size() > max_size) {
frontmost_application_history_.pop_back();
}
}

private:
void execute_cg_event_double_click(const software_function_details::cg_event_double_click& cg_event_double_click) {
if (!check_trusted_) {
Expand Down Expand Up @@ -64,6 +113,38 @@ class software_function_handler final : public pqrs::dispatcher::extra::dispatch
pqrs::osx::workspace::open_application_by_bundle_identifier(*v);
} else if (auto v = open_application.get_file_path()) {
pqrs::osx::workspace::open_application_by_file_path(*v);
} else if (auto v = open_application.get_history_index()) {
auto history_index = *v;
for (const auto& h : frontmost_application_history_) {
// Target only applications that are currently running.

// Since there are cases where the bundle paths differ even if the bundle_identifier is the same, prioritize using the bundle path.
if (auto bundle_path = h.get_bundle_path()) {
if (!pqrs::osx::workspace::application_running_by_file_path(*bundle_path)) {
continue;
}
if (history_index == 0) {
pqrs::osx::workspace::open_application_by_file_path(*bundle_path);
}

} else if (auto bundle_identifier = h.get_bundle_identifier()) {
if (!pqrs::osx::workspace::application_running_by_bundle_identifier(*bundle_identifier)) {
continue;
}
if (history_index == 0) {
pqrs::osx::workspace::open_application_by_bundle_identifier(*bundle_identifier);
}

} else {
continue;
}

if (history_index > 0) {
--history_index;
} else {
break;
}
}
}
}

Expand All @@ -89,6 +170,8 @@ class software_function_handler final : public pqrs::dispatcher::extra::dispatch

private:
bool check_trusted_;
// Stored in order from the newest at the beginning.
std::deque<pqrs::osx::frontmost_application_monitor::application> frontmost_application_history_;
};
} // namespace console_user_server
} // namespace krbn
16 changes: 16 additions & 0 deletions src/share/types/software_function_details/open_application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,20 @@ class open_application {
file_path_ = value;
}

const std::optional<size_t>& get_history_index(void) const {
return history_index_;
}

void set_history_index(const std::optional<size_t>& value) {
history_index_ = value;
}

constexpr bool operator==(const open_application&) const = default;

private:
std::optional<std::string> bundle_identifier_;
std::optional<std::string> file_path_;
std::optional<size_t> history_index_;
};

inline void to_json(nlohmann::json& json, const open_application& value) {
Expand All @@ -43,6 +52,9 @@ inline void to_json(nlohmann::json& json, const open_application& value) {
if (auto v = value.get_file_path()) {
json["file_path"] = *v;
}
if (auto v = value.get_history_index()) {
json["history_index"] = *v;
}
}

inline void from_json(const nlohmann::json& json, open_application& value) {
Expand All @@ -55,6 +67,9 @@ inline void from_json(const nlohmann::json& json, open_application& value) {
} else if (k == "file_path") {
pqrs::json::requires_string(v, "`" + k + "`");
value.set_file_path(v.get<std::string>());
} else if (k == "history_index") {
pqrs::json::requires_number(v, "`" + k + "`");
value.set_history_index(v.get<size_t>());
} else {
throw pqrs::json::unmarshal_error(fmt::format("unknown key: `{0}`", k));
}
Expand All @@ -71,6 +86,7 @@ struct hash<krbn::software_function_details::open_application> final {

pqrs::hash::combine(h, value.get_bundle_identifier());
pqrs::hash::combine(h, value.get_file_path());
pqrs::hash::combine(h, value.get_history_index());

return h;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,17 @@
},
"error": "`software_function` error: `open_application` error: `file_path` must be string, but is `null`"
},
{
"class": "event_definition",
"input": {
"software_function": {
"open_application": {
"history_index": null
}
}
},
"error": "`software_function` error: `open_application` error: `history_index` must be number, but is `null`"
},
{
"class": "event_definition",
"input": {
Expand Down
11 changes: 11 additions & 0 deletions tests/src/types/src/software_function_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ void run_software_function_test(void) {
expect(nlohmann::json(value) == json);
}

{
auto json = nlohmann::json::object({
{"history_index", 1},
});

auto value = json.get<krbn::software_function_details::open_application>();
expect(1 == value.get_history_index());

expect(nlohmann::json(value) == json);
}

//
// set_mouse_cursor_position
//
Expand Down

0 comments on commit fef2648

Please sign in to comment.