Freitag, 5. August 2016

[QDBUS] Create tabs in yakuake and pidgin, not dolphin

QDBus is an easier way to interact with DBus then using dbus-send. There is also a GUI qdbusviewer that might help finding and executing stuff. But qdbus and grep seem to be a bit better at that for me.

Executing qdbus will show all services that you can talk to via dbus. Given a servicename it will print possible paths and sub-paths. And when a path is also appended then it will print out methods and signals which can be called or registered to. The output will include the names and types of parameters and the return-type.



Yakuake - Let's find it:
$ qdbus | grep yakuake
 org.kde.yakuake
$ qdbus org.kde.yakuake
/
/MainApplication
/Sessions
/Sessions/1
/Sessions/2
/Sessions/3
/Sessions/4
/Sessions/7
/Sessions/8
/Windows
/Windows/1
/Windows/2
/Windows/3
/Windows/4
/Windows/7
/Windows/8
/org
/org/kde
/org/kde/yakuake
/yakuake
/yakuake/sessions
/yakuake/tabs
/yakuake/window
You see the missing 5 and 6 in Sessions and Windows? That is because I already closed them and yakuake is not reusing those numbers but continues its numbering.
For now /Sessions/1 and /yakuake/sessions are the paths we need.
$ qdbus org.kde.yakuake /Sessions/1
method QByteArray org.kde.konsole.Session.codec()
method QStringList org.kde.konsole.Session.environment()
method bool org.kde.konsole.Session.flowControlEnabled()
method int org.kde.konsole.Session.foregroundProcessId()
method int org.kde.konsole.Session.historySize()
method bool org.kde.konsole.Session.isMonitorActivity()
method bool org.kde.konsole.Session.isMonitorSilence()
method int org.kde.konsole.Session.processId()
method void org.kde.konsole.Session.runCommand(QString command)
method void org.kde.konsole.Session.sendMouseEvent(int buttons, int column, int line, int eventType)
method void org.kde.konsole.Session.sendText(QString text)
method bool org.kde.konsole.Session.setCodec(QByteArray codec)
method void org.kde.konsole.Session.setEnvironment(QStringList environment)
method void org.kde.konsole.Session.setFlowControlEnabled(bool enabled)
method void org.kde.konsole.Session.setHistorySize(int lines)
method void org.kde.konsole.Session.setMonitorActivity(bool)
method void org.kde.konsole.Session.setMonitorSilence(bool)
method void org.kde.konsole.Session.setMonitorSilenceSeconds(int seconds)
method void org.kde.konsole.Session.setTabTitleFormat(int context, QString format)
method void org.kde.konsole.Session.setTitle(int role, QString title)
method QString org.kde.konsole.Session.shellSessionId()
method QString org.kde.konsole.Session.tabTitleFormat(int context)
method QString org.kde.konsole.Session.title(int role)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface_name, QString property_name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface_name)
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface_name, QVariantMap changed_properties, QStringList invalidated_properties)
method void org.freedesktop.DBus.Properties.Set(QString interface_name, QString property_name, QDBusVariant value)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
$ qdbus org.kde.yakuake /yakuake/sessions
method int org.kde.yakuake.activeSessionId()
method int org.kde.yakuake.activeTerminalId()
method int org.kde.yakuake.addSession()
method int org.kde.yakuake.addSessionQuad()
method int org.kde.yakuake.addSessionTwoHorizontal()
method int org.kde.yakuake.addSessionTwoVertical()
method bool org.kde.yakuake.hasTerminalsWithKeyboardInputDisabled(int sessionId)
method bool org.kde.yakuake.hasTerminalsWithKeyboardInputEnabled(int sessionId)
method bool org.kde.yakuake.hasTerminalsWithMonitorActivityDisabled(int sessionId)
method bool org.kde.yakuake.hasTerminalsWithMonitorActivityEnabled(int sessionId)
method bool org.kde.yakuake.hasTerminalsWithMonitorSilenceDisabled(int sessionId)
method bool org.kde.yakuake.hasTerminalsWithMonitorSilenceEnabled(int sessionId)
method bool org.kde.yakuake.hasUnclosableSessions()
method bool org.kde.yakuake.isSessionClosable(int sessionId)
method bool org.kde.yakuake.isSessionKeyboardInputEnabled(int sessionId)
method bool org.kde.yakuake.isSessionMonitorActivityEnabled(int sessionId)
method bool org.kde.yakuake.isSessionMonitorSilenceEnabled(int sessionId)
method bool org.kde.yakuake.isTerminalKeyboardInputEnabled(int terminalId)
method bool org.kde.yakuake.isTerminalMonitorActivityEnabled(int terminalId)
method bool org.kde.yakuake.isTerminalMonitorSilenceEnabled(int terminalId)
method void org.kde.yakuake.raiseSession(int sessionId)
method void org.kde.yakuake.removeSession(int sessionId)
method void org.kde.yakuake.removeTerminal(int terminalId)
method void org.kde.yakuake.runCommand(QString command)
method void org.kde.yakuake.runCommandInTerminal(int terminalId, QString command)
method int org.kde.yakuake.sessionIdForTerminalId(int terminalId)
method QString org.kde.yakuake.sessionIdList()
method void org.kde.yakuake.setSessionClosable(int sessionId, bool closable)
method void org.kde.yakuake.setSessionKeyboardInputEnabled(int sessionId, bool enabled)
method void org.kde.yakuake.setSessionMonitorActivityEnabled(int sessionId, bool enabled)
method void org.kde.yakuake.setSessionMonitorSilenceEnabled(int sessionId, bool enabled)
method void org.kde.yakuake.setTerminalKeyboardInputEnabled(int terminalId, bool enabled)
method void org.kde.yakuake.setTerminalMonitorActivityEnabled(int terminalId, bool enabled)
method void org.kde.yakuake.setTerminalMonitorSilenceEnabled(int terminalId, bool enabled)
method int org.kde.yakuake.splitSessionLeftRight(int sessionId)
method int org.kde.yakuake.splitSessionTopBottom(int sessionId)
method int org.kde.yakuake.splitTerminalLeftRight(int terminalId)
method int org.kde.yakuake.splitTerminalTopBottom(int terminalId)
method QString org.kde.yakuake.terminalIdList()
method QString org.kde.yakuake.terminalIdsForSessionId(int sessionId)
method int org.kde.yakuake.tryGrowTerminalBottom(int terminalId)
method int org.kde.yakuake.tryGrowTerminalBottom(int terminalId, uint pixels)
method int org.kde.yakuake.tryGrowTerminalLeft(int terminalId)
method int org.kde.yakuake.tryGrowTerminalLeft(int terminalId, uint pixels)
method int org.kde.yakuake.tryGrowTerminalRight(int terminalId)
method int org.kde.yakuake.tryGrowTerminalRight(int terminalId, uint pixels)
method int org.kde.yakuake.tryGrowTerminalTop(int terminalId)
method int org.kde.yakuake.tryGrowTerminalTop(int terminalId, uint pixels)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface_name, QString property_name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface_name)
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface_name, QVariantMap changed_properties, QStringList invalidated_properties)
method void org.freedesktop.DBus.Properties.Set(QString interface_name, QString property_name, QDBusVariant value)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
We have some possibilities to do what we want. We know that after starting yakuake we have a session 1. We can already put some stuff in there. Either execute a command or insert some text. This is done either in /Sessions/1 via
method void org.kde.konsole.Session.runCommand(QString command)
method void org.kde.konsole.Session.sendText(QString text)
 or in /yakuake/sessions via
 method void org.kde.yakuake.runCommandInTerminal(int terminalId, QString command)
Executing a command in session 1 will be:
qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal 0 "echo \"Hello World!\""
or
qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "echo \"Hello World!\""
The terminalId is 0. One less then the session number.
Let's add another session and put something on the line without executing it:
qdbus org.kde.yakuake /yakuake/sessions org.kde.yakuake.addSession
qdbus org.kde.yakuake /Sessions/2 org.kde.konsole.Session.sendText "# Hello World!"
Running a command seems to do nothing more special then putting the command on the line and "pushing" return. At least it is working when I do stuff like:
qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "sudo echo \"Hello World!\""
qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "my_awesome_password"
 or
qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "ipython"
qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "import os"

qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "print(os.path.curdir)"

Pidgin:
$ qdbus | grep pidgin
 im.pidgin.purple.PurpleService
$ qdbus im.pidgin.purple.PurpleService
/
/im
/im/pidgin
/im/pidgin/purple
/im/pidgin/purple/PurpleObject
$ qdbus im.pidgin.purple.PurpleService /im/pidgin/purple/PurpleObject
# too much output ...
method int im.pidgin.purple.PurpleInterface.PurpleAccountsFind(QString name, QString protocol)
method int im.pidgin.purple.PurpleInterface.PurpleAccountsFindAny(QString name, QString protocol)
method QDBusRawType::ai im.pidgin.purple.PurpleInterface.PurpleAccountsGetAll()
method int im.pidgin.purple.PurpleInterface.PurpleConversationNew(int type, int account, QString name)
method QString im.pidgin.purple.PurpleInterface.PurpleAccountGetProtocolId(int account)
method QString im.pidgin.purple.PurpleInterface.PurpleAccountGetProtocolName(int account)
method QString im.pidgin.purple.PurpleInterface.PurpleAccountGetUsername(int account)
# too much output ...
For pidgin we do a bit more to open new conversations. First get the number of your account and then open a new conversation window. Do you know the account name and protocol? Then use PurpleAccountsFind. I thought I knew but couldn't find a thing. With PurpleAccountsGetAll I found the numbers to all accounts. Put a number in PurpleAccountGetUsername and
PurpleAccountGetProtocolId (and not PurpleAccountGetProtocolName) and you will have the account name and protocol that can be used in PurpleAccountsFind.

