diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..50941707 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "Bash(rg:*)", + "Bash(g++:*)", + "Bash(cmake:*)", + "Bash(sudo apt:*)", + "Bash(sudo apt install:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/src/gui/src/Action.cpp b/src/gui/src/Action.cpp index f34d1e8f..cf4f5ddf 100644 --- a/src/gui/src/Action.cpp +++ b/src/gui/src/Action.cpp @@ -40,7 +40,10 @@ Action::Action() : m_SwitchDirection(switchLeft), m_LockCursorMode(lockCursorToggle), m_ActiveOnRelease(false), - m_HasScreens(false) + m_HasScreens(false), + m_hasCustomPosition(false), + m_customX(0), + m_customY(0) { } @@ -87,6 +90,12 @@ QString Action::text() const case switchToScreen: text += "("; text += switchScreenName(); + if (hasCustomPosition()) { + text += ","; + text += QString::number(customX()); + text += ","; + text += QString::number(customY()); + } text += ")"; break; @@ -133,6 +142,9 @@ void Action::loadSettings(QSettings& settings) setLockCursorMode(settings.value("lockCursorToScreen", lockCursorToggle).toInt()); setActiveOnRelease(settings.value("activeOnRelease", false).toBool()); setHaveScreens(settings.value("hasScreens", false).toBool()); + setHasCustomPosition(settings.value("hasCustomPosition", false).toBool()); + setCustomX(settings.value("customX", 0).toInt()); + setCustomY(settings.value("customY", 0).toInt()); } void Action::saveSettings(QSettings& settings) const @@ -153,6 +165,9 @@ void Action::saveSettings(QSettings& settings) const settings.setValue("lockCursorToScreen", lockCursorMode()); settings.setValue("activeOnRelease", activeOnRelease()); settings.setValue("hasScreens", haveScreens()); + settings.setValue("hasCustomPosition", hasCustomPosition()); + settings.setValue("customX", customX()); + settings.setValue("customY", customY()); } QTextStream& operator<<(QTextStream& outStream, const Action& action) diff --git a/src/gui/src/Action.h b/src/gui/src/Action.h index c260badc..f5ff55ac 100644 --- a/src/gui/src/Action.h +++ b/src/gui/src/Action.h @@ -72,6 +72,15 @@ class Action bool haveScreens() const { return m_HasScreens; } void setHaveScreens(bool b) { m_HasScreens = b; } + bool hasCustomPosition() const { return m_hasCustomPosition; } + void setHasCustomPosition(bool b) { m_hasCustomPosition = b; } + + int customX() const { return m_customX; } + void setCustomX(int x) { m_customX = x; } + + int customY() const { return m_customY; } + void setCustomY(int y) { m_customY = y; } + private: KeySequence m_KeySequence; int m_Type; @@ -81,6 +90,9 @@ class Action int m_LockCursorMode; bool m_ActiveOnRelease; bool m_HasScreens; + bool m_hasCustomPosition; + int m_customX; + int m_customY; static const char* m_ActionTypeNames[]; static const char* m_SwitchDirectionNames[]; diff --git a/src/gui/src/ActionDialog.cpp b/src/gui/src/ActionDialog.cpp index 89a037e3..8032b85b 100644 --- a/src/gui/src/ActionDialog.cpp +++ b/src/gui/src/ActionDialog.cpp @@ -73,6 +73,11 @@ ActionDialog::ActionDialog(QWidget* parent, ServerConfig& config, Hotkey& hotkey idx++; } } + + // Initialize coordinate controls + m_pCheckBoxCustomPosition->setChecked(m_Action.hasCustomPosition()); + m_pSpinBoxX->setValue(m_Action.customX()); + m_pSpinBoxY->setValue(m_Action.customY()); } void ActionDialog::accept() @@ -93,6 +98,23 @@ void ActionDialog::accept() m_Action.setSwitchDirection(m_pComboSwitchInDirection->currentIndex()); m_Action.setLockCursorMode(m_pComboLockCursorToScreen->currentIndex()); m_Action.setActiveOnRelease(m_pRadioHotkeyReleased->isChecked()); + + // Save coordinate settings only for switchToScreen action + if (m_pButtonGroupType->checkedId() == Action::switchToScreen) { + m_Action.setHasCustomPosition(m_pCheckBoxCustomPosition->isChecked()); + if (m_pCheckBoxCustomPosition->isChecked()) { + m_Action.setCustomX(m_pSpinBoxX->value()); + m_Action.setCustomY(m_pSpinBoxY->value()); + } else { + m_Action.setCustomX(0); + m_Action.setCustomY(0); + } + } else { + // Clear custom position for other action types + m_Action.setHasCustomPosition(false); + m_Action.setCustomX(0); + m_Action.setCustomY(0); + } QDialog::accept(); } diff --git a/src/gui/src/ActionDialogBase.ui b/src/gui/src/ActionDialogBase.ui index 9c6ad0a0..4036bd3a 100644 --- a/src/gui/src/ActionDialogBase.ui +++ b/src/gui/src/ActionDialogBase.ui @@ -142,6 +142,96 @@ + + + + + + Set specific position + + + false + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + X: + + + false + + + + + + + false + + + 0 + + + 9999 + + + + + + + Y: + + + false + + + + + + + false + + + 0 + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + @@ -588,5 +678,85 @@ + + m_pRadioSwitchToScreen + toggled(bool) + m_pCheckBoxCustomPosition + setEnabled(bool) + + + 148 + 291 + + + 148 + 310 + + + + + m_pCheckBoxCustomPosition + toggled(bool) + m_pLabelX + setEnabled(bool) + + + 148 + 310 + + + 50 + 330 + + + + + m_pCheckBoxCustomPosition + toggled(bool) + m_pSpinBoxX + setEnabled(bool) + + + 148 + 310 + + + 100 + 330 + + + + + m_pCheckBoxCustomPosition + toggled(bool) + m_pLabelY + setEnabled(bool) + + + 148 + 310 + + + 150 + 330 + + + + + m_pCheckBoxCustomPosition + toggled(bool) + m_pSpinBoxY + setEnabled(bool) + + + 148 + 310 + + + 200 + 330 + + + diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp index bcdb88cd..52913028 100644 --- a/src/lib/server/Config.cpp +++ b/src/lib/server/Config.cpp @@ -1141,8 +1141,8 @@ void Config::parseAction(ConfigReadContext& s, const std::string& name, */ else if (name == "switchToScreen") { - if (args.size() != 1) { - throw XConfigRead(s, "syntax for action: switchToScreen(name)"); + if (args.size() != 1 && args.size() != 3) { + throw XConfigRead(s, "syntax for action: switchToScreen(name[,x,y])"); } std::string screen = args[0]; @@ -1153,7 +1153,19 @@ void Config::parseAction(ConfigReadContext& s, const std::string& name, throw XConfigRead(s, "unknown screen name in switchToScreen"); } - action = new InputFilter::SwitchToScreenAction(m_events, screen); + if (args.size() == 3) { + // Parse coordinates + int x, y; + try { + x = std::stoi(args[1]); + y = std::stoi(args[2]); + } catch (const std::exception&) { + throw XConfigRead(s, "invalid coordinates in switchToScreen"); + } + action = new InputFilter::SwitchToScreenAction(m_events, screen, x, y); + } else { + action = new InputFilter::SwitchToScreenAction(m_events, screen); + } } else if (name == "toggleScreen") { diff --git a/src/lib/server/InputFilter.cpp b/src/lib/server/InputFilter.cpp index a0dce17c..c2f7ad9f 100644 --- a/src/lib/server/InputFilter.cpp +++ b/src/lib/server/InputFilter.cpp @@ -322,6 +322,20 @@ InputFilter::LockCursorToScreenAction::perform(const Event& event) InputFilter::SwitchToScreenAction::SwitchToScreenAction(IEventQueue* events, const std::string& screen) : m_screen(screen), + m_hasCustomPosition(false), + m_x(0), + m_y(0), + m_events(events) +{ + // do nothing +} + +InputFilter::SwitchToScreenAction::SwitchToScreenAction(IEventQueue* events, + const std::string& screen, int x, int y) : + m_screen(screen), + m_hasCustomPosition(true), + m_x(x), + m_y(y), m_events(events) { // do nothing @@ -332,15 +346,38 @@ std::string InputFilter::SwitchToScreenAction::getScreen() const return m_screen; } +bool InputFilter::SwitchToScreenAction::hasCustomPosition() const +{ + return m_hasCustomPosition; +} + +int InputFilter::SwitchToScreenAction::getX() const +{ + return m_x; +} + +int InputFilter::SwitchToScreenAction::getY() const +{ + return m_y; +} + InputFilter::Action* InputFilter::SwitchToScreenAction::clone() const { - return new SwitchToScreenAction(*this); + if (m_hasCustomPosition) { + return new SwitchToScreenAction(m_events, m_screen, m_x, m_y); + } else { + return new SwitchToScreenAction(m_events, m_screen); + } } std::string InputFilter::SwitchToScreenAction::format() const { - return barrier::string::sprintf("switchToScreen(%s)", m_screen.c_str()); + if (m_hasCustomPosition) { + return barrier::string::sprintf("switchToScreen(%s,%d,%d)", m_screen.c_str(), m_x, m_y); + } else { + return barrier::string::sprintf("switchToScreen(%s)", m_screen.c_str()); + } } void @@ -356,8 +393,12 @@ InputFilter::SwitchToScreenAction::perform(const Event& event) } // send event - Server::SwitchToScreenInfo* info = - Server::SwitchToScreenInfo::alloc(screen); + Server::SwitchToScreenInfo* info; + if (m_hasCustomPosition) { + info = Server::SwitchToScreenInfo::alloc(screen, m_x, m_y); + } else { + info = Server::SwitchToScreenInfo::alloc(screen); + } m_events->addEvent(Event(m_events->forServer().switchToScreen(), event.getTarget(), info, Event::kDeliverImmediately)); diff --git a/src/lib/server/InputFilter.h b/src/lib/server/InputFilter.h index 5e6ef9cf..61f0bc55 100644 --- a/src/lib/server/InputFilter.h +++ b/src/lib/server/InputFilter.h @@ -153,8 +153,12 @@ public: class SwitchToScreenAction : public Action { public: SwitchToScreenAction(IEventQueue* events, const std::string& screen); + SwitchToScreenAction(IEventQueue* events, const std::string& screen, int x, int y); std::string getScreen() const; + bool hasCustomPosition() const; + int getX() const; + int getY() const; // Action overrides virtual Action* clone() const; @@ -163,6 +167,9 @@ public: private: std::string m_screen; + bool m_hasCustomPosition; + int m_x; + int m_y; IEventQueue* m_events; }; diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index a169db16..acb964f2 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -1404,7 +1404,13 @@ Server::handleSwitchToScreenEvent(const Event& event, void*) LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen)); } else { - jumpToScreen(index->second); + if (info->m_hasCustomPosition) { + // Switch to specific coordinates + switchScreen(index->second, info->m_x, info->m_y, false); + } else { + // Use default behavior - jump to last cursor position + jumpToScreen(index->second); + } } } @@ -2329,6 +2335,22 @@ Server::SwitchToScreenInfo::alloc(const std::string& screen) SwitchToScreenInfo* info = (SwitchToScreenInfo*)malloc(sizeof(SwitchToScreenInfo) + screen.size()); + info->m_hasCustomPosition = false; + info->m_x = 0; + info->m_y = 0; + strcpy(info->m_screen, screen.c_str()); + return info; +} + +Server::SwitchToScreenInfo* +Server::SwitchToScreenInfo::alloc(const std::string& screen, SInt32 x, SInt32 y) +{ + SwitchToScreenInfo* info = + (SwitchToScreenInfo*)malloc(sizeof(SwitchToScreenInfo) + + screen.size()); + info->m_hasCustomPosition = true; + info->m_x = x; + info->m_y = y; strcpy(info->m_screen, screen.c_str()); return info; } diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index ae8b2bd5..40f8e7b8 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -63,8 +63,12 @@ public: class SwitchToScreenInfo { public: static SwitchToScreenInfo* alloc(const std::string& screen); + static SwitchToScreenInfo* alloc(const std::string& screen, SInt32 x, SInt32 y); public: + bool m_hasCustomPosition; + SInt32 m_x; + SInt32 m_y; // this is a C-string; this type is a variable size structure char m_screen[1]; };