Skip to content

Commit

Permalink
#638: client support for starting new commands
Browse files Browse the repository at this point in the history
* default config file updates
* new icon
* man page
* new shortcut (Alt+Shift+F2)
* new tray menu entry
* new GUI dialog
* new capabilities: "start-new-commands" and also expose "exit-with-children"
* new packet type "start-command"

git-svn-id: https://xpra.org/svn/Xpra/trunk@8306 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Dec 27, 2014
1 parent 11172c9 commit 78cd544
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 3 deletions.
5 changes: 5 additions & 0 deletions src/etc/xpra/xpra.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ keyboard-sync = yes
ssh = %(ssh_command)s

# Key Shortcuts:
key-shortcut = Meta+Shift+F2:show_start_new_command
key-shortcut = Meta+Shift+F4:quit
key-shortcut = Meta+Shift+F8:magic_key
key-shortcut = Meta+Shift+F11:show_session_info
Expand All @@ -234,6 +235,10 @@ key-shortcut = Meta+Shift+F11:show_session_info
# This causes the server to terminate when the last child has exited:
exit-with-children = no

# Allows clients to start new commands in the server context:
#start-new-commands = yes
start-new-commands = no

# Video encoders loaded by the server
# (all of them unless specified)
# examples:
Expand Down
Binary file added src/icons/forward.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/man/xpra.1
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ Sends \fIMESSAGE\fP to the log.
Shows the session information window. The optional \fITabName\fP
allows the information tab shown to be selected. Use the value
\fIhelp\fP to get the list of options.
.IP \fBshow_start_new_command\fP
Shows the start new command dialog.
.IP \fBmagic_key\fP
Placeholder which can be used by some window layouts.
.IP \fBvoid\fP
Expand Down
4 changes: 4 additions & 0 deletions src/xpra/client/client_window_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ def void(self):
def show_session_info(self, *args):
self._client.show_session_info(*args)

def show_start_new_command(self, *args):
self._client.show_start_new_command(*args)


def log(self, message=""):
log.info(message)

Expand Down
15 changes: 15 additions & 0 deletions src/xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __init__(self):
UIXpraClient.__init__(self)
self.session_info = None
self.bug_report = None
self.start_new_command = None
#opengl bits:
self.client_supports_opengl = False
self.opengl_enabled = False
Expand Down Expand Up @@ -93,8 +94,22 @@ def cleanup(self):
if self.bug_report:
self.bug_report.destroy()
self.bug_report = None
if self.start_new_command:
self.start_new_command.destroy()
self.start_new_command = None
UIXpraClient.cleanup(self)


def show_start_new_command(self, *args):
if self.start_new_command is None:
from xpra.client.gtk_base.start_new_command import getStartNewCommand
def run_command_cb(command):
self.send_start_command(command, command, False)
self.start_new_command = getStartNewCommand(run_command_cb)
self.start_new_command.show()
return self.start_new_command


def show_session_info(self, *args):
if self.session_info and not self.session_info.is_closed:
#exists already: just raise its window:
Expand Down
12 changes: 12 additions & 0 deletions src/xpra/client/gtk_base/gtk_tray_menu_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ def set_menu_title(*args):
menu.append(self.make_raisewindowsmenuitem())
#menu.append(item("Options", "configure", None, self.options))
menu.append(gtk.SeparatorMenuItem())
menu.append(self.make_startnewcommandmenuitem())
menu.append(self.make_disconnectmenuitem())
if show_close:
menu.append(self.make_closemenuitem())
Expand Down Expand Up @@ -899,6 +900,17 @@ def raise_windows(*args):
win.present()
return self.handshake_menuitem("Raise Windows", "raise.png", None, raise_windows)