To open a new conversation with your buddy you need his(her) name. Either check pidgin or got through all numbers found in  im.pidgin.purple.PurpleInterface.PurpleBlistGetBuddies and get their names via im.pidgin.purple.PurpleInterface.PurpleBlistGetBuddies.

Finally use PurpleConversationNew to create the window/tab:
PURPLE_CONV_TYPE_IM=1
ACCOUNT=`qdbus im.pidgin.purple.PurpleService /im/pidgin/purple/PurpleObject PurpleAccountsFind "my_name@my.host/ressource" "prpl-jabber"`
qdbus im.pidgin.purple.PurpleService im.pidgin.purple.PurpleInterface.PurpleConversationNew $PURPLE_CONV_TYPE_IM $ACCOUNT "my_buddy@my.host"

Dolphin:
$ qdbus | grep dolphin
 org.kde.dolphin-6364
 org.kde.dolphin-6371
 org.kde.dolphin-32069
There it is. The first problem. There is not just "the" dolphin. Each window has its own PID.
$ ps aux | grep dolphin
 user  6364  0.0  0.2 544824 35600 ?        Sl   12:40   0:14 /usr/bin/dolphin --daemon
 user  6371  0.0  0.6 723668 78716 ?        Sl   12:50   4:46 /usr/bin/dolphin
 user 32069  0.0  0.6 678496 73300 ?        Sl   12:53   0:02 /usr/bin/dolphin
