spice-gtk/gtk/controller/controller.vala
Marc-André Lureau 8a1643d651 controller: async flush read/write
Windows namedpipes behave a bit differently from Unix socket, and may
return incomplete read/write. By using 2 read/write() helpers, try to
complete the operation before returning. Since the IO operation may be
splitted over several call, we make sure the buffer pointer is on the
heap. We use exception for EOF or BROKEN_PIPE condition, which also
simplifies the code.

To really work with namedpipe, the giowin32streams need to be fixed as
well to handle concurrent read & write properly, see for details:
https://bugzilla.gnome.org/show_bug.cgi?id=679288
2012-07-10 13:08:34 +02:00

257 lines
7.2 KiB
Vala

// Copyright (C) 2011 Red Hat, Inc.
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
using GLib;
using Custom;
using Win32;
using Spice;
using SpiceProtocol;
namespace SpiceCtrl {
public errordomain Error {
VALUE,
}
public class Controller: Object {
public string host { private set; get; }
public uint32 port { private set; get; }
public uint32 sport { private set; get; }
public string password { private set; get; }
public SpiceProtocol.Controller.Display display_flags { private set; get; }
public string tls_ciphers { private set; get; }
public string host_subject { private set; get; }
public string ca_file { private set; get; }
public string title { private set; get; }
public string hotkeys { private set; get; }
public string[] secure_channels { private set; get; }
public string[] disable_channels { private set; get; }
public SpiceCtrl.Menu? menu { private set; get; }
public bool enable_smartcard { private set; get; }
public bool send_cad { private set; get; }
public string[] disable_effects {private set; get; }
public uint32 color_depth {private set; get; }
public bool enable_usbredir { private set; get; }
public bool enable_usb_autoshare { private set; get; }
public string usb_filter { private set; get; }
public signal void do_connect ();
public signal void show ();
public signal void hide ();
public signal void client_connected ();
public void menu_item_click_msg (int32 item_id) {
var msg = SpiceProtocol.Controller.MsgValue ();
msg.base.size = (uint32)sizeof (SpiceProtocol.Controller.MsgValue);
msg.base.id = SpiceProtocol.Controller.MsgId.MENU_ITEM_CLICK;
msg.value = item_id;
unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
send_msg (p);
}
public async bool send_msg (uint8[] p) throws GLib.Error {
// vala FIXME: pass Controller.Msg instead
// vala doesn't keep reference on the struct in async methods
// it copies only base, which is not enough to transmit the whole
// message.
try {
if (excl_connection != null) {
yield output_stream_write (excl_connection.output_stream, p);
} else {
foreach (var c in clients)
yield output_stream_write (c.output_stream, p);
}
} catch (GLib.Error e) {
warning (e.message);
}
return true;
}
private GLib.IOStream? excl_connection;
private int nclients;
List<IOStream> clients;
private bool handle_message (SpiceProtocol.Controller.Msg* msg) {
var v = (SpiceProtocol.Controller.MsgValue*)(msg);
var d = (SpiceProtocol.Controller.MsgData*)(msg);
unowned string str = (string)(&d.data);
switch (msg.id) {
case SpiceProtocol.Controller.MsgId.HOST:
host = str;
break;
case SpiceProtocol.Controller.MsgId.PORT:
port = v.value;
break;
case SpiceProtocol.Controller.MsgId.SPORT:
sport = v.value;
break;
case SpiceProtocol.Controller.MsgId.PASSWORD:
password = str;
break;
case SpiceProtocol.Controller.MsgId.SECURE_CHANNELS:
secure_channels = str.split(",");
break;
case SpiceProtocol.Controller.MsgId.DISABLE_CHANNELS:
disable_channels = str.split(",");
break;
case SpiceProtocol.Controller.MsgId.TLS_CIPHERS:
tls_ciphers = str;
break;
case SpiceProtocol.Controller.MsgId.CA_FILE:
ca_file = str;
break;
case SpiceProtocol.Controller.MsgId.HOST_SUBJECT:
host_subject = str;
break;
case SpiceProtocol.Controller.MsgId.FULL_SCREEN:
display_flags = (SpiceProtocol.Controller.Display)v.value;
break;
case SpiceProtocol.Controller.MsgId.SET_TITLE:
title = str;
break;
case SpiceProtocol.Controller.MsgId.ENABLE_SMARTCARD:
enable_smartcard = (bool)v.value;
break;
case SpiceProtocol.Controller.MsgId.CREATE_MENU:
menu = new SpiceCtrl.Menu.from_string (str);
break;
case SpiceProtocol.Controller.MsgId.DELETE_MENU:
menu = null;
break;
case SpiceProtocol.Controller.MsgId.SEND_CAD:
send_cad = (bool)v.value;
break;
case SpiceProtocol.Controller.MsgId.HOTKEYS:
hotkeys = str;
break;
case SpiceProtocol.Controller.MsgId.COLOR_DEPTH:
color_depth = v.value;
break;
case SpiceProtocol.Controller.MsgId.DISABLE_EFFECTS:
disable_effects = str.split(",");
break;
case SpiceProtocol.Controller.MsgId.CONNECT:
do_connect ();
break;
case SpiceProtocol.Controller.MsgId.SHOW:
show ();
break;
case SpiceProtocol.Controller.MsgId.HIDE:
hide ();
break;
case SpiceProtocol.Controller.MsgId.ENABLE_USB:
enable_usbredir = (bool)v.value;
break;
case SpiceProtocol.Controller.MsgId.ENABLE_USB_AUTOSHARE:
enable_usb_autoshare = (bool)v.value;
break;
case SpiceProtocol.Controller.MsgId.USB_FILTER:
usb_filter = str;
break;
default:
warn_if_reached ();
return false;
}
return true;
}
private async void handle_client (IOStream c) throws GLib.Error {
var excl = false;
debug ("new socket client, reading init header");
var p = new uint8[sizeof(SpiceProtocol.Controller.Init)];
var init = (SpiceProtocol.Controller.Init*)p;
yield input_stream_read (c.input_stream, p);
if (warn_if (init.base.magic != SpiceProtocol.Controller.MAGIC))
return;
if (warn_if (init.base.version != SpiceProtocol.Controller.VERSION))
return;
if (warn_if (init.base.size < sizeof (SpiceProtocol.Controller.Init)))
return;
if (warn_if (init.credentials != 0))
return;
if (warn_if (excl_connection != null))
return;
excl = (bool)(init.flags & SpiceProtocol.Controller.Flag.EXCLUSIVE);
if (excl) {
if (nclients > 1) {
warning (@"Can't make the client exclusive, there is already $nclients connected clients");
return;
}
excl_connection = c;
}
client_connected ();
for (;;) {
var t = new uint8[sizeof(SpiceProtocol.Controller.Msg)];
yield input_stream_read (c.input_stream, t);
var msg = (SpiceProtocol.Controller.Msg*)t;
debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ());
if (warn_if (msg.size < sizeof (SpiceProtocol.Controller.Msg)))
break;
if (msg.size > sizeof (SpiceProtocol.Controller.Msg)) {
t.resize ((int)msg.size);
msg = (SpiceProtocol.Controller.Msg*)t;
yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.Controller.Msg):msg.size]);
}
handle_message (msg);
}
if (excl)
excl_connection = null;
}
public Controller() {
}
public async void listen (string? addr = null) throws GLib.Error, SpiceCtrl.Error
{
var listener = ControllerListener.new_listener (addr);
for (;;) {
var c = yield listener.accept_async ();
nclients += 1;
clients.append (c);
try {
yield handle_client (c);
} catch (GLib.Error e) {
warning (e.message);
}
c.close ();
clients.remove (c);
nclients -= 1;
}
}
}
} // SpiceCtrl