def make_startnewcommandmenuitem(self):
self.startnewcommand = self.menuitem("Run Command", "forward.png", "Run a new command on the server", self.client.show_start_new_command)
self.startnewcommand.set_sensitive(False)
def enable_start_new_command(*args):
log("enable_start_new_command%s start_new_command=%s", args, self.client.start_new_commands)
self.startnewcommand.set_sensitive(self.client.start_new_commands)
if not self.client.start_new_commands:
self.startnewcommand.set_tooltip_text("Not supported by the server")
self.client.connect("handshake-complete", enable_start_new_command)
return self.startnewcommand

def make_disconnectmenuitem(self):
def menu_quit(*args):
self.client.disconnect_and_quit(EXIT_OK, CLIENT_EXIT)
Expand Down
172 changes: 172 additions & 0 deletions src/xpra/client/gtk_base/start_new_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.


import os.path
import sys
import signal

from xpra.platform.gui import init as gui_init
gui_init()

from xpra.gtk_common.gobject_compat import import_gtk, import_gdk, import_gobject, import_pango

gtk = import_gtk()
gdk = import_gdk()
gobject = import_gobject()
gobject.threads_init()
pango = import_pango()


from xpra.gtk_common.gtk_util import gtk_main, add_close_accel, scaled_image, pixbuf_new_from_file, \
WIN_POS_CENTER, STATE_NORMAL
from xpra.platform.paths import get_icon_dir
from xpra.log import Logger, enable_debug_for
log = Logger("util")


_instance = None
def getStartNewCommand(run_callback):
global _instance
if _instance is None:
_instance = StartNewCommand(run_callback)
return _instance


class StartNewCommand(object):

def __init__(self, run_callback=None):
self.run_callback = run_callback
self.window = gtk.Window()
self.window.connect("destroy", self.close)
self.window.set_default_size(400, 150)
self.window.set_border_width(20)
self.window.set_title("Start New Command")
self.window.modify_bg(STATE_NORMAL, gdk.Color(red=65535, green=65535, blue=65535))

icon_pixbuf = self.get_icon("forward.png")
if icon_pixbuf:
self.window.set_icon(icon_pixbuf)
self.window.set_position(WIN_POS_CENTER)

vbox = gtk.VBox(False, 0)
vbox.set_spacing(0)

# Label:
label = gtk.Label("Command to run:")
label.modify_font(pango.FontDescription("sans 14"))
al = gtk.Alignment(xalign=0, yalign=0.5, xscale=0.0, yscale=0)
al.add(label)
vbox.add(al)

# Actual command:
self.entry = gtk.Entry(max=100)
self.entry.set_width_chars(32)
self.entry.connect('activate', self.run_command)
vbox.add(self.entry)

# Buttons:
hbox = gtk.HBox(False, 20)
vbox.pack_start(hbox)
def btn(label, tooltip, callback, icon_name=None):
btn = gtk.Button(label)
btn.set_tooltip_text(tooltip)
btn.connect("clicked", callback)
if icon_name:
icon = self.get_icon(icon_name)
if icon:
btn.set_image(scaled_image(icon, 24))
hbox.pack_start(btn)
return btn
btn("Run", "Run this command", self.run_command, "forward.png")
btn("Cancel", "", self.close, "quit.png")

def accel_close(*args):
self.close()
add_close_accel(self.window, accel_close)
vbox.show_all()
self.window.vbox = vbox
self.window.add(vbox)


def show(self):
log("show()")
self.window.show()
self.window.present()

def hide(self):
log("hide()")
self.window.hide()

def close(self, *args):
log("close%s", args)
self.hide()

def destroy(self, *args):
log("destroy%s", args)
if self.window:
self.window.destroy()
self.window = None


def run(self):
log("run()")
gtk_main()
log("run() gtk_main done")

def quit(self, *args):
log("quit%s", args)
self.destroy()
gtk.main_quit()


def get_icon(self, icon_name):
icon_filename = os.path.join(get_icon_dir(), icon_name)
if os.path.exists(icon_filename):
return pixbuf_new_from_file(icon_filename)
return None


def run_command(self, *args):
self.hide()
command = self.entry.get_text()
if self.run_callback:
self.run_callback(command)