And one of them even is a daemon. Not an open window.
$ qdbus org.kde.dolphin-32069
/
/FileUndoManager
/MainApplication
/dolphin
/dolphin/Dolphin_1
/dolphin/Dolphin_1/actions
/dolphin/Dolphin_1/actions/new_window
/dolphin/Dolphin_1/actions/new_tab
/dolphin/Dolphin_1/actions/close_tab
/dolphin/Dolphin_1/actions/file_quit
/dolphin/Dolphin_1/actions/edit_undo
/dolphin/Dolphin_1/actions/edit_cut
/dolphin/Dolphin_1/actions/edit_copy
/dolphin/Dolphin_1/actions/edit_paste
/dolphin/Dolphin_1/actions/edit_find
/dolphin/Dolphin_1/actions/select_all
/dolphin/Dolphin_1/actions/invert_selection
/dolphin/Dolphin_1/actions/split_view
/dolphin/Dolphin_1/actions/reload
/dolphin/Dolphin_1/actions/stop
/dolphin/Dolphin_1/actions/editable_location
/dolphin/Dolphin_1/actions/replace_location
/dolphin/Dolphin_1/actions/go_back
/dolphin/Dolphin_1/actions/undo_close_tab
/dolphin/Dolphin_1/actions/go_forward
/dolphin/Dolphin_1/actions/go_up
/dolphin/Dolphin_1/actions/go_home
/dolphin/Dolphin_1/actions/show_filter_bar
/dolphin/Dolphin_1/actions/compare_files
/dolphin/Dolphin_1/actions/open_terminal
/dolphin/Dolphin_1/actions/options_show_menubar
/dolphin/Dolphin_1/actions/options_configure
/dolphin/Dolphin_1/actions/activate_next_tab
/dolphin/Dolphin_1/actions/activate_prev_tab
/dolphin/Dolphin_1/actions/open_in_new_tab
/dolphin/Dolphin_1/actions/open_in_new_tabs
/dolphin/Dolphin_1/actions/open_in_new_window
/dolphin/Dolphin_1/actions/create_dir
/dolphin/Dolphin_1/actions/rename
/dolphin/Dolphin_1/actions/move_to_trash
/dolphin/Dolphin_1/actions/delete
/dolphin/Dolphin_1/actions/delete_shortcut
/dolphin/Dolphin_1/actions/properties
/dolphin/Dolphin_1/actions/icons
/dolphin/Dolphin_1/actions/compact
/dolphin/Dolphin_1/actions/details
/dolphin/Dolphin_1/actions/view_mode
/dolphin/Dolphin_1/actions/view_zoom_in
/dolphin/Dolphin_1/actions/view_zoom_out
/dolphin/Dolphin_1/actions/show_preview
/dolphin/Dolphin_1/actions/descending
/dolphin/Dolphin_1/actions/folders_first
/dolphin/Dolphin_1/actions/sort_by_text
/dolphin/Dolphin_1/actions/sort_by_size
/dolphin/Dolphin_1/actions/sort_by_date
/dolphin/Dolphin_1/actions/sort_by_type
/dolphin/Dolphin_1/actions/sort_by_rating
/dolphin/Dolphin_1/actions/sort_by_tags
/dolphin/Dolphin_1/actions/sort_by_comment
/dolphin/Dolphin_1/actions/Dokument
/dolphin/Dolphin_1/actions/Bild
/dolphin/Dolphin_1/actions/Audio
/dolphin/Dolphin_1/actions/Weitere
/dolphin/Dolphin_1/actions/sort
/dolphin/Dolphin_1/actions/show_size
/dolphin/Dolphin_1/actions/show_date
/dolphin/Dolphin_1/actions/show_type
/dolphin/Dolphin_1/actions/show_rating
/dolphin/Dolphin_1/actions/show_tags
/dolphin/Dolphin_1/actions/show_comment
/dolphin/Dolphin_1/actions/Dokument
/dolphin/Dolphin_1/actions/Bild
/dolphin/Dolphin_1/actions/Audio
/dolphin/Dolphin_1/actions/Weitere
/dolphin/Dolphin_1/actions/additional_info
/dolphin/Dolphin_1/actions/show_in_groups
/dolphin/Dolphin_1/actions/show_hidden_files
/dolphin/Dolphin_1/actions/view_properties
/dolphin/Dolphin_1/actions/lock_panels
/dolphin/Dolphin_1/actions/show_information_panel
/dolphin/Dolphin_1/actions/show_folders_panel
/dolphin/Dolphin_1/actions/show_terminal_panel
/dolphin/Dolphin_1/actions/show_places_panel
/dolphin/Dolphin_1/actions/options_configure_keybinding
/dolphin/Dolphin_1/actions/options_configure_toolbars
/org
/org/freedesktop
/org/freedesktop/FileManager1
/org/kde
/org/kde/dolphin
How is this supposed to be used with qdbus?
$ qdbus org.kde.dolphin-32069 /dolphin/Dolphin_1/actions/new_tab
property readwrite bool org.qtproject.Qt.QAction.autoRepeat
property readwrite bool org.qtproject.Qt.QAction.checkable
property readwrite bool org.qtproject.Qt.QAction.checked
property readwrite bool org.qtproject.Qt.QAction.enabled
property readwrite QString org.qtproject.Qt.QAction.iconText
property readwrite bool org.qtproject.Qt.QAction.iconVisibleInMenu
property readwrite int org.qtproject.Qt.QAction.menuRole
property readwrite int org.qtproject.Qt.QAction.priority
property readwrite int org.qtproject.Qt.QAction.shortcutContext
property readwrite QString org.qtproject.Qt.QAction.statusTip
property readwrite QString org.qtproject.Qt.QAction.text
property readwrite QString org.qtproject.Qt.QAction.toolTip
property readwrite bool org.qtproject.Qt.QAction.visible
property readwrite QString org.qtproject.Qt.QAction.whatsThis
method void org.qtproject.Qt.QAction.hover()
method void org.qtproject.Qt.QAction.setChecked(bool)
method void org.qtproject.Qt.QAction.setDisabled(bool b)
method void org.qtproject.Qt.QAction.setEnabled(bool)
method void org.qtproject.Qt.QAction.setVisible(bool)
method void org.qtproject.Qt.QAction.toggle()
method void org.qtproject.Qt.QAction.trigger()
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface_name, QString property_name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface_name)
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface_name, QVariantMap changed_properties, QStringList invalidated_properties)
method void org.freedesktop.DBus.Properties.Set(QString interface_name, QString property_name, QDBusVariant value)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
I can trigger this and it will open a new tab. But how to change its location? replace_location ? I can trigger that one too. It will mark the location for change. But then I would need to click and change it myself. Not what is wanted.

