Commit 99163a70 authored by Dan Pascu's avatar Dan Pascu

Added screen sharing support

parent 92ab48d0
...@@ -14,4 +14,5 @@ ...@@ -14,4 +14,5 @@
^MANIFEST$ ^MANIFEST$
(^|/)\.directory$ (^|/)\.directory$
(^|/)\.komodotools$ (^|/)\.komodotools$
(^|/)blink/screensharing/_rfb.c$
(^|/)resources/icons/work/sandbox($|/) (^|/)resources/icons/work/sandbox($|/)
...@@ -125,6 +125,8 @@ class Blink(QApplication): ...@@ -125,6 +125,8 @@ class Blink(QApplication):
self.chat_window.addAction(self.main_window.preferences_action) self.chat_window.addAction(self.main_window.preferences_action)
self.chat_window.addAction(self.main_window.transfers_window_action) self.chat_window.addAction(self.main_window.transfers_window_action)
self.chat_window.addAction(self.main_window.logs_window_action) self.chat_window.addAction(self.main_window.logs_window_action)
self.chat_window.addAction(self.main_window.received_files_window_action)
self.chat_window.addAction(self.main_window.screenshots_window_action)
self.ip_address_monitor = IPAddressMonitor() self.ip_address_monitor = IPAddressMonitor()
self.log_manager = LogManager() self.log_manager = LogManager()
......
...@@ -646,6 +646,8 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -646,6 +646,8 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
sliding_panels = True sliding_panels = True
__streamtypes__ = {'chat', 'screen-sharing', 'video'} # the stream types for which we show the chat window
def __init__(self, parent=None): def __init__(self, parent=None):
super(ChatWindow, self).__init__(parent) super(ChatWindow, self).__init__(parent)
with Resources.directory: with Resources.directory:
...@@ -732,6 +734,9 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -732,6 +734,9 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
self.control_button.actions.disconnect = QAction("Disconnect", self, triggered=self._AH_Disconnect) self.control_button.actions.disconnect = QAction("Disconnect", self, triggered=self._AH_Disconnect)
self.control_button.actions.add_audio = QAction("Add audio", self, triggered=self._AH_AddAudio) self.control_button.actions.add_audio = QAction("Add audio", self, triggered=self._AH_AddAudio)
self.control_button.actions.remove_audio = QAction("Remove audio", self, triggered=self._AH_RemoveAudio) self.control_button.actions.remove_audio = QAction("Remove audio", self, triggered=self._AH_RemoveAudio)
self.control_button.actions.share_my_screen = QAction("Share my screen", self, triggered=self._AH_ShareMyScreen)
self.control_button.actions.request_screen = QAction("Request screen", self, triggered=self._AH_RequestScreen)
self.control_button.actions.end_screen_sharing = QAction("End screen sharing", self, triggered=self._AH_EndScreenSharing)
self.control_button.actions.dump_session = QAction("Dump session", self, triggered=self._AH_DumpSession) # remove later -Dan self.control_button.actions.dump_session = QAction("Dump session", self, triggered=self._AH_DumpSession) # remove later -Dan
self.control_button.actions.main_window = QAction("Main Window", self, triggered=self._AH_MainWindow, shortcut='Ctrl+B', shortcutContext=Qt.ApplicationShortcut) self.control_button.actions.main_window = QAction("Main Window", self, triggered=self._AH_MainWindow, shortcut='Ctrl+B', shortcutContext=Qt.ApplicationShortcut)
...@@ -852,7 +857,16 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -852,7 +857,16 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
else: else:
menu.addAction(self.control_button.actions.disconnect) menu.addAction(self.control_button.actions.disconnect)
if state == 'connected': if state == 'connected':
menu.addAction(self.control_button.actions.add_audio if 'audio' not in blink_session.streams else self.control_button.actions.remove_audio) stream_types = blink_session.streams.types
if 'audio' not in stream_types:
menu.addAction(self.control_button.actions.add_audio)
elif stream_types != {'audio'} and not stream_types.intersection({'screen-sharing', 'video'}):
menu.addAction(self.control_button.actions.remove_audio)
if 'screen-sharing' not in stream_types:
menu.addAction(self.control_button.actions.request_screen)
menu.addAction(self.control_button.actions.share_my_screen)
elif stream_types != {'screen-sharing'}:
menu.addAction(self.control_button.actions.end_screen_sharing)
#menu.addAction(self.control_button.actions.dump_session) # remove this later -Dan #menu.addAction(self.control_button.actions.dump_session) # remove this later -Dan
self.control_button.setMenu(menu) self.control_button.setMenu(menu)
...@@ -865,6 +879,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -865,6 +879,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
have_session = blink_session.state in ('connecting/*', 'connected/*', 'ending') have_session = blink_session.state in ('connecting/*', 'connected/*', 'ending')
have_audio = 'audio' in blink_session.streams have_audio = 'audio' in blink_session.streams
have_chat = 'chat' in blink_session.streams have_chat = 'chat' in blink_session.streams
have_screen = 'screen-sharing' in blink_session.streams
if update_visibility: if update_visibility:
self.status_value_label.setEnabled(have_session) self.status_value_label.setEnabled(have_session)
...@@ -877,11 +892,13 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -877,11 +892,13 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
self.audio_ice_status_value_label.setEnabled(have_audio) self.audio_ice_status_value_label.setEnabled(have_audio)
self.chat_value_widget.setEnabled(have_chat) self.chat_value_widget.setEnabled(have_chat)
self.chat_addresses_value_label.setEnabled(have_chat) self.chat_addresses_value_label.setEnabled(have_chat)
self.screen_value_widget.setEnabled(have_screen)
session_info = blink_session.info session_info = blink_session.info
audio_info = blink_session.info.streams.audio audio_info = blink_session.info.streams.audio
video_info = blink_session.info.streams.video video_info = blink_session.info.streams.video
chat_info = blink_session.info.streams.chat chat_info = blink_session.info.streams.chat
screen_info = blink_session.info.streams.screen_sharing
if 'status' in elements and blink_session.state in ('initialized', 'connecting/*', 'connected/*', 'ended'): if 'status' in elements and blink_session.state in ('initialized', 'connecting/*', 'connected/*', 'ended'):
state_map = {'initialized': 'Disconnected', state_map = {'initialized': 'Disconnected',
...@@ -954,6 +971,14 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -954,6 +971,14 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
else: else:
self.chat_addresses_value_label.setText(u'N/A') self.chat_addresses_value_label.setText(u'N/A')
if screen_info.remote_address is not None and screen_info.mode == 'active':
self.screen_value_label.setText(u'Viewing remote')
elif screen_info.remote_address is not None and screen_info.mode == 'passive':
self.screen_value_label.setText(u'Sharing local')
else:
self.screen_value_label.setText(u'N/A')
self.screen_encryption_label.setVisible(screen_info.remote_address is not None and screen_info.transport=='tls')
if 'statistics' in elements: if 'statistics' in elements:
self.duration_value_label.value = session_info.duration self.duration_value_label.value = session_info.duration
self.audio_latency_graph.data = audio_info.latency self.audio_latency_graph.data = audio_info.latency
...@@ -1132,11 +1157,11 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -1132,11 +1157,11 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
self.traffic_graph.update() self.traffic_graph.update()
def _NH_BlinkSessionNewIncoming(self, notification): def _NH_BlinkSessionNewIncoming(self, notification):
if 'chat' in notification.sender.streams.types: if notification.sender.streams.types.intersection(self.__streamtypes__):
self.show() self.show()
def _NH_BlinkSessionNewOutgoing(self, notification): def _NH_BlinkSessionNewOutgoing(self, notification):
if 'chat' in notification.sender.stream_descriptions.types: if notification.sender.stream_descriptions.types.intersection(self.__streamtypes__):
self.show() self.show()
def _NH_BlinkSessionDidReinitializeForIncoming(self, notification): def _NH_BlinkSessionDidReinitializeForIncoming(self, notification):
...@@ -1145,7 +1170,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -1145,7 +1170,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
selection_model = self.session_list.selectionModel() selection_model = self.session_list.selectionModel()
selection_model.select(model.index(position), selection_model.ClearAndSelect) selection_model.select(model.index(position), selection_model.ClearAndSelect)
self.session_list.scrollTo(model.index(position), QListView.EnsureVisible) # or PositionAtCenter self.session_list.scrollTo(model.index(position), QListView.EnsureVisible) # or PositionAtCenter
if 'chat' in notification.sender.streams.types: if notification.sender.streams.types.intersection(self.__streamtypes__):
self.show() self.show()
def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification): def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification):
...@@ -1154,7 +1179,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -1154,7 +1179,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
selection_model = self.session_list.selectionModel() selection_model = self.session_list.selectionModel()
selection_model.select(model.index(position), selection_model.ClearAndSelect) selection_model.select(model.index(position), selection_model.ClearAndSelect)
self.session_list.scrollTo(model.index(position), QListView.EnsureVisible) # or PositionAtCenter self.session_list.scrollTo(model.index(position), QListView.EnsureVisible) # or PositionAtCenter
if 'chat' in notification.sender.stream_descriptions.types: if notification.sender.stream_descriptions.types.intersection(self.__streamtypes__):
self.show() self.show()
# use BlinkSessionNewIncoming/Outgoing to show the chat window if there is a chat stream available (like with reinitialize) instead of using the sessionAdded signal from the model -Dan # use BlinkSessionNewIncoming/Outgoing to show the chat window if there is a chat stream available (like with reinitialize) instead of using the sessionAdded signal from the model -Dan
...@@ -1163,7 +1188,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -1163,7 +1188,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
# different class for them that posts different notifications. in that case we can do in in NewIncoming/Outgoing -Dan # different class for them that posts different notifications. in that case we can do in in NewIncoming/Outgoing -Dan
def _NH_BlinkSessionWillAddStream(self, notification): def _NH_BlinkSessionWillAddStream(self, notification):
if notification.data.stream.type == 'chat': if notification.data.stream.type in self.__streamtypes__:
self.show() self.show()
def _NH_BlinkSessionDidRemoveStream(self, notification): def _NH_BlinkSessionDidRemoveStream(self, notification):
...@@ -1421,6 +1446,21 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin): ...@@ -1421,6 +1446,21 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
def _AH_RemoveAudio(self): def _AH_RemoveAudio(self):
self.selected_session.blink_session.remove_stream(self.selected_session.blink_session.streams.get('audio')) self.selected_session.blink_session.remove_stream(self.selected_session.blink_session.streams.get('audio'))
def _AH_RequestScreen(self):
if 'audio' in self.selected_session.blink_session.streams:
self.selected_session.blink_session.add_stream(StreamDescription('screen-sharing', mode='viewer'))
else:
self.selected_session.blink_session.add_streams([StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])
def _AH_ShareMyScreen(self):
if 'audio' in self.selected_session.blink_session.streams:
self.selected_session.blink_session.add_stream(StreamDescription('screen-sharing', mode='server'))
else:
self.selected_session.blink_session.add_streams([StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _AH_EndScreenSharing(self):
self.selected_session.blink_session.remove_stream(self.selected_session.blink_session.streams.get('screen-sharing'))
def _AH_DumpSession(self): def _AH_DumpSession(self):
blink_session = self.selected_session.blink_session blink_session = self.selected_session.blink_session
print "state: %r" % blink_session.state print "state: %r" % blink_session.state
......
...@@ -114,6 +114,13 @@ class ChatWindowSettings(SettingsGroup): ...@@ -114,6 +114,13 @@ class ChatWindowSettings(SettingsGroup):
font_size = Setting(type=int, default=None, nillable=True) font_size = Setting(type=int, default=None, nillable=True)
class BlinkScreenSharingSettings(SettingsGroup):
screenshots_directory = Setting(type=Path, default=Path(os.path.expanduser('~/Downloads')))
scale = Setting(type=bool, default=True)
open_fullscreen = Setting(type=bool, default=False)
open_viewonly = Setting(type=bool, default=False)
class BlinkPresenceSettings(SettingsGroup): class BlinkPresenceSettings(SettingsGroup):
current_state = Setting(type=PresenceState, default=PresenceState('Available')) current_state = Setting(type=PresenceState, default=PresenceState('Available'))
state_history = Setting(type=PresenceStateList, default=PresenceStateList()) state_history = Setting(type=PresenceStateList, default=PresenceStateList())
...@@ -126,4 +133,5 @@ class BlinkSettings(SettingsObject): ...@@ -126,4 +133,5 @@ class BlinkSettings(SettingsObject):
chat_window = ChatWindowSettings chat_window = ChatWindowSettings
presence = BlinkPresenceSettings presence = BlinkPresenceSettings
screen_sharing = BlinkScreenSharingSettings
...@@ -3060,8 +3060,8 @@ class ContactListView(QListView): ...@@ -3060,8 +3060,8 @@ class ContactListView(QListView):
menu.addAction(self.actions.start_chat_session) menu.addAction(self.actions.start_chat_session)
#menu.addAction(self.actions.send_sms) #menu.addAction(self.actions.send_sms)
menu.addAction(self.actions.send_files) menu.addAction(self.actions.send_files)
#menu.addAction(self.actions.request_screen) menu.addAction(self.actions.request_screen)
#menu.addAction(self.actions.share_my_screen) menu.addAction(self.actions.share_my_screen)
menu.addSeparator() menu.addSeparator()
menu.addAction(self.actions.add_group) menu.addAction(self.actions.add_group)
menu.addAction(self.actions.add_contact) menu.addAction(self.actions.add_contact)
...@@ -3307,10 +3307,14 @@ class ContactListView(QListView): ...@@ -3307,10 +3307,14 @@ class ContactListView(QListView):
session_manager.send_file(contact, contact.uri, filename) session_manager.send_file(contact, contact.uri, filename)
def _AH_RequestScreen(self): def _AH_RequestScreen(self):
pass contact = self.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
session_manager = SessionManager()
session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])
def _AH_ShareMyScreen(self): def _AH_ShareMyScreen(self):
pass contact = self.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
session_manager = SessionManager()
session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _DH_ApplicationXBlinkGroupList(self, event, index, rect, item): def _DH_ApplicationXBlinkGroupList(self, event, index, rect, item):
model = self.model() model = self.model()
...@@ -3459,8 +3463,8 @@ class ContactSearchListView(QListView): ...@@ -3459,8 +3463,8 @@ class ContactSearchListView(QListView):
menu.addAction(self.actions.start_chat_session) menu.addAction(self.actions.start_chat_session)
#menu.addAction(self.actions.send_sms) #menu.addAction(self.actions.send_sms)
menu.addAction(self.actions.send_files) menu.addAction(self.actions.send_files)
#menu.addAction(self.actions.request_screen) menu.addAction(self.actions.request_screen)
#menu.addAction(self.actions.share_my_screen) menu.addAction(self.actions.share_my_screen)
menu.addSeparator() menu.addSeparator()
menu.addAction(self.actions.edit_item) menu.addAction(self.actions.edit_item)
menu.addAction(self.actions.delete_item) menu.addAction(self.actions.delete_item)
...@@ -3640,10 +3644,14 @@ class ContactSearchListView(QListView): ...@@ -3640,10 +3644,14 @@ class ContactSearchListView(QListView):
session_manager.send_file(contact, contact.uri, filename) session_manager.send_file(contact, contact.uri, filename)
def _AH_RequestScreen(self): def _AH_RequestScreen(self):
pass contact = self.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
session_manager = SessionManager()
session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])
def _AH_ShareMyScreen(self): def _AH_ShareMyScreen(self):
pass contact = self.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
session_manager = SessionManager()
session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _DH_TextUriList(self, event, index, rect, item): def _DH_TextUriList(self, event, index, rect, item):
if index.isValid(): if index.isValid():
...@@ -3736,8 +3744,8 @@ class ContactDetailView(QListView): ...@@ -3736,8 +3744,8 @@ class ContactDetailView(QListView):
menu.addAction(self.actions.start_chat_session) menu.addAction(self.actions.start_chat_session)
#menu.addAction(self.actions.send_sms) #menu.addAction(self.actions.send_sms)
menu.addAction(self.actions.send_files) menu.addAction(self.actions.send_files)
#menu.addAction(self.actions.request_screen) menu.addAction(self.actions.request_screen)
#menu.addAction(self.actions.share_my_screen) menu.addAction(self.actions.share_my_screen)
menu.addSeparator() menu.addSeparator()
if isinstance(selected_item, ContactURI) and model.contact_detail.editable: if isinstance(selected_item, ContactURI) and model.contact_detail.editable:
menu.addAction(self.actions.make_uri_default) menu.addAction(self.actions.make_uri_default)
...@@ -3875,10 +3883,26 @@ class ContactDetailView(QListView): ...@@ -3875,10 +3883,26 @@ class ContactDetailView(QListView):
session_manager.send_file(contact, selected_uri, filename) session_manager.send_file(contact, selected_uri, filename)
def _AH_RequestScreen(self): def _AH_RequestScreen(self):
pass contact = self.contact_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
selected_indexes = self.selectionModel().selectedIndexes()
item = selected_indexes[0].data(Qt.UserRole) if selected_indexes else None
if isinstance(item, ContactURI):
selected_uri = item.uri
else:
selected_uri = contact.uri
session_manager = SessionManager()
session_manager.create_session(contact, selected_uri, [StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])
def _AH_ShareMyScreen(self): def _AH_ShareMyScreen(self):
pass contact = self.contact_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
selected_indexes = self.selectionModel().selectedIndexes()
item = selected_indexes[0].data(Qt.UserRole) if selected_indexes else None
if isinstance(item, ContactURI):
selected_uri = item.uri
else:
selected_uri = contact.uri
session_manager = SessionManager()
session_manager.create_session(contact, selected_uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _DH_ApplicationXBlinkSession(self, event, index, rect, item): def _DH_ApplicationXBlinkSession(self, event, index, rect, item):
event.ignore(rect) event.ignore(rect)
......
...@@ -205,6 +205,8 @@ class MainWindow(base_class, ui_class): ...@@ -205,6 +205,8 @@ class MainWindow(base_class, ui_class):
self.chat_window_action.triggered.connect(self._AH_ChatWindowActionTriggered) self.chat_window_action.triggered.connect(self._AH_ChatWindowActionTriggered)
self.transfers_window_action.triggered.connect(self._AH_TransfersWindowActionTriggered) self.transfers_window_action.triggered.connect(self._AH_TransfersWindowActionTriggered)
self.logs_window_action.triggered.connect(self._AH_LogsWindowActionTriggered) self.logs_window_action.triggered.connect(self._AH_LogsWindowActionTriggered)
self.received_files_window_action.triggered.connect(self._AH_ReceivedFilesWindowActionTriggered)
self.screenshots_window_action.triggered.connect(self._AH_ScreenshotsWindowActionTriggered)
def setupUi(self): def setupUi(self):
super(MainWindow, self).setupUi(self) super(MainWindow, self).setupUi(self)
...@@ -216,6 +218,11 @@ class MainWindow(base_class, ui_class): ...@@ -216,6 +218,11 @@ class MainWindow(base_class, ui_class):
self.input_devices_group = QActionGroup(self) self.input_devices_group = QActionGroup(self)
self.alert_devices_group = QActionGroup(self) self.alert_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)
self.screen_sharing_button.addAction(self.request_screen_action)
self.screen_sharing_button.addAction(self.share_my_screen_action)
# adjust search box height depending on theme as the value set in designer isn't suited for all themes # adjust search box height depending on theme as the value set in designer isn't suited for all themes
search_box = self.search_box search_box = self.search_box
option = QStyleOptionFrameV2() option = QStyleOptionFrameV2()
...@@ -251,7 +258,7 @@ class MainWindow(base_class, ui_class): ...@@ -251,7 +258,7 @@ class MainWindow(base_class, ui_class):
def enable_call_buttons(self, enabled): def enable_call_buttons(self, enabled):
self.audio_call_button.setEnabled(enabled) self.audio_call_button.setEnabled(enabled)
self.chat_session_button.setEnabled(enabled) self.chat_session_button.setEnabled(enabled)
self.screen_sharing_button.setEnabled(False) self.screen_sharing_button.setEnabled(enabled)
def load_audio_devices(self): def load_audio_devices(self):
settings = SIPSimpleSettings() settings = SIPSimpleSettings()
...@@ -386,6 +393,18 @@ class MainWindow(base_class, ui_class): ...@@ -386,6 +393,18 @@ class MainWindow(base_class, ui_class):
makedirs(directory) makedirs(directory)
QDesktopServices.openUrl(QUrl.fromLocalFile(directory)) QDesktopServices.openUrl(QUrl.fromLocalFile(directory))
def _AH_ReceivedFilesWindowActionTriggered(self, checked):
settings = SIPSimpleSettings()
directory = settings.file_transfer.directory.normalized
makedirs(directory)
QDesktopServices.openUrl(QUrl.fromLocalFile(directory))
def _AH_ScreenshotsWindowActionTriggered(self, checked):
settings = BlinkSettings()
directory = settings.screen_sharing.screenshots_directory.normalized
makedirs(directory)
QDesktopServices.openUrl(QUrl.fromLocalFile(directory))
def _AH_VoicemailActionTriggered(self, action, checked): def _AH_VoicemailActionTriggered(self, action, checked):
account = action.data() account = action.data()
contact, contact_uri = URIUtils.find_contact(account.voicemail_uri, display_name='Voicemail') contact, contact_uri = URIUtils.find_contact(account.voicemail_uri, display_name='Voicemail')
...@@ -496,6 +515,34 @@ class MainWindow(base_class, ui_class): ...@@ -496,6 +515,34 @@ class MainWindow(base_class, ui_class):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(contact, contact_uri, [StreamDescription('chat')], connect=False) session_manager.create_session(contact, contact_uri, [StreamDescription('chat')], connect=False)
def _AH_RequestScreenActionTriggered(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_RequestScreen()
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('screen-sharing', mode='viewer'), StreamDescription('audio')])
def _AH_ShareMyScreenActionTriggered(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_ShareMyScreen()
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('screen-sharing', mode='server'), StreamDescription('audio')])
def _SH_BreakConference(self): def _SH_BreakConference(self):
active_session = self.session_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole) active_session = self.session_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
self.session_model.breakConference(active_session.client_conference) self.session_model.breakConference(active_session.client_conference)
......
...@@ -286,6 +286,12 @@ class PreferencesWindow(base_class, ui_class): ...@@ -286,6 +286,12 @@ class PreferencesWindow(base_class, ui_class):
self.session_info_style_button.clicked.connect(self._SH_SessionInfoStyleButtonClicked) self.session_info_style_button.clicked.connect(self._SH_SessionInfoStyleButtonClicked)
self.traffic_units_button.clicked.connect(self._SH_TrafficUnitsButtonClicked) self.traffic_units_button.clicked.connect(self._SH_TrafficUnitsButtonClicked)
# Screen sharing
self.screenshots_directory_browse_button.clicked.connect(self._SH_ScreenshotsDirectoryBrowseButtonClicked)
self.screen_sharing_scale_button.clicked.connect(self._SH_ScreenSharingScaleButtonClicked)
self.screen_sharing_fullscreen_button.clicked.connect(self._SH_ScreenSharingFullscreenButtonClicked)
self.screen_sharing_viewonly_button.clicked.connect(self._SH_ScreenSharingViewonlyButtonClicked)
# File transfer # File transfer
self.download_directory_browse_button.clicked.connect(self._SH_DownloadDirectoryBrowseButtonClicked) self.download_directory_browse_button.clicked.connect(self._SH_DownloadDirectoryBrowseButtonClicked)
self.file_transfer_alert_button.clicked.connect(self._SH_FileTransferAlertButtonClicked) self.file_transfer_alert_button.clicked.connect(self._SH_FileTransferAlertButtonClicked)
...@@ -591,6 +597,12 @@ class PreferencesWindow(base_class, ui_class): ...@@ -591,6 +597,12 @@ class PreferencesWindow(base_class, ui_class):
self.session_info_style_button.setChecked(blink_settings.chat_window.session_info.alternate_style) self.session_info_style_button.setChecked(blink_settings.chat_window.session_info.alternate_style)
self.traffic_units_button.setChecked(blink_settings.chat_window.session_info.bytes_per_second) self.traffic_units_button.setChecked(blink_settings.chat_window.session_info.bytes_per_second)
# Screen sharing settings
self.screenshots_directory_editor.setText(blink_settings.screen_sharing.screenshots_directory or u'')
self.screen_sharing_scale_button.setChecked(blink_settings.screen_sharing.scale)
self.screen_sharing_fullscreen_button.setChecked(blink_settings.screen_sharing.open_fullscreen)
self.screen_sharing_viewonly_button.setChecked(blink_settings.screen_sharing.open_viewonly)
# File transfer settings # File transfer settings
self.download_directory_editor.setText(settings.file_transfer.directory or u'') self.download_directory_editor.setText(settings.file_transfer.directory or u'')
self.file_transfer_alert_button.setChecked(settings.sounds.play_file_alerts) self.file_transfer_alert_button.setChecked(settings.sounds.play_file_alerts)
...@@ -1290,6 +1302,34 @@ class PreferencesWindow(base_class, ui_class): ...@@ -1290,6 +1302,34 @@ class PreferencesWindow(base_class, ui_class):
settings.chat_window.session_info.bytes_per_second = checked settings.chat_window.session_info.bytes_per_second = checked
settings.save() settings.save()
# Screen sharing signal handlers
def _SH_ScreenshotsDirectoryBrowseButtonClicked(self, checked):
# TODO: open the file selection dialog in non-modal mode. Same for the one for TLS CA list and the IconSelector from contacts. -Dan
settings = BlinkSettings()
directory = settings.screen_sharing.screenshots_directory or os.path.expanduser('~')
directory = QFileDialog.getExistingDirectory(self, u'Select Screenshots Directory', directory) or None
if directory is not None:
directory = os.path.normpath(directory)
if directory != settings.screen_sharing.screenshots_directory:
self.screenshots_directory_editor.setText(directory)
settings.screen_sharing.screenshots_directory = directory
settings.save()
def _SH_ScreenSharingScaleButtonClicked(self, checked):
settings = BlinkSettings()
settings.screen_sharing.scale = checked
settings.save()
def _SH_ScreenSharingFullscreenButtonClicked(self, checked):
settings = BlinkSettings()
settings.screen_sharing.open_fullscreen = checked
settings.save()
def _SH_ScreenSharingViewonlyButtonClicked(self, checked):
settings = BlinkSettings()
settings.screen_sharing.open_viewonly = checked
settings.save()
# File transfer signal handlers # File transfer signal handlers
def _SH_DownloadDirectoryBrowseButtonClicked(self, checked): def _SH_DownloadDirectoryBrowseButtonClicked(self, checked):
# TODO: open the file selection dialog in non-modal mode. Same for the one for TLS CA list and the IconSelector from contacts. -Dan # TODO: open the file selection dialog in non-modal mode. Same for the one for TLS CA list and the IconSelector from contacts. -Dan
......
...@@ -93,8 +93,8 @@ class BlinkPresenceState(object): ...@@ -93,8 +93,8 @@ class BlinkPresenceState(object):
service.capabilities.text = False service.capabilities.text = False
service.capabilities.message = True service.capabilities.message = True
service.capabilities.file_transfer = True service.capabilities.file_transfer = True
service.capabilities.screen_sharing_server = False service.capabilities.screen_sharing_server = True
service.capabilities.screen_sharing_client = False service.capabilities.screen_sharing_client = True
service.display_name = self.account.display_name or None service.display_name = self.account.display_name or None
service.icon = "%s#blink-icon%s" % (self.account.xcap.icon.url, self.account.xcap.icon.etag) if self.account.xcap.icon is not None else None service.icon = "%s#blink-icon%s" % (self.account.xcap.icon.url, self.account.xcap.icon.etag) if self.account.xcap.icon is not None else None
service.device_info = pidf.DeviceInfo(instance_id, description=hostname, user_agent=settings.user_agent) service.device_info = pidf.DeviceInfo(instance_id, description=hostname, user_agent=settings.user_agent)
......
# Copyright (C) 2014 AG Projects. See LICENSE for details.
#
__all__ = ['ScreensharingWindow', 'VNCViewer', 'VNCClient', 'RFBSettings', 'ServerDefault', 'TrueColor', 'HighColor', 'LowColor']
from blink.screensharing.vncclient import VNCClient, RFBSettings, ServerDefault, TrueColor, HighColor, LowColor
from blink.screensharing.vncviewer import ScreensharingWindow, VNCViewer
This diff is collapsed.
# Copyright (C) 2014 AG Projects. See LICENSE for details.
#
__all__ = ['VNCClient', 'RFBSettings', 'ServerDefault', 'TrueColor', 'HighColor', 'LowColor']
from PyQt4.QtCore import QObject, QSize, QSocketNotifier, QThread, pyqtSignal
from PyQt4.QtGui import QApplication
from application.notification import NotificationCenter
from application.python import Null
from application.python.descriptor import WriteOnceAttribute
from blink.event import EventBase
from blink.screensharing._rfb import RFBClient, RFBClientError
class RFBSettings(object):
depth = WriteOnceAttribute()
quality = WriteOnceAttribute()
compression = WriteOnceAttribute()
encodings = WriteOnceAttribute()
def __init__(self, depth, quality, compression, encodings):
if depth not in (8, 16, 24, 32, None):
raise ValueError("invalid depth value: %r (should be one of 8, 16, 24, 32 or None)" % depth)
allowed_levels = range(10)
if quality not in allowed_levels:
raise ValueError("invalid quality value: %r (should be between 0..9)" % quality)
if compression not in allowed_levels:
raise ValueError("invalid compression value: %r (should be between 0..9)" % compression)
if not isinstance(encodings, str) or not encodings:
raise ValueError("invalid encodings value: %r (should be a non-empty string)" % encodings)
self.depth = depth
self.quality = quality
self.compression = compression
self.encodings = encodings
def __eq__(self, other):
if isinstance(other, RFBSettings):
return (self.depth, self.quality, self.compression, self.encodings) == (other.depth, other.quality, other.compression, other.encodings)
return NotImplemented
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return '{0.__class__.__name__}(depth={0.depth!r}, quality={0.quality!r}, compression={0.compression!r}, encodings={0.encodings!r})'.format(self)
ServerDefault = RFBSettings(depth=None, quality=9, compression=9, encodings='copyrect zlib zrle ultra hextile corre rre raw')
TrueColor = RFBSettings(depth=24, quality=9, compression=9, encodings='copyrect zlib zrle ultra hextile corre rre raw')
HighColor = RFBSettings(depth=16, quality=9, compression=9, encodings='copyrect zlib zrle ultra hextile corre rre raw')
LowColor = RFBSettings(depth=8, quality=9, compression=9, encodings='copyrect zlib zrle ultra hextile corre rre raw')
class RFBConfigureClientEvent(EventBase):
pass
class RFBKeyEvent(EventBase):
def __init__(self, key, down):
super(RFBKeyEvent, self).__init__()
self.key = key
self.down = down
class RFBMouseEvent(EventBase):
def __init__(self, x, y, button_mask):
super(RFBMouseEvent, self).__init__()
self.x = x
self.y = y
self.button_mask = button_mask
class RFBCutTextEvent(EventBase):
def __init__(self, text):
super(RFBCutTextEvent, self).__init__()
self.text = text
class VNCClient(QObject):
started = pyqtSignal()
finished = pyqtSignal()
imageSizeChanged = pyqtSignal(QSize)
imageChanged = pyqtSignal(int, int, int, int)
passwordRequested = pyqtSignal(bool)
textCut = pyqtSignal(unicode)
def __init__(self, host, port, settings, parent=None):
super(VNCClient, self).__init__(parent)
self.thread = QThread()
self.moveToThread(self.thread)
self.host = host
self.port = port
self.settings = settings
self.username = None
self.password = None
self.rfb_client = None
self.socket_notifier = None
self.thread.started.connect(self._SH_ThreadStarted)
self.thread.finished.connect(self._SH_ThreadFinished)
def _get_settings(self):
return self.__dict__['settings']
def _set_settings(self, settings):
old_settings = self.__dict__.get('settings', None)
if settings == old_settings:
return
self.__dict__['settings'] = settings
if self.thread.isRunning():
QApplication.postEvent(self, RFBConfigureClientEvent())
settings = property(_get_settings, _set_settings)
del _get_settings, _set_settings
@property
def image(self):
return self.rfb_client.image if self.rfb_client is not None else None
def start(self):
self.thread.start()
def stop(self):
self.thread.quit()
def key_event(self, key, down):
if self.thread.isRunning():
QApplication.postEvent(self, RFBKeyEvent(key, down))
def mouse_event(self, x, y, button_mask):
if self.thread.isRunning():
QApplication.postEvent(self, RFBMouseEvent(x, y, button_mask))
def cut_text_event(self, text):
if text and self.thread.isRunning():
QApplication.postEvent(self, RFBCutTextEvent(text))
def _SH_ThreadStarted(self):
self.started.emit()
notification_center = NotificationCenter()
notification_center.post_notification('VNCClientWillStart', sender=self)
self.rfb_client = RFBClient(parent=self)
try:
self.rfb_client.connect()
except RFBClientError:
self.thread.quit()
else:
self.socket_notifier = QSocketNotifier(self.rfb_client.socket, QSocketNotifier.Read, self)
self.socket_notifier.activated.connect(self._SH_SocketNotifierActivated)
notification_center.post_notification('VNCClientDidStart', sender=self)
def _SH_ThreadFinished(self):
self.finished.emit()
notification_center = NotificationCenter()
notification_center.post_notification('VNCClientWillEnd', sender=self)
if self.socket_notifier is not None:
self.socket_notifier.activated.disconnect(self._SH_SocketNotifierActivated)
self.socket_notifier = None
self.rfb_client = None
notification_center.post_notification('VNCClientDidEnd', sender=self)
def _SH_SocketNotifierActivated(self, sock):
self.socket_notifier.setEnabled(False)
try:
self.rfb_client.handle_server_message()
except RFBClientError:
self.thread.quit()
else:
self.socket_notifier.setEnabled(True)
def _SH_ConfigureRFBClient(self):
if self.rfb_client is not None:
self.rfb_client.configure()
def customEvent(self, event):
handler = getattr(self, '_EH_%s' % event.name, Null)
handler(event)
def _EH_RFBConfigureClientEvent(self, event):
if self.rfb_client is not None:
self.rfb_client.configure()
def _EH_RFBKeyEvent(self, event):
if self.rfb_client is not None:
self.rfb_client.send_key_event(event.key, event.down)
def _EH_RFBMouseEvent(self, event):
if self.rfb_client is not None:
self.rfb_client.send_pointer_event(event.x, event.y, event.button_mask)
def _EH_RFBCutTextEvent(self, event):
if self.rfb_client is not None:
self.rfb_client.send_client_cut_text(event.text)
This diff is collapsed.
This diff is collapsed.
#!/bin/sh
python setup.py build_ext --inplace $@
...@@ -657,12 +657,12 @@ padding: 2px;</string> ...@@ -657,12 +657,12 @@ padding: 2px;</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset>
<normaloff>icons/green-handset.png</normaloff>icons/green-handset.png</iconset> <normaloff>icons/handset.svg</normaloff>icons/handset.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>18</width> <width>16</width>
<height>18</height> <height>16</height>
</size> </size>
</property> </property>
</widget> </widget>
...@@ -690,8 +690,8 @@ padding: 2px;</string> ...@@ -690,8 +690,8 @@ padding: 2px;</string>
</property> </property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>18</width> <width>16</width>
<height>18</height> <height>16</height>
</size> </size>
</property> </property>
<property name="popupMode"> <property name="popupMode">
...@@ -718,12 +718,12 @@ padding: 2px;</string> ...@@ -718,12 +718,12 @@ padding: 2px;</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset>
<normaloff>icons/display18.png</normaloff>icons/display18.png</iconset> <normaloff>icons/work/screen.svg</normaloff>icons/work/screen.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
<size> <size>
<width>18</width> <width>16</width>
<height>18</height> <height>16</height>
</size> </size>
</property> </property>
<property name="popupMode"> <property name="popupMode">
...@@ -1052,6 +1052,8 @@ padding: 2px;</string> ...@@ -1052,6 +1052,8 @@ padding: 2px;</string>
<addaction name="chat_window_action"/> <addaction name="chat_window_action"/>
<addaction name="transfers_window_action"/> <addaction name="transfers_window_action"/>
<addaction name="logs_window_action"/> <addaction name="logs_window_action"/>
<addaction name="received_files_window_action"/>
<addaction name="screenshots_window_action"/>
</widget> </widget>
<addaction name="blink_menu"/> <addaction name="blink_menu"/>
<addaction name="call_menu"/> <addaction name="call_menu"/>
...@@ -1276,6 +1278,28 @@ padding: 2px;</string> ...@@ -1276,6 +1278,28 @@ padding: 2px;</string>
<string>Play a sound for &amp;received messages</string> <string>Play a sound for &amp;received messages</string>
</property> </property>
</action> </action>
<action name="received_files_window_action">
<property name="text">
<string>Received &amp;Files</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
<action name="screenshots_window_action">
<property name="text">
<string>&amp;Screenshots</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
......
This diff is collapsed.
<?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="close-active.svg"
inkscape:version="0.48.4 r9939"
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="layer15"
showguides="true"
inkscape:guide-bbox="true"><inkscape:grid
type="xygrid"
id="grid2988"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview><g
inkscape:groupmode="layer"
id="layer15"
inkscape:label="Close active"
style="display:inline"><path
sodipodi:type="arc"
style="fill:url(#radialGradient11771);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.38959551;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path11668"
sodipodi:cx="12"
sodipodi:cy="18"
sodipodi:rx="26.947977"
sodipodi:ry="26.947977"
d="m 38.947977,18 a 26.947977,26.947977 0 1 1 -53.895954,0 26.947977,26.947977 0 1 1 53.895954,0 z"
transform="matrix(1.4843415,0,0,1.4843415,30.187902,21.281853)" /><path
style="fill:none;stroke:#000000;stroke-width:7.99999952000000025;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 63.556404,32.443596 32.443596,63.556404"
id="path11670"
inkscape:connector-curvature="0" /><path
style="fill:none;stroke:#000000;stroke-width:7.99999952000000025;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 63.556404,63.556404 32.443596,32.443596"
id="path11670-8"
inkscape:connector-curvature="0" /></g></svg>
\ No newline at end of file
<?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="close.svg"
inkscape:version="0.48.4 r9939"
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="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:#d30000;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" /></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="g12004"
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="Close"
id="g12004"
inkscape:groupmode="layer"
style="display:inline"><path
transform="matrix(1.4843415,0,0,1.4843415,30.187902,21.281853)"
d="m 38.947977,18 a 26.947977,26.947977 0 1 1 -53.895954,0 26.947977,26.947977 0 1 1 53.895954,0 z"
sodipodi:ry="26.947977"
sodipodi:rx="26.947977"
sodipodi:cy="18"
sodipodi:cx="12"
id="path12006"
style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.38959551000000037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /><path
inkscape:connector-curvature="0"
id="path12008"
d="M 63.556404,32.443596 32.443596,63.556404"
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path12010"
d="M 63.556404,63.556404 32.443596,32.443596"
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"?>
<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"
enable-background="new 0 0 96 96"
height="96px"
id="bigger"
version="1.1"
viewBox="0 0 96 96"
width="96px"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="fullscreen-exit.svg"><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" /><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="g7032"
showguides="true"
inkscape:guide-bbox="true"><inkscape:grid
type="xygrid"
id="grid2988"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview><g
inkscape:groupmode="layer"
id="g7032"
inkscape:label="Fullscreen exit"
style="display:inline"><path
inkscape:connector-curvature="0"
id="path7034"
d="M 60,36 C 84.000001,12 84.000001,12 84.000001,12"
style="fill:none;stroke:#000000;stroke-width:7.99999952;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 80,39.999999 c -24,0 -24,0 -24,0 l 0,-24"
id="path7036"
inkscape:connector-curvature="0" /><path
inkscape:connector-curvature="0"
id="path7038"
d="M 36.000001,59.999999 C 12,83.999999 12,83.999999 12,83.999999"
style="fill:none;stroke:#000000;stroke-width:7.99999952;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 16.000001,56 c 24,0 24,0 24,0 l 0,24"
id="path7040"
inkscape:connector-curvature="0" /></g></svg>
\ No newline at end of file
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 96 96"
height="96px"
id="bigger"
version="1.1"
viewBox="0 0 96 96"
width="96px"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="fullscreen.svg"><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" /><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="g6591"
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:label="Fullscreen"
id="g6591"
inkscape:groupmode="layer"><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 80,15.999999 c -24.000001,24 -24.000001,24 -24.000001,24"
id="path6595"
inkscape:connector-curvature="0" /><path
inkscape:connector-curvature="0"
id="path6597"
d="m 60,12 c 24,0 24,0 24,0 l 0,24"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><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 16,80.000001 c 24.000001,-24 24.000001,-24 24.000001,-24"
id="path6601"
inkscape:connector-curvature="0" /><path
inkscape:connector-curvature="0"
id="path6603"
d="M 36,84 C 12,84 12,84 12,84 L 12,60"
style="fill:none;stroke:#000000;stroke-width:8;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"?>
<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"
enable-background="new 0 0 50 50"
height="16"
id="Layer_1"
version="1.1"
viewBox="0 0 16 16"
width="16"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="handset.svg"><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></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs9" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1782"
inkscape:window-height="1130"
id="namedview7"
showgrid="true"
inkscape:zoom="57.5"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"><inkscape:grid
type="xygrid"
id="grid3756"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" /></sodipodi:namedview><rect
height="50"
width="50"
id="rect3"
x="0"
y="-34"
style="fill:none" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Handset"><path
d="m 5.5396163,9.252177 c 0,0 0.5563174,0.971756 0.6899968,1.226155 0.1341593,0.253679 0.1679992,0.516477 0.07056,0.676077 -0.1478397,0.241679 -1.4978334,2.271109 -1.6173529,2.441988 -0.1197594,0.17088 -0.4617578,0.253198 -0.7967963,0.01824 C 3.5507455,13.380878 2.2259516,12.301363 2.2401116,11.843925 2.2549916,11.385768 2.2967516,9.480896 5.8897746,5.888833 9.481838,2.29725 11.387189,2.25477 11.845107,2.24013 c 0.457919,-0.01464 1.537914,1.310154 1.772152,1.645192 0.233999,0.335519 0.14592,0.677997 -0.01872,0.797037 -0.18744,0.135359 -2.210391,1.469753 -2.44247,1.616632 -0.158159,0.10056 -0.422398,0.0636 -0.676316,-0.07056 C 10.225114,6.094992 9.2535981,5.538675 9.2535981,5.538675 c 0,0 -0.6688769,0.392158 -1.9948708,1.718392 -1.3262339,1.326474 -1.719112,1.99511 -1.719112,1.99511 z"
stroke-miterlimit="10"
id="path5"
inkscape:connector-curvature="0"
style="fill:#333333;fill-opacity:1;stroke:#222222;stroke-width:0.47999778;stroke-miterlimit:10;stroke-opacity:1" /></g></svg>
\ No newline at end of file
<?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="minimize-active.svg"
inkscape:version="0.48.4 r9939"
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="g3101"
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="Minimize active"
id="g3101"
inkscape:groupmode="layer"
style="display:inline"><path
transform="matrix(1.4843415,0,0,1.4843415,30.187902,21.281853)"
d="m 38.947977,18 a 26.947977,26.947977 0 1 1 -53.895954,0 26.947977,26.947977 0 1 1 53.895954,0 z"
sodipodi:ry="26.947977"
sodipodi:rx="26.947977"
sodipodi:cy="18"
sodipodi:cx="12"
id="path3103"
style="fill:url(#radialGradient3989);fill-opacity:1;stroke:#000000;stroke-width:5.38959551;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /><path
inkscape:connector-curvature="0"
id="path3105"
d="M 70.000078,48 25.999922,48"
style="fill:none;stroke:#000000;stroke-width:7.99999952000000025;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"?>
<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="minimize.svg"
inkscape:version="0.48.4 r9939"
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="g3082"
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="g3082"
inkscape:label="Minimize"><path
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:5.38959551;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3084"
sodipodi:cx="12"
sodipodi:cy="18"
sodipodi:rx="26.947977"
sodipodi:ry="26.947977"
d="m 38.947977,18 a 26.947977,26.947977 0 1 1 -53.895954,0 26.947977,26.947977 0 1 1 53.895954,0 z"
transform="matrix(1.4843415,0,0,1.4843415,30.187902,21.281853)" /><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 70.000078,48 25.999922,48"
id="path3088"
inkscape:connector-curvature="0" /></g></svg>
\ No newline at end of file
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 96 96"
height="96px"
id="bigger"
version="1.1"
viewBox="0 0 96 96"
width="96px"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="scale.svg"><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" /><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="g4423"
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:label="Scale"
id="g4423"
inkscape:groupmode="layer"><path
inkscape:connector-curvature="0"
id="path4425"
d="M 80,4 H 68 c -2.209,0 -4,1.791 -4,4 0,2.209 1.791,4 4,4 h 12 c 2.21,0 4,1.79 4,4 v 12 c 0,2.209 1.791,4 4,4 2.209,0 4,-1.791 4,-4 V 16 C 92,9.373 86.627,4 80,4 z" /><path
inkscape:connector-curvature="0"
id="path4427"
d="M 28,4 H 16 C 9.373,4 4,9.373 4,16 v 12 c 0,2.209 1.791,4 4,4 2.209,0 4,-1.791 4,-4 V 16 c 0,-2.21 1.79,-4 4,-4 h 12 c 2.209,0 4,-1.791 4,-4 0,-2.209 -1.791,-4 -4,-4 z" /><path
inkscape:connector-curvature="0"
id="path4429"
d="m 88,64 c -2.209,0 -4,1.791 -4,4 v 12 c 0,2.21 -1.79,4 -4,4 H 68 c -2.209,0 -4,1.791 -4,4 0,2.209 1.791,4 4,4 h 12 c 6.627,0 12,-5.373 12,-12 V 68 c 0,-2.209 -1.791,-4 -4,-4 z" /><path
inkscape:connector-curvature="0"
id="path4431"
d="M 28,84 H 16 c -2.21,0 -4,-1.79 -4,-4 V 68 c 0,-2.209 -1.791,-4 -4,-4 -2.209,0 -4,1.791 -4,4 v 12 c 0,6.627 5.373,12 12,12 h 12 c 2.209,0 4,-1.791 4,-4 0,-2.209 -1.791,-4 -4,-4 z" /><rect
ry="10"
y="32"
x="24"
height="32"
width="48"
id="rect4433"
style="fill:#000000;fill-opacity:1;stroke: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="svg6921"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="screen.svg">
<defs
id="defs6923" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="53.9375"
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="1667"
inkscape:window-height="1073"
inkscape:window-x="83"
inkscape:window-y="22"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid6929"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata6926">
<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="Screen"
inkscape:groupmode="layer">
<rect
style="fill:#545454;fill-opacity:1;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
id="rect7463"
width="11"
height="7"
x="2.5"
y="2.5" />
<rect
style="fill:none;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="rect7465"
width="7"
height="0.19999999"
x="4.5"
y="13.3" />
<rect
style="fill:#333333;fill-opacity:1;stroke:#333333;stroke-width:0.81199998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="rect7465-2"
width="4.1880002"
height="1.188"
x="-13.594"
y="7.4060001"
transform="matrix(0,-1,1,0,0,0)" />
</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="screenshot.svg"
inkscape:version="0.48.4 r9939"
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="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="#linearGradient11102"
id="radialGradient11108"
cx="14.5"
cy="13"
fx="14.5"
fy="13"
r="10.060094"
gradientTransform="matrix(1,0,0,0.95029867,0,0.64611723)"
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="g8714"
showguides="true"
inkscape:guide-bbox="true"><inkscape:grid
type="xygrid"
id="grid2988"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview><g
inkscape:groupmode="layer"
id="g8714"
inkscape:label="Screenshot"
style="display:inline"><rect
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6;display:inline"
id="rect9869"
width="42"
height="14"
x="27"
y="19"
rx="4.5778842"
ry="3.5097113" /><rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:6.00000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6;display:inline"
id="rect9845"
width="82"
height="46"
x="7"
y="31"
ry="5.0138731"
rx="5.0138731" /><path
sodipodi:type="arc"
style="fill:none;stroke:#ffffff;stroke-width:3.72409511;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
id="path9681"
sodipodi:cx="14.5"
sodipodi:cy="13"
sodipodi:rx="8.5"
sodipodi:ry="8"
d="M 23,13 A 8.5,8 0 1 1 6,13 8.5,8 0 1 1 23,13 z"
transform="matrix(1.8235294,0,0,1.9375002,21.558824,28.812497)" /></g></svg>
\ No newline at end of file
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 96 96"
height="96px"
id="bigger"
version="1.1"
viewBox="0 0 96 96"
width="96px"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="viewonly.svg"><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" /><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="g5470"
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="View only"
id="g5470"
inkscape:groupmode="layer"
style="display:inline"><path
transform="translate(-2.0000003,-10.000001)"
d="m 66,46 a 28,28 0 1 1 -56,0 28,28 0 1 1 56,0 z"
sodipodi:ry="28"
sodipodi:rx="28"
sodipodi:cy="46"
sodipodi:cx="38"
id="path5472"
style="fill:none;stroke:#000000;stroke-width:7.99999952;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /><path
inkscape:connector-curvature="0"
id="path5474"
d="M 88,87.999999 C 56,55.39701 56,56.000768 56,56.000768"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></svg>
\ No newline at end of file
This diff is collapsed.
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 50 50"
height="16"
id="Layer_1"
version="1.1"
viewBox="0 0 16 16"
width="16"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="handset.svg"><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></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs9" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1782"
inkscape:window-height="1130"
id="namedview7"
showgrid="true"
inkscape:zoom="57.5"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"><inkscape:grid
type="xygrid"
id="grid3756"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" /></sodipodi:namedview><rect
height="50"
width="50"
id="rect3"
x="0"
y="-34"
style="fill:none" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Handset"><path
d="m 5.5396163,9.252177 c 0,0 0.5563174,0.971756 0.6899968,1.226155 0.1341593,0.253679 0.1679992,0.516477 0.07056,0.676077 -0.1478397,0.241679 -1.4978334,2.271109 -1.6173529,2.441988 -0.1197594,0.17088 -0.4617578,0.253198 -0.7967963,0.01824 C 3.5507455,13.380878 2.2259516,12.301363 2.2401116,11.843925 2.2549916,11.385768 2.2967516,9.480896 5.8897746,5.888833 9.481838,2.29725 11.387189,2.25477 11.845107,2.24013 c 0.457919,-0.01464 1.537914,1.310154 1.772152,1.645192 0.233999,0.335519 0.14592,0.677997 -0.01872,0.797037 -0.18744,0.135359 -2.210391,1.469753 -2.44247,1.616632 -0.158159,0.10056 -0.422398,0.0636 -0.676316,-0.07056 C 10.225114,6.094992 9.2535981,5.538675 9.2535981,5.538675 c 0,0 -0.6688769,0.392158 -1.9948708,1.718392 -1.3262339,1.326474 -1.719112,1.99511 -1.719112,1.99511 z"
stroke-miterlimit="10"
id="path5"
inkscape:connector-curvature="0"
style="fill:#333333;fill-opacity:1;stroke:#222222;stroke-width:0.47999778;stroke-miterlimit:10;stroke-opacity:1" /></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="svg6921"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="screen.svg">
<defs
id="defs6923" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="53.9375"
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="1667"
inkscape:window-height="1073"
inkscape:window-x="83"
inkscape:window-y="22"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid6929"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata6926">
<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="Screen"
inkscape:groupmode="layer">
<rect
style="fill:#545454;fill-opacity:1;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
id="rect7463"
width="11"
height="7"
x="2.5"
y="2.5" />
<rect
style="fill:none;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="rect7465"
width="7"
height="0.19999999"
x="4.5"
y="13.3" />
<rect
style="fill:#333333;fill-opacity:1;stroke:#333333;stroke-width:0.81199998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="rect7465-2"
width="4.1880002"
height="1.188"
x="-13.594"
y="7.4060001"
transform="matrix(0,-1,1,0,0,0)" />
</g>
</svg>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python #!/usr/bin/env python
from distutils.core import setup from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
from itertools import chain from itertools import chain
import glob import glob
import os import os
...@@ -32,6 +35,7 @@ setup(name = "blink", ...@@ -32,6 +35,7 @@ setup(name = "blink",
"Programming Language :: Python" "Programming Language :: Python"
], ],
packages = find_packages('blink'), packages = find_packages('blink'),
ext_modules = cythonize([Extension(name="blink.screensharing._rfb", sources=["blink/screensharing/_rfb.pyx"], libraries=["vncclient"])]),
data_files = list_resources('resources', destination_directory='share/blink'), data_files = list_resources('resources', destination_directory='share/blink'),
scripts = ['bin/blink'] scripts = ['bin/blink']
) )
......
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