def main():
from xpra.platform import init as platform_init
from xpra.platform.gui import ready as gui_ready
platform_init("Start-New-Command", "Start New Command")

#logging init:
if "-v" in sys.argv:
enable_debug_for("util")

from xpra.os_util import SIGNAMES
from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable
gtk_main_quit_on_fatal_exceptions_enable()

app = StartNewCommand()
app.close = app.quit
def app_signal(signum, frame):
print("")
log.info("got signal %s", SIGNAMES.get(signum, signum))
app.quit()
signal.signal(signal.SIGINT, app_signal)
signal.signal(signal.SIGTERM, app_signal)
try:
gui_ready()
app.show()
app.run()
except KeyboardInterrupt:
pass
return 0


if __name__ == "__main__":
v = main()
sys.exit(v)
7 changes: 7 additions & 0 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def __init__(self):
self.pings = False
self.xsettings_enabled = False
self.server_dbus_proxy = False
self.start_new_commands = False

self.client_supports_opengl = False
self.client_supports_notifications = False
Expand Down Expand Up @@ -743,6 +744,11 @@ def mask_to_names(self, mask):
return self.keyboard_helper.mask_to_names(mask)


def send_start_command(self, name, command, ignore):
log("send_start_command(%s, %s, %s)", name, command, ignore)
self.send("start-command", name, command, ignore)


def send_focus(self, wid):
focuslog("send_focus(%s)", wid)
self.send("focus", wid, self.get_current_modifiers())
Expand Down Expand Up @@ -1227,6 +1233,7 @@ def parse_server_capabilities(self):
self.server_compressors = c.strlistget("compressors", ["zlib"])
self.clipboard_enabled = self.client_supports_clipboard and self.server_supports_clipboard
self.server_dbus_proxy = c.boolget("dbus_proxy")
self.start_new_commands = c.boolget("start-new-commands")
self.mmap_enabled = self.supports_mmap and self.mmap_enabled and c.boolget("mmap_enabled")
if self.mmap_enabled:
mmap_token = c.intget("mmap_token")
Expand Down
9 changes: 8 additions & 1 deletion src/xpra/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ def read_xpra_defaults():
"opengl" : bool,
"mdns" : bool,
"swap-keys" : bool,
"start-new-commands": bool,
#arrays of strings:
"encodings" : list,
"video-encoders" : list,
Expand Down Expand Up @@ -398,6 +399,7 @@ def get_defaults():
"windows" : True,
"exit-with-children": False,
"exit-with-client" : False,
"start-new-commands": False,
"exit-ssh" : True,
"opengl" : OPENGL_DEFAULT,
"mdns" : False,
Expand All @@ -410,7 +412,12 @@ def get_defaults():
"microphone-codec" : [],
"compressors" : ["all"],
"packet-encoders" : ["all"],
"key-shortcut" : ["Meta+Shift+F4:quit", "Meta+Shift+F8:magic_key", "Meta+Shift+F11:show_session_info"],
"key-shortcut" : [
"Meta+Shift+F2:show_start_new_command",
"Meta+Shift+F4:quit",
"Meta+Shift+F8:magic_key",
"Meta+Shift+F11:show_session_info"
],
"bind-tcp" : None,
"start" : None,
"start-child" : None,
Expand Down
4 changes: 4 additions & 0 deletions src/xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ def legacy_bool_parse(optionname, newoptionname=None):
group.add_option("--exit-with-children", action="store_true",
dest="exit_with_children", default=defaults.exit_with_children,
help="Terminate the server when the last --start-child command(s) exit")
group.add_option("--start-new-commands", action="store", metavar="yes|no",
dest="start_new_commands", default=defaults.start_new_commands,
help="Allows clients to execute new commands on the server. Default: %s." % enabled_str(defaults.start_new_commands))

if supports_server:
group.add_option("--tcp-proxy", action="store",
dest="tcp_proxy", default=defaults.tcp_proxy,
Expand Down
Loading

0 comments on commit 78cd544

Please sign in to comment.