There is this other path:
$ qdbus org.kde.dolphin-32069 /org/kde/dolphin
method void org.freedesktop.Application.Activate(QVariantMap platform-data)
method void org.freedesktop.Application.ActivateAction(QString action_name, QVariantList parameter, QVariantMap platform-data)
method void org.freedesktop.Application.Open(QStringList uris, QVariantMap platform-data)
method int org.kde.KDBusService.CommandLine(QStringList arguments, QString working-dir, QVariantMap platform-data)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface_name, QString property_name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface_name)
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface_name, QVariantMap changed_properties, QStringList invalidated_properties)
method void org.freedesktop.DBus.Properties.Set(QString interface_name, QString property_name, QDBusVariant value)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
where ActivateAction looks promissing. But trying something gives me an error:
$ qdbus org.kde.dolphin-32069 /org/kde/dolphin org.freedesktop.Application.ActivateAction new_tab "(" ")" ""
Sorry, can't pass arg of type 'QVariantMap'.
The internet seems to say that that type is not supported by qdbus:
https://forum.kde.org/viewtopic.php?f=17&t=85292
http://doc.qt.io/qt-5.7/qdbustypesystem.html

Meeh.

At least you can append URLs to dolphin on the commandline the open them all in tabs:
dolphin ~ ~/Downloads ~/Projects ~/Pictures ~/FooBar

Final notes - using dbus-send:
dbus-send --print-reply  --session --dest=org.kde.yakuake /yakuake/sessions org.kde.yakuake.addSession

Now put those lines together in a script, execute it and it will open all your needed tabs at the right places with the right tools and prefilled commands. Just like this:
#!/bin/sh

qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.runCommand "echo \"
This could be your text.\""
qdbus org.kde.yakuake /Sessions/1 org.kde.konsole.Session.sendText "# This could be your text."

qdbus org.kde.yakuake /yakuake/sessions org.kde.yakuake.addSession
qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal 1 "jupyter-notebook"

qdbus org.kde.yakuake /yakuake/sessions org.kde.yakuake.addSession
qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal 2 "ipython"

qdbus org.kde.yakuake /yakuake/sessions org.kde.yakuake.addSession
qdbus org.kde.yakuake /Sessions/4 org.kde.konsole.Session.runCommand "vim -S ~/session.vim"


dolphin ~ ~/Downloads ~/Pictures ~/Projects &

account=`qdbus im.pidgin.purple.PurpleService /im/pidgin/purple/PurpleObject PurpleAccountsFind "admin@my.project.com/FooBar" "prpl-jabber"`
PURPLE_CONV_TYPE_IM=1
qdbus --literal im.pidgin.purple.PurpleService /im/pidgin/purple/PurpleObject PurpleConversationNew $PURPLE_CONV_TYPE_IM $account "co-worker-1@my.project.com"
qdbus --literal im.pidgin.purple.PurpleService /im/pidgin/purple/PurpleObject PurpleConversationNew $PURPLE_CONV_TYPE_IM $account "co-worker-2@my.project.com"
qdbus --literal im.pidgin.purple.PurpleService /im/pidgin/purple/PurpleObject PurpleConversationNew $PURPLE_CONV_TYPE_IM $account "co-worker-3@my.project.com"