Commit 56aaa8dd authored by Dan Pascu's avatar Dan Pascu

Added video support

parent e92659fe
Video fixes
-----------
- global screenshots/download directory (for all sections)
- consider if we should always show the mute/hold/close buttons (even in
attached mode)?
- run the preview at the normal framerate before we connect (while big)?
- hide scrollbar in chat widget when video is overlayed on it?
- right click on camera preview bring up context menu to select camera
- make detaching animation have a duration that is proportional with the
distance traveled, so that it appears to be similarly fast no matter
how far it detaches
- maybe don't show the camera preview if the video device is None
- hide preview (and buttons?) while we animate?
- preview limited to parent (resize still has issues)
- double click to restore default size for preview? (might be problematic)
- if audio is removed blink-qt puts the session on hold 5 seconds later
when the AudioSessionItem is destroyed
- custom icons for each window (chat, video, file transfer, ...)
Code refactoring
----------------
......@@ -60,8 +82,59 @@ Ideas:
900x550 (230 splitter), 925x550 (240 splitter), 950x550 (250 splitter)
CPU usage increases for video:
------------------------------
painting camera preview @10fps (static image no producer connected)
in chat: +4-5%
detached: +3-4%
fullscren: +4-7%
producer @25fps connected, not paining
in chat: +8-9%
detached: +8-9%
fullscreen: +6-7%
producer @25fps connected, paining @10fps
in chat: +13-14%
detached: +12%
fullscreen: +12-15%
- exceptions:
error: Exception occured in observer <blink.chatwindow.ChatWindow object at 0xab33d5cc> while handling notification 'BlinkSessionInfoUpdated'
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/application/notification.py", line 216, in post_notification
observer.handle_notification(notification)
File "<string>", line 1, in handle_notification
File "/home/dan/work/voip/blink-qt/blink/util.py", line 36, in wrapper
function(*args, **kw)
File "/home/dan/work/voip/blink-qt/blink/chatwindow.py", line 1653, in handle_notification
handler(notification)
File "/home/dan/work/voip/blink-qt/blink/chatwindow.py", line 1767, in _NH_BlinkSessionInfoUpdated
self._update_session_info_panel(elements=notification.data.elements)
File "/home/dan/work/voip/blink-qt/blink/chatwindow.py", line 1481, in _update_session_info_panel
self.video_value_label.setText(video_info.codec or 'N/A')
File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 158, in codec
return '{0.codec_name} {0.framerate:.3g}fps'.format(self) if self.codec_name else None
ValueError: Unknown format code 'g' for object of type 'str'
error: Exception occured in observer <blink.sessions.AudioSessionListView object at 0xab300a04> while handling notification 'BlinkActiveSessionDidChange'
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/application/notification.py", line 216, in post_notification
observer.handle_notification(notification)
File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 2466, in handle_notification
handler(notification)
File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 2480, in _NH_BlinkActiveSessionDidChange
position = model.sessions.index(notification.data.active_session.items.audio)
ValueError: None is not in list
error: Exception occured in observer <sipsimple.streams.msrp.ScreenSharingStream object at 0x7fa65c2a3550> while handling notification 'MediaStreamWillEnd'
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/application/notification.py", line 216, in post_notification
......
......@@ -19,6 +19,8 @@ sip.setapi('QVariant', 2)
from PyQt4.QtCore import Qt, QEvent
from PyQt4.QtGui import QApplication
QApplication.setAttribute(Qt.AA_X11InitThreads, True)
from application import log
from application.notification import IObserver, NotificationCenter, NotificationData
from application.python import Null
......
This diff is collapsed.
......@@ -7,7 +7,7 @@ __all__ = ['AccountExtension', 'BonjourAccountExtension']
from sipsimple.account import BonjourMSRPSettings, MessageSummarySettings, MSRPSettings, PresenceSettings, RTPSettings, SIPSettings, TLSSettings, XCAPSettings
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension, RuntimeSetting
from sipsimple.configuration.datatypes import AudioCodecList, Hostname, MSRPConnectionModel, MSRPTransport, NonNegativeInteger, SIPTransportList, SRTPEncryption
from sipsimple.configuration.datatypes import AudioCodecList, Hostname, MSRPConnectionModel, MSRPTransport, NonNegativeInteger, SIPTransportList, SRTPEncryption, VideoCodecList
from sipsimple.util import user_info
from blink.configuration.datatypes import ApplicationDataPath, HTTPURL, IconDescriptor, SoundFile
......@@ -42,6 +42,7 @@ class PSTNSettings(SettingsGroup):
class RTPSettingsExtension(RTPSettings):
audio_codec_order = Setting(type=AudioCodecList, default=None, nillable=True)
video_codec_order = Setting(type=VideoCodecList, default=None, nillable=True)
inband_dtmf = Setting(type=bool, default=True)
srtp_encryption = Setting(type=SRTPEncryption, default='optional')
use_srtp_without_tls = Setting(type=bool, default=True)
......
......@@ -9,7 +9,7 @@ import platform
import sys
from sipsimple.configuration import Setting, SettingsGroup, SettingsObject, SettingsObjectExtension
from sipsimple.configuration.datatypes import AudioCodecList, NonNegativeInteger, PositiveInteger, Path, SampleRate
from sipsimple.configuration.datatypes import AudioCodecList, NonNegativeInteger, PositiveInteger, Path, SampleRate, VideoCodecList
from sipsimple.configuration.settings import AudioSettings, ChatSettings, EchoCancellerSettings, FileTransferSettings, LogsSettings, RTPSettings, TLSSettings
from blink import __version__
......@@ -60,6 +60,7 @@ class LogsSettingsExtension(LogsSettings):
class RTPSettingsExtension(RTPSettings):
audio_codec_order = Setting(type=AudioCodecList, default=AudioCodecList(('opus', 'G722', 'speex', 'GSM', 'iLBC', 'PCMU', 'PCMA')))
video_codec_order = Setting(type=VideoCodecList, default=VideoCodecList(('H264',)))
class ServerSettings(SettingsGroup):
......@@ -120,6 +121,10 @@ class BlinkScreenSharingSettings(SettingsGroup):
open_viewonly = Setting(type=bool, default=False)
class BlinkVideoSettings(SettingsGroup):
screenshots_directory = Setting(type=Path, default=Path('~/Downloads'))
class BlinkPresenceSettings(SettingsGroup):
current_state = Setting(type=PresenceState, default=PresenceState('Available'))
state_history = Setting(type=PresenceStateList, default=PresenceStateList())
......@@ -133,4 +138,5 @@ class BlinkSettings(SettingsObject):
chat_window = ChatWindowSettings
presence = BlinkPresenceSettings
screen_sharing = BlinkScreenSharingSettings
video = BlinkVideoSettings
This diff is collapsed.
......@@ -139,6 +139,7 @@ class MainWindow(base_class, ui_class):
self.add_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
self.add_search_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
self.audio_call_button.clicked.connect(self._SH_AudioCallButtonClicked)
self.video_call_button.clicked.connect(self._SH_VideoCallButtonClicked)
self.chat_session_button.clicked.connect(self._SH_ChatSessionButtonClicked)
self.back_to_contacts_button.clicked.connect(self.search_box.clear) # this can be set in designer -Dan
self.conference_button.makeConference.connect(self._SH_MakeConference)
......@@ -192,6 +193,7 @@ class MainWindow(base_class, ui_class):
self.output_devices_group.triggered.connect(self._AH_AudioOutputDeviceChanged)
self.input_devices_group.triggered.connect(self._AH_AudioInputDeviceChanged)
self.alert_devices_group.triggered.connect(self._AH_AudioAlertDeviceChanged)
self.video_devices_group.triggered.connect(self._AH_VideoDeviceChanged)
self.mute_action.triggered.connect(self._SH_MuteButtonClicked)
self.silent_action.triggered.connect(self._SH_SilentButtonClicked)
......@@ -216,6 +218,7 @@ class MainWindow(base_class, ui_class):
self.output_devices_group = QActionGroup(self)
self.input_devices_group = QActionGroup(self)
self.alert_devices_group = QActionGroup(self)
self.video_devices_group = QActionGroup(self)
self.request_screen_action = QAction('Request screen', self, triggered=self._AH_RequestScreenActionTriggered)
self.share_my_screen_action = QAction('Share my screen', self, triggered=self._AH_ShareMyScreenActionTriggered)
......@@ -256,6 +259,7 @@ class MainWindow(base_class, ui_class):
def enable_call_buttons(self, enabled):
self.audio_call_button.setEnabled(enabled)
self.video_call_button.setEnabled(enabled)
self.chat_session_button.setEnabled(enabled)
self.screen_sharing_button.setEnabled(enabled)
......@@ -310,6 +314,25 @@ class MainWindow(base_class, ui_class):
if settings.audio.alert_device == action.data():
action.setChecked(True)
def load_video_devices(self):
settings = SIPSimpleSettings()
action = QAction(u'System default', self.video_devices_group)
action.setData(u'system_default')
self.video_camera_menu.addAction(action)
self.video_camera_menu.addSeparator()
for device in SIPApplication.engine.video_devices:
action = QAction(device, self.video_devices_group)
action.setData(device)
self.video_camera_menu.addAction(action)
action = QAction(u'None', self.video_devices_group)
action.setData(None)
self.video_camera_menu.addAction(action)
for action in self.video_devices_group.actions():
action.setCheckable(True)
if settings.video.device == action.data():
action.setChecked(True)
def _AH_AccountActionTriggered(self, action, enabled):
account = action.data()
account.enabled = enabled
......@@ -330,6 +353,11 @@ class MainWindow(base_class, ui_class):
settings.audio.output_device = action.data()
settings.save()
def _AH_VideoDeviceChanged(self, action):
settings = SIPSimpleSettings()
settings.video.device = action.data()
settings.save()
def _AH_AutoAcceptChatActionTriggered(self, checked):
settings = SIPSimpleSettings()
settings.chat.auto_accept = checked
......@@ -495,6 +523,20 @@ class MainWindow(base_class, ui_class):
session_manager = SessionManager()
session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])
def _SH_VideoCallButtonClicked(self):
list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
if list_view.detail_view.isVisible():
list_view.detail_view._AH_StartVideoCall()
else:
selected_indexes = list_view.selectionModel().selectedIndexes()
if selected_indexes:
contact = selected_indexes[0].data(Qt.UserRole)
contact_uri = contact.uri
else:
contact, contact_uri = URIUtils.find_contact(self.search_box.text())
session_manager = SessionManager()
session_manager.create_session(contact, contact_uri, [StreamDescription('audio'), StreamDescription('video')])
def _SH_ChatSessionButtonClicked(self):
list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
if list_view.detail_view.isVisible():
......@@ -720,6 +762,7 @@ class MainWindow(base_class, ui_class):
def _NH_SIPApplicationDidStart(self, notification):
self.load_audio_devices()
self.load_video_devices()
notification.center.add_observer(self, name='CFGSettingsObjectDidChange')
notification.center.add_observer(self, name='AudioDevicesDidChange')
blink_settings = BlinkSettings()
......@@ -769,6 +812,9 @@ class MainWindow(base_class, ui_class):
if 'audio.alert_device' in notification.data.modified:
action = (action for action in self.alert_devices_group.actions() if action.data() == settings.audio.alert_device).next()
action.setChecked(True)
if 'video.device' in notification.data.modified:
action = (action for action in self.video_devices_group.actions() if action.data() == settings.video.device).next()
action.setChecked(True)
if 'answering_machine.enabled' in notification.data.modified:
self.answering_machine_action.setChecked(settings.answering_machine.enabled)
if 'chat.auto_accept' in notification.data.modified:
......
This diff is collapsed.
......@@ -11,6 +11,7 @@ import re
import string
import sys
from abc import ABCMeta, abstractproperty
from collections import defaultdict, deque
from datetime import datetime, timedelta
from functools import partial
......@@ -54,6 +55,8 @@ from blink.widgets.util import ContextMenuActions, QtDynamicProperty
class RTPStreamInfo(object):
__metaclass__ = ABCMeta
dataset_size = 5000
average_interval = 10
......@@ -78,9 +81,9 @@ class RTPStreamInfo(object):
self._total_packets_discarded = 0
self._average_loss_queue = deque(maxlen=self.average_interval)
@property
@abstractproperty
def codec(self):
return '%s %dkHz' % (self.codec_name, self.sample_rate/1000) if self.codec_name else None
raise NotImplementedError
def _update(self, stream):
if stream is not None:
......@@ -114,6 +117,8 @@ class RTPStreamInfo(object):
class MSRPStreamInfo(object):
__metaclass__ = ABCMeta
def __init__(self):
self.local_address = None
self.remote_address = None
......@@ -137,6 +142,33 @@ class MSRPStreamInfo(object):
self.__init__()
class AudioStreamInfo(RTPStreamInfo):
@property
def codec(self):
return '{} {}kHz'.format(self.codec_name, self.sample_rate//1000) if self.codec_name else None
class VideoStreamInfo(RTPStreamInfo):
def __init__(self):
super(VideoStreamInfo, self).__init__()
self.framerate = None
@property
def codec(self):
return '{0.codec_name} {0.framerate:.3g}fps'.format(self) if self.codec_name else None
def _update(self, stream):
super(VideoStreamInfo, self)._update(stream)
try:
self.framerate = stream.producer.framerate
except AttributeError:
pass
class ChatStreamInfo(MSRPStreamInfo):
pass
class ScreenSharingStreamInfo(MSRPStreamInfo):
def __init__(self):
super(ScreenSharingStreamInfo, self).__init__()
......@@ -152,9 +184,9 @@ class StreamsInfo(object):
__slots__ = 'audio', 'video', 'chat', 'screen_sharing'
def __init__(self):
self.audio = RTPStreamInfo()
self.video = RTPStreamInfo()
self.chat = MSRPStreamInfo()
self.audio = AudioStreamInfo()
self.video = VideoStreamInfo()
self.chat = ChatStreamInfo()
self.screen_sharing = ScreenSharingStreamInfo()
def __getitem__(self, key):
......@@ -966,6 +998,10 @@ class BlinkSession(QObject):
self.recording = False
notification.center.post_notification('BlinkSessionDidChangeRecordingState', sender=self, data=NotificationData(recording=self.recording))
def _NH_VideoStreamRemoteFormatDidChange(self, notification):
self.info.streams.video._update(notification.sender)
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
def _NH_BlinkContactDidChange(self, notification):
notification.center.post_notification('BlinkSessionContactDidChange', sender=self)
......@@ -2664,6 +2700,14 @@ class ChatSessionItem(object):
def chat_stream(self):
return self.blink_session.streams.get('chat')
@property
def audio_stream(self):
return self.blink_session.streams.get('audio')
@property
def video_stream(self):
return self.blink_session.streams.get('video')
def _get_remote_composing(self):
return self.__dict__['remote_composing']
......
# Copyright (C) 2014 AG Projects. See LICENSE for details.
#
from __future__ import division
__all__ = ['VideoSurface']
from PyQt4.QtCore import Qt, QMetaObject, QPoint, QRect, QTimer, pyqtSignal
from PyQt4.QtGui import QColor, QCursor, QIcon, QImage, QPainter, QPixmap, QTransform, QWidget
from application.python.types import MarkerType
from math import ceil
from operator import truediv
from sipsimple.core import FrameBufferVideoRenderer
from blink.resources import Resources
class Container(object): pass
class Cursors(Container): pass
class InteractionState(object):
def __init__(self):
self.moving = False
self.resizing = False
self.resize_corner = None
self.mouse_last_position = None
self.initial_geometry = None
@property
def active(self):
return self.moving or self.resizing
def clear(self):
self.__init__()
class VideoSurface(QWidget):
class TopLeftCorner: __metaclass__ = MarkerType
class TopRightCorner: __metaclass__ = MarkerType
class BottomLeftCorner: __metaclass__ = MarkerType
class BottomRightCorner: __metaclass__ = MarkerType
adjusted = pyqtSignal(QRect, QRect) # the widget was adjusted by the user (if interactive)
interactive = False # if the widget can be interacted with (moved, resized)
mirror = False # mirror the image horizontally
def __init__(self, parent=None, framerate=None):
super(VideoSurface, self).__init__(parent)
self.setAttribute(Qt.WA_OpaquePaintEvent, True)
self.setMouseTracking(True)
self.cursors = Cursors()
self.cursors.resize_top = QCursor(QIcon(Resources.get('icons/resize-top.svg')).pixmap(16), hotX=8, hotY=0)
self.cursors.resize_bottom = QCursor(QIcon(Resources.get('icons/resize-bottom.svg')).pixmap(16), hotX=8, hotY=16)
if framerate is not None:
self._clock = QTimer()
self._clock.setInterval(1000/framerate)
self._clock.timeout.connect(self.update)
else:
self._clock = None
self._interaction = InteractionState()
self._image = None
def __getattr__(self, name):
if name == '_renderer':
return self.__dict__.setdefault(name, FrameBufferVideoRenderer(self._handle_frame))
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
def _get_producer(self):
return self._renderer.producer
def _set_producer(self, producer):
if self._clock is not None:
self._clock.stop()
self._renderer.producer = producer
if self._clock is not None and producer is not None:
self._clock.start()
producer = property(_get_producer, _set_producer)
del _get_producer, _set_producer
@property
def aspect(self):
producer = self._renderer.producer
return truediv(*producer.framesize) if producer is not None else 16/9
def width_for_height(self, height):
return int(ceil(height * self.aspect))
def height_for_width(self, width):
return int(ceil(width / self.aspect))
def heightForWidth(self, width):
return int(ceil(width * 9/16))
def stop(self):
if self._clock is not None:
self._clock.stop()
self._renderer.close()
del self._renderer
def _handle_frame(self, frame):
self._image = QImage(frame.data, frame.width, frame.height, QImage.Format_ARGB32)
if self._clock is None:
QMetaObject.invokeMethod(self, 'update', Qt.QueuedConnection)
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(self.rect(), QColor('#101010'))
image = self._image
if image is not None:
if self.height() < 240:
fast_scaler = QTransform()
scale = 297/image.height()
if self.mirror:
fast_scaler.scale(-scale, scale)
else:
fast_scaler.scale(scale, scale)
rect = event.rect()
painter.drawPixmap(rect, QPixmap.fromImage(image).transformed(fast_scaler).scaledToHeight(self.height(), Qt.SmoothTransformation), rect)
else:
transform = QTransform()
scale = min(self.width()/image.width(), self.height()/image.height())
if self.mirror:
transform.translate((self.width() + image.width()*scale)/2, (self.height() - image.height()*scale)/2)
transform.scale(-scale, scale)
else:
transform.translate((self.width() - image.width()*scale)/2, (self.height() - image.height()*scale)/2)
transform.scale(scale, scale)
inverse_transform, invertible = transform.inverted()
rect = inverse_transform.mapRect(event.rect()).adjusted(-1, -1, 1, 1).intersected(image.rect())
painter.setTransform(transform)
if self.height() > 400:
painter.drawPixmap(rect, QPixmap.fromImage(image), rect)
else:
painter.drawImage(rect, image, rect)
painter.end()
def mousePressEvent(self, event):
if self.interactive and event.button() == Qt.LeftButton and event.modifiers() == Qt.NoModifier:
if self.rect().adjusted(0, 10, 0, -10).contains(event.pos()):
self._interaction.moving = True
else:
self._interaction.resizing = True
event_x = event.x()
event_y = event.y()
half_width = self.width() / 2
half_height = self.height() / 2
if event_x < half_width and event_y < half_height:
self._interaction.resize_corner = self.TopLeftCorner
elif event_x >= half_width and event_y < half_height:
self._interaction.resize_corner = self.TopRightCorner
elif event_x < half_width and event_y >= half_height:
self._interaction.resize_corner = self.BottomLeftCorner
else:
self._interaction.resize_corner = self.BottomRightCorner
self._interaction.mouse_last_position = event.globalPos()
self._interaction.initial_geometry = self.geometry()
def mouseReleaseEvent(self, event):
if self._interaction.active and self._interaction.initial_geometry != self.geometry():
self.adjusted.emit(self._interaction.initial_geometry, self.geometry())
self._interaction.clear()
def mouseMoveEvent(self, event):
if self._interaction.moving:
mouse_position = event.globalPos()
offset = mouse_position - self._interaction.mouse_last_position
if self.parent() is not None:
parent_rect = self.parent().rect()
old_geometry = self.geometry()
new_geometry = old_geometry.translated(offset)
if new_geometry.left() < 0:
new_geometry.moveLeft(0)
if new_geometry.top() < 0:
new_geometry.moveTop(0)
if new_geometry.right() > parent_rect.right():
new_geometry.moveRight(parent_rect.right())
if new_geometry.bottom() > parent_rect.bottom():
new_geometry.moveBottom(parent_rect.bottom())
offset = new_geometry.topLeft() - old_geometry.topLeft()
self.move(self.pos() + offset)
self._interaction.mouse_last_position += offset
elif self._interaction.resizing:
mouse_position = event.globalPos()
delta_y = mouse_position.y() - self._interaction.mouse_last_position.y()
geometry = self.geometry()
if self._interaction.resize_corner is self.TopLeftCorner:
delta_x = -(self.width_for_height(geometry.height() - delta_y) - geometry.width())
geometry.setTopLeft(geometry.topLeft() + QPoint(delta_x, delta_y))
elif self._interaction.resize_corner is self.TopRightCorner:
delta_x = (self.width_for_height(geometry.height() - delta_y) - geometry.width())
geometry.setTopRight(geometry.topRight() + QPoint(delta_x, delta_y))
elif self._interaction.resize_corner is self.BottomLeftCorner:
delta_x = -(self.width_for_height(geometry.height() + delta_y) - geometry.width())
geometry.setBottomLeft(geometry.bottomLeft() + QPoint(delta_x, delta_y))
else:
delta_x = (self.width_for_height(geometry.height() + delta_y) - geometry.width())
geometry.setBottomRight(geometry.bottomRight() + QPoint(delta_x, delta_y))
if self.minimumHeight() <= geometry.height() <= self.maximumHeight() and (self.parent() is None or self.parent().rect().contains(geometry)):
self.setGeometry(geometry)
self._interaction.mouse_last_position = mouse_position
elif self.interactive:
mouse_position = event.pos()
topbar_rect = QRect(0, 0, self.width(), 10)
if self.rect().adjusted(0, 10, 0, -10).contains(mouse_position):
self.setCursor(Qt.ArrowCursor)
elif topbar_rect.contains(mouse_position):
self.setCursor(self.cursors.resize_top)
else:
self.setCursor(self.cursors.resize_bottom)
def closeEvent(self, event):
super(VideoSurface, self).closeEvent(event)
self.stop()
......@@ -667,6 +667,35 @@ padding: 2px;</string>
</property>
</widget>
</item>
<item>
<widget class="ToolButton" name="video_call_button">
<property name="minimumSize">
<size>
<width>29</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>29</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Make a video call</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons/camera.svg</normaloff>icons/camera.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="ToolButton" name="chat_session_button">
<property name="minimumSize">
......@@ -1029,6 +1058,15 @@ padding: 2px;</string>
<normaloff>icons/bell.svg</normaloff>icons/bell.svg</iconset>
</property>
</widget>
<widget class="QMenu" name="video_camera_menu">
<property name="title">
<string>Video &amp;Camera</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons/camera.svg</normaloff>icons/camera.svg</iconset>
</property>
</widget>
<addaction name="redial_action"/>
<addaction name="join_conference_action"/>
<addaction name="voicemail_menu"/>
......@@ -1037,6 +1075,7 @@ padding: 2px;</string>
<addaction name="input_device_menu"/>
<addaction name="output_device_menu"/>
<addaction name="alert_device_menu"/>
<addaction name="video_camera_menu"/>
<addaction name="separator"/>
<addaction name="mute_action"/>
<addaction name="silent_action"/>
......
......@@ -1100,10 +1100,10 @@ QToolButton:pressed {
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="status_title_label">
<item row="9" column="0">
<widget class="QLabel" name="screen_title_label">
<property name="text">
<string>Status</string>
<string>Screen</string>
</property>
<property name="role" stdset="0">
<string notr="true">title</string>
......@@ -1126,6 +1126,16 @@ QToolButton:pressed {
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="status_title_label">
<property name="text">
<string>Status</string>
</property>
<property name="role" stdset="0">
<string notr="true">title</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="DurationLabel" name="duration_value_label">
<property name="sizePolicy">
......@@ -1145,7 +1155,7 @@ QToolButton:pressed {
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<item row="13" column="0" colspan="2">
<spacer name="info_panel_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
......@@ -1239,17 +1249,7 @@ QToolButton:pressed {
</layout>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="screen_title_label">
<property name="text">
<string>Screen</string>
</property>
<property name="role" stdset="0">
<string notr="true">title</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="9" column="1">
<widget class="QWidget" name="screen_value_widget" native="true">
<property name="role" stdset="0">
<string notr="true">value</string>
......@@ -1347,7 +1347,7 @@ QToolButton:pressed {
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="11" column="0" colspan="2">
<widget class="QWidget" name="packet_loss_widget" native="true">
<layout class="QVBoxLayout" name="packet_loss_layout">
<property name="spacing">
......@@ -1420,7 +1420,7 @@ QToolButton:pressed {
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<item row="12" column="0" colspan="2">
<widget class="QWidget" name="traffic_widget" native="true">
<layout class="QVBoxLayout" name="speed_layout">
<property name="spacing">
......@@ -1636,7 +1636,7 @@ QToolButton:pressed {
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<widget class="QWidget" name="latency_widget" native="true">
<layout class="QVBoxLayout" name="latency_layout">
<property name="spacing">
......@@ -1816,6 +1816,94 @@ QToolButton:pressed {
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="video_title_label">
<property name="text">
<string>Video</string>
</property>
<property name="role" stdset="0">
<string notr="true">title</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QWidget" name="video_value_widget" native="true">
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
<layout class="QHBoxLayout" name="video_value_layout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ElidedLabel" name="video_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>H264 25fps</string>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="video_connection_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>icons/connection-direct.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="video_encryption_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>icons/lock-grey-12.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
......@@ -2044,7 +2132,7 @@ QToolButton:hover {
<number>0</number>
</property>
<item>
<widget class="ChatWebView" name="chat_view">
<widget class="ChatWebView" name="chat_view" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
......@@ -2060,7 +2148,7 @@ QToolButton:hover {
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="url">
<property name="url" stdset="0">
<url>
<string>about:blank</string>
</url>
......
......@@ -106,6 +106,11 @@
<string>Start voice calls by default</string>
</property>
</item>
<item>
<property name="text">
<string>Start video calls by default</string>
</property>
</item>
<item>
<property name="text">
<string>Start chat sessions by default</string>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="attach.svg"
inkscape:version="0.48.5 r10040"
xml:space="preserve"
width="96px"
viewBox="0 0 96 96"
version="1.1"
id="bigger"
height="96px"
enable-background="new 0 0 96 96"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs9"><linearGradient
id="linearGradient3983"><stop
style="stop-color:#00ff00;stop-opacity:1;"
offset="0"
id="stop3985" /><stop
style="stop-color:#00d000;stop-opacity:1;"
offset="1"
id="stop3987" /></linearGradient><linearGradient
id="linearGradient12315"><stop
style="stop-color:#ff0000;stop-opacity:1;"
offset="0"
id="stop12317" /><stop
style="stop-color:#d30000;stop-opacity:1;"
offset="1"
id="stop12319" /></linearGradient><linearGradient
id="linearGradient11765"><stop
style="stop-color:#ff0000;stop-opacity:1;"
offset="0"
id="stop11767" /><stop
style="stop-color:#d00000;stop-opacity:1;"
offset="1"
id="stop11769" /></linearGradient><linearGradient
id="linearGradient11102"><stop
style="stop-color:#000000;stop-opacity:0.43137255;"
offset="0"
id="stop11104" /><stop
style="stop-color:#464646;stop-opacity:1;"
offset="1"
id="stop11106" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11765"
id="radialGradient11771"
cx="12"
cy="18"
fx="12"
fy="18"
r="29.642775"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient12315"
id="radialGradient12321"
cx="48"
cy="48"
fx="48"
fy="48"
r="44"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3983"
id="radialGradient3989"
cx="12"
cy="18"
fx="12"
fy="18"
r="29.642775"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1623"
inkscape:window-height="1075"
id="namedview7"
showgrid="true"
inkscape:zoom="9.0104167"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-x="90"
inkscape:window-y="9"
inkscape:window-maximized="0"
inkscape:current-layer="g3113"
showguides="true"
inkscape:guide-bbox="true"><inkscape:grid
type="xygrid"
id="grid2988"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview><g
inkscape:label="Attach"
id="g3113"
inkscape:groupmode="layer"
style="display:inline"><rect
ry="3.8571427"
y="-48"
x="-84"
height="36"
width="36"
id="rect3117"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
transform="scale(-1,-1)" /><path
inkscape:connector-curvature="0"
id="path3119"
d="M 48,47.999998 C 16,80 16,80 16,80"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path3121"
d="m 12,56 c 0,28 0,28 0,28 l 28,0"
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg4429"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="camera.svg">
<defs
id="defs4431" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="53.8125"
inkscape:cx="8"
inkscape:cy="8"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1665"
inkscape:window-height="1071"
inkscape:window-x="67"
inkscape:window-y="32"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid4437"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata4434">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Camera"
inkscape:groupmode="layer">
<path
style="fill:#545454;fill-opacity:1;stroke:#333333;stroke-width:0.99999994000000003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 13.5,3.5 c 0,2.4571428 0,6.542857 0,9 -1.222222,-1.228572 -2.777778,-2.7714284 -4,-4 l 0,3 -7,0 c 0,-1.638095 0,-5.3619048 0,-7 l 7,0 0,3 c 0,0 2.777778,-2.7714286 4,-4 z"
id="path4967"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="detach.svg"
inkscape:version="0.48.5 r10040"
xml:space="preserve"
width="96px"
viewBox="0 0 96 96"
version="1.1"
id="bigger"
height="96px"
enable-background="new 0 0 96 96"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs9"><linearGradient
id="linearGradient3983"><stop
style="stop-color:#00ff00;stop-opacity:1;"
offset="0"
id="stop3985" /><stop
style="stop-color:#00d000;stop-opacity:1;"
offset="1"
id="stop3987" /></linearGradient><linearGradient
id="linearGradient12315"><stop
style="stop-color:#ff0000;stop-opacity:1;"
offset="0"
id="stop12317" /><stop
style="stop-color:#d30000;stop-opacity:1;"
offset="1"
id="stop12319" /></linearGradient><linearGradient
id="linearGradient11765"><stop
style="stop-color:#ff0000;stop-opacity:1;"
offset="0"
id="stop11767" /><stop
style="stop-color:#d00000;stop-opacity:1;"
offset="1"
id="stop11769" /></linearGradient><linearGradient
id="linearGradient11102"><stop
style="stop-color:#000000;stop-opacity:0.43137255;"
offset="0"
id="stop11104" /><stop
style="stop-color:#464646;stop-opacity:1;"
offset="1"
id="stop11106" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11765"
id="radialGradient11771"
cx="12"
cy="18"
fx="12"
fy="18"
r="29.642775"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient12315"
id="radialGradient12321"
cx="48"
cy="48"
fx="48"
fy="48"
r="44"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3983"
id="radialGradient3989"
cx="12"
cy="18"
fx="12"
fy="18"
r="29.642775"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1623"
inkscape:window-height="1075"
id="namedview7"
showgrid="true"
inkscape:zoom="9.0104167"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-x="114"
inkscape:window-y="10"
inkscape:window-maximized="0"
inkscape:current-layer="g3123"
showguides="true"
inkscape:guide-bbox="true"><inkscape:grid
type="xygrid"
id="grid2988"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview><g
style="display:inline"
inkscape:groupmode="layer"
id="g3123"
inkscape:label="Detach"><rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:7.99999857;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect3127"
width="36"
height="36"
x="-84"
y="-48"
ry="3.8571432"
transform="scale(-1,-1)" /><path
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 12,83.999996 C 40.000001,55.999997 40.000001,55.999997 40.000001,55.999997"
id="path3129"
inkscape:connector-curvature="0" /><path
style="fill:none;stroke:#000000;stroke-width:8.00000095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 44.000001,80 c 0,-28 0,-28 0,-28 L 16,52"
id="path3131"
inkscape:connector-curvature="0" /></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg2"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="resize-bottom.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#969696"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.19607843"
inkscape:pageshadow="2"
inkscape:zoom="55.4375"
inkscape:cx="8"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="g4151"
showgrid="true"
inkscape:window-width="1688"
inkscape:window-height="1097"
inkscape:window-x="80"
inkscape:window-y="0"
inkscape:window-maximized="0">
<inkscape:grid
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px"
type="xygrid"
id="grid2985" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
inkscape:label="Resize bottom"
inkscape:groupmode="layer"
id="g4151"
transform="translate(0,-1036.3622)">
<path
style="fill:none;stroke:#ffffff;stroke-width:3.5999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0002178,1047.5618 c 0,-9.3996 0,-9.3996 0,-9.3996"
id="path4153"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path4155"
d="m 12.20022,1044.1622 c -4.2000022,4.4 -4.2000022,4.4 -4.2000022,4.4 l -4.2000029,-4.4"
style="fill:none;stroke:#ffffff;stroke-width:3.5999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<path
inkscape:connector-curvature="0"
id="path4157"
d="m 14.000218,1050.3623 -12.0004361,0"
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<path
inkscape:connector-curvature="0"
id="path4159"
d="m 8.0002178,1047.5618 c 0,-9.3996 0,-9.3996 0,-9.3996"
style="fill:none;stroke:#000000;stroke-width:1.59999442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<path
style="fill:none;stroke:#000000;stroke-width:1.59999478;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 12.200221,1044.1622 c -4.2000032,4.4 -4.2000032,4.4 -4.2000032,4.4 l -4.2000029,-4.4"
id="path4161"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1.99999964;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 14.000218,1050.3623 -12.0004361,0"
id="path4163"
inkscape:connector-curvature="0" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg2"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="resize-top.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#969696"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.19607843"
inkscape:pageshadow="2"
inkscape:zoom="55.4375"
inkscape:cx="8"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="g3154"
showgrid="true"
inkscape:window-width="1688"
inkscape:window-height="1097"
inkscape:window-x="80"
inkscape:window-y="0"
inkscape:window-maximized="0">
<inkscape:grid
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px"
type="xygrid"
id="grid2985" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(0,-1036.3622)"
id="g3154"
inkscape:groupmode="layer"
inkscape:label="Resize top thick"
style="display:inline">
<path
inkscape:connector-curvature="0"
id="path4068-0"
d="m 7.9997821,1041.1627 c 0,9.3996 0,9.3996 0,9.3996"
style="fill:none;stroke:#ffffff;stroke-width:3.5999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<path
style="fill:none;stroke:#ffffff;stroke-width:3.5999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 3.7997795,1044.5623 c 4.2000026,-4.4 4.2000026,-4.4 4.2000026,-4.4 l 4.2000029,4.4"
id="path4070-1"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 1.9997819,1038.3622 12.0004361,0"
id="path4072-4"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1.59999442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 7.9997821,1041.1627 c 0,9.3996 0,9.3996 0,9.3996"
id="path4036-2"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path4038-2"
d="m 3.7997791,1044.5623 c 4.200003,-4.4 4.200003,-4.4 4.200003,-4.4 l 4.2000029,4.4"
style="fill:none;stroke:#000000;stroke-width:1.59999478;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<path
inkscape:connector-curvature="0"
id="path4040-3"
d="m 1.9997819,1038.3622 12.0004361,0"
style="fill:none;stroke:#000000;stroke-width:1.99999964;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
</g>
</svg>
......@@ -9,7 +9,7 @@
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="actions.svg"
inkscape:version="0.48.4 r9939"
inkscape:version="0.48.5 r10040"
xml:space="preserve"
width="96px"
viewBox="0 0 96 96"
......@@ -318,6 +318,70 @@
d="M 15.999999,79.999996 C 39.999998,56 39.999998,56 39.999998,56"
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
inkscape:groupmode="layer"
id="g3096"
inkscape:label="Roll down"
style="display:none"><path
inkscape:connector-curvature="0"
id="path3102"
d="m 48.000004,78.000001 c 0,-61.000109 0,-61.000109 0,-61.000109"
style="fill:none;stroke:#000000;stroke-width:7.99999714;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 66.000001,64.000001 C 48,84.000001 48,84.000001 48,84.000001 l -18.000001,-20"
id="path3104"
inkscape:connector-curvature="0" /><path
style="fill:none;stroke:#000000;stroke-width:12.00000095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 13.999872,14 68.000256,0"
id="path3885"
inkscape:connector-curvature="0" /></g><g
style="display:none"
inkscape:label="Roll up"
id="g3910"
inkscape:groupmode="layer"><path
style="fill:none;stroke:#000000;stroke-width:7.99999762;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 48,27.99993 c 0,56.000071 0,56.000071 0,56.000071"
id="path3912"
inkscape:connector-curvature="0" /><path
inkscape:connector-curvature="0"
id="path3914"
d="M 29.999999,43.999929 C 48,23.999929 48,23.999929 48,23.999929 l 18.000001,20"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path3916"
d="m 13.999872,14 68.000256,0"
style="fill:none;stroke:#000000;stroke-width:12.00000095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
inkscape:groupmode="layer"
id="g3104"
inkscape:label="Slide right"
style="display:none"><path
inkscape:connector-curvature="0"
id="path3106"
d="M 68.00007,48 C 11.999999,48 11.999999,48 11.999999,48"
style="fill:none;stroke:#000000;stroke-width:7.99999762;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 52.000071,29.999999 c 20,18.000001 20,18.000001 20,18.000001 l -20,18.000001"
id="path3108"
inkscape:connector-curvature="0" /><path
style="fill:none;stroke:#000000;stroke-width:12.00000095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 82,13.999872 0,68.000256"
id="path3110"
inkscape:connector-curvature="0" /></g><g
style="display:none"
inkscape:label="Slide left"
id="g3112"
inkscape:groupmode="layer"><path
style="fill:none;stroke:#000000;stroke-width:7.99999762;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 27.999931,48 c 56.000071,0 56.000071,0 56.000071,0"
id="path3114"
inkscape:connector-curvature="0" /><path
inkscape:connector-curvature="0"
id="path3116"
d="M 43.99993,66.000001 C 23.99993,48 23.99993,48 23.99993,48 l 20,-18.000001"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path3118"
d="m 14.000001,82.000128 0,-68.000256"
style="fill:none;stroke:#000000;stroke-width:12.00000095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Rectangle expand"
style="display:none"><path
......@@ -439,6 +503,46 @@
d="m 55,23 c 0,18 0,18 0,18 l 18,0"
id="path5361"
inkscape:connector-curvature="0" /></g><g
style="display:none"
inkscape:groupmode="layer"
id="g3123"
inkscape:label="Detach"><rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:7.99999857;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect3127"
width="36"
height="36"
x="-84"
y="-48"
ry="3.8571432"
transform="scale(-1,-1)" /><path
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 12,83.999996 C 40.000001,55.999997 40.000001,55.999997 40.000001,55.999997"
id="path3129"
inkscape:connector-curvature="0" /><path
style="fill:none;stroke:#000000;stroke-width:8.00000095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 44.000001,80 c 0,-28 0,-28 0,-28 L 16,52"
id="path3131"
inkscape:connector-curvature="0" /></g><g
inkscape:label="Attach"
id="g3113"
inkscape:groupmode="layer"
style="display:none"><rect
ry="3.8571427"
y="-48"
x="-84"
height="36"
width="36"
id="rect3117"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
transform="scale(-1,-1)" /><path
inkscape:connector-curvature="0"
id="path3119"
d="M 48,47.999998 C 16,80 16,80 16,80"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path3121"
d="m 12,56 c 0,28 0,28 0,28 l 28,0"
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
inkscape:groupmode="layer"
id="g8714"
inkscape:label="Screenshot"
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg4429"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="camera.svg">
<defs
id="defs4431" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="53.8125"
inkscape:cx="8"
inkscape:cy="8"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1665"
inkscape:window-height="1071"
inkscape:window-x="67"
inkscape:window-y="32"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid4437"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="11.5,13"
id="guide4947" />
<sodipodi:guide
orientation="1,0"
position="2,10"
id="guide4949" />
<sodipodi:guide
orientation="1,0"
position="14,11.5"
id="guide4951" />
<sodipodi:guide
orientation="0,1"
position="11,3"
id="guide4953" />
<sodipodi:guide
orientation="0,1"
position="1.5,8"
id="guide4955" />
<sodipodi:guide
orientation="1,0"
position="10,12.5"
id="guide4957" />
<sodipodi:guide
orientation="0,1"
position="11,12"
id="guide4959" />
<sodipodi:guide
orientation="0,1"
position="10.5,4"
id="guide4961" />
</sodipodi:namedview>
<metadata
id="metadata4434">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Camera"
inkscape:groupmode="layer">
<path
style="fill:#545454;fill-opacity:1;stroke:#333333;stroke-width:0.99999994000000003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 13.5,3.5 c 0,2.4571428 0,6.542857 0,9 -1.222222,-1.228572 -2.777778,-2.7714284 -4,-4 l 0,3 -7,0 c 0,-1.638095 0,-5.3619048 0,-7 l 7,0 0,3 c 0,0 2.777778,-2.7714286 4,-4 z"
id="path4967"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment