Home » Technology » Making CopyQ work with Sway

Making CopyQ work with Sway

In CopyQ, go to File > Commands/Global shortcuts, create a new Script and paste in the following code to use ydotool to paste so you don’t have to manually ctrl+v after selecting a snippet from history.

Source of the code below: https://github.com/hluk/copyq-commands/blob/master/Scripts/wayland-support.ini Thank you hluk!!!

/*
This adds support for KDE, Gnome, Sway and Hyprland Wayland sessions.

For Sway and Hyprland, this requires:
- `ydotool` utility to send copy/paste shortcuts to applications
- `grim` for taking screenshot
- `slurp` for selecting screenshot area

For KDE, this requires Spectacle for taking screenshots.

Getting current window title is not supported in KDE.

Global shortcut commands can be triggered with:

    copyq triggerGlobalShortcut {COMMAND_NAME}

On Gnome, clipboard monitor is executed as X11 app using XWayland.

Links:
- https://github.com/ReimuNotMoe/ydotool
- https://github.com/emersion/grim
- https://github.com/emersion/slurp
*/

function isSway() {
    return env('SWAYSOCK').length != 0
}

function isHyprland() {
    return env('HYPRLAND_CMD').length != 0
}

function isGnome() {
    return str(env('XAUTHORITY')).includes('mutter-Xwayland')
}

function run() {
    var p = execute.apply(this, arguments)
    if (!p) {
        throw 'Failed to start ' + arguments[0]
    }
    if (p.exitCode && p.stderr.length) {
        throw 'Failed command ' + arguments[0] + ': ' + str(p.stderr)
    }
    return p.stdout
}

function swayGetTree() {
    var tree = run('swaymsg', '-t', 'get_tree')
    return JSON.parse(str(tree))
}

function swayFindFocused(tree) {
    var nodes = tree['nodes'].concat(tree['floating_nodes'])
    for (var i in nodes) {
        var node = nodes[i]
        if (node['focused'])
            return node
        var focusedNode = swayFindFocused(node)
        if (focusedNode)
            return focusedNode
    }
    return undefined
}

function hyprlandFindFocused() {
    var window = run('hyprctl', '-j', 'activewindow')
    return JSON.parse(str(window))
}

function sendShortcut(...shortcut) {
    sleep(100)
    run('ydotool', 'key', ...shortcut)
}

global.currentWindowTitle = function() {
    if (isSway()) {
        var tree = swayGetTree()
        var focusedNode = swayFindFocused(tree)
        return focusedNode ? focusedNode['name'] : ''
    } else if (isHyprland()) {
        var focusedWindow = hyprlandFindFocused()
        return focusedWindow ? focusedWindow['title'] : ''
    }
    return ''
}

global.paste = function() {
    sendShortcut('42:1', '110:1', '110:0', '42:0')
}

var copy_ = global.copy
global.copy = function() {
    if (arguments.length == 0) {
        sendShortcut('29:1', '46:1', '46:0', '29:0')
    } else {
        copy_.apply(this, arguments)
    }
}

global.focusPrevious = function() {
    hide()
}

var monitorClipboard_ = monitorClipboard
monitorClipboard = function() {
    if (isGnome() && env('QT_QPA_PLATFORM') != 'xcb') {
        serverLog('Starting X11 clipboard monitor')
        setEnv('QT_QPA_PLATFORM', 'xcb')
        execute('copyq', '--clipboard-access', 'monitorClipboard')
        serverLog('Stopping X11 clipboard monitor')
        return
    }
    return monitorClipboard_()
}

var onClipboardChanged_ = onClipboardChanged
onClipboardChanged = function() {
    var title = currentWindowTitle()
    if (title)
        setData(mimeWindowTitle, title)
    onClipboardChanged_()
}

screenshot = function(format, screenName) {
    if (isSway() || isHyprland())
        return run('grim', '-t', format || 'png', '-')
    return run(
        'spectacle',
        '--background',
        '--nonotify',
        '--pointer',
        '--output',
        '/dev/stdout',
    )
}

screenshotSelect = function(format, screenName) {
    if (isSway() || isHyprland()) {
        var geometry = run('slurp')
        geometry = str(geometry).trim()
        return run('grim', '-c', '-g', geometry, '-t', format || 'png', '-')
    }
    return run(
        'spectacle',
        '--background',
        '--nonotify',
        '--pointer',
        '--region',
        '--output',
        '/dev/stdout',
    )
}

global.triggerGlobalShortcut = function(commandName) {
    var cmds = commands()
    for (var i in cmds) {
        var cmd = cmds[i]
        if (cmd.isGlobalShortcut && cmd.enable && cmd.name == commandName)
            return action(cmd.cmd)
    }
    throw 'Failed to find enabled global command with given name'
}

Leave a Comment