Make repl.lua copyright notice more prominent
This commit is contained in:
parent
79c34f823e
commit
f5c6b8fcf9
@ -47,6 +47,7 @@ common:
|
|||||||
chmod 755 $(LIB_PATH)/syncplay/
|
chmod 755 $(LIB_PATH)/syncplay/
|
||||||
cp -r resources/hicolor $(SHARE_PATH)/icons/
|
cp -r resources/hicolor $(SHARE_PATH)/icons/
|
||||||
cp -r resources/*.png $(LIB_PATH)/syncplay/resources/
|
cp -r resources/*.png $(LIB_PATH)/syncplay/resources/
|
||||||
|
cp -r resources/*.lua $(LIB_PATH)/syncplay/resources/
|
||||||
cp -r resources/lua/intf/*.lua $(LIB_PATH)/syncplay/resources/lua/intf/
|
cp -r resources/lua/intf/*.lua $(LIB_PATH)/syncplay/resources/lua/intf/
|
||||||
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/app-install/icons/
|
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/app-install/icons/
|
||||||
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/pixmaps/
|
cp resources/hicolor/48x48/apps/syncplay.png $(SHARE_PATH)/pixmaps/
|
||||||
|
|||||||
@ -671,6 +671,8 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
|||||||
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
|
'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png',
|
||||||
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png',
|
'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png',
|
||||||
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png',
|
'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png',
|
||||||
|
'resources/user_comment.png',
|
||||||
|
'resources/error.png',
|
||||||
'resources/film_folder_edit.png',
|
'resources/film_folder_edit.png',
|
||||||
'resources/film_edit.png',
|
'resources/film_edit.png',
|
||||||
'resources/folder_film.png',
|
'resources/folder_film.png',
|
||||||
@ -679,7 +681,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock
|
|||||||
'resources/email_go.png',
|
'resources/email_go.png',
|
||||||
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
|
'resources/world_add.png', 'resources/film_add.png', 'resources/delete.png', 'resources/spinner.mng'
|
||||||
]
|
]
|
||||||
resources = ["resources/icon.ico", "resources/syncplay.png", "resources/license.rtf", "resources/third-party-notices.rtf"]
|
resources = ["resources/icon.ico", "resources/syncplay.png", "resources/syncplayintf.lua", "resources/license.rtf", "resources/third-party-notices.rtf"]
|
||||||
resources.extend(guiIcons)
|
resources.extend(guiIcons)
|
||||||
intf_resources = ["resources/lua/intf/syncplay.lua"]
|
intf_resources = ["resources/lua/intf/syncplay.lua"]
|
||||||
|
|
||||||
|
|||||||
BIN
resources/error.png
Normal file
BIN
resources/error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 666 B |
979
resources/syncplayintf.lua
Normal file
979
resources/syncplayintf.lua
Normal file
@ -0,0 +1,979 @@
|
|||||||
|
-- syncplayintf.lua -- An interface for communication between mpv and Syncplay
|
||||||
|
-- Author: Etoh, utilising repl.lua code by James Ross-Gowan (see below)
|
||||||
|
-- Thanks: RiCON, James Ross-Gowan, Argon-, wm4, uau
|
||||||
|
|
||||||
|
-- Includes code copied/adapted from repl.lua -- A graphical REPL for mpv input commands
|
||||||
|
--
|
||||||
|
-- c 2016, James Ross-Gowan
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
-- purpose with or without fee is hereby granted, provided that the above
|
||||||
|
-- copyright notice and this permission notice appear in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
-- See https://github.com/rossy/mpv-repl for a copy of repl.lua
|
||||||
|
|
||||||
|
local CANVAS_WIDTH = 1920
|
||||||
|
local CANVAS_HEIGHT = 1080
|
||||||
|
local ROW_HEIGHT = 100
|
||||||
|
local chat_format = "{\\fs50}{\an1}"
|
||||||
|
local max_scrolling_rows = 100
|
||||||
|
local MOVEMENT_PER_SECOND = 200
|
||||||
|
local TICK_INTERVAL = 0.01
|
||||||
|
local CHAT_MODE_CHATROOM = "Chatroom"
|
||||||
|
local CHAT_MODE_SUBTITLE = "Subtitle"
|
||||||
|
local CHAT_MODE_SCROLLING = "Scrolling"
|
||||||
|
local last_chat_time = 0
|
||||||
|
local use_alpha_rows_for_chat = true
|
||||||
|
local MOOD_NEUTRAL = 0
|
||||||
|
local MOOD_BAD = 1
|
||||||
|
local MOOD_GOOD = 2
|
||||||
|
local WORDWRAPIFY_MAGICWORD = "{\\\\fscx0} {\\\\fscx100}"
|
||||||
|
local SCROLLING_ADDITIONAL_BOTTOM_MARGIN = 75
|
||||||
|
local default_oscvisibility_state = "never"
|
||||||
|
|
||||||
|
local ALPHA_WARNING_TEXT_COLOUR = "FF00FF" -- RBG
|
||||||
|
local HINT_TEXT_COLOUR = "AAAAAA" -- RBG
|
||||||
|
local NEUTRAL_ALERT_TEXT_COLOUR = "FFFFFF" -- RBG
|
||||||
|
local BAD_ALERT_TEXT_COLOUR = "0000FF" -- RBG
|
||||||
|
local GOOD_ALERT_TEXT_COLOUR = "00FF00" -- RBG
|
||||||
|
local NOTIFICATION_TEXT_COLOUR = "FFFF00" -- RBG
|
||||||
|
|
||||||
|
local FONT_SIZE_MULTIPLIER = 2
|
||||||
|
|
||||||
|
local chat_log = {}
|
||||||
|
|
||||||
|
local assdraw = require "mp.assdraw"
|
||||||
|
|
||||||
|
local opt = require 'mp.options'
|
||||||
|
|
||||||
|
local repl_active = false
|
||||||
|
local insert_mode = false
|
||||||
|
local line = ''
|
||||||
|
local cursor = 1
|
||||||
|
local key_hints_enabled = false
|
||||||
|
|
||||||
|
function format_scrolling(xpos, ypos, text)
|
||||||
|
local chat_message = "\n"..chat_format .. "{\\pos("..xpos..","..ypos..")\\q2}"..text.."\\N\\n"
|
||||||
|
return string.format(chat_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
function format_chatroom(text)
|
||||||
|
local chat_message = chat_format .. text .."\\N\\n"
|
||||||
|
return string.format(chat_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
function clear_chat()
|
||||||
|
chat_log = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local alert_osd = ""
|
||||||
|
local last_alert_osd_time = nil
|
||||||
|
local alert_osd_mood = MOOD_NEUTRAL
|
||||||
|
|
||||||
|
local notification_osd = ""
|
||||||
|
local last_notification_osd_time = nil
|
||||||
|
local notification_osd_mood = MOOD_NEUTRAL
|
||||||
|
|
||||||
|
function set_alert_osd(osd_message, mood)
|
||||||
|
alert_osd = osd_message
|
||||||
|
last_alert_osd_time = mp.get_time()
|
||||||
|
alert_osd_mood = mood
|
||||||
|
end
|
||||||
|
|
||||||
|
function set_notification_osd(osd_message, mood)
|
||||||
|
notification_osd = osd_message
|
||||||
|
last_notification_osd_time = mp.get_time()
|
||||||
|
notification_osd_mood = mood
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_chat(chat_message, mood)
|
||||||
|
last_chat_time = mp.get_time()
|
||||||
|
local entry = #chat_log+1
|
||||||
|
for i = 1, #chat_log do
|
||||||
|
if chat_log[i].text == '' then
|
||||||
|
entry = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local row = ((entry-1) % max_scrolling_rows)+1
|
||||||
|
if opts['chatOutputMode'] == CHAT_MODE_CHATROOM then
|
||||||
|
if entry > opts['chatMaxLines'] then
|
||||||
|
table.remove(chat_log, 1)
|
||||||
|
entry = entry - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
chat_log[entry] = { xpos=CANVAS_WIDTH, timecreated=mp.get_time(), text=tostring(chat_message), row=row }
|
||||||
|
end
|
||||||
|
|
||||||
|
function chat_update()
|
||||||
|
local ass = assdraw.ass_new()
|
||||||
|
local chat_ass = ''
|
||||||
|
local rowsAdded = 0
|
||||||
|
local to_add = ''
|
||||||
|
local incrementRow = 0
|
||||||
|
if opts['chatOutputMode'] == CHAT_MODE_CHATROOM and chat_log ~= {} then
|
||||||
|
local timedelta = mp.get_time() - last_chat_time
|
||||||
|
if timedelta >= opts['chatTimeout'] then
|
||||||
|
clear_chat()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rowsAdded,to_add = process_alert_osd()
|
||||||
|
if to_add ~= nil and to_add ~= "" then
|
||||||
|
chat_ass = to_add
|
||||||
|
end
|
||||||
|
incrementRow,to_add = process_notification_osd(rowsAdded)
|
||||||
|
rowsAdded = rowsAdded + incrementRow
|
||||||
|
if to_add ~= nil and to_add ~= "" then
|
||||||
|
chat_ass = chat_ass .. to_add
|
||||||
|
end
|
||||||
|
|
||||||
|
if #chat_log > 0 then
|
||||||
|
for i = 1, #chat_log do
|
||||||
|
local to_add = process_chat_item(i,rowsAdded)
|
||||||
|
if to_add ~= nil and to_add ~= "" then
|
||||||
|
chat_ass = chat_ass .. to_add
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local xpos = opts['chatLeftMargin']
|
||||||
|
local ypos = opts['chatTopMargin']
|
||||||
|
chat_ass = "\n".."{\\pos("..xpos..","..ypos..")}".. chat_ass
|
||||||
|
|
||||||
|
if use_alpha_rows_for_chat == false and opts['chatDirectInput'] == true then
|
||||||
|
local alphawarning_ass = assdraw.ass_new()
|
||||||
|
alphawarning_ass = "{\\a6}{\\1c&H"..ALPHA_WARNING_TEXT_COLOUR.."}"..opts['alphakey-mode-warning-first-line'].."\n{\\a6}{\\1c&H"..ALPHA_WARNING_TEXT_COLOUR.."}"..opts['alphakey-mode-warning-second-line']
|
||||||
|
ass:append(alphawarning_ass)
|
||||||
|
elseif opts['chatOutputMode'] == CHAT_MODE_CHATROOM and opts['chatInputPosition'] == "Top" then
|
||||||
|
ass:append(chat_ass)
|
||||||
|
ass:append(input_ass())
|
||||||
|
else
|
||||||
|
ass:append(input_ass())
|
||||||
|
ass:append(chat_ass)
|
||||||
|
end
|
||||||
|
mp.set_osd_ass(CANVAS_WIDTH,CANVAS_HEIGHT, ass.text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function process_alert_osd()
|
||||||
|
local rowsCreated = 0
|
||||||
|
local stringToAdd = ""
|
||||||
|
if alert_osd ~= "" and mp.get_time() - last_alert_osd_time < opts['alertTimeout'] and last_alert_osd_time ~= nil then
|
||||||
|
local messageColour
|
||||||
|
if alert_osd_mood == MOOD_NEUTRAL then
|
||||||
|
messageColour = "{\\1c&H"..NEUTRAL_ALERT_TEXT_COLOUR.."}"
|
||||||
|
elseif alert_osd_mood == MOOD_BAD then
|
||||||
|
messageColour = "{\\1c&H"..BAD_ALERT_TEXT_COLOUR.."}"
|
||||||
|
elseif alert_osd_mood == MOOD_GOOD then
|
||||||
|
messageColour = "{\\1c&H"..GOOD_ALERT_TEXT_COLOUR.."}"
|
||||||
|
end
|
||||||
|
local messageString = wordwrapify_string(alert_osd)
|
||||||
|
local startRow = 0
|
||||||
|
if messageString ~= '' and messageString ~= nil then
|
||||||
|
local toDisplay
|
||||||
|
rowsCreated = rowsCreated + 1
|
||||||
|
messageString = messageColour..messageString
|
||||||
|
if stringToAdd ~= "" then
|
||||||
|
stringToAdd = stringToAdd .. format_chatroom(messageString)
|
||||||
|
else
|
||||||
|
stringToAdd = format_chatroom(messageString)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return rowsCreated, stringToAdd
|
||||||
|
end
|
||||||
|
|
||||||
|
function process_notification_osd(startRow)
|
||||||
|
local rowsCreated = 0
|
||||||
|
local startRow = startRow
|
||||||
|
local stringToAdd = ""
|
||||||
|
if notification_osd ~= "" and mp.get_time() - last_notification_osd_time < opts['alertTimeout'] and last_notification_osd_time ~= nil then
|
||||||
|
local messageColour
|
||||||
|
messageColour = "{\\1c&H"..NOTIFICATION_TEXT_COLOUR.."}"
|
||||||
|
local messageString
|
||||||
|
messageString = wordwrapify_string(notification_osd)
|
||||||
|
messageString = messageColour..messageString
|
||||||
|
messageString = format_chatroom(messageString)
|
||||||
|
stringToAdd = messageString
|
||||||
|
rowsCreated = 1
|
||||||
|
end
|
||||||
|
return rowsCreated, stringToAdd
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function process_chat_item(i, rowsAdded)
|
||||||
|
if opts['chatOutputMode'] == CHAT_MODE_CHATROOM then
|
||||||
|
return process_chat_item_chatroom(i, rowsAdded)
|
||||||
|
elseif opts['chatOutputMode'] == CHAT_MODE_SCROLLING then
|
||||||
|
return process_chat_item_scrolling(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function process_chat_item_scrolling(i)
|
||||||
|
local timecreated = chat_log[i].timecreated
|
||||||
|
local timedelta = mp.get_time() - timecreated
|
||||||
|
local xpos = CANVAS_WIDTH - (timedelta*MOVEMENT_PER_SECOND)
|
||||||
|
local text = chat_log[i].text
|
||||||
|
if text ~= '' then
|
||||||
|
local roughlen = string.len(text) * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER) * 1.5
|
||||||
|
if xpos > (-1*roughlen) then
|
||||||
|
local row = chat_log[i].row-1+opts['scrollingFirstRowOffset']
|
||||||
|
local ypos = opts['chatTopMargin']+(row * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
|
||||||
|
return format_scrolling(xpos,ypos,text)
|
||||||
|
else
|
||||||
|
chat_log[i].text = ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function process_chat_item_chatroom(i, startRow)
|
||||||
|
local text = chat_log[i].text
|
||||||
|
if text ~= '' then
|
||||||
|
local text = wordwrapify_string(text)
|
||||||
|
local rowNumber = i+startRow-1
|
||||||
|
return(format_chatroom(text))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function process_chat_item_subtitle(i)
|
||||||
|
local timecreated = chat_log[i].timecreated
|
||||||
|
local timedelta = mp.get_time() - timecreated
|
||||||
|
local xpos = CANVAS_WIDTH - (timedelta*MOVEMENT_PER_SECOND)
|
||||||
|
local text = chat_log[i].text
|
||||||
|
if text ~= '' then
|
||||||
|
local roughlen = string.len(text) * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER)
|
||||||
|
if xpos > (-1*roughlen) then
|
||||||
|
local row = chat_log[i].row
|
||||||
|
local ypos = row * (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER)
|
||||||
|
return(format_scrolling(xpos,ypos,text))
|
||||||
|
else
|
||||||
|
chat_log[i].text = ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
chat_timer=mp.add_periodic_timer(TICK_INTERVAL, chat_update)
|
||||||
|
|
||||||
|
mp.register_script_message('chat', function(e)
|
||||||
|
add_chat(e)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Chat OSD
|
||||||
|
|
||||||
|
mp.register_script_message('chat-osd-neutral', function(e)
|
||||||
|
add_chat(e,MOOD_NEUTRAL)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('chat-osd-bad', function(e)
|
||||||
|
add_chat(e,MOOD_BAD)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('chat-osd-good', function(e)
|
||||||
|
add_chat(e,MOOD_GOOD)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Alert OSD
|
||||||
|
|
||||||
|
mp.register_script_message('alert-osd-neutral', function(e)
|
||||||
|
set_alert_osd(e,MOOD_NEUTRAL)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('alert-osd-bad', function(e)
|
||||||
|
set_alert_osd(e,MOOD_BAD)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('alert-osd-good', function(e)
|
||||||
|
set_alert_osd(e,MOOD_GOOD)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Notification OSD
|
||||||
|
|
||||||
|
mp.register_script_message('notification-osd-neutral', function(e)
|
||||||
|
set_notification_osd(e,MOOD_NEUTRAL)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('notification-osd-bad', function(e)
|
||||||
|
set_notification_osd(e,MOOD_BAD)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('notification-osd-good', function(e)
|
||||||
|
set_notification_osd(e,MOOD_GOOD)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
mp.register_script_message('set_syncplayintf_options', function(e)
|
||||||
|
set_syncplayintf_options(e)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Default options
|
||||||
|
local utils = require 'mp.utils'
|
||||||
|
local options = require 'mp.options'
|
||||||
|
opts = {
|
||||||
|
|
||||||
|
-- All drawing is scaled by this value, including the text borders and the
|
||||||
|
-- cursor. Change it if you have a high-DPI display.
|
||||||
|
scale = 1,
|
||||||
|
-- Set the font used for the REPL and the console. This probably doesn't
|
||||||
|
-- have to be a monospaced font.
|
||||||
|
['chatInputFontFamily'] = 'monospace',
|
||||||
|
-- Enable/Disable
|
||||||
|
['chatInputEnabled'] = true,
|
||||||
|
['chatOutputEnabled'] = true,
|
||||||
|
['OscVisibilityChangeCompatible'] = false,
|
||||||
|
-- Set the font size used for the REPL and the console. This will be
|
||||||
|
-- multiplied by "scale."
|
||||||
|
['chatInputRelativeFontSize'] = 14,
|
||||||
|
['chatInputFontWeight'] = 1,
|
||||||
|
['chatInputFontUnderline'] = false,
|
||||||
|
['chatInputFontColor'] = "#000000",
|
||||||
|
['chatInputPosition'] = "Top",
|
||||||
|
['MaxChatMessageLength'] = 500,
|
||||||
|
['chatOutputFontFamily'] = "sans serif",
|
||||||
|
['chatOutputFontSize'] = 50,
|
||||||
|
['chatOutputFontWeight'] = 1,
|
||||||
|
['chatOutputFontUnderline'] = false,
|
||||||
|
['chatOutputFontColor'] = "#FFFFFF",
|
||||||
|
['chatOutputMode'] = "Chatroom",
|
||||||
|
['scrollingFirstRowOffset'] = 2,
|
||||||
|
-- Can be "Chatroom", "Subtitle" or "Scrolling" style
|
||||||
|
['chatMaxLines'] = 7,
|
||||||
|
['chatTopMargin'] = 25,
|
||||||
|
['chatLeftMargin'] = 20,
|
||||||
|
['chatDirectInput'] = true,
|
||||||
|
--
|
||||||
|
['notificationTimeout'] = 3,
|
||||||
|
['alertTimeout'] = 5,
|
||||||
|
['chatTimeout'] = 7,
|
||||||
|
--
|
||||||
|
['inputPromptStartCharacter'] = ">",
|
||||||
|
['inputPromptEndCharacter'] = "<",
|
||||||
|
['backslashSubstituteCharacter'] = "|",
|
||||||
|
--Lang:
|
||||||
|
['mpv-key-tab-hint'] = "[TAB] to toggle access to alphabet row key shortcuts.",
|
||||||
|
['mpv-key-hint'] = "[ENTER] to send message. [ESC] to escape chat mode.",
|
||||||
|
['alphakey-mode-warning-first-line'] = "You can temporarily use old mpv bindings with a-z keys.",
|
||||||
|
['alphakey-mode-warning-second-line'] = "Press [TAB] to return to Syncplay chat mode.",
|
||||||
|
}
|
||||||
|
|
||||||
|
function detect_platform()
|
||||||
|
local o = {}
|
||||||
|
-- Kind of a dumb way of detecting the platform but whatever
|
||||||
|
if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
|
||||||
|
return 'windows'
|
||||||
|
elseif mp.get_property_native('options/input-app-events', o) ~= o then
|
||||||
|
return 'macos'
|
||||||
|
end
|
||||||
|
return 'linux'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Pick a better default font for Windows and macOS
|
||||||
|
local platform = detect_platform()
|
||||||
|
if platform == 'windows' then
|
||||||
|
opts.font = 'Consolas'
|
||||||
|
elseif platform == 'macos' then
|
||||||
|
opts.font = 'Menlo'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply user-set options
|
||||||
|
options.read_options(opts)
|
||||||
|
|
||||||
|
-- Escape a string for verbatim display on the OSD
|
||||||
|
function ass_escape(str)
|
||||||
|
-- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
|
||||||
|
-- it isn't followed by a recognised character, so add a zero-width
|
||||||
|
-- non-breaking space
|
||||||
|
str = str:gsub('\\', '\\\239\187\191')
|
||||||
|
str = str:gsub('{', '\\{')
|
||||||
|
str = str:gsub('}', '\\}')
|
||||||
|
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
|
||||||
|
-- consecutive newlines
|
||||||
|
str = str:gsub('\n', '\239\187\191\n')
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
function update()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
function input_ass()
|
||||||
|
if not repl_active then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
last_chat_time = mp.get_time() -- to keep chat messages showing while entering input
|
||||||
|
local bold
|
||||||
|
if opts['chatInputFontWeight'] < 75 then
|
||||||
|
bold = 0
|
||||||
|
else
|
||||||
|
bold = 1
|
||||||
|
end
|
||||||
|
local underline = opts['chatInputFontUnderline'] and 1 or 0
|
||||||
|
local red = string.sub(opts['chatInputFontColor'],2,3)
|
||||||
|
local green = string.sub(opts['chatInputFontColor'],4,5)
|
||||||
|
local blue = string.sub(opts['chatInputFontColor'],6,7)
|
||||||
|
local fontColor = blue .. green .. red
|
||||||
|
local style = '{\\r' ..
|
||||||
|
'\\1a&H00&\\3a&H00&\\4a&H99&' ..
|
||||||
|
'\\1c&H'..fontColor..'&\\3c&H111111&\\4c&H000000&' ..
|
||||||
|
'\\fn' .. opts['chatInputFontFamily'] .. '\\fs' .. (opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER) .. '\\b' .. bold ..
|
||||||
|
'\\bord2\\xshad0\\yshad1\\fsp0\\q1}'
|
||||||
|
|
||||||
|
local after_style = '{\\u' .. underline .. '}'
|
||||||
|
local cheight = opts['chatInputRelativeFontSize'] * FONT_SIZE_MULTIPLIER * 8
|
||||||
|
local cglyph = '_'
|
||||||
|
local before_cur = wordwrapify_string(ass_escape(line:sub(1, cursor - 1)))
|
||||||
|
local after_cur = wordwrapify_string(ass_escape(line:sub(cursor)))
|
||||||
|
local secondary_pos = "10,"..tostring(10+(opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
|
||||||
|
|
||||||
|
local alignment = 7
|
||||||
|
local position = "5,5"
|
||||||
|
local start_marker = opts['inputPromptStartCharacter']
|
||||||
|
local end_marker = ""
|
||||||
|
if opts['chatInputPosition'] == "Middle" then
|
||||||
|
alignment = 5
|
||||||
|
position = tostring(CANVAS_WIDTH/2)..","..tostring(CANVAS_HEIGHT/2)
|
||||||
|
secondary_pos = tostring(CANVAS_WIDTH/2)..","..tostring((CANVAS_HEIGHT/2)+20+(opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
|
||||||
|
end_marker = "{\\u0}"..opts['inputPromptEndCharacter']
|
||||||
|
elseif opts['chatInputPosition'] == "Bottom" then
|
||||||
|
alignment = 1
|
||||||
|
position = tostring(5)..","..tostring(CANVAS_HEIGHT-5)
|
||||||
|
secondary_pos = "10,"..tostring(CANVAS_HEIGHT-(20+(opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local osd_help_message = opts['mpv-key-hint']
|
||||||
|
if opts['chatDirectInput'] then
|
||||||
|
osd_help_message = opts['mpv-key-tab-hint'] .. " " .. osd_help_message
|
||||||
|
end
|
||||||
|
local help_prompt = '\\N\\n{\\an'..alignment..'\\pos('..secondary_pos..')\\fn' .. opts['chatOutputFontFamily'] .. '\\fs' .. ((opts['chatInputRelativeFontSize']*FONT_SIZE_MULTIPLIER)/1.25) .. '\\1c&H'..HINT_TEXT_COLOUR..'}' .. osd_help_message
|
||||||
|
|
||||||
|
local firststyle = "{\\an"..alignment.."}{\\pos("..position..")}"
|
||||||
|
if opts['chatOutputEnabled'] and opts['chatOutputMode'] == CHAT_MODE_CHATROOM and opts['chatInputPosition'] == "Top" then
|
||||||
|
firststyle = get_output_style().."{'\\1c&H'"..fontColor.."}"
|
||||||
|
before_cur = before_cur .. firststyle
|
||||||
|
after_cur = after_cur .. firststyle
|
||||||
|
help_prompt = '\\N\\n'..firststyle..'{\\1c&H'..HINT_TEXT_COLOUR..'}' .. osd_help_message .. '\\N\\n'
|
||||||
|
end
|
||||||
|
if key_hints_enabled == false then help_prompt = "" end
|
||||||
|
|
||||||
|
return firststyle..style..start_marker.." "..after_style..before_cur..style..cglyph..style..after_style..after_cur..end_marker..help_prompt
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_output_style()
|
||||||
|
local bold
|
||||||
|
if opts['chatOutputFontWeight'] < 75 then
|
||||||
|
bold = 0
|
||||||
|
else
|
||||||
|
bold = 1
|
||||||
|
end
|
||||||
|
local underline = opts['chatOutputFontUnderline'] and 1 or 0
|
||||||
|
local red = string.sub(opts['chatOutputFontColor'],2,3)
|
||||||
|
local green = string.sub(opts['chatOutputFontColor'],4,5)
|
||||||
|
local blue = string.sub(opts['chatOutputFontColor'],6,7)
|
||||||
|
local fontColor = blue .. green .. red
|
||||||
|
local style = '{\\r' ..
|
||||||
|
'\\1a&H00&\\3a&H00&\\4a&H99&' ..
|
||||||
|
'\\1c&H'..fontColor..'&\\3c&H111111&\\4c&H000000&' ..
|
||||||
|
'\\fn' .. opts['chatOutputFontFamily'] .. '\\fs' .. (opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER) .. '\\b' .. bold ..
|
||||||
|
'\\u' .. underline .. '\\a5\\MarginV=500' .. '}'
|
||||||
|
|
||||||
|
--mp.osd_message("",0)
|
||||||
|
return style
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function escape()
|
||||||
|
set_active(false)
|
||||||
|
clear()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set the REPL visibility (`, Esc)
|
||||||
|
function set_active(active)
|
||||||
|
if use_alpha_rows_for_chat == false then active = false end
|
||||||
|
if active == repl_active then return end
|
||||||
|
if active then
|
||||||
|
repl_active = true
|
||||||
|
insert_mode = false
|
||||||
|
mp.enable_key_bindings('repl-input', 'allow-hide-cursor+allow-vo-dragging')
|
||||||
|
else
|
||||||
|
repl_active = false
|
||||||
|
mp.disable_key_bindings('repl-input')
|
||||||
|
end
|
||||||
|
if default_oscvisibility_state ~= "never" and opts['OscVisibilityChangeCompatible'] == true then
|
||||||
|
if active then
|
||||||
|
mp.commandv("script-message", "osc-visibility","never", "no-osd")
|
||||||
|
else
|
||||||
|
mp.commandv("script-message", "osc-visibility",default_oscvisibility_state, "no-osd")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Show the repl if hidden and replace its contents with 'text'
|
||||||
|
-- (script-message-to repl type)
|
||||||
|
function show_and_type(text)
|
||||||
|
text = text or ''
|
||||||
|
|
||||||
|
line = text
|
||||||
|
cursor = line:len() + 1
|
||||||
|
insert_mode = false
|
||||||
|
if repl_active then
|
||||||
|
update()
|
||||||
|
else
|
||||||
|
set_active(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
|
||||||
|
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
|
||||||
|
function next_utf8(str, pos)
|
||||||
|
if pos > str:len() then return pos end
|
||||||
|
repeat
|
||||||
|
pos = pos + 1
|
||||||
|
until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
|
||||||
|
return pos
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
|
||||||
|
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
|
||||||
|
|
||||||
|
|
||||||
|
-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
|
||||||
|
function prev_utf8(str, pos)
|
||||||
|
if pos <= 1 then return pos end
|
||||||
|
repeat
|
||||||
|
pos = pos - 1
|
||||||
|
until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
|
||||||
|
return pos
|
||||||
|
end
|
||||||
|
|
||||||
|
function trim_string(line,maxCharacters)
|
||||||
|
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
|
||||||
|
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
|
||||||
|
|
||||||
|
local str = line
|
||||||
|
if str == nil or str == "" or str:len() <= maxCharacters then
|
||||||
|
return str, ""
|
||||||
|
end
|
||||||
|
local pos = 0
|
||||||
|
local oldPos = -1
|
||||||
|
local chars = 0
|
||||||
|
|
||||||
|
repeat
|
||||||
|
oldPos = pos
|
||||||
|
pos = next_utf8(str, pos)
|
||||||
|
chars = chars + 1
|
||||||
|
until pos == oldPos or chars > maxCharacters
|
||||||
|
return str:sub(1,pos-1), str:sub(pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
function wordwrapify_string(line)
|
||||||
|
-- Used to ensure characters wrap on a per-character rather than per-word basis
|
||||||
|
-- to avoid issues with long filenames, etc.
|
||||||
|
|
||||||
|
local str = line
|
||||||
|
if str == nil or str == "" then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local newstr = ""
|
||||||
|
local currentChar = 0
|
||||||
|
local nextChar = 0
|
||||||
|
local chars = 0
|
||||||
|
local maxChars = str:len()
|
||||||
|
|
||||||
|
repeat
|
||||||
|
nextChar = next_utf8(str, currentChar)
|
||||||
|
if nextChar == currentChar then
|
||||||
|
return newstr
|
||||||
|
end
|
||||||
|
local charToTest = str:sub(currentChar,nextChar-1)
|
||||||
|
if charToTest ~= "\\" and charToTest ~= "{" and charToTest ~= "}" and charToTest ~= "%" then
|
||||||
|
newstr = newstr .. WORDWRAPIFY_MAGICWORD .. str:sub(currentChar,nextChar-1)
|
||||||
|
else
|
||||||
|
newstr = newstr .. str:sub(currentChar,nextChar-1)
|
||||||
|
end
|
||||||
|
currentChar = nextChar
|
||||||
|
until currentChar > maxChars
|
||||||
|
newstr = string.gsub(newstr,opts['backslashSubstituteCharacter'], '\\\239\187\191') -- Workaround for \ escape issues
|
||||||
|
return newstr
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function trim_input()
|
||||||
|
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
|
||||||
|
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
|
||||||
|
|
||||||
|
local str = line
|
||||||
|
if str == nil or str == "" or str:len() <= opts['MaxChatMessageLength'] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pos = 0
|
||||||
|
local oldPos = -1
|
||||||
|
local chars = 0
|
||||||
|
|
||||||
|
repeat
|
||||||
|
oldPos = pos
|
||||||
|
pos = next_utf8(str, pos)
|
||||||
|
chars = chars + 1
|
||||||
|
until pos == oldPos or chars > opts['MaxChatMessageLength']
|
||||||
|
line = line:sub(1,pos-1)
|
||||||
|
if cursor > pos then
|
||||||
|
cursor = pos
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Insert a character at the current cursor position (' '-'~', Shift+Enter)
|
||||||
|
function handle_char_input(c)
|
||||||
|
if c == nil then return end
|
||||||
|
if c == "\\" then c = opts['backslashSubstituteCharacter'] end
|
||||||
|
if key_hints_enabled and (string.len(line) > 0 or opts['chatDirectInput'] == false) then
|
||||||
|
key_hints_enabled = false
|
||||||
|
end
|
||||||
|
set_active(true)
|
||||||
|
if insert_mode then
|
||||||
|
line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
|
||||||
|
else
|
||||||
|
line = line:sub(1, cursor - 1) .. c .. line:sub(cursor)
|
||||||
|
end
|
||||||
|
cursor = cursor + c:len()
|
||||||
|
trim_input()
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove the character behind the cursor (Backspace)
|
||||||
|
function handle_backspace()
|
||||||
|
if cursor <= 1 then return end
|
||||||
|
local prev = prev_utf8(line, cursor)
|
||||||
|
line = line:sub(1, prev - 1) .. line:sub(cursor)
|
||||||
|
cursor = prev
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove the character in front of the cursor (Del)
|
||||||
|
function handle_del()
|
||||||
|
if cursor > line:len() then return end
|
||||||
|
line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Toggle insert mode (Ins)
|
||||||
|
function handle_ins()
|
||||||
|
insert_mode = not insert_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
--local was_active_before_tab = false
|
||||||
|
|
||||||
|
function handle_tab()
|
||||||
|
use_alpha_rows_for_chat = not use_alpha_rows_for_chat
|
||||||
|
if use_alpha_rows_for_chat then
|
||||||
|
mp.enable_key_bindings('repl-alpha-input')
|
||||||
|
--set_active(was_active_before_tab)
|
||||||
|
else
|
||||||
|
mp.disable_key_bindings('repl-alpha-input')
|
||||||
|
--was_active_before_tab = repl_active
|
||||||
|
--set_active(false)
|
||||||
|
escape()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move the cursor to the next character (Right)
|
||||||
|
function next_char(amount)
|
||||||
|
cursor = next_utf8(line, cursor)
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move the cursor to the previous character (Left)
|
||||||
|
function prev_char(amount)
|
||||||
|
cursor = prev_utf8(line, cursor)
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clear the current line (Ctrl+C)
|
||||||
|
function clear()
|
||||||
|
line = ''
|
||||||
|
cursor = 1
|
||||||
|
insert_mode = false
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D)
|
||||||
|
function maybe_exit()
|
||||||
|
if line == '' then
|
||||||
|
set_active(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run the current command and clear the line (Enter)
|
||||||
|
function handle_enter()
|
||||||
|
if not repl_active then
|
||||||
|
set_active(true)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
set_active(false)
|
||||||
|
|
||||||
|
if line == '' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
key_hints_enabled = false
|
||||||
|
line = string.gsub(line,"\\", "\\\\")
|
||||||
|
line = string.gsub(line,"\"", "\\\"")
|
||||||
|
mp.command('print-text "<chat>'..line..'</chat>"')
|
||||||
|
clear()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move the cursor to the beginning of the line (HOME)
|
||||||
|
function go_home()
|
||||||
|
cursor = 1
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move the cursor to the end of the line (END)
|
||||||
|
function go_end()
|
||||||
|
cursor = line:len() + 1
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Delete from the cursor to the end of the line (Ctrl+K)
|
||||||
|
function del_to_eol()
|
||||||
|
line = line:sub(1, cursor - 1)
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Delete from the cursor back to the start of the line (Ctrl+U)
|
||||||
|
function del_to_start()
|
||||||
|
line = line:sub(cursor)
|
||||||
|
cursor = 1
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
|
||||||
|
function get_clipboard(clip)
|
||||||
|
if platform == 'linux' then
|
||||||
|
local res = utils.subprocess({ args = {
|
||||||
|
'xclip', '-selection', clip and 'clipboard' or 'primary', '-out'
|
||||||
|
} })
|
||||||
|
if not res.error then
|
||||||
|
return res.stdout
|
||||||
|
end
|
||||||
|
elseif platform == 'windows' then
|
||||||
|
local res = utils.subprocess({ args = {
|
||||||
|
'powershell', '-NoProfile', '-Command', [[& {
|
||||||
|
Trap {
|
||||||
|
Write-Error -ErrorRecord $_
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$clip = ""
|
||||||
|
if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
|
||||||
|
$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
|
||||||
|
} else {
|
||||||
|
Add-Type -AssemblyName PresentationCore
|
||||||
|
$clip = [Windows.Clipboard]::GetText()
|
||||||
|
}
|
||||||
|
|
||||||
|
$clip = $clip -Replace "`r",""
|
||||||
|
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
|
||||||
|
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
|
||||||
|
}]]
|
||||||
|
} })
|
||||||
|
if not res.error then
|
||||||
|
return res.stdout
|
||||||
|
end
|
||||||
|
elseif platform == 'macos' then
|
||||||
|
local res = utils.subprocess({ args = { 'pbpaste' } })
|
||||||
|
if not res.error then
|
||||||
|
return res.stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Paste text from the window-system's clipboard. 'clip' determines whether the
|
||||||
|
-- clipboard or the primary selection buffer is used (on X11 only.)
|
||||||
|
function paste(clip)
|
||||||
|
local text = get_clipboard(clip)
|
||||||
|
local before_cur = line:sub(1, cursor - 1)
|
||||||
|
local after_cur = line:sub(cursor)
|
||||||
|
line = before_cur .. text .. after_cur
|
||||||
|
cursor = cursor + text:len()
|
||||||
|
trim_input()
|
||||||
|
update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The REPL has pretty specific requirements for key bindings that aren't
|
||||||
|
-- really satisified by any of mpv's helper methods, since they must be in
|
||||||
|
-- their own input section, but they must also raise events on key-repeat.
|
||||||
|
-- Hence, this function manually creates an input section and puts a list of
|
||||||
|
-- bindings in it.
|
||||||
|
function add_repl_bindings(bindings)
|
||||||
|
local cfg = ''
|
||||||
|
for i, binding in ipairs(bindings) do
|
||||||
|
local key = binding[1]
|
||||||
|
local fn = binding[2]
|
||||||
|
local name = '__repl_binding_' .. i
|
||||||
|
mp.add_forced_key_binding(nil, name, fn, 'repeatable')
|
||||||
|
cfg = cfg .. key .. ' script-binding ' .. mp.script_name .. '/' ..
|
||||||
|
name .. '\n'
|
||||||
|
end
|
||||||
|
mp.commandv('define-section', 'repl-input', cfg, 'force')
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_repl_alpharow_bindings(bindings)
|
||||||
|
local cfg = ''
|
||||||
|
for i, binding in ipairs(bindings) do
|
||||||
|
local key = binding[1]
|
||||||
|
local fn = binding[2]
|
||||||
|
local name = '__repl_alpha_binding_' .. i
|
||||||
|
mp.add_forced_key_binding(nil, name, fn, 'repeatable')
|
||||||
|
cfg = cfg .. key .. ' script-binding ' .. mp.script_name .. '/' ..
|
||||||
|
name .. '\n'
|
||||||
|
end
|
||||||
|
mp.commandv('define-section', 'repl-alpha-input', cfg, 'force')
|
||||||
|
mp.enable_key_bindings('repl-alpha-input')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mapping from characters to mpv key names
|
||||||
|
local binding_name_map = {
|
||||||
|
[' '] = 'SPACE',
|
||||||
|
['#'] = 'SHARP',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- List of input bindings. This is a weird mashup between common GUI text-input
|
||||||
|
-- bindings and readline bindings.
|
||||||
|
local bindings = {
|
||||||
|
{ 'esc', function() escape() end },
|
||||||
|
{ 'bs', handle_backspace },
|
||||||
|
{ 'shift+bs', handle_backspace },
|
||||||
|
{ 'del', handle_del },
|
||||||
|
{ 'shift+del', handle_del },
|
||||||
|
{ 'ins', handle_ins },
|
||||||
|
{ 'left', function() prev_char() end },
|
||||||
|
{ 'right', function() next_char() end },
|
||||||
|
{ 'up', function() clear() end },
|
||||||
|
{ 'home', go_home },
|
||||||
|
{ 'end', go_end },
|
||||||
|
{ 'ctrl+c', clear },
|
||||||
|
{ 'ctrl+d', maybe_exit },
|
||||||
|
{ 'ctrl+k', del_to_eol },
|
||||||
|
{ 'ctrl+l', clear_log_buffer },
|
||||||
|
{ 'ctrl+u', del_to_start },
|
||||||
|
{ 'ctrl+v', function() paste(true) end },
|
||||||
|
{ 'meta+v', function() paste(true) end },
|
||||||
|
}
|
||||||
|
local alpharowbindings = {}
|
||||||
|
-- Add bindings for all the printable US-ASCII characters from ' ' to '~'
|
||||||
|
-- inclusive. Note, this is a pretty hacky way to do text input. mpv's input
|
||||||
|
-- system was designed for single-key key bindings rather than text input, so
|
||||||
|
-- things like dead-keys and non-ASCII input won't work. This is probably okay
|
||||||
|
-- though, since all mpv's commands and properties can be represented in ASCII.
|
||||||
|
for b = (' '):byte(), ('~'):byte() do
|
||||||
|
local c = string.char(b)
|
||||||
|
local binding = binding_name_map[c] or c
|
||||||
|
bindings[#bindings + 1] = {binding, function() handle_char_input(c) end}
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_alpharowbinding(firstchar,lastchar)
|
||||||
|
for b = (firstchar):byte(), (lastchar):byte() do
|
||||||
|
local c = string.char(b)
|
||||||
|
local alphabinding = binding_name_map[c] or c
|
||||||
|
alpharowbindings[#alpharowbindings + 1] = {alphabinding, function() handle_char_input(c) end}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_specialalphabindings(charinput)
|
||||||
|
local alphabindingarray = charinput
|
||||||
|
for i, alphabinding in ipairs(alphabindingarray) do
|
||||||
|
alpharowbindings[#alpharowbindings + 1] = {alphabinding, function() handle_char_input(alphabinding) end }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
add_alpharowbinding('a','z')
|
||||||
|
add_alpharowbinding('A','Z')
|
||||||
|
add_alpharowbinding('/','/')
|
||||||
|
add_alpharowbinding(':',':')
|
||||||
|
add_alpharowbinding('(',')')
|
||||||
|
add_alpharowbinding('{','}')
|
||||||
|
add_alpharowbinding(':',';')
|
||||||
|
add_alpharowbinding('<','>')
|
||||||
|
add_alpharowbinding(',','.')
|
||||||
|
add_alpharowbinding('|','|')
|
||||||
|
add_alpharowbinding('\\','\\')
|
||||||
|
add_alpharowbinding('?','?')
|
||||||
|
add_alpharowbinding('[',']')
|
||||||
|
add_alpharowbinding('#','#')
|
||||||
|
add_alpharowbinding('~','~')
|
||||||
|
add_alpharowbinding('\'','\'')
|
||||||
|
add_alpharowbinding('@','@')
|
||||||
|
|
||||||
|
add_specialalphabindings({'à','è','ì','ò','ù','À','È','Ì','Ò','Ù'})
|
||||||
|
add_specialalphabindings({'á', 'é', 'í', 'ó', 'ú', 'ý', 'Á', 'É', 'Í', 'Ó', 'Ú', 'Ý'})
|
||||||
|
add_specialalphabindings({'â', 'ê', 'î', 'ô', 'û', 'Â', 'Ê', 'Î', 'Ô', 'Û'})
|
||||||
|
add_specialalphabindings({'ã', 'ñ', 'õ', 'Ã', 'Ñ', 'Õ'})
|
||||||
|
add_specialalphabindings({'ä', 'ë', 'ï', 'ö', 'ü', 'ÿ', 'Ä', 'Ë', 'Ï', 'Ö', 'Ü', 'Ÿ'})
|
||||||
|
add_specialalphabindings({'å', 'Å','æ','Æ','œ','Œ','ç','Ç','ð','Ð','ø','Ø','¿','¡','ß'})
|
||||||
|
add_specialalphabindings({'¤','†','×','÷','‡','±','—','–','¶','§','ˆ','˜','«','»','¦','‰','©','®','™'})
|
||||||
|
add_specialalphabindings({'ž','Ž'})
|
||||||
|
add_specialalphabindings({'ª','Þ','þ','ƒ','µ','°','º','•','„','“','…','¬','¥','£','€','¢','¹','²','³','½','¼','¾'})
|
||||||
|
add_specialalphabindings({'·','Ĉ','ĉ','Ĝ','ĝ','Ĥ','ĥ','Ĵ','ĵ','Ŝ','ŝ','Ŭ','ŭ'})
|
||||||
|
add_specialalphabindings({'Б','б','В','в','Г','г','Д','д','Е','е','Ё','ё','Ж','ж','З','з'})
|
||||||
|
add_specialalphabindings({'И','и','Й','й','К','к','Л','л','М','м','Н','н','О','о','П','п'})
|
||||||
|
add_specialalphabindings({'Р','р','С','с','Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ч','ч'})
|
||||||
|
add_specialalphabindings({'Ш','ш','Щ','щ','Ъ','ъ','Ы','ы','Ь','ь','Э','э','Ю','ю','Я','я'})
|
||||||
|
add_specialalphabindings({'≥','≠'})
|
||||||
|
|
||||||
|
add_repl_bindings(bindings)
|
||||||
|
|
||||||
|
-- Add a script-message to show the REPL and fill it with the provided text
|
||||||
|
mp.register_script_message('type', function(text)
|
||||||
|
show_and_type(text)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local syncplayintfSet = false
|
||||||
|
mp.command('print-text "<get_syncplayintf_options>"')
|
||||||
|
|
||||||
|
function readyMpvAfterSettingsKnown()
|
||||||
|
if syncplayintfSet == false then
|
||||||
|
local vertical_output_area = CANVAS_HEIGHT-(opts['chatTopMargin']+opts['chatBottomMargin']+((opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER)*opts['scrollingFirstRowOffset'])+SCROLLING_ADDITIONAL_BOTTOM_MARGIN)
|
||||||
|
max_scrolling_rows = math.floor(vertical_output_area/(opts['chatOutputRelativeFontSize']*FONT_SIZE_MULTIPLIER))
|
||||||
|
local user_opts = { visibility = "auto", }
|
||||||
|
opt.read_options(user_opts, "osc")
|
||||||
|
default_oscvisibility_state = user_opts.visibility
|
||||||
|
if opts['chatInputEnabled'] == true then
|
||||||
|
key_hints_enabled = true
|
||||||
|
mp.add_forced_key_binding('enter', handle_enter)
|
||||||
|
mp.add_forced_key_binding('kp_enter', handle_enter)
|
||||||
|
if opts['chatDirectInput'] == true then
|
||||||
|
add_repl_alpharow_bindings(alpharowbindings)
|
||||||
|
mp.add_forced_key_binding('tab', handle_tab)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
syncplayintfSet = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function set_syncplayintf_options(input)
|
||||||
|
--mp.command('print-text "<chat>...'..input..'</chat>"')
|
||||||
|
for option, value in string.gmatch(input, "([^ ,=]+)=([^,]+)") do
|
||||||
|
local valueType = type(opts[option])
|
||||||
|
if valueType == "number" then
|
||||||
|
value = tonumber(value)
|
||||||
|
elseif valueType == "boolean" then
|
||||||
|
if value == "True" then
|
||||||
|
value = true
|
||||||
|
else
|
||||||
|
value = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
opts[option] = value
|
||||||
|
--mp.command('print-text "<chat>'..option.."="..tostring(value).." - "..valueType..'</chat>"')
|
||||||
|
end
|
||||||
|
chat_format = get_output_style()
|
||||||
|
readyMpvAfterSettingsKnown()
|
||||||
|
end
|
||||||
BIN
resources/user_comment.png
Normal file
BIN
resources/user_comment.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 743 B |
@ -1,4 +1,4 @@
|
|||||||
version = '1.5.1'
|
version = '1.5.2'
|
||||||
milestone = 'Yoitsu'
|
milestone = 'Yoitsu'
|
||||||
release_number = '51'
|
release_number = '55'
|
||||||
projectURL = 'http://syncplay.pl/'
|
projectURL = 'http://syncplay.pl/'
|
||||||
|
|||||||
@ -145,8 +145,8 @@ class SyncplayClient(object):
|
|||||||
|
|
||||||
def initPlayer(self, player):
|
def initPlayer(self, player):
|
||||||
self._player = player
|
self._player = player
|
||||||
if not self._player.secondaryOSDSupported:
|
if not self._player.alertOSDSupported:
|
||||||
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION
|
constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_ALERT_OSD_WARNING_DURATION
|
||||||
self.scheduleAskPlayer()
|
self.scheduleAskPlayer()
|
||||||
self.__playerReady.callback(player)
|
self.__playerReady.callback(player)
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ class SyncplayClient(object):
|
|||||||
if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
|
if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
|
||||||
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD:
|
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD:
|
||||||
self.playlist.advancePlaylistCheck()
|
self.playlist.advancePlaylistCheck()
|
||||||
elif pauseChange and self.serverFeatures["readiness"]:
|
elif pauseChange and self.serverFeatures.has_key("readiness") and self.serverFeatures["readiness"]:
|
||||||
if currentLength == 0 or currentLength == -1 or\
|
if currentLength == 0 or currentLength == -1 or\
|
||||||
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
|
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
|
||||||
pauseChange = self._toggleReady(pauseChange, paused)
|
pauseChange = self._toggleReady(pauseChange, paused)
|
||||||
@ -546,15 +546,32 @@ class SyncplayClient(object):
|
|||||||
"sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION),
|
"sharedPlaylists": utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION),
|
||||||
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
|
"chat": utils.meetsMinVersion(self.serverVersion, constants.CHAT_MIN_VERSION),
|
||||||
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
|
"readiness": utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION),
|
||||||
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION)
|
"managedRooms": utils.meetsMinVersion(self.serverVersion, constants.CONTROLLED_ROOMS_MIN_VERSION),
|
||||||
|
"maxChatMessageLength": constants.FALLBACK_MAX_CHAT_MESSAGE_LENGTH,
|
||||||
|
"maxUsernameLength": constants.FALLBACK_MAX_USERNAME_LENGTH,
|
||||||
|
"maxRoomNameLength": constants.FALLBACK_MAX_ROOM_NAME_LENGTH,
|
||||||
|
"maxFilenameLength": constants.FALLBACK_MAX_FILENAME_LENGTH
|
||||||
}
|
}
|
||||||
|
|
||||||
if featureList:
|
if featureList:
|
||||||
self.serverFeatures.update(featureList)
|
self.serverFeatures.update(featureList)
|
||||||
if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION):
|
if not utils.meetsMinVersion(self.serverVersion, constants.SHARED_PLAYLIST_MIN_VERSION):
|
||||||
self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion))
|
self.ui.showErrorMessage(getMessage("shared-playlists-not-supported-by-server-error").format(constants.SHARED_PLAYLIST_MIN_VERSION, self.serverVersion))
|
||||||
elif not self.serverFeatures["sharedPlaylists"]:
|
elif not self.serverFeatures["sharedPlaylists"]:
|
||||||
self.ui.showErrorMessage(getMessage("shared-playlists-disabled-by-server-error"))
|
self.ui.showErrorMessage(getMessage("shared-playlists-disabled-by-server-error"))
|
||||||
|
# TODO: Have messages for all unsupported & disabled features
|
||||||
|
constants.MAX_CHAT_MESSAGE_LENGTH = self.serverFeatures["maxChatMessageLength"]
|
||||||
|
constants.MAX_USERNAME_LENGTH = self.serverFeatures["maxUsernameLength"]
|
||||||
|
constants.MAX_ROOM_NAME_LENGTH = self.serverFeatures["maxRoomNameLength"]
|
||||||
|
constants.MAX_FILENAME_LENGTH = self.serverFeatures["maxFilenameLength"]
|
||||||
|
constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(constants.MAX_CHAT_MESSAGE_LENGTH),
|
||||||
|
u"inputPromptStartCharacter={}".format(constants.MPV_INPUT_PROMPT_START_CHARACTER),
|
||||||
|
u"inputPromptEndCharacter={}".format(constants.MPV_INPUT_PROMPT_END_CHARACTER),
|
||||||
|
u"backslashSubstituteCharacter={}".format(
|
||||||
|
constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
|
||||||
self.ui.setFeatures(self.serverFeatures)
|
self.ui.setFeatures(self.serverFeatures)
|
||||||
|
if self._player:
|
||||||
|
self._player.setFeatures(self.serverFeatures)
|
||||||
|
|
||||||
def getSanitizedCurrentUserFile(self):
|
def getSanitizedCurrentUserFile(self):
|
||||||
if self.userlist.currentUser.file:
|
if self.userlist.currentUser.file:
|
||||||
@ -583,16 +600,29 @@ class SyncplayClient(object):
|
|||||||
def getUsername(self):
|
def getUsername(self):
|
||||||
return self.userlist.currentUser.username
|
return self.userlist.currentUser.username
|
||||||
|
|
||||||
|
def chatIsEnabled(self):
|
||||||
|
return True
|
||||||
|
# TODO: Allow chat to be disabled
|
||||||
|
|
||||||
|
def getFeatures(self):
|
||||||
|
features = dict()
|
||||||
|
|
||||||
|
# Can change during runtime:
|
||||||
|
features["sharedPlaylists"] = self.sharedPlaylistIsEnabled() # Can change during runtime
|
||||||
|
features["chat"] = self.chatIsEnabled() # Can change during runtime
|
||||||
|
|
||||||
|
# Static for this version/release of Syncplay:
|
||||||
|
features["featureList"] = True
|
||||||
|
features["readiness"] = True
|
||||||
|
features["managedRooms"] = True
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
def setRoom(self, roomName, resetAutoplay=False):
|
def setRoom(self, roomName, resetAutoplay=False):
|
||||||
self.userlist.currentUser.room = roomName
|
self.userlist.currentUser.room = roomName
|
||||||
if resetAutoplay:
|
if resetAutoplay:
|
||||||
self.resetAutoPlayState()
|
self.resetAutoPlayState()
|
||||||
|
|
||||||
def sendChat(self,message):
|
|
||||||
if self._protocol and self._protocol.logged:
|
|
||||||
message = utils.truncateText(message,constants.MAX_CHAT_MESSAGE_LENGTH)
|
|
||||||
self._protocol.sendChatMessage(message)
|
|
||||||
|
|
||||||
def sendRoom(self):
|
def sendRoom(self):
|
||||||
room = self.userlist.currentUser.room
|
room = self.userlist.currentUser.room
|
||||||
if self._protocol and self._protocol.logged and room:
|
if self._protocol and self._protocol.logged and room:
|
||||||
@ -632,8 +662,8 @@ class SyncplayClient(object):
|
|||||||
if self._protocol and self._protocol.logged:
|
if self._protocol and self._protocol.logged:
|
||||||
self._protocol.sendList()
|
self._protocol.sendList()
|
||||||
|
|
||||||
def showUserList(self):
|
def showUserList(self, altUI=None):
|
||||||
self.userlist.showUserList()
|
self.userlist.showUserList(altUI)
|
||||||
|
|
||||||
def getPassword(self):
|
def getPassword(self):
|
||||||
if self.thisIsPublicServer():
|
if self.thisIsPublicServer():
|
||||||
@ -700,19 +730,30 @@ class SyncplayClient(object):
|
|||||||
if promptForAction:
|
if promptForAction:
|
||||||
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
|
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
|
||||||
|
|
||||||
def requireMinServerVersion(minVersion):
|
def requireServerFeature(featureRequired):
|
||||||
def requireMinVersionDecorator(f):
|
def requireServerFeatureDecorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(self, *args, **kwds):
|
def wrapper(self, *args, **kwds):
|
||||||
if not utils.meetsMinVersion(self.serverVersion,minVersion):
|
if self.serverVersion == "0.0.0":
|
||||||
if self.serverVersion != "0.0.0":
|
self.ui.showDebugMessage(
|
||||||
self.ui.showErrorMessage(getMessage("not-supported-by-server-error").format(minVersion, self.serverVersion))
|
"Tried to check server version too soon (testing support for: {})".format(featureRequired))
|
||||||
else:
|
return None
|
||||||
self.ui.showDebugMessage("Tried to check server version too soon (threshold: {})".format(minVersion))
|
if not self.serverFeatures.has_key(featureRequired) or not self.serverFeatures[featureRequired]:
|
||||||
|
featureName = getMessage(u"feature-{}".format(featureRequired))
|
||||||
|
self.ui.showErrorMessage(getMessage("not-supported-by-server-error").format(featureName))
|
||||||
return
|
return
|
||||||
return f(self, *args, **kwds)
|
return f(self, *args, **kwds)
|
||||||
return wrapper
|
return wrapper
|
||||||
return requireMinVersionDecorator
|
return requireServerFeatureDecorator
|
||||||
|
|
||||||
|
@requireServerFeature("chat")
|
||||||
|
def sendChat(self,message):
|
||||||
|
if self._protocol and self._protocol.logged:
|
||||||
|
message = utils.truncateText(message,constants.MAX_CHAT_MESSAGE_LENGTH)
|
||||||
|
self._protocol.sendChatMessage(message)
|
||||||
|
|
||||||
|
def sendFeaturesUpdate(self, features):
|
||||||
|
self._protocol.sendFeaturesUpdate(features)
|
||||||
|
|
||||||
def changePlaylistEnabledState(self, newState):
|
def changePlaylistEnabledState(self, newState):
|
||||||
oldState = self.sharedPlaylistIsEnabled()
|
oldState = self.sharedPlaylistIsEnabled()
|
||||||
@ -780,7 +821,7 @@ class SyncplayClient(object):
|
|||||||
allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount())
|
allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount())
|
||||||
autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft))
|
autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft))
|
||||||
countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage)
|
countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage)
|
||||||
self.ui.showOSDMessage(countdownMessage, 1, secondaryOSD=True)
|
self.ui.showOSDMessage(countdownMessage, 1, OSDType=constants.OSD_ALERT, mood=constants.MESSAGE_GOODNEWS)
|
||||||
if self.autoplayTimeLeft <= 0:
|
if self.autoplayTimeLeft <= 0:
|
||||||
self.setPaused(False)
|
self.setPaused(False)
|
||||||
self.stopAutoplayCountdown()
|
self.stopAutoplayCountdown()
|
||||||
@ -792,11 +833,11 @@ class SyncplayClient(object):
|
|||||||
self.ui.updateAutoPlayState(False)
|
self.ui.updateAutoPlayState(False)
|
||||||
self.stopAutoplayCountdown()
|
self.stopAutoplayCountdown()
|
||||||
|
|
||||||
@requireMinServerVersion(constants.USER_READY_MIN_VERSION)
|
@requireServerFeature("readiness")
|
||||||
def toggleReady(self, manuallyInitiated=True):
|
def toggleReady(self, manuallyInitiated=True):
|
||||||
self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated)
|
self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated)
|
||||||
|
|
||||||
@requireMinServerVersion(constants.USER_READY_MIN_VERSION)
|
@requireServerFeature("readiness")
|
||||||
def changeReadyState(self, newState, manuallyInitiated=True):
|
def changeReadyState(self, newState, manuallyInitiated=True):
|
||||||
oldState = self.userlist.currentUser.isReady()
|
oldState = self.userlist.currentUser.isReady()
|
||||||
if newState != oldState:
|
if newState != oldState:
|
||||||
@ -811,7 +852,12 @@ class SyncplayClient(object):
|
|||||||
if oldReadyState != isReady:
|
if oldReadyState != isReady:
|
||||||
self._warnings.checkReadyStates()
|
self._warnings.checkReadyStates()
|
||||||
|
|
||||||
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION)
|
@requireServerFeature("managedRooms")
|
||||||
|
def setUserFeatures(self, username, features):
|
||||||
|
self.userlist.setFeatures(username, features)
|
||||||
|
self.ui.userListChange()
|
||||||
|
|
||||||
|
@requireServerFeature("managedRooms")
|
||||||
def createControlledRoom(self, roomName):
|
def createControlledRoom(self, roomName):
|
||||||
controlPassword = utils.RandomStringGenerator.generate_room_password()
|
controlPassword = utils.RandomStringGenerator.generate_room_password()
|
||||||
self.lastControlPasswordAttempt = controlPassword
|
self.lastControlPasswordAttempt = controlPassword
|
||||||
@ -830,7 +876,7 @@ class SyncplayClient(object):
|
|||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION)
|
@requireServerFeature("managedRooms")
|
||||||
def identifyAsController(self, controlPassword):
|
def identifyAsController(self, controlPassword):
|
||||||
controlPassword = self.stripControlPassword(controlPassword)
|
controlPassword = self.stripControlPassword(controlPassword)
|
||||||
self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword))
|
self.ui.showMessage(getMessage("identifying-as-controller-notification").format(controlPassword))
|
||||||
@ -924,7 +970,7 @@ class SyncplayClient(object):
|
|||||||
|
|
||||||
def _checkIfYouReAloneInTheRoom(self, OSDOnly):
|
def _checkIfYouReAloneInTheRoom(self, OSDOnly):
|
||||||
if self._userlist.areYouAloneInRoom():
|
if self._userlist.areYouAloneInRoom():
|
||||||
self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True)
|
self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, OSDType=constants.OSD_ALERT, mood=constants.MESSAGE_BADNEWS)
|
||||||
if not OSDOnly:
|
if not OSDOnly:
|
||||||
self._ui.showMessage(getMessage("alone-in-the-room"), True)
|
self._ui.showMessage(getMessage("alone-in-the-room"), True)
|
||||||
if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running:
|
if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running:
|
||||||
@ -954,8 +1000,10 @@ class SyncplayClient(object):
|
|||||||
if not self._client._player or self._client.autoplayTimerIsRunning():
|
if not self._client._player or self._client.autoplayTimerIsRunning():
|
||||||
return
|
return
|
||||||
osdMessage = None
|
osdMessage = None
|
||||||
|
messageMood = constants.MESSAGE_GOODNEWS
|
||||||
fileDifferencesForRoom = self._userlist.getFileDifferencesForRoom()
|
fileDifferencesForRoom = self._userlist.getFileDifferencesForRoom()
|
||||||
if not self._userlist.areAllFilesInRoomSame() and fileDifferencesForRoom is not None:
|
if not self._userlist.areAllFilesInRoomSame() and fileDifferencesForRoom is not None:
|
||||||
|
messageMood = constants.MESSAGE_BADNEWS
|
||||||
fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom)
|
fileDifferencesMessage = getMessage("room-file-differences").format(fileDifferencesForRoom)
|
||||||
if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported():
|
if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported():
|
||||||
if self._userlist.areAllUsersInRoomReady():
|
if self._userlist.areAllUsersInRoomReady():
|
||||||
@ -970,9 +1018,10 @@ class SyncplayClient(object):
|
|||||||
if self._userlist.areAllUsersInRoomReady():
|
if self._userlist.areAllUsersInRoomReady():
|
||||||
osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount())
|
osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount())
|
||||||
else:
|
else:
|
||||||
|
messageMood = constants.MESSAGE_BADNEWS
|
||||||
osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())
|
osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())
|
||||||
if osdMessage:
|
if osdMessage:
|
||||||
self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True)
|
self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, OSDType=constants.OSD_ALERT, mood=messageMood)
|
||||||
|
|
||||||
def __displayMessageOnOSD(self, warningName, warningFunction):
|
def __displayMessageOnOSD(self, warningName, warningFunction):
|
||||||
if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]:
|
if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]:
|
||||||
@ -1001,6 +1050,7 @@ class SyncplayUser(object):
|
|||||||
self.room = room
|
self.room = room
|
||||||
self.file = file_
|
self.file = file_
|
||||||
self._controller = False
|
self._controller = False
|
||||||
|
self._features = {}
|
||||||
|
|
||||||
def setFile(self, filename, duration, size, path=None):
|
def setFile(self, filename, duration, size, path=None):
|
||||||
file_ = {
|
file_ = {
|
||||||
@ -1054,6 +1104,9 @@ class SyncplayUser(object):
|
|||||||
def setReady(self, ready):
|
def setReady(self, ready):
|
||||||
self.ready = ready
|
self.ready = ready
|
||||||
|
|
||||||
|
def setFeatures(self, features):
|
||||||
|
self._features = features
|
||||||
|
|
||||||
class SyncplayUserlist(object):
|
class SyncplayUserlist(object):
|
||||||
def __init__(self, ui, client):
|
def __init__(self, ui, client):
|
||||||
self.currentUser = SyncplayUser()
|
self.currentUser = SyncplayUser()
|
||||||
@ -1132,7 +1185,7 @@ class SyncplayUserlist(object):
|
|||||||
if differentDuration: differences.append(getMessage("file-difference-duration"))
|
if differentDuration: differences.append(getMessage("file-difference-duration"))
|
||||||
return ", ".join(differences)
|
return ", ".join(differences)
|
||||||
|
|
||||||
def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=None):
|
def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=None, features={}):
|
||||||
if username == self.currentUser.username:
|
if username == self.currentUser.username:
|
||||||
if isController is not None:
|
if isController is not None:
|
||||||
self.currentUser.setControllerStatus(isController)
|
self.currentUser.setControllerStatus(isController)
|
||||||
@ -1143,7 +1196,7 @@ class SyncplayUserlist(object):
|
|||||||
user.setControllerStatus(isController)
|
user.setControllerStatus(isController)
|
||||||
self._users[username] = user
|
self._users[username] = user
|
||||||
user.setReady(isReady)
|
user.setReady(isReady)
|
||||||
|
user.setFeatures(features)
|
||||||
if not noMessage:
|
if not noMessage:
|
||||||
self.__showUserChangeMessage(username, room, file_)
|
self.__showUserChangeMessage(username, room, file_)
|
||||||
self.userListChange(room)
|
self.userListChange(room)
|
||||||
@ -1310,7 +1363,7 @@ class SyncplayUserlist(object):
|
|||||||
def hasRoomStateChanged(self):
|
def hasRoomStateChanged(self):
|
||||||
return self._roomUsersChanged
|
return self._roomUsersChanged
|
||||||
|
|
||||||
def showUserList(self):
|
def showUserList(self, altUI=None):
|
||||||
rooms = {}
|
rooms = {}
|
||||||
for user in self._users.itervalues():
|
for user in self._users.itervalues():
|
||||||
if user.room not in rooms:
|
if user.room not in rooms:
|
||||||
@ -1320,7 +1373,10 @@ class SyncplayUserlist(object):
|
|||||||
rooms[self.currentUser.room] = []
|
rooms[self.currentUser.room] = []
|
||||||
rooms[self.currentUser.room].append(self.currentUser)
|
rooms[self.currentUser.room].append(self.currentUser)
|
||||||
rooms = self.sortList(rooms)
|
rooms = self.sortList(rooms)
|
||||||
self.ui.showUserList(self.currentUser, rooms)
|
if altUI:
|
||||||
|
altUI.showUserList(self.currentUser, rooms)
|
||||||
|
else:
|
||||||
|
self.ui.showUserList(self.currentUser, rooms)
|
||||||
self._client.autoplayCheck()
|
self._client.autoplayCheck()
|
||||||
|
|
||||||
def clearList(self):
|
def clearList(self):
|
||||||
@ -1336,10 +1392,10 @@ class UiManager(object):
|
|||||||
def __init__(self, client, ui):
|
def __init__(self, client, ui):
|
||||||
self._client = client
|
self._client = client
|
||||||
self.__ui = ui
|
self.__ui = ui
|
||||||
self.lastPrimaryOSDMessage = None
|
self.lastNotificatinOSDMessage = None
|
||||||
self.lastPrimaryOSDEndTime = None
|
self.lastNotificationOSDEndTime = None
|
||||||
self.lastSecondaryOSDMessage = None
|
self.lastAlertOSDMessage = None
|
||||||
self.lastSecondaryOSDEndTime = None
|
self.lastAlertOSDEndTime = None
|
||||||
self.lastError = ""
|
self.lastError = ""
|
||||||
|
|
||||||
def setPlaylist(self, newPlaylist, newIndexFilename=None):
|
def setPlaylist(self, newPlaylist, newIndexFilename=None):
|
||||||
@ -1358,8 +1414,16 @@ class UiManager(object):
|
|||||||
if constants.DEBUG_MODE and message.rstrip():
|
if constants.DEBUG_MODE and message.rstrip():
|
||||||
sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()).decode('utf-8'),message.rstrip()))
|
sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()).decode('utf-8'),message.rstrip()))
|
||||||
|
|
||||||
def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False):
|
def showChatMessage(self, username, userMessage):
|
||||||
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD)
|
messageString = u"<{}> {}".format(username, userMessage)
|
||||||
|
if self._client._player.chatOSDSupported and self._client._config["chatOutputEnabled"]:
|
||||||
|
self._client._player.displayChatMessage(username,userMessage)
|
||||||
|
else:
|
||||||
|
self.showOSDMessage(messageString, duration=constants.OSD_DURATION)
|
||||||
|
self.__ui.showMessage(messageString)
|
||||||
|
|
||||||
|
def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION,mood=constants.MESSAGE_NEUTRAL):
|
||||||
|
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
|
||||||
self.__ui.showMessage(message, noTimestamp)
|
self.__ui.showMessage(message, noTimestamp)
|
||||||
|
|
||||||
def updateAutoPlayState(self, newState):
|
def updateAutoPlayState(self, newState):
|
||||||
@ -1368,28 +1432,28 @@ class UiManager(object):
|
|||||||
def showUserList(self, currentUser, rooms):
|
def showUserList(self, currentUser, rooms):
|
||||||
self.__ui.showUserList(currentUser, rooms)
|
self.__ui.showUserList(currentUser, rooms)
|
||||||
|
|
||||||
def showOSDMessage(self, message, duration=constants.OSD_DURATION, secondaryOSD=False):
|
def showOSDMessage(self, message, duration=constants.OSD_DURATION, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||||
autoplayConditionsMet = self._client.autoplayConditionsMet()
|
autoplayConditionsMet = self._client.autoplayConditionsMet()
|
||||||
if secondaryOSD and not constants.SHOW_OSD_WARNINGS and not self._client.autoplayTimerIsRunning():
|
if OSDType == constants.OSD_ALERT and not constants.SHOW_OSD_WARNINGS and not self._client.autoplayTimerIsRunning():
|
||||||
return
|
return
|
||||||
if not self._client._player:
|
if not self._client._player:
|
||||||
return
|
return
|
||||||
if constants.SHOW_OSD and self._client and self._client._player:
|
if constants.SHOW_OSD and self._client and self._client._player:
|
||||||
if not self._client._player.secondaryOSDSupported:
|
if not self._client._player.alertOSDSupported:
|
||||||
if secondaryOSD:
|
if OSDType == constants.OSD_ALERT:
|
||||||
self.lastSecondaryOSDMessage = message
|
self.lastAlertOSDMessage = message
|
||||||
if autoplayConditionsMet:
|
if autoplayConditionsMet:
|
||||||
self.lastSecondaryOSDEndTime = time.time() + 1.0
|
self.lastAlertOSDEndTime = time.time() + 1.0
|
||||||
else:
|
else:
|
||||||
self.lastSecondaryOSDEndTime = time.time() + constants.NO_SECONDARY_OSD_WARNING_DURATION
|
self.lastAlertOSDEndTime = time.time() + constants.NO_ALERT_OSD_WARNING_DURATION
|
||||||
if self.lastPrimaryOSDEndTime and time.time() < self.lastPrimaryOSDEndTime:
|
if self.lastNotificationOSDEndTime and time.time() < self.lastNotificationOSDEndTime:
|
||||||
message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastPrimaryOSDMessage)
|
message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastNotificatinOSDMessage)
|
||||||
else:
|
else:
|
||||||
self.lastPrimaryOSDMessage = message
|
self.lastNotificatinOSDMessage = message
|
||||||
self.lastPrimaryOSDEndTime = time.time() + constants.OSD_DURATION
|
self.lastNotificationOSDEndTime = time.time() + constants.OSD_DURATION
|
||||||
if self.lastSecondaryOSDEndTime and time.time() < self.lastSecondaryOSDEndTime:
|
if self.lastAlertOSDEndTime and time.time() < self.lastAlertOSDEndTime:
|
||||||
message = u"{}{}{}".format(self.lastSecondaryOSDMessage, self._client._player.osdMessageSeparator, message)
|
message = u"{}{}{}".format(self.lastAlertOSDMessage, self._client._player.osdMessageSeparator, message)
|
||||||
self._client._player.displayMessage(message, int(duration * 1000), secondaryOSD)
|
self._client._player.displayMessage(message, int(duration * 1000), OSDType, mood)
|
||||||
|
|
||||||
def setControllerStatus(self, username, isController):
|
def setControllerStatus(self, username, isController):
|
||||||
self.__ui.setControllerStatus(username, isController)
|
self.__ui.setControllerStatus(username, isController)
|
||||||
@ -1411,6 +1475,9 @@ class UiManager(object):
|
|||||||
def updateRoomName(self, room=""):
|
def updateRoomName(self, room=""):
|
||||||
self.__ui.updateRoomName(room)
|
self.__ui.updateRoomName(room)
|
||||||
|
|
||||||
|
def executeCommand(self, command):
|
||||||
|
self.__ui.executeCommand(command)
|
||||||
|
|
||||||
def drop(self):
|
def drop(self):
|
||||||
self.__ui.drop()
|
self.__ui.drop()
|
||||||
|
|
||||||
@ -1799,7 +1866,7 @@ class FileSwitchManager(object):
|
|||||||
if os.path.isfile(filepath):
|
if os.path.isfile(filepath):
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
if highPriority and self.folderSearchEnabled:
|
if highPriority and self.folderSearchEnabled and self.mediaDirectories is not None:
|
||||||
directoryList = self.mediaDirectories
|
directoryList = self.mediaDirectories
|
||||||
# Spin up hard drives to prevent premature timeout
|
# Spin up hard drives to prevent premature timeout
|
||||||
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
randomFilename = u"RandomFile"+unicode(random.randrange(10000, 99999))+u".txt"
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
# coding:utf8
|
||||||
# You might want to change these
|
# You might want to change these
|
||||||
DEFAULT_PORT = 8999
|
DEFAULT_PORT = 8999
|
||||||
OSD_DURATION = 3.0
|
OSD_DURATION = 3.0
|
||||||
OSD_WARNING_MESSAGE_DURATION = 5.0
|
OSD_WARNING_MESSAGE_DURATION = 5.0
|
||||||
NO_SECONDARY_OSD_WARNING_DURATION = 13.0
|
NO_ALERT_OSD_WARNING_DURATION = 13.0
|
||||||
MPC_OSD_POSITION = 1 #Right corner, 1 for left
|
MPC_OSD_POSITION = 1 #Right corner, 1 for left
|
||||||
MPLAYER_OSD_LEVEL = 1
|
MPLAYER_OSD_LEVEL = 1
|
||||||
UI_TIME_FORMAT = "[%X] "
|
UI_TIME_FORMAT = "[%X] "
|
||||||
@ -56,12 +57,23 @@ PLAYLIST_MAX_CHARACTERS = 10000
|
|||||||
PLAYLIST_MAX_ITEMS = 250
|
PLAYLIST_MAX_ITEMS = 250
|
||||||
MAXIMUM_TAB_WIDTH = 350
|
MAXIMUM_TAB_WIDTH = 350
|
||||||
TAB_PADDING = 30
|
TAB_PADDING = 30
|
||||||
|
DEFAULT_WINDOWS_MONOSPACE_FONT = "Consolas"
|
||||||
|
DEFAULT_OSX_MONOSPACE_FONT = "Menlo"
|
||||||
|
FALLBACK_MONOSPACE_FONT = "Monospace"
|
||||||
|
DEFAULT_CHAT_FONT_SIZE = 24
|
||||||
|
DEFAULT_CHAT_INPUT_FONT_COLOR = "#FFFF00"
|
||||||
|
DEFAULT_CHAT_OUTPUT_FONT_COLOR = "#FFFF00"
|
||||||
|
DEFAULT_CHAT_FONT_WEIGHT = 1
|
||||||
|
|
||||||
# Maximum character lengths (for client and server)
|
# Max numbers are used by server (and client pre-connection). Once connected client gets values from server featureList (or uses 'fallback' versions for old servers)
|
||||||
MAX_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters
|
MAX_CHAT_MESSAGE_LENGTH = 125 # Number of displayed characters
|
||||||
MAX_USERNAME_LENGTH = 16 # Number of displayed characters
|
MAX_USERNAME_LENGTH = 20 # Number of displayed characters
|
||||||
MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters
|
MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters
|
||||||
MAX_FILENAME_LENGTH = 250 # Number of displayed characters
|
MAX_FILENAME_LENGTH = 250 # Number of displayed characters
|
||||||
|
FALLBACK_MAX_CHAT_MESSAGE_LENGTH = 50 # Number of displayed characters
|
||||||
|
FALLBACK_MAX_USERNAME_LENGTH = 16 # Number of displayed characters
|
||||||
|
FALLBACK_MAX_ROOM_NAME_LENGTH = 35 # Number of displayed characters
|
||||||
|
FALLBACK_MAX_FILENAME_LENGTH = 250 # Number of displayed characters
|
||||||
|
|
||||||
# Options for the File Switch feature:
|
# Options for the File Switch feature:
|
||||||
FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 25.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up)
|
FOLDER_SEARCH_FIRST_FILE_TIMEOUT = 25.0 # Secs - How long to wait to find the first file in folder search (to take account of HDD spin up)
|
||||||
@ -133,7 +145,7 @@ MPC_ICONPATH = "mpc-hc.png"
|
|||||||
MPC64_ICONPATH = "mpc-hc64.png"
|
MPC64_ICONPATH = "mpc-hc64.png"
|
||||||
MPC_BE_ICONPATH = "mpc-be.png"
|
MPC_BE_ICONPATH = "mpc-be.png"
|
||||||
|
|
||||||
MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.']
|
MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.', '[syncplayintf] Lua error']
|
||||||
|
|
||||||
#Changing these is usually not something you're looking for
|
#Changing these is usually not something you're looking for
|
||||||
PLAYER_ASK_DELAY = 0.1
|
PLAYER_ASK_DELAY = 0.1
|
||||||
@ -179,6 +191,19 @@ MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open']
|
|||||||
MPV_SLAVE_ARGS = ['--msg-level=all=error,cplayer=info,term-msg=info', '--input-terminal=no', '--input-file=/dev/stdin']
|
MPV_SLAVE_ARGS = ['--msg-level=all=error,cplayer=info,term-msg=info', '--input-terminal=no', '--input-file=/dev/stdin']
|
||||||
MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=duration:${=length:0}}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes']
|
MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=duration:${=length:0}}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes']
|
||||||
MPV_NEW_VERSION = False
|
MPV_NEW_VERSION = False
|
||||||
|
MPV_OSC_VISIBILITY_CHANGE_VERSION = False
|
||||||
|
MPV_INPUT_PROMPT_START_CHARACTER = u"〉"
|
||||||
|
MPV_INPUT_PROMPT_END_CHARACTER = u" 〈"
|
||||||
|
MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER = u"\"
|
||||||
|
MPV_SYNCPLAYINTF_OPTIONS_TO_SEND = ["chatInputEnabled","chatInputFontFamily", "chatInputRelativeFontSize", "chatInputFontWeight","chatInputFontUnderline",
|
||||||
|
"chatInputFontColor", "chatInputPosition","chatOutputFontFamily","chatOutputRelativeFontSize",
|
||||||
|
"chatOutputFontWeight","chatOutputFontUnderline","chatOutputMode","chatMaxLines",
|
||||||
|
"chatTopMargin","chatLeftMargin","chatBottomMargin","chatDirectInput",
|
||||||
|
"notificationTimeout","alertTimeout","chatTimeout","chatOutputEnabled"]
|
||||||
|
|
||||||
|
MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(MAX_CHAT_MESSAGE_LENGTH),u"inputPromptStartCharacter={}".format(MPV_INPUT_PROMPT_START_CHARACTER),u"inputPromptEndCharacter={}".format(MPV_INPUT_PROMPT_END_CHARACTER),u"backslashSubstituteCharacter={}".format(MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
|
||||||
|
# Note: Constants updated in client.py->checkForFeatureSupport
|
||||||
|
MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND = ["mpv-key-tab-hint","mpv-key-hint", "alphakey-mode-warning-first-line", "alphakey-mode-warning-second-line"]
|
||||||
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
|
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
|
||||||
'--play-and-pause', '--start-time=0']
|
'--play-and-pause', '--start-time=0']
|
||||||
VLC_SLAVE_MACOS_ARGS = ['--verbose=2', '--no-file-logging']
|
VLC_SLAVE_MACOS_ARGS = ['--verbose=2', '--no-file-logging']
|
||||||
@ -200,6 +225,9 @@ UNPAUSE_IFALREADYREADY_MODE = "IfAlreadyReady"
|
|||||||
UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady"
|
UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady"
|
||||||
UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady"
|
UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady"
|
||||||
UNPAUSE_ALWAYS_MODE = "Always"
|
UNPAUSE_ALWAYS_MODE = "Always"
|
||||||
|
INPUT_POSITION_TOP = "Top"
|
||||||
|
INPUT_POSITION_MIDDLE = "Middle"
|
||||||
|
INPUT_POSITION_BOTTOM = "Bottom"
|
||||||
|
|
||||||
VLC_EOF_DURATION_THRESHOLD = 2.0
|
VLC_EOF_DURATION_THRESHOLD = 2.0
|
||||||
|
|
||||||
@ -217,6 +245,17 @@ FILEITEM_SWITCH_FILE_SWITCH = 1
|
|||||||
FILEITEM_SWITCH_STREAM_SWITCH = 2
|
FILEITEM_SWITCH_STREAM_SWITCH = 2
|
||||||
PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3
|
PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3
|
||||||
|
|
||||||
|
MESSAGE_NEUTRAL = "neutral"
|
||||||
|
MESSAGE_BADNEWS = "bad"
|
||||||
|
MESSAGE_GOODNEWS = "good"
|
||||||
|
|
||||||
|
OSD_NOTIFICATION = "notification" # Also known as PrimaryOSD
|
||||||
|
OSD_ALERT = "alert" # Also known as SecondaryOSD
|
||||||
|
OSD_CHAT = "chat"
|
||||||
|
|
||||||
|
CHATROOM_MODE = "Chatroom"
|
||||||
|
SCROLLING_MODE = "Scrolling"
|
||||||
|
|
||||||
SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
|
SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
|
||||||
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
|
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
|
||||||
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params
|
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params
|
||||||
|
|||||||
@ -66,4 +66,6 @@ def getMessage(type_, locale=None):
|
|||||||
if messages["en"].has_key(type_):
|
if messages["en"].has_key(type_):
|
||||||
return unicode(messages["en"][type_])
|
return unicode(messages["en"][type_])
|
||||||
else:
|
else:
|
||||||
raise KeyError(type_)
|
print u"WARNING: Cannot find message '{}'!".format(type_)
|
||||||
|
return "!{}".format(type_) # TODO: Remove
|
||||||
|
#raise KeyError(type_)
|
||||||
@ -17,23 +17,23 @@ de = {
|
|||||||
"connected-successful-notification" : u"Erfolgreich mit Server verbunden",
|
"connected-successful-notification" : u"Erfolgreich mit Server verbunden",
|
||||||
"retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds
|
"retrying-notification" : u"%s, versuche erneut in %d Sekunden...", # Seconds
|
||||||
|
|
||||||
"rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit <{}>", # User
|
"rewind-notification" : u"Zurückgespult wegen Zeitdifferenz mit {}", # User
|
||||||
"fastforward-notification" : u"Vorgespult wegen Zeitdifferenz mit <{}>", # User
|
"fastforward-notification" : u"Vorgespult wegen Zeitdifferenz mit {}", # User
|
||||||
"slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit <{}>", # User
|
"slowdown-notification" : u"Verlangsamt wegen Zeitdifferenz mit {}", # User
|
||||||
"revert-notification" : u"Normalgeschwindigkeit",
|
"revert-notification" : u"Normalgeschwindigkeit",
|
||||||
|
|
||||||
"pause-notification" : u"<{}> pausierte", # User
|
"pause-notification" : u"{} pausierte", # User
|
||||||
"unpause-notification" : u"<{}> startete", # User
|
"unpause-notification" : u"{} startete", # User
|
||||||
"seek-notification" : u"<{}> sprang von {} nach {}", # User, from time, to time
|
"seek-notification" : u"{} sprang von {} nach {}", # User, from time, to time
|
||||||
|
|
||||||
"current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset
|
"current-offset-notification" : u"Aktueller Offset: {} Sekunden", # Offset
|
||||||
|
|
||||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate
|
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.", # TODO: Translate
|
||||||
|
|
||||||
"room-join-notification" : u"<{}> hat den Raum '{}' betreten", # User
|
"room-join-notification" : u"{} hat den Raum '{}' betreten", # User
|
||||||
"left-notification" : u"<{}> ist gegangen", # User
|
"left-notification" : u"{} ist gegangen", # User
|
||||||
"left-paused-notification" : u"<{}> ist gegangen, <{}> pausierte", # User who left, User who paused
|
"left-paused-notification" : u"{} ist gegangen, {} pausierte", # User who left, User who paused
|
||||||
"playing-notification" : u"<{}> spielt '{}' ({})", # User, file, duration
|
"playing-notification" : u"{} spielt '{}' ({})", # User, file, duration
|
||||||
"playing-notification/room-addendum" : u" in Raum: '{}'", # Room
|
"playing-notification/room-addendum" : u" in Raum: '{}'", # Room
|
||||||
|
|
||||||
"not-all-ready" : u"Noch nicht bereit: {}", # Usernames
|
"not-all-ready" : u"Noch nicht bereit: {}", # Usernames
|
||||||
@ -44,11 +44,11 @@ de = {
|
|||||||
"autoplaying-notification" : u"Starte in {}...", # Number of seconds until playback will start
|
"autoplaying-notification" : u"Starte in {}...", # Number of seconds until playback will start
|
||||||
|
|
||||||
"identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator"
|
"identifying-as-controller-notification" : u"Identifiziere als Raumleiter mit Passwort '{}'...", # TODO: find a better translation to "room operator"
|
||||||
"failed-to-identify-as-controller-notification" : u"<{}> konnte sich nicht als Raumleiter identifizieren.",
|
"failed-to-identify-as-controller-notification" : u"{} konnte sich nicht als Raumleiter identifizieren.",
|
||||||
"authenticated-as-controller-notification" : u"<{}> authentifizierte sich als Raumleiter",
|
"authenticated-as-controller-notification" : u"{} authentifizierte sich als Raumleiter",
|
||||||
"created-controlled-room-notification" : u"Gesteuerten Raum '{}' mit Passwort '{}' erstellt. Bitte diese Informationen für die Zukunft aufheben!", # RoomName, operatorPassword
|
"created-controlled-room-notification" : u"Gesteuerten Raum '{}' mit Passwort '{}' erstellt. Bitte diese Informationen für die Zukunft aufheben!", # RoomName, operatorPassword
|
||||||
|
|
||||||
"file-different-notification" : u"Deine Datei scheint sich von <{}>s zu unterscheiden", # User
|
"file-different-notification" : u"Deine Datei scheint sich von {}s zu unterscheiden", # User
|
||||||
"file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: {}",
|
"file-differences-notification" : u"Deine Datei unterscheidet sich auf folgende Art: {}",
|
||||||
"room-file-differences" : u"Unterschiedlich in: {}", # File differences (filename, size, and/or duration)
|
"room-file-differences" : u"Unterschiedlich in: {}", # File differences (filename, size, and/or duration)
|
||||||
"file-difference-filename" : u"Name",
|
"file-difference-filename" : u"Name",
|
||||||
@ -225,6 +225,7 @@ de = {
|
|||||||
"messages-label" : u"Nachrichten",
|
"messages-label" : u"Nachrichten",
|
||||||
"messages-osd-title" : u"OSD-(OnScreenDisplay)-Einstellungen",
|
"messages-osd-title" : u"OSD-(OnScreenDisplay)-Einstellungen",
|
||||||
"messages-other-title" : u"Weitere Display-Einstellungen",
|
"messages-other-title" : u"Weitere Display-Einstellungen",
|
||||||
|
"chat-label" : u"Chat", # TODO: Translate
|
||||||
"privacy-label" : u"Privatsphäre",
|
"privacy-label" : u"Privatsphäre",
|
||||||
"privacy-title" : u"Privatsphäreneinstellungen",
|
"privacy-title" : u"Privatsphäreneinstellungen",
|
||||||
"unpause-title" : u"Wenn du Play drückst, auf Bereit setzen und:",
|
"unpause-title" : u"Wenn du Play drückst, auf Bereit setzen und:",
|
||||||
@ -234,6 +235,27 @@ de = {
|
|||||||
"unpause-always" : u"Immer wiedergeben",
|
"unpause-always" : u"Immer wiedergeben",
|
||||||
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into German
|
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)", # TODO: Translate into German
|
||||||
|
|
||||||
|
"chat-title": u"Chat message input", # TODO: Translate
|
||||||
|
"chatinputenabled-label": u"Enable chat input via mpv (using enter key)", # TODO: Translate
|
||||||
|
"chatdirectinput-label" : u"Allow instant chat input (bypass having to press enter key to chat)", # TODO: Translate
|
||||||
|
"chatinputfont-label": u"Chat input font", # TODO: Translate
|
||||||
|
"chatfont-label": u"Set font", # TODO: Translate
|
||||||
|
"chatcolour-label": u"Set colour", # TODO: Translate
|
||||||
|
"chatinputposition-label": u"Position of message input area in mpv", # TODO: Translate
|
||||||
|
"chat-top-option": u"Top", # TODO: Translate
|
||||||
|
"chat-middle-option": u"Middle", # TODO: Translate
|
||||||
|
"chat-bottom-option": u"Bottom", # TODO: Translate
|
||||||
|
"chatoutputfont-label": u"Chat output font", # TODO: Translate
|
||||||
|
"chatoutputenabled-label": u"Enable chat output in media player (mpv only for now)", # TODO: Translate
|
||||||
|
"chatoutputposition-label": u"Output mode", # TODO: Translate
|
||||||
|
"chat-chatroom-option": u"Chatroom style", # TODO: Translate
|
||||||
|
"chat-scrolling-option": u"Scrolling style", # TODO: Translate
|
||||||
|
|
||||||
|
"mpv-key-tab-hint": u"[TAB] to toggle access to alphabet row key shortcuts.", # TODO: Translate
|
||||||
|
"mpv-key-hint": u"[ENTER] to send message. [ESC] to escape chat mode.", # TODO: Translate
|
||||||
|
"alphakey-mode-warning-first-line": u"You can temporarily use old mpv bindings with a-z keys.", # TODO: Translate
|
||||||
|
"alphakey-mode-warning-second-line": u"Press [TAB] to return to Syncplay chat mode.", # TODO: Translate
|
||||||
|
|
||||||
"help-label" : u"Hilfe",
|
"help-label" : u"Hilfe",
|
||||||
"reset-label" : u"Standardwerte zurücksetzen",
|
"reset-label" : u"Standardwerte zurücksetzen",
|
||||||
"run-label" : u"Syncplay starten",
|
"run-label" : u"Syncplay starten",
|
||||||
@ -357,6 +379,22 @@ de = {
|
|||||||
"unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.",
|
"unpause-ifminusersready-tooltip" : u"Wenn du Play drückst und nicht bereit bist, wird nur gestartet, wenn die minimale Anzahl anderer Benutzer bereit ist.",
|
||||||
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into German
|
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.", # TODO: Translate into German
|
||||||
|
|
||||||
|
"chatinputenabled-tooltip": u"Enable chat input in mpv (press enter to chat, enter to send, escape to cancel)", # TODO: Translate
|
||||||
|
"chatdirectinput-tooltip" : u"Skip having to press 'enter' to go into chat input mode in mpv. Press TAB in mpv to temporarily disable this feature.", # TODO: Translate
|
||||||
|
"font-label-tooltip": u"Font used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.", # TODO: Translate
|
||||||
|
"set-input-font-tooltip": u"Font family used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.", # TODO: Translate
|
||||||
|
"set-input-colour-tooltip": u"Font colour used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.", # TODO: Translate
|
||||||
|
"chatinputposition-tooltip": u"Location in mpv where chat input text will appear when you press enter and type.", # TODO: Translate
|
||||||
|
"chatinputposition-top-tooltip": u"Place chat input at top of mpv window.", # TODO: Translate
|
||||||
|
"chatinputposition-middle-tooltip": u"Place chat input in dead centre of mpv window.", # TODO: Translate
|
||||||
|
"chatinputposition-bottom-tooltip": u"Place chat input at bottom of mpv window.", # TODO: Translate
|
||||||
|
"chatoutputenabled-tooltip": u"Show chat messages in OSD (if supported by media player).", # TODO: Translate
|
||||||
|
"font-output-label-tooltip": u"Chat output font.", # TODO: Translate
|
||||||
|
"set-output-font-tooltip": u"Font used for when displaying chat messages.", # TODO: Translate
|
||||||
|
"chatoutputmode-tooltip": u"How chat messages are displayed.", # TODO: Translate
|
||||||
|
"chatoutputmode-chatroom-tooltip": u"Display new lines of chat directly below previous line.", # TODO: Translate
|
||||||
|
"chatoutputmode-scrolling-tooltip": u"Scroll chat text from right to left.", # TODO: Translate
|
||||||
|
|
||||||
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
|
"help-tooltip" : u"Öffnet Hilfe auf syncplay.pl [Englisch]",
|
||||||
"reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.",
|
"reset-tooltip" : u"Alle Einstellungen auf Standardwerte zurücksetzen.",
|
||||||
"update-server-list-tooltip" : u"Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.",
|
"update-server-list-tooltip" : u"Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.",
|
||||||
@ -420,6 +458,7 @@ de = {
|
|||||||
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
|
"addurlstoplaylist-menu-label" : u"Add URL(s) to bottom of playlist",
|
||||||
"editplaylist-menu-label": u"Edit playlist",
|
"editplaylist-menu-label": u"Edit playlist",
|
||||||
|
|
||||||
|
"open-containing-folder": u"Open folder containing this file",
|
||||||
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
|
"addusersfiletoplaylist-menu-label" : u"Add {} file to playlist", # item owner indicator
|
||||||
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
|
"addusersstreamstoplaylist-menu-label" : u"Add {} stream to playlist", # item owner indicator
|
||||||
"openusersstream-menu-label" : u"Open {} stream", # [username]'s
|
"openusersstream-menu-label" : u"Open {} stream", # [username]'s
|
||||||
|
|||||||
@ -17,23 +17,23 @@ en = {
|
|||||||
"connected-successful-notification" : "Successfully connected to server",
|
"connected-successful-notification" : "Successfully connected to server",
|
||||||
"retrying-notification" : "%s, Retrying in %d seconds...", # Seconds
|
"retrying-notification" : "%s, Retrying in %d seconds...", # Seconds
|
||||||
|
|
||||||
"rewind-notification" : "Rewinded due to time difference with <{}>", # User
|
"rewind-notification" : "Rewinded due to time difference with {}", # User
|
||||||
"fastforward-notification" : "Fast-forwarded due to time difference with <{}>", # User
|
"fastforward-notification" : "Fast-forwarded due to time difference with {}", # User
|
||||||
"slowdown-notification" : "Slowing down due to time difference with <{}>", # User
|
"slowdown-notification" : "Slowing down due to time difference with {}", # User
|
||||||
"revert-notification" : "Reverting speed back to normal",
|
"revert-notification" : "Reverting speed back to normal",
|
||||||
|
|
||||||
"pause-notification" : u"<{}> paused", # User
|
"pause-notification" : u"{} paused", # User
|
||||||
"unpause-notification" : u"<{}> unpaused", # User
|
"unpause-notification" : u"{} unpaused", # User
|
||||||
"seek-notification" : u"<{}> jumped from {} to {}", # User, from time, to time
|
"seek-notification" : u"{} jumped from {} to {}", # User, from time, to time
|
||||||
|
|
||||||
"current-offset-notification" : "Current offset: {} seconds", # Offset
|
"current-offset-notification" : "Current offset: {} seconds", # Offset
|
||||||
|
|
||||||
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.",
|
"media-directory-list-updated-notification" : u"Syncplay media directories have been updated.",
|
||||||
|
|
||||||
"room-join-notification" : u"<{}> has joined the room: '{}'", # User
|
"room-join-notification" : u"{} has joined the room: '{}'", # User
|
||||||
"left-notification" : u"<{}> has left", # User
|
"left-notification" : u"{} has left", # User
|
||||||
"left-paused-notification" : u"<{}> left, <{}> paused", # User who left, User who paused
|
"left-paused-notification" : u"{} left, {} paused", # User who left, User who paused
|
||||||
"playing-notification" : u"<{}> is playing '{}' ({})", # User, file, duration
|
"playing-notification" : u"{} is playing '{}' ({})", # User, file, duration
|
||||||
"playing-notification/room-addendum" : u" in room: '{}'", # Room
|
"playing-notification/room-addendum" : u" in room: '{}'", # Room
|
||||||
|
|
||||||
"not-all-ready" : u"Not ready: {}", # Usernames
|
"not-all-ready" : u"Not ready: {}", # Usernames
|
||||||
@ -44,11 +44,11 @@ en = {
|
|||||||
"autoplaying-notification" : u"Auto-playing in {}...", # Number of seconds until playback will start
|
"autoplaying-notification" : u"Auto-playing in {}...", # Number of seconds until playback will start
|
||||||
|
|
||||||
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...",
|
"identifying-as-controller-notification" : u"Identifying as room operator with password '{}'...",
|
||||||
"failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room operator.",
|
"failed-to-identify-as-controller-notification" : u"{} failed to identify as a room operator.",
|
||||||
"authenticated-as-controller-notification" : u"<{}> authenticated as a room operator",
|
"authenticated-as-controller-notification" : u"{} authenticated as a room operator",
|
||||||
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, operatorPassword
|
"created-controlled-room-notification" : u"Created managed room '{}' with password '{}'. Please save this information for future reference!", # RoomName, operatorPassword
|
||||||
|
|
||||||
"file-different-notification" : "File you are playing appears to be different from <{}>'s", # User
|
"file-different-notification" : "File you are playing appears to be different from {}'s", # User
|
||||||
"file-differences-notification" : u"Your file differs in the following way(s): {}", # Differences
|
"file-differences-notification" : u"Your file differs in the following way(s): {}", # Differences
|
||||||
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration)
|
"room-file-differences" : u"File differences: {}", # File differences (filename, size, and/or duration)
|
||||||
"file-difference-filename" : u"name",
|
"file-difference-filename" : u"name",
|
||||||
@ -133,7 +133,12 @@ en = {
|
|||||||
"vlc-failed-noscript": "VLC has reported that the syncplay.lua interface script has not been installed. Please refer to http://syncplay.pl/LUA/ for instructions.",
|
"vlc-failed-noscript": "VLC has reported that the syncplay.lua interface script has not been installed. Please refer to http://syncplay.pl/LUA/ for instructions.",
|
||||||
"vlc-failed-versioncheck": "This version of VLC is not supported by Syncplay.",
|
"vlc-failed-versioncheck": "This version of VLC is not supported by Syncplay.",
|
||||||
|
|
||||||
"not-supported-by-server-error" : "This feature is not supported by the server. The feature requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion
|
"feature-sharedPlaylists" : u"shared playlists", # used for not-supported-by-server-error
|
||||||
|
"feature-chat" : u"chat", # used for not-supported-by-server-error
|
||||||
|
"feature-readiness" : u"readiness", # used for not-supported-by-server-error
|
||||||
|
"feature-managedRooms" : u"managed rooms", # used for not-supported-by-server-error
|
||||||
|
|
||||||
|
"not-supported-by-server-error" : u"The {} feature is not supported by this server..", #feature
|
||||||
"shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion
|
"shared-playlists-not-supported-by-server-error" : "The shared playlists feature may not be supported by the server. To ensure that it works correctly requires a server running Syncplay {}+, but the server is running Syncplay {}.", #minVersion, serverVersion
|
||||||
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.",
|
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.",
|
||||||
|
|
||||||
@ -225,6 +230,7 @@ en = {
|
|||||||
"messages-label" : "Messages",
|
"messages-label" : "Messages",
|
||||||
"messages-osd-title" : "On-screen Display settings",
|
"messages-osd-title" : "On-screen Display settings",
|
||||||
"messages-other-title" : "Other display settings",
|
"messages-other-title" : "Other display settings",
|
||||||
|
"chat-label" : u"Chat",
|
||||||
"privacy-label" : "Privacy", # Currently unused, but will be brought back if more space is needed in Misc tab
|
"privacy-label" : "Privacy", # Currently unused, but will be brought back if more space is needed in Misc tab
|
||||||
"privacy-title" : "Privacy settings",
|
"privacy-title" : "Privacy settings",
|
||||||
"unpause-title" : u"If you press play, set as ready and:",
|
"unpause-title" : u"If you press play, set as ready and:",
|
||||||
@ -234,6 +240,27 @@ en = {
|
|||||||
"unpause-always" : u"Always unpause",
|
"unpause-always" : u"Always unpause",
|
||||||
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)",
|
"syncplay-trusteddomains-title": u"Trusted domains (for streaming services and hosted content)",
|
||||||
|
|
||||||
|
"chat-title" : u"Chat message input",
|
||||||
|
"chatinputenabled-label" : u"Enable chat input via mpv",
|
||||||
|
"chatdirectinput-label" : u"Allow instant chat input (bypass having to press enter key to chat)",
|
||||||
|
"chatinputfont-label" : u"Chat input font",
|
||||||
|
"chatfont-label" : u"Set font",
|
||||||
|
"chatcolour-label" : u"Set colour",
|
||||||
|
"chatinputposition-label" : u"Position of message input area in mpv",
|
||||||
|
"chat-top-option" : u"Top",
|
||||||
|
"chat-middle-option" : u"Middle",
|
||||||
|
"chat-bottom-option" : u"Bottom",
|
||||||
|
"chatoutputfont-label": u"Chat output font",
|
||||||
|
"chatoutputenabled-label": u"Enable chat output in media player (mpv only for now)",
|
||||||
|
"chatoutputposition-label": u"Output mode",
|
||||||
|
"chat-chatroom-option": u"Chatroom style",
|
||||||
|
"chat-scrolling-option": u"Scrolling style",
|
||||||
|
|
||||||
|
"mpv-key-tab-hint": u"[TAB] to toggle access to alphabet row key shortcuts.",
|
||||||
|
"mpv-key-hint": u"[ENTER] to send message. [ESC] to escape chat mode.",
|
||||||
|
"alphakey-mode-warning-first-line": u"You can temporarily use old mpv bindings with a-z keys.",
|
||||||
|
"alphakey-mode-warning-second-line": u"Press [TAB] to return to Syncplay chat mode.",
|
||||||
|
|
||||||
"help-label" : "Help",
|
"help-label" : "Help",
|
||||||
"reset-label" : "Restore defaults",
|
"reset-label" : "Restore defaults",
|
||||||
"run-label" : "Run Syncplay",
|
"run-label" : "Run Syncplay",
|
||||||
@ -355,6 +382,22 @@ en = {
|
|||||||
"unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only unpause if others are ready and minimum users threshold is met.",
|
"unpause-ifminusersready-tooltip" : u"If you press unpause when not ready, it will only unpause if others are ready and minimum users threshold is met.",
|
||||||
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.",
|
"trusteddomains-arguments-tooltip" : u"Domains that it is okay for Syncplay to automatically switch to when shared playlists is enabled.",
|
||||||
|
|
||||||
|
"chatinputenabled-tooltip" : u"Enable chat input in mpv (press enter to chat, enter to send, escape to cancel)",
|
||||||
|
"chatdirectinput-tooltip" : u"Skip having to press 'enter' to go into chat input mode in mpv. Press TAB in mpv to temporarily disable this feature.",
|
||||||
|
"font-label-tooltip" : u"Font used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",
|
||||||
|
"set-input-font-tooltip" : u"Font family used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",
|
||||||
|
"set-input-colour-tooltip" : u"Font colour used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",
|
||||||
|
"chatinputposition-tooltip" : u"Location in mpv where chat input text will appear when you press enter and type.",
|
||||||
|
"chatinputposition-top-tooltip" : u"Place chat input at top of mpv window.",
|
||||||
|
"chatinputposition-middle-tooltip" : u"Place chat input in dead centre of mpv window.",
|
||||||
|
"chatinputposition-bottom-tooltip" : u"Place chat input at bottom of mpv window.",
|
||||||
|
"chatoutputenabled-tooltip": u"Show chat messages in OSD (if supported by media player).",
|
||||||
|
"font-output-label-tooltip": u"Chat output font.",
|
||||||
|
"set-output-font-tooltip": u"Font used for when displaying chat messages.",
|
||||||
|
"chatoutputmode-tooltip": u"How chat messages are displayed.",
|
||||||
|
"chatoutputmode-chatroom-tooltip": u"Display new lines of chat directly below previous line.",
|
||||||
|
"chatoutputmode-scrolling-tooltip": u"Scroll chat text from right to left.",
|
||||||
|
|
||||||
"help-tooltip" : "Opens the Syncplay.pl user guide.",
|
"help-tooltip" : "Opens the Syncplay.pl user guide.",
|
||||||
"reset-tooltip" : "Reset all settings to the default configuration.",
|
"reset-tooltip" : "Reset all settings to the default configuration.",
|
||||||
"update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.",
|
"update-server-list-tooltip" : u"Connect to syncplay.pl to update list of public servers.",
|
||||||
|
|||||||
@ -17,23 +17,23 @@ ru = {
|
|||||||
"connected-successful-notification" : u"Соединение с сервером установлено",
|
"connected-successful-notification" : u"Соединение с сервером установлено",
|
||||||
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
|
"retrying-notification" : u"%s, следующая попытка через %d секунд(ы)...", # Seconds
|
||||||
|
|
||||||
"rewind-notification" : u"Перемотано из-за разницы во времени с <{}>", # User
|
"rewind-notification" : u"Перемотано из-за разницы во времени с {}", # User
|
||||||
"fastforward-notification" : u"Ускорено из-за разницы во времени с <{}>", # User
|
"fastforward-notification" : u"Ускорено из-за разницы во времени с {}", # User
|
||||||
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с <{}>", # User
|
"slowdown-notification" : u"Воспроизведение замедлено из-за разницы во времени с {}", # User
|
||||||
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
|
"revert-notification" : u"Возвращаемся к нормальной скорости воспроизведения",
|
||||||
|
|
||||||
"pause-notification" : u"<{}> приостановил воспроизведение", # User
|
"pause-notification" : u"{} приостановил воспроизведение", # User
|
||||||
"unpause-notification" : u"<{}> возобновил воспроизведение", # User
|
"unpause-notification" : u"{} возобновил воспроизведение", # User
|
||||||
"seek-notification" : u"<{}> перемотал с {} на {}", # User, from time, to time
|
"seek-notification" : u"{} перемотал с {} на {}", # User, from time, to time
|
||||||
|
|
||||||
"current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset
|
"current-offset-notification" : u"Текущее смещение: {} секунд(ы)", # Offset
|
||||||
|
|
||||||
"media-directory-list-updated-notification" : u"Папки воспроизведения обновлены.",
|
"media-directory-list-updated-notification" : u"Папки воспроизведения обновлены.",
|
||||||
|
|
||||||
"room-join-notification" : u"<{}> зашел в комнату: '{}'", # User
|
"room-join-notification" : u"{} зашел в комнату: '{}'", # User
|
||||||
"left-notification" : u"<{}> покинул комнату", # User
|
"left-notification" : u"{} покинул комнату", # User
|
||||||
"left-paused-notification" : u"<{}> покинул комнату, <{}> приостановил воспроизведение", # User who left, User who paused
|
"left-paused-notification" : u"{} покинул комнату, {} приостановил воспроизведение", # User who left, User who paused
|
||||||
"playing-notification" : u"<{}> включил '{}' ({})", # User, file, duration
|
"playing-notification" : u"{} включил '{}' ({})", # User, file, duration
|
||||||
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room
|
"playing-notification/room-addendum" : u" в комнате: '{}'", # Room
|
||||||
|
|
||||||
"not-all-ready" : u"Не готовы: {}", # Usernames
|
"not-all-ready" : u"Не готовы: {}", # Usernames
|
||||||
@ -44,11 +44,11 @@ ru = {
|
|||||||
"autoplaying-notification" : u"Автовоспроизведение через {}...", # Number of seconds until playback will start
|
"autoplaying-notification" : u"Автовоспроизведение через {}...", # Number of seconds until playback will start
|
||||||
|
|
||||||
"identifying-as-controller-notification" : u"Идентификация как оператора комнаты с паролем '{}'...",
|
"identifying-as-controller-notification" : u"Идентификация как оператора комнаты с паролем '{}'...",
|
||||||
"failed-to-identify-as-controller-notification" : u"<{}> не прошел идентификацию в качестве оператора комнаты.",
|
"failed-to-identify-as-controller-notification" : u"{} не прошел идентификацию в качестве оператора комнаты.",
|
||||||
"authenticated-as-controller-notification" : u"<{}> вошел как оператор комнаты.",
|
"authenticated-as-controller-notification" : u"{} вошел как оператор комнаты.",
|
||||||
"created-controlled-room-notification" : u"Создана управляемая комната '{}' с паролем '{}'. Сохраните эти данные!", # RoomName, operatorPassword
|
"created-controlled-room-notification" : u"Создана управляемая комната '{}' с паролем '{}'. Сохраните эти данные!", # RoomName, operatorPassword
|
||||||
|
|
||||||
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит <{}>.", # User
|
"file-different-notification" : u"Вероятно, файл, который Вы смотрите, отличается от того, который смотрит {}.", # User
|
||||||
"file-differences-notification" : u"Ваш файл отличается: {}", # Differences
|
"file-differences-notification" : u"Ваш файл отличается: {}", # Differences
|
||||||
"room-file-differences" : u"Несовпадения файла: {}", # File differences (filename, size, and/or duration)
|
"room-file-differences" : u"Несовпадения файла: {}", # File differences (filename, size, and/or duration)
|
||||||
"file-difference-filename" : u"имя",
|
"file-difference-filename" : u"имя",
|
||||||
@ -135,7 +135,13 @@ ru = {
|
|||||||
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
|
"vlc-failed-versioncheck" : u"Данная версия VLC не поддерживается Syncplay. Пожалуйста, используйте VLC версии 2 или выше.",
|
||||||
"vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error
|
"vlc-failed-other" : u"Во время загрузки скрипта интерфейса syncplay.lua в VLC произошла следующая ошибка: {}", # Syncplay Error
|
||||||
|
|
||||||
"not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. Требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
|
"feature-sharedPlaylists": u"shared playlists", # used for not-supported-by-server-error # TODO: Translate
|
||||||
|
"feature-chat": u"chat", # used for not-supported-by-server-error # TODO: Translate
|
||||||
|
"feature-readiness": u"readiness", # used for not-supported-by-server-error # TODO: Translate
|
||||||
|
"feature-managedRooms": u"managed rooms", # used for not-supported-by-server-error # TODO: Translate
|
||||||
|
|
||||||
|
"not-supported-by-server-error": u"The {} feature is not supported by this server..", # feature # TODO: Translate
|
||||||
|
#OLD TRANSLATION: "not-supported-by-server-error" : u"Эта возможность не поддерживается сервером. Требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
|
||||||
"shared-playlists-not-supported-by-server-error" : u"Общие списки воспроизведения могут не поддерживаться сервером. Для корректной работы требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
|
"shared-playlists-not-supported-by-server-error" : u"Общие списки воспроизведения могут не поддерживаться сервером. Для корректной работы требуется сервер Syncplay {}+, вы подключены к серверу Syncplay {}.", #minVersion, serverVersion
|
||||||
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate
|
"shared-playlists-disabled-by-server-error" : "The shared playlist feature has been disabled in the server configuration. To use this feature you will need to connect to a different server.", # TODO: Translate
|
||||||
|
|
||||||
@ -227,6 +233,7 @@ ru = {
|
|||||||
"messages-label" : u"Сообщения",
|
"messages-label" : u"Сообщения",
|
||||||
"messages-osd-title" : u"Настройки OSD",
|
"messages-osd-title" : u"Настройки OSD",
|
||||||
"messages-other-title" : u"Другие настройки отображения",
|
"messages-other-title" : u"Другие настройки отображения",
|
||||||
|
"chat-label" : u"Chat", # TODO: Translate
|
||||||
"privacy-label" : u"Приватность",
|
"privacy-label" : u"Приватность",
|
||||||
"privacy-title" : u"Настройки приватности",
|
"privacy-title" : u"Настройки приватности",
|
||||||
"unpause-title" : u"Если вы стартуете, то:",
|
"unpause-title" : u"Если вы стартуете, то:",
|
||||||
@ -237,6 +244,27 @@ ru = {
|
|||||||
"syncplay-trusteddomains-title": u"Доверенные сайты (стрим-сервисы, видеохостинги, файлы в сети)",
|
"syncplay-trusteddomains-title": u"Доверенные сайты (стрим-сервисы, видеохостинги, файлы в сети)",
|
||||||
"addtrusteddomain-menu-label" : u"Добавить {} как доверенный сайт", # Domain
|
"addtrusteddomain-menu-label" : u"Добавить {} как доверенный сайт", # Domain
|
||||||
|
|
||||||
|
"chat-title": u"Chat message input", # TODO: Translate
|
||||||
|
"chatinputenabled-label": u"Enable chat input via mpv (using enter key)", # TODO: Translate
|
||||||
|
"chatdirectinput-label" : u"Allow instant chat input (bypass having to press enter key to chat)", # TODO: Translate
|
||||||
|
"chatinputfont-label": u"Chat input font", # TODO: Translate
|
||||||
|
"chatfont-label": u"Set font", # TODO: Translate
|
||||||
|
"chatcolour-label": u"Set colour", # TODO: Translate
|
||||||
|
"chatinputposition-label": u"Position of message input area in mpv", # TODO: Translate
|
||||||
|
"chat-top-option": u"Top", # TODO: Translate
|
||||||
|
"chat-middle-option": u"Middle", # TODO: Translate
|
||||||
|
"chat-bottom-option": u"Bottom", # TODO: Translate
|
||||||
|
"chatoutputfont-label": u"Chat output font", # TODO: Translate
|
||||||
|
"chatoutputenabled-label": u"Enable chat output in media player (mpv only for now)", # TODO: Translate
|
||||||
|
"chatoutputposition-label": u"Output mode", # TODO: Translate
|
||||||
|
"chat-chatroom-option": u"Chatroom style", # TODO: Translate
|
||||||
|
"chat-scrolling-option": u"Scrolling style", # TODO: Translate
|
||||||
|
|
||||||
|
"mpv-key-tab-hint": u"[TAB] to toggle access to alphabet row key shortcuts.", # TODO: Translate
|
||||||
|
"mpv-key-hint": u"[ENTER] to send message. [ESC] to escape chat mode.", # TODO: Translate
|
||||||
|
"alphakey-mode-warning-first-line": u"You can temporarily use old mpv bindings with a-z keys.", # TODO: Translate
|
||||||
|
"alphakey-mode-warning-second-line": u"Press [TAB] to return to Syncplay chat mode.", # TODO: Translate
|
||||||
|
|
||||||
"help-label" : u"Помощь",
|
"help-label" : u"Помощь",
|
||||||
"reset-label" : u"Сброс настроек",
|
"reset-label" : u"Сброс настроек",
|
||||||
"run-label" : u"Запустить",
|
"run-label" : u"Запустить",
|
||||||
@ -357,6 +385,22 @@ ru = {
|
|||||||
"unpause-ifminusersready-tooltip" : u"Когда вы стартуете не готовым, воспроизведение начнется, если остальные готовы и присутствует достаточное число зрителей.",
|
"unpause-ifminusersready-tooltip" : u"Когда вы стартуете не готовым, воспроизведение начнется, если остальные готовы и присутствует достаточное число зрителей.",
|
||||||
"trusteddomains-arguments-tooltip" : u"Сайты, которые разрешены для автоматического воспроизведения из общего списка воспроизведения.",
|
"trusteddomains-arguments-tooltip" : u"Сайты, которые разрешены для автоматического воспроизведения из общего списка воспроизведения.",
|
||||||
|
|
||||||
|
"chatinputenabled-tooltip": u"Enable chat input in mpv (press enter to chat, enter to send, escape to cancel)",# TODO: Translate
|
||||||
|
"chatdirectinput-tooltip" : u"Skip having to press 'enter' to go into chat input mode in mpv. Press TAB in mpv to temporarily disable this feature.", # TODO: Translate
|
||||||
|
"font-label-tooltip": u"Font used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",# TODO: Translate
|
||||||
|
"set-input-font-tooltip": u"Font family used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",# TODO: Translate
|
||||||
|
"set-input-colour-tooltip": u"Font colour used for when entering chat messages in mpv. Client-side only, so doesn't affect what other see.",# TODO: Translate
|
||||||
|
"chatinputposition-tooltip": u"Location in mpv where chat input text will appear when you press enter and type.",# TODO: Translate
|
||||||
|
"chatinputposition-top-tooltip": u"Place chat input at top of mpv window.", # TODO: Translate
|
||||||
|
"chatinputposition-middle-tooltip": u"Place chat input in dead centre of mpv window.", # TODO: Translate
|
||||||
|
"chatinputposition-bottom-tooltip": u"Place chat input at bottom of mpv window.", # TODO: Translate
|
||||||
|
"chatoutputenabled-tooltip": u"Show chat messages in OSD (if supported by media player).", # TODO: Translate
|
||||||
|
"font-output-label-tooltip": u"Chat output font.", # TODO: Translate
|
||||||
|
"set-output-font-tooltip": u"Font used for when displaying chat messages.", # TODO: Translate
|
||||||
|
"chatoutputmode-tooltip": u"How chat messages are displayed.", # TODO: Translate
|
||||||
|
"chatoutputmode-chatroom-tooltip": u"Display new lines of chat directly below previous line.", # TODO: Translate
|
||||||
|
"chatoutputmode-scrolling-tooltip": u"Scroll chat text from right to left.", # TODO: Translate
|
||||||
|
|
||||||
"help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.",
|
"help-tooltip" : u"Открыть Руководство Пользователя на Syncplay.pl.",
|
||||||
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
|
"reset-tooltip" : u"Сбрасывает все настройки Syncplay в начальное состояние.",
|
||||||
"update-server-list-tooltip" : u"Обновить список публичных серверов от syncplay.pl.",
|
"update-server-list-tooltip" : u"Обновить список публичных серверов от syncplay.pl.",
|
||||||
@ -419,6 +463,7 @@ ru = {
|
|||||||
"addurlstoplaylist-menu-label" : u"Добавить ссылку в очередь",
|
"addurlstoplaylist-menu-label" : u"Добавить ссылку в очередь",
|
||||||
"editplaylist-menu-label": u"Редактировать список",
|
"editplaylist-menu-label": u"Редактировать список",
|
||||||
|
|
||||||
|
"open-containing-folder": u"Open folder containing this file", # TODO: Traslate
|
||||||
"addusersfiletoplaylist-menu-label" : u"Добавить файл {} в список воспроизведения", # item owner indicator
|
"addusersfiletoplaylist-menu-label" : u"Добавить файл {} в список воспроизведения", # item owner indicator
|
||||||
"addusersstreamstoplaylist-menu-label" : u"Добавить поток {} в список воспроизведения", # item owner indicator
|
"addusersstreamstoplaylist-menu-label" : u"Добавить поток {} в список воспроизведения", # item owner indicator
|
||||||
"openusersstream-menu-label" : u"Открыть поток от {}", # [username]'s
|
"openusersstream-menu-label" : u"Открыть поток от {}", # [username]'s
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class BasePlayer(object):
|
|||||||
'''
|
'''
|
||||||
Display given message on player's OSD or similar means
|
Display given message on player's OSD or similar means
|
||||||
'''
|
'''
|
||||||
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000)):
|
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -34,6 +34,12 @@ class BasePlayer(object):
|
|||||||
def setPaused(self, value):
|
def setPaused(self, value):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
'''
|
||||||
|
@type value: list
|
||||||
|
'''
|
||||||
|
def setFeatures(self, featureList):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@type value: float
|
@type value: float
|
||||||
'''
|
'''
|
||||||
|
|||||||
@ -306,8 +306,9 @@ class MpcHcApi:
|
|||||||
|
|
||||||
class MPCHCAPIPlayer(BasePlayer):
|
class MPCHCAPIPlayer(BasePlayer):
|
||||||
speedSupported = False
|
speedSupported = False
|
||||||
secondaryOSDSupported = False
|
alertOSDSupported = False
|
||||||
customOpenDialog = False
|
customOpenDialog = False
|
||||||
|
chatOSDSupported = False
|
||||||
osdMessageSeparator = "; "
|
osdMessageSeparator = "; "
|
||||||
|
|
||||||
def __init__(self, client):
|
def __init__(self, client):
|
||||||
@ -399,7 +400,7 @@ class MPCHCAPIPlayer(BasePlayer):
|
|||||||
def openFile(self, filePath, resetPosition=False):
|
def openFile(self, filePath, resetPosition=False):
|
||||||
self._mpcApi.openFile(filePath)
|
self._mpcApi.openFile(filePath)
|
||||||
|
|
||||||
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False):
|
def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||||
self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration)
|
self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration)
|
||||||
|
|
||||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||||
@ -411,6 +412,8 @@ class MPCHCAPIPlayer(BasePlayer):
|
|||||||
self._mpcApi.pause()
|
self._mpcApi.pause()
|
||||||
else:
|
else:
|
||||||
self._mpcApi.unpause()
|
self._mpcApi.unpause()
|
||||||
|
def setFeatures(self, featureList):
|
||||||
|
pass
|
||||||
|
|
||||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||||
def setPosition(self, value):
|
def setPosition(self, value):
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
# coding:utf8
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
@ -11,7 +12,8 @@ from syncplay.utils import isWindows
|
|||||||
class MplayerPlayer(BasePlayer):
|
class MplayerPlayer(BasePlayer):
|
||||||
speedSupported = True
|
speedSupported = True
|
||||||
customOpenDialog = False
|
customOpenDialog = False
|
||||||
secondaryOSDSupported = False
|
alertOSDSupported = False
|
||||||
|
chatOSDSupported = False
|
||||||
osdMessageSeparator = "; "
|
osdMessageSeparator = "; "
|
||||||
|
|
||||||
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
|
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
|
||||||
@ -88,8 +90,15 @@ class MplayerPlayer(BasePlayer):
|
|||||||
def _getProperty(self, property_):
|
def _getProperty(self, property_):
|
||||||
self._listener.sendLine("get_property {}".format(property_))
|
self._listener.sendLine("get_property {}".format(property_))
|
||||||
|
|
||||||
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False):
|
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
|
||||||
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, self._stripNewlines(message), duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
|
messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace("<NEWLINE>", "\\n")
|
||||||
|
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
|
||||||
|
|
||||||
|
def displayChatMessage(self, username, message):
|
||||||
|
messageString = u"<{}> {}".format(username, message)
|
||||||
|
messageString = self._sanitizeText(messageString.replace("\\n", "<NEWLINE>")).replace("<NEWLINE>", "\\n")
|
||||||
|
duration = int(constants.OSD_DURATION * 1000)
|
||||||
|
self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, messageString, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8'))
|
||||||
|
|
||||||
def setSpeed(self, value):
|
def setSpeed(self, value):
|
||||||
self._setProperty('speed', "{:.2f}".format(value))
|
self._setProperty('speed', "{:.2f}".format(value))
|
||||||
@ -105,6 +114,9 @@ class MplayerPlayer(BasePlayer):
|
|||||||
self.setPaused(self._client.getGlobalPaused())
|
self.setPaused(self._client.getGlobalPaused())
|
||||||
self.setPosition(self._client.getGlobalPosition())
|
self.setPosition(self._client.getGlobalPosition())
|
||||||
|
|
||||||
|
def setFeatures(self, featureList):
|
||||||
|
pass
|
||||||
|
|
||||||
def setPosition(self, value):
|
def setPosition(self, value):
|
||||||
self._position = max(value,0)
|
self._position = max(value,0)
|
||||||
self._setProperty(self.POSITION_QUERY, "{}".format(value))
|
self._setProperty(self.POSITION_QUERY, "{}".format(value))
|
||||||
@ -130,9 +142,16 @@ class MplayerPlayer(BasePlayer):
|
|||||||
def _getPosition(self):
|
def _getPosition(self):
|
||||||
self._getProperty(self.POSITION_QUERY)
|
self._getProperty(self.POSITION_QUERY)
|
||||||
|
|
||||||
def _stripNewlines(self, text):
|
def _sanitizeText(self, text):
|
||||||
text = text.replace("\r", "")
|
text = text.replace("\r", "")
|
||||||
text = text.replace("\n", "")
|
text = text.replace("\n", "")
|
||||||
|
text = text.replace("\\\"", "<SYNCPLAY_QUOTE>")
|
||||||
|
text = text.replace("\"", "<SYNCPLAY_QUOTE>")
|
||||||
|
text = text.replace("%", "%%")
|
||||||
|
text = text.replace("\\", "\\\\")
|
||||||
|
text = text.replace("{", "\\\\{")
|
||||||
|
text = text.replace("}", "\\\\}")
|
||||||
|
text = text.replace("<SYNCPLAY_QUOTE>","\\\"")
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def _quoteArg(self, arg):
|
def _quoteArg(self, arg):
|
||||||
@ -158,6 +177,8 @@ class MplayerPlayer(BasePlayer):
|
|||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
if line:
|
if line:
|
||||||
self._client.ui.showDebugMessage("player << {}".format(line))
|
self._client.ui.showDebugMessage("player << {}".format(line))
|
||||||
|
line = line.replace("[cplayer] ", "") # -v workaround
|
||||||
|
line = line.replace("[term-msg] ", "") # -v workaround
|
||||||
line = line.replace(" cplayer: ","") # --msg-module workaround
|
line = line.replace(" cplayer: ","") # --msg-module workaround
|
||||||
line = line.replace(" term-msg: ", "")
|
line = line.replace(" term-msg: ", "")
|
||||||
if "Failed to get value of property" in line or "=(unavailable)" in line or line == "ANS_filename=" or line == "ANS_length=" or line == "ANS_path=":
|
if "Failed to get value of property" in line or "=(unavailable)" in line or line == "ANS_filename=" or line == "ANS_length=" or line == "ANS_path=":
|
||||||
@ -277,6 +298,9 @@ class MplayerPlayer(BasePlayer):
|
|||||||
self.lastSendTime = None
|
self.lastSendTime = None
|
||||||
self.lastNotReadyTime = None
|
self.lastNotReadyTime = None
|
||||||
self.__playerController = playerController
|
self.__playerController = playerController
|
||||||
|
if not self.__playerController._client._config["chatOutputEnabled"]:
|
||||||
|
self.__playerController.alertOSDSupported = False
|
||||||
|
self.__playerController.chatOSDSupported = False
|
||||||
if self.__playerController.getPlayerPathErrors(playerPath,filePath):
|
if self.__playerController.getPlayerPathErrors(playerPath,filePath):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
if filePath and '://' not in filePath:
|
if filePath and '://' not in filePath:
|
||||||
@ -332,6 +356,18 @@ class MplayerPlayer(BasePlayer):
|
|||||||
self.__playerController.lineReceived(line)
|
self.__playerController.lineReceived(line)
|
||||||
self.__playerController.drop()
|
self.__playerController.drop()
|
||||||
|
|
||||||
|
def sendChat(self, message):
|
||||||
|
if message:
|
||||||
|
if message[:1] == "/" and message <> "/":
|
||||||
|
command = message[1:]
|
||||||
|
if command and command[:1] == "/":
|
||||||
|
message = message[1:]
|
||||||
|
else:
|
||||||
|
self.__playerController.reactor.callFromThread(self.__playerController._client.ui.executeCommand,
|
||||||
|
command)
|
||||||
|
return
|
||||||
|
self.__playerController.reactor.callFromThread(self.__playerController._client.sendChat, message)
|
||||||
|
|
||||||
def isReadyForSend(self):
|
def isReadyForSend(self):
|
||||||
self.checkForReadinessOverride()
|
self.checkForReadinessOverride()
|
||||||
return self.readyToSend
|
return self.readyToSend
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
|
# coding:utf8
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from syncplay.players.mplayer import MplayerPlayer
|
from syncplay.players.mplayer import MplayerPlayer
|
||||||
from syncplay.messages import getMessage
|
from syncplay.messages import getMessage
|
||||||
from syncplay import constants
|
from syncplay import constants
|
||||||
from syncplay.utils import isURL
|
from syncplay.utils import isURL, findResourcePath
|
||||||
import os, sys, time
|
import os, sys, time
|
||||||
|
|
||||||
class MpvPlayer(MplayerPlayer):
|
class MpvPlayer(MplayerPlayer):
|
||||||
RE_VERSION = re.compile('.*mpv (\d+)\.(\d+)\.\d+.*')
|
RE_VERSION = re.compile('.*mpv (\d+)\.(\d+)\.\d+.*')
|
||||||
osdMessageSeparator = "\\n"
|
osdMessageSeparator = "\\n"
|
||||||
|
osdMessageSeparator = "; " # TODO: Make conditional
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run(client, playerPath, filePath, args):
|
def run(client, playerPath, filePath, args):
|
||||||
@ -17,6 +19,9 @@ class MpvPlayer(MplayerPlayer):
|
|||||||
except:
|
except:
|
||||||
ver = None
|
ver = None
|
||||||
constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 6
|
constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 6
|
||||||
|
constants.MPV_OSC_VISIBILITY_CHANGE_VERSION = False if ver is None else int(ver.group(1)) > 0 or int(ver.group(2)) >= 28
|
||||||
|
if not constants.MPV_OSC_VISIBILITY_CHANGE_VERSION:
|
||||||
|
client.ui.showDebugMessage(u"This version of mpv is not known to be compatible with changing the OSC visibility. Please use mpv >=0.28.0.")
|
||||||
if constants.MPV_NEW_VERSION:
|
if constants.MPV_NEW_VERSION:
|
||||||
return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)
|
||||||
else:
|
else:
|
||||||
@ -30,6 +35,7 @@ class MpvPlayer(MplayerPlayer):
|
|||||||
args.extend(constants.MPV_SLAVE_ARGS)
|
args.extend(constants.MPV_SLAVE_ARGS)
|
||||||
if constants.MPV_NEW_VERSION:
|
if constants.MPV_NEW_VERSION:
|
||||||
args.extend(constants.MPV_SLAVE_ARGS_NEW)
|
args.extend(constants.MPV_SLAVE_ARGS_NEW)
|
||||||
|
args.extend([u"--script={}".format(findResourcePath("syncplayintf.lua"))])
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -108,6 +114,25 @@ class OldMpvPlayer(MpvPlayer):
|
|||||||
class NewMpvPlayer(OldMpvPlayer):
|
class NewMpvPlayer(OldMpvPlayer):
|
||||||
lastResetTime = None
|
lastResetTime = None
|
||||||
lastMPVPositionUpdate = None
|
lastMPVPositionUpdate = None
|
||||||
|
alertOSDSupported = True
|
||||||
|
chatOSDSupported = True
|
||||||
|
|
||||||
|
def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION,
|
||||||
|
mood=constants.MESSAGE_NEUTRAL):
|
||||||
|
if not self._client._config["chatOutputEnabled"]:
|
||||||
|
super(self.__class__, self).displayMessage(message=message,duration=duration,OSDType=OSDType,mood=mood)
|
||||||
|
return
|
||||||
|
messageString = self._sanitizeText(message.replace("\\n", "<NEWLINE>")).replace("\\\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER).replace("<NEWLINE>", "\\n")
|
||||||
|
self._listener.sendLine(u'script-message-to syncplayintf {}-osd-{} "{}"'.format(OSDType, mood, messageString))
|
||||||
|
|
||||||
|
def displayChatMessage(self, username, message):
|
||||||
|
if not self._client._config["chatOutputEnabled"]:
|
||||||
|
super(self.__class__, self).displayChatMessage(username,message)
|
||||||
|
return
|
||||||
|
username = self._sanitizeText(username.replace("\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
|
||||||
|
message = self._sanitizeText(message.replace("\\",constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER))
|
||||||
|
messageString = u"<{}> {}".format(username, message)
|
||||||
|
self._listener.sendLine(u'script-message-to syncplayintf chat "{}"'.format(messageString))
|
||||||
|
|
||||||
def setPaused(self, value):
|
def setPaused(self, value):
|
||||||
if self._paused == value:
|
if self._paused == value:
|
||||||
@ -199,6 +224,9 @@ class NewMpvPlayer(OldMpvPlayer):
|
|||||||
self._clearFileLoaded()
|
self._clearFileLoaded()
|
||||||
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True)
|
self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath)), notReadyAfterThis=True)
|
||||||
|
|
||||||
|
def setFeatures(self, featureList):
|
||||||
|
self.sendMpvOptions()
|
||||||
|
|
||||||
def setPosition(self, value):
|
def setPosition(self, value):
|
||||||
if value < constants.DO_NOT_RESET_POSITION_THRESHOLD and self._recentlyReset():
|
if value < constants.DO_NOT_RESET_POSITION_THRESHOLD and self._recentlyReset():
|
||||||
self._client.ui.showDebugMessage("Did not seek as recently reset and {} below 'do not reset position' threshold".format(value))
|
self._client.ui.showDebugMessage("Did not seek as recently reset and {} below 'do not reset position' threshold".format(value))
|
||||||
@ -222,9 +250,29 @@ class NewMpvPlayer(OldMpvPlayer):
|
|||||||
else:
|
else:
|
||||||
self._storePosition(0)
|
self._storePosition(0)
|
||||||
|
|
||||||
|
def sendMpvOptions(self):
|
||||||
|
options = []
|
||||||
|
for option in constants.MPV_SYNCPLAYINTF_OPTIONS_TO_SEND:
|
||||||
|
options.append(u"{}={}".format(option, self._client._config[option]))
|
||||||
|
for option in constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND:
|
||||||
|
options.append(option)
|
||||||
|
for option in constants.MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND:
|
||||||
|
options.append(u"{}={}".format(option, getMessage(option)))
|
||||||
|
options.append(u"OscVisibilityChangeCompatible={}".format(constants.MPV_OSC_VISIBILITY_CHANGE_VERSION))
|
||||||
|
options_string = ", ".join(options)
|
||||||
|
self._listener.sendLine(u'script-message-to syncplayintf set_syncplayintf_options "{}"'.format(options_string))
|
||||||
|
self._setOSDPosition()
|
||||||
|
|
||||||
def _handleUnknownLine(self, line):
|
def _handleUnknownLine(self, line):
|
||||||
self.mpvErrorCheck(line)
|
self.mpvErrorCheck(line)
|
||||||
|
|
||||||
|
if "<chat>" in line:
|
||||||
|
line = line.decode("utf-8").replace(constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER, "\\").encode("utf-8")
|
||||||
|
self._listener.sendChat(line[6:-7])
|
||||||
|
|
||||||
|
if "<get_syncplayintf_options>" in line:
|
||||||
|
self.sendMpvOptions()
|
||||||
|
|
||||||
if line == "<SyncplayUpdateFile>" or "Playing:" in line:
|
if line == "<SyncplayUpdateFile>" or "Playing:" in line:
|
||||||
self._listener.setReadyToSend(False)
|
self._listener.setReadyToSend(False)
|
||||||
self._clearFileLoaded()
|
self._clearFileLoaded()
|
||||||
@ -236,6 +284,11 @@ class NewMpvPlayer(OldMpvPlayer):
|
|||||||
elif "Failed" in line or "failed" in line or "No video or audio streams selected" in line or "error" in line:
|
elif "Failed" in line or "failed" in line or "No video or audio streams selected" in line or "error" in line:
|
||||||
self._listener.setReadyToSend(True)
|
self._listener.setReadyToSend(True)
|
||||||
|
|
||||||
|
def _setOSDPosition(self):
|
||||||
|
if self._client._config['chatMoveOSD'] and (self._client._config['chatOutputEnabled'] or (self._client._config['chatInputEnabled'] and self._client._config['chatInputPosition'] == constants.INPUT_POSITION_TOP)):
|
||||||
|
self._setProperty("osd-align-y", "bottom")
|
||||||
|
self._setProperty("osd-margin-y", int(self._client._config['chatOSDMargin']))
|
||||||
|
|
||||||
def _recentlyReset(self):
|
def _recentlyReset(self):
|
||||||
if not self.lastResetTime:
|
if not self.lastResetTime:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -16,7 +16,8 @@ from syncplay.utils import isBSD, isLinux, isWindows, isMacOS
|
|||||||
class VlcPlayer(BasePlayer):
|
class VlcPlayer(BasePlayer):
|
||||||
speedSupported = True
|
speedSupported = True
|
||||||
customOpenDialog = False
|
customOpenDialog = False
|
||||||
secondaryOSDSupported = True
|
chatOSDSupported = False
|
||||||
|
alertOSDSupported = True
|
||||||
osdMessageSeparator = "; "
|
osdMessageSeparator = "; "
|
||||||
|
|
||||||
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
|
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
|
||||||
@ -118,9 +119,9 @@ class VlcPlayer(BasePlayer):
|
|||||||
else:
|
else:
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, secondaryOSD=False):
|
def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, OSDType=constants.OSD_DURATION, mood=constants.MESSAGE_NEUTRAL):
|
||||||
duration /= 1000
|
duration /= 1000
|
||||||
if secondaryOSD == False:
|
if OSDType != constants.OSD_ALERT:
|
||||||
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
|
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
|
||||||
else:
|
else:
|
||||||
self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))
|
self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))
|
||||||
@ -128,6 +129,9 @@ class VlcPlayer(BasePlayer):
|
|||||||
def setSpeed(self, value):
|
def setSpeed(self, value):
|
||||||
self._listener.sendLine("set-rate: {:.2n}".format(value))
|
self._listener.sendLine("set-rate: {:.2n}".format(value))
|
||||||
|
|
||||||
|
def setFeatures(self, featureList):
|
||||||
|
pass
|
||||||
|
|
||||||
def setPosition(self, value):
|
def setPosition(self, value):
|
||||||
self._lastVLCPositionUpdate = time.time()
|
self._lastVLCPositionUpdate = time.time()
|
||||||
self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))
|
self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import syncplay
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
import time
|
import time
|
||||||
from syncplay.messages import getMessage
|
from syncplay.messages import getMessage
|
||||||
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT
|
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
|
||||||
|
from syncplay.utils import meetsMinVersion
|
||||||
|
|
||||||
class JSONCommandProtocol(LineReceiver):
|
class JSONCommandProtocol(LineReceiver):
|
||||||
def handleMessages(self, messages):
|
def handleMessages(self, messages):
|
||||||
@ -107,6 +107,7 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
if room: hello["room"] = {"name" :room}
|
if room: hello["room"] = {"name" :room}
|
||||||
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
|
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
|
||||||
hello["realversion"] = syncplay.version
|
hello["realversion"] = syncplay.version
|
||||||
|
hello["features"] = self._client.getFeatures()
|
||||||
self.sendMessage({"Hello": hello})
|
self.sendMessage({"Hello": hello})
|
||||||
|
|
||||||
def _SetUser(self, users):
|
def _SetUser(self, users):
|
||||||
@ -147,6 +148,11 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
self._client.playlist.changeToPlaylistIndex(values['index'], values['user'])
|
self._client.playlist.changeToPlaylistIndex(values['index'], values['user'])
|
||||||
elif command == "playlistChange":
|
elif command == "playlistChange":
|
||||||
self._client.playlist.changePlaylist(values['files'], values['user'])
|
self._client.playlist.changePlaylist(values['files'], values['user'])
|
||||||
|
elif command == "features":
|
||||||
|
self._client.setUserFeatures(values["username"],values['features'])
|
||||||
|
|
||||||
|
def sendFeaturesUpdate(self, features):
|
||||||
|
self.sendSet({"features": features})
|
||||||
|
|
||||||
def sendSet(self, setting):
|
def sendSet(self, setting):
|
||||||
self.sendMessage({"Set": setting})
|
self.sendMessage({"Set": setting})
|
||||||
@ -173,7 +179,8 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
||||||
isController = user[1]['controller'] if 'controller' in user[1] else False
|
isController = user[1]['controller'] if 'controller' in user[1] else False
|
||||||
isReady = user[1]['isReady'] if 'isReady' in user[1] else None
|
isReady = user[1]['isReady'] if 'isReady' in user[1] else None
|
||||||
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady)
|
features = user[1]['features'] if 'features' in user[1] else None
|
||||||
|
self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady, features=features)
|
||||||
self._client.userlist.showUserList()
|
self._client.userlist.showUserList()
|
||||||
|
|
||||||
def sendList(self):
|
def sendList(self):
|
||||||
@ -249,9 +256,9 @@ class SyncClientProtocol(JSONCommandProtocol):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
def handleChat(self,message):
|
def handleChat(self,message):
|
||||||
|
username = message['username']
|
||||||
userMessage = message['message']
|
userMessage = message['message']
|
||||||
messageString = u"<{}> {}".format(message['username'], userMessage)
|
self._client.ui.showChatMessage(username, userMessage)
|
||||||
self._client.ui.showMessage(messageString)
|
|
||||||
|
|
||||||
def setReady(self, isReady, manuallyInitiated=True):
|
def setReady(self, isReady, manuallyInitiated=True):
|
||||||
self.sendSet({
|
self.sendSet({
|
||||||
@ -286,6 +293,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
def __init__(self, factory):
|
def __init__(self, factory):
|
||||||
self._factory = factory
|
self._factory = factory
|
||||||
self._version = None
|
self._version = None
|
||||||
|
self._features = None
|
||||||
self._logged = False
|
self._logged = False
|
||||||
self.clientIgnoringOnTheFly = 0
|
self.clientIgnoringOnTheFly = 0
|
||||||
self.serverIgnoringOnTheFly = 0
|
self.serverIgnoringOnTheFly = 0
|
||||||
@ -319,6 +327,16 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason):
|
||||||
self._factory.removeWatcher(self._watcher)
|
self._factory.removeWatcher(self._watcher)
|
||||||
|
|
||||||
|
def getFeatures(self):
|
||||||
|
if not self._features:
|
||||||
|
self._features = {}
|
||||||
|
self._features["sharedPlaylists"] = meetsMinVersion(self._version, SHARED_PLAYLIST_MIN_VERSION)
|
||||||
|
self._features["chat"] = meetsMinVersion(self._version, CHAT_MIN_VERSION)
|
||||||
|
self._features["featureList"] = False
|
||||||
|
self._features["readiness"] = meetsMinVersion(self._version, USER_READY_MIN_VERSION)
|
||||||
|
self._features["managedRooms"] = meetsMinVersion(self._version, CONTROLLED_ROOMS_MIN_VERSION)
|
||||||
|
return self._features
|
||||||
|
|
||||||
def isLogged(self):
|
def isLogged(self):
|
||||||
return self._logged
|
return self._logged
|
||||||
|
|
||||||
@ -345,7 +363,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
roomName = None
|
roomName = None
|
||||||
version = hello["version"] if hello.has_key("version") else None
|
version = hello["version"] if hello.has_key("version") else None
|
||||||
version = hello["realversion"] if hello.has_key("realversion") else version
|
version = hello["realversion"] if hello.has_key("realversion") else version
|
||||||
return username, serverPassword, roomName, version
|
features = hello["features"] if hello.has_key("features") else None
|
||||||
|
return username, serverPassword, roomName, version, features
|
||||||
|
|
||||||
def _checkPassword(self, serverPassword):
|
def _checkPassword(self, serverPassword):
|
||||||
if self._factory.password:
|
if self._factory.password:
|
||||||
@ -358,7 +377,7 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def handleHello(self, hello):
|
def handleHello(self, hello):
|
||||||
username, serverPassword, roomName, version = self._extractHelloArguments(hello)
|
username, serverPassword, roomName, version, features = self._extractHelloArguments(hello)
|
||||||
if not username or not roomName or not version:
|
if not username or not roomName or not version:
|
||||||
self.dropWithError(getMessage("hello-server-error"))
|
self.dropWithError(getMessage("hello-server-error"))
|
||||||
return
|
return
|
||||||
@ -366,14 +385,22 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
if not self._checkPassword(serverPassword):
|
if not self._checkPassword(serverPassword):
|
||||||
return
|
return
|
||||||
self._version = version
|
self._version = version
|
||||||
|
self.setFeatures(features)
|
||||||
self._factory.addWatcher(self, username, roomName)
|
self._factory.addWatcher(self, username, roomName)
|
||||||
self._logged = True
|
self._logged = True
|
||||||
self.sendHello(version)
|
self.sendHello(version)
|
||||||
|
|
||||||
|
@requireLogged
|
||||||
def handleChat(self,chatMessage):
|
def handleChat(self,chatMessage):
|
||||||
if not self._factory.disableChat:
|
if not self._factory.disableChat:
|
||||||
self._factory.sendChat(self._watcher,chatMessage)
|
self._factory.sendChat(self._watcher,chatMessage)
|
||||||
|
|
||||||
|
def setFeatures(self, features):
|
||||||
|
self._features = features
|
||||||
|
|
||||||
|
def sendFeaturesUpdate(self):
|
||||||
|
self.sendSet({"features": self.getFeatures()})
|
||||||
|
|
||||||
def setWatcher(self, watcher):
|
def setWatcher(self, watcher):
|
||||||
self._watcher = watcher
|
self._watcher = watcher
|
||||||
|
|
||||||
@ -410,6 +437,9 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
self._factory.setPlaylist(self._watcher, set_[1]['files'])
|
self._factory.setPlaylist(self._watcher, set_[1]['files'])
|
||||||
elif command == "playlistIndex":
|
elif command == "playlistIndex":
|
||||||
self._factory.setPlaylistIndex(self._watcher, set_[1]['index'])
|
self._factory.setPlaylistIndex(self._watcher, set_[1]['index'])
|
||||||
|
elif command == "features":
|
||||||
|
#TODO: Check
|
||||||
|
self._watcher.setFeatures(set_[1])
|
||||||
|
|
||||||
def sendSet(self, setting):
|
def sendSet(self, setting):
|
||||||
self.sendMessage({"Set": setting})
|
self.sendMessage({"Set": setting})
|
||||||
@ -476,7 +506,8 @@ class SyncServerProtocol(JSONCommandProtocol):
|
|||||||
"position": 0,
|
"position": 0,
|
||||||
"file": watcher.getFile() if watcher.getFile() else {},
|
"file": watcher.getFile() if watcher.getFile() else {},
|
||||||
"controller": watcher.isController(),
|
"controller": watcher.isController(),
|
||||||
"isReady": watcher.isReady()
|
"isReady": watcher.isReady(),
|
||||||
|
"features": watcher.getFeatures()
|
||||||
}
|
}
|
||||||
userlist[room.getName()][watcher.getName()] = userFile
|
userlist[room.getName()][watcher.getName()] = userFile
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,11 @@ class SyncFactory(Factory):
|
|||||||
features["readiness"] = not self.disableReady
|
features["readiness"] = not self.disableReady
|
||||||
features["managedRooms"] = True
|
features["managedRooms"] = True
|
||||||
features["chat"] = not self.disableChat
|
features["chat"] = not self.disableChat
|
||||||
|
features["maxChatMessageLength"] = constants.MAX_CHAT_MESSAGE_LENGTH
|
||||||
|
features["maxUsernameLength"] = constants.MAX_USERNAME_LENGTH
|
||||||
|
features["maxRoomNameLength"] = constants.MAX_ROOM_NAME_LENGTH
|
||||||
|
features["maxFilenameLength"] = constants.MAX_FILENAME_LENGTH
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
def getMotd(self, userIp, username, room, clientVersion):
|
def getMotd(self, userIp, username, room, clientVersion):
|
||||||
@ -108,7 +113,7 @@ class SyncFactory(Factory):
|
|||||||
self._roomManager.broadcast(watcher, l)
|
self._roomManager.broadcast(watcher, l)
|
||||||
|
|
||||||
def sendJoinMessage(self, watcher):
|
def sendJoinMessage(self, watcher):
|
||||||
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion()}) if w != watcher else None
|
l = lambda w: w.sendSetting(watcher.getName(), watcher.getRoom(), None, {"joined": True, "version": watcher.getVersion(), "features": watcher.getFeatures()}) if w != watcher else None
|
||||||
self._roomManager.broadcast(watcher, l)
|
self._roomManager.broadcast(watcher, l)
|
||||||
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), watcher.isReady(), False))
|
||||||
|
|
||||||
@ -413,6 +418,10 @@ class Watcher(object):
|
|||||||
def setReady(self, ready):
|
def setReady(self, ready):
|
||||||
self._ready = ready
|
self._ready = ready
|
||||||
|
|
||||||
|
def getFeatures(self):
|
||||||
|
features = self._connector.getFeatures()
|
||||||
|
return features
|
||||||
|
|
||||||
def isReady(self):
|
def isReady(self):
|
||||||
if self._server.disableReady:
|
if self._server.disableReady:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from syncplay.messages import getMessage, setLanguage, isValidLanguage
|
|||||||
from syncplay.players.playerFactory import PlayerFactory
|
from syncplay.players.playerFactory import PlayerFactory
|
||||||
from syncplay.utils import isMacOS
|
from syncplay.utils import isMacOS
|
||||||
import codecs
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
class InvalidConfigValue(Exception):
|
class InvalidConfigValue(Exception):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
@ -64,6 +65,29 @@ class ConfigurationGetter(object):
|
|||||||
"showNonControllerOSD" : False,
|
"showNonControllerOSD" : False,
|
||||||
"showContactInfo" : True,
|
"showContactInfo" : True,
|
||||||
"showDurationNotification" : True,
|
"showDurationNotification" : True,
|
||||||
|
"chatInputEnabled" : True,
|
||||||
|
"chatInputFontFamily" : 'sans-serif',
|
||||||
|
"chatInputRelativeFontSize" : constants.DEFAULT_CHAT_FONT_SIZE,
|
||||||
|
"chatInputFontWeight" : constants.DEFAULT_CHAT_FONT_WEIGHT,
|
||||||
|
"chatInputFontUnderline": False,
|
||||||
|
"chatInputFontColor": constants.DEFAULT_CHAT_INPUT_FONT_COLOR,
|
||||||
|
"chatInputPosition": constants.INPUT_POSITION_TOP,
|
||||||
|
"chatDirectInput": False,
|
||||||
|
"chatOutputEnabled": True,
|
||||||
|
"chatOutputFontFamily": 'sans-serif',
|
||||||
|
"chatOutputRelativeFontSize": constants.DEFAULT_CHAT_FONT_SIZE,
|
||||||
|
"chatOutputFontWeight": constants.DEFAULT_CHAT_FONT_WEIGHT,
|
||||||
|
"chatOutputFontUnderline": False,
|
||||||
|
"chatOutputMode": constants.CHATROOM_MODE,
|
||||||
|
"chatMaxLines": 7,
|
||||||
|
"chatTopMargin": 25,
|
||||||
|
"chatLeftMargin": 20,
|
||||||
|
"chatBottomMargin": 30,
|
||||||
|
"chatMoveOSD": True,
|
||||||
|
"chatOSDMargin": 110,
|
||||||
|
"notificationTimeout": 3,
|
||||||
|
"alertTimeout": 5,
|
||||||
|
"chatTimeout": 7,
|
||||||
"publicServers" : []
|
"publicServers" : []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +130,13 @@ class ConfigurationGetter(object):
|
|||||||
"sharedPlaylistEnabled",
|
"sharedPlaylistEnabled",
|
||||||
"loopAtEndOfPlaylist",
|
"loopAtEndOfPlaylist",
|
||||||
"loopSingleFiles",
|
"loopSingleFiles",
|
||||||
"onlySwitchToTrustedDomains"
|
"onlySwitchToTrustedDomains",
|
||||||
|
"chatInputEnabled",
|
||||||
|
"chatInputFontUnderline",
|
||||||
|
"chatDirectInput",
|
||||||
|
"chatMoveOSD",
|
||||||
|
"chatOutputEnabled",
|
||||||
|
"chatOutputFontUnderline"
|
||||||
]
|
]
|
||||||
self._tristate = [
|
self._tristate = [
|
||||||
"checkForUpdatesAutomatically",
|
"checkForUpdatesAutomatically",
|
||||||
@ -125,6 +155,22 @@ class ConfigurationGetter(object):
|
|||||||
"rewindThreshold",
|
"rewindThreshold",
|
||||||
"fastforwardThreshold",
|
"fastforwardThreshold",
|
||||||
"autoplayMinUsers",
|
"autoplayMinUsers",
|
||||||
|
"chatInputRelativeFontSize",
|
||||||
|
"chatInputFontWeight",
|
||||||
|
"chatOutputFontWeight",
|
||||||
|
"chatOutputRelativeFontSize",
|
||||||
|
"chatMaxLines",
|
||||||
|
"chatTopMargin",
|
||||||
|
"chatLeftMargin",
|
||||||
|
"chatBottomMargin",
|
||||||
|
"chatOSDMargin",
|
||||||
|
"notificationTimeout",
|
||||||
|
"alertTimeout",
|
||||||
|
"chatTimeout"
|
||||||
|
]
|
||||||
|
|
||||||
|
self._hexadecimal = [
|
||||||
|
"chatInputFontColor"
|
||||||
]
|
]
|
||||||
|
|
||||||
self._iniStructure = {
|
self._iniStructure = {
|
||||||
@ -143,7 +189,19 @@ class ConfigurationGetter(object):
|
|||||||
"onlySwitchToTrustedDomains", "trustedDomains","publicServers"],
|
"onlySwitchToTrustedDomains", "trustedDomains","publicServers"],
|
||||||
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD",
|
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD",
|
||||||
"showDifferentRoomOSD", "showSameRoomOSD",
|
"showDifferentRoomOSD", "showSameRoomOSD",
|
||||||
"showNonControllerOSD", "showDurationNotification"],
|
"showNonControllerOSD", "showDurationNotification",
|
||||||
|
"chatInputEnabled","chatInputFontUnderline",
|
||||||
|
"chatInputFontFamily", "chatInputRelativeFontSize",
|
||||||
|
"chatInputFontWeight", "chatInputFontColor",
|
||||||
|
"chatInputPosition","chatDirectInput",
|
||||||
|
"chatOutputFontFamily", "chatOutputRelativeFontSize",
|
||||||
|
"chatOutputFontWeight", "chatOutputFontUnderline",
|
||||||
|
"chatOutputMode", "chatMaxLines",
|
||||||
|
"chatTopMargin", "chatLeftMargin",
|
||||||
|
"chatBottomMargin", "chatDirectInput",
|
||||||
|
"chatMoveOSD", "chatOSDMargin",
|
||||||
|
"notificationTimeout", "alertTimeout",
|
||||||
|
"chatTimeout","chatOutputEnabled"],
|
||||||
"general": ["language", "checkForUpdatesAutomatically",
|
"general": ["language", "checkForUpdatesAutomatically",
|
||||||
"lastCheckedForUpdates"]
|
"lastCheckedForUpdates"]
|
||||||
}
|
}
|
||||||
@ -197,6 +255,11 @@ class ConfigurationGetter(object):
|
|||||||
for key in self._numeric:
|
for key in self._numeric:
|
||||||
self._config[key] = float(self._config[key])
|
self._config[key] = float(self._config[key])
|
||||||
|
|
||||||
|
for key in self._hexadecimal:
|
||||||
|
match = re.search(r'^#(?:[0-9a-fA-F]){6}$', self._config[key])
|
||||||
|
if not match:
|
||||||
|
self._config[key] = u"#FFFFFF"
|
||||||
|
|
||||||
for key in self._required:
|
for key in self._required:
|
||||||
if key == "playerPath":
|
if key == "playerPath":
|
||||||
player = None
|
player = None
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
|
from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage
|
||||||
from syncplay import constants
|
from syncplay import constants
|
||||||
from syncplay.utils import isBSD, isLinux, isMacOS
|
from syncplay.utils import isBSD, isLinux, isMacOS, isWindows
|
||||||
from syncplay.utils import resourcespath, posixresourcespath
|
from syncplay.utils import resourcespath, posixresourcespath
|
||||||
class GuiConfiguration:
|
class GuiConfiguration:
|
||||||
def __init__(self, config, error=None, defaultConfig=None):
|
def __init__(self, config, error=None, defaultConfig=None):
|
||||||
@ -518,7 +518,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
def connectChildren(self, widget):
|
def connectChildren(self, widget):
|
||||||
widgetName = str(widget.objectName())
|
widgetName = str(widget.objectName())
|
||||||
if self.subitems.has_key(widgetName) and isinstance(widget, QCheckBox):
|
if self.subitems.has_key(widgetName):
|
||||||
widget.stateChanged.connect(lambda: self.updateSubwidgets(self, widget))
|
widget.stateChanged.connect(lambda: self.updateSubwidgets(self, widget))
|
||||||
self.updateSubwidgets(self, widget)
|
self.updateSubwidgets(self, widget)
|
||||||
|
|
||||||
@ -872,6 +872,155 @@ class ConfigDialog(QtWidgets.QDialog):
|
|||||||
self.syncSettingsLayout.setAlignment(Qt.AlignTop)
|
self.syncSettingsLayout.setAlignment(Qt.AlignTop)
|
||||||
self.stackedLayout.addWidget(self.syncSettingsFrame)
|
self.stackedLayout.addWidget(self.syncSettingsFrame)
|
||||||
|
|
||||||
|
def addChatTab(self):
|
||||||
|
self.chatFrame = QtWidgets.QFrame()
|
||||||
|
self.chatLayout = QtWidgets.QVBoxLayout()
|
||||||
|
self.chatLayout.setAlignment(Qt.AlignTop)
|
||||||
|
|
||||||
|
# Input
|
||||||
|
self.chatInputGroup = QtWidgets.QGroupBox(getMessage("chat-title"))
|
||||||
|
self.chatInputLayout = QtWidgets.QGridLayout()
|
||||||
|
self.chatLayout.addWidget(self.chatInputGroup)
|
||||||
|
self.chatInputGroup.setLayout(self.chatInputLayout)
|
||||||
|
self.chatInputEnabledCheckbox = QCheckBox(getMessage("chatinputenabled-label"))
|
||||||
|
self.chatInputEnabledCheckbox.setObjectName("chatInputEnabled")
|
||||||
|
self.chatInputLayout.addWidget(self.chatInputEnabledCheckbox, 1, 0, 1,1, Qt.AlignLeft)
|
||||||
|
|
||||||
|
self.chatDirectInputCheckbox = QCheckBox(getMessage("chatdirectinput-label"))
|
||||||
|
self.chatDirectInputCheckbox.setObjectName("chatDirectInput")
|
||||||
|
self.chatDirectInputCheckbox.setStyleSheet(
|
||||||
|
constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||||
|
self.chatInputLayout.addWidget(self.chatDirectInputCheckbox, 2, 0, 1,1, Qt.AlignLeft)
|
||||||
|
|
||||||
|
self.inputFontLayout = QtWidgets.QHBoxLayout()
|
||||||
|
self.inputFontLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.inputFontFrame = QtWidgets.QFrame()
|
||||||
|
self.inputFontFrame.setLayout(self.inputFontLayout)
|
||||||
|
self.inputFontFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
|
||||||
|
self.chatFontLabel = QLabel(getMessage("chatinputfont-label"), self)
|
||||||
|
self.chatFontLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||||
|
self.chatFontLabel.setObjectName("font-label")
|
||||||
|
self.chatInputFontButton = QtWidgets.QPushButton(getMessage("chatfont-label"))
|
||||||
|
self.chatInputFontButton.setObjectName("set-input-font")
|
||||||
|
self.chatInputFontButtonGroup = QtWidgets.QButtonGroup()
|
||||||
|
self.chatInputFontButtonGroup.addButton(self.chatInputFontButton)
|
||||||
|
self.chatInputFontButton.released.connect(lambda: self.fontDialog("chatInput"))
|
||||||
|
self.chatInputColourButton = QtWidgets.QPushButton(getMessage("chatcolour-label"))
|
||||||
|
self.chatInputColourButton.setObjectName("set-input-colour")
|
||||||
|
self.chatInputColourButtonGroup = QtWidgets.QButtonGroup()
|
||||||
|
self.chatInputColourButtonGroup.addButton(self.chatInputColourButton)
|
||||||
|
self.chatInputColourButton.released.connect(lambda: self.colourDialog("chatInput"))
|
||||||
|
self.inputFontLayout.addWidget(self.chatFontLabel, Qt.AlignLeft)
|
||||||
|
self.inputFontLayout.addWidget(self.chatInputFontButton, Qt.AlignLeft)
|
||||||
|
self.inputFontLayout.addWidget(self.chatInputColourButton, Qt.AlignLeft)
|
||||||
|
self.chatInputLayout.addWidget(self.inputFontFrame, 3, 0, 1, 3, Qt.AlignLeft)
|
||||||
|
|
||||||
|
self.chatInputPositionFrame = QtWidgets.QFrame()
|
||||||
|
self.chatInputPositionLayout = QtWidgets.QHBoxLayout()
|
||||||
|
self.chatInputPositionLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.chatInputPositionFrame.setLayout(self.chatInputPositionLayout)
|
||||||
|
self.chatInputPositionFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
|
||||||
|
self.chatInputPositionLabel = QLabel(getMessage("chatinputposition-label"), self)
|
||||||
|
self.chatInputPositionLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||||
|
self.chatInputPositionGroup = QButtonGroup()
|
||||||
|
self.chatInputTopOption = QRadioButton(getMessage("chat-top-option"))
|
||||||
|
self.chatInputMiddleOption = QRadioButton(getMessage("chat-middle-option"))
|
||||||
|
self.chatInputBottomOption = QRadioButton(getMessage("chat-bottom-option"))
|
||||||
|
self.chatInputPositionGroup.addButton(self.chatInputTopOption)
|
||||||
|
self.chatInputPositionGroup.addButton(self.chatInputMiddleOption)
|
||||||
|
self.chatInputPositionGroup.addButton(self.chatInputBottomOption)
|
||||||
|
|
||||||
|
self.chatInputPositionLabel.setObjectName("chatinputposition")
|
||||||
|
self.chatInputTopOption.setObjectName("chatinputposition-top" + constants.CONFIG_NAME_MARKER + "chatInputPosition" + constants.CONFIG_VALUE_MARKER + constants.INPUT_POSITION_TOP)
|
||||||
|
self.chatInputMiddleOption.setObjectName("chatinputposition-middle" + constants.CONFIG_NAME_MARKER + "chatInputPosition" + constants.CONFIG_VALUE_MARKER + constants.INPUT_POSITION_MIDDLE)
|
||||||
|
self.chatInputBottomOption.setObjectName("chatinputposition-bottom" + constants.CONFIG_NAME_MARKER + "chatInputPosition" + constants.CONFIG_VALUE_MARKER + constants.INPUT_POSITION_BOTTOM)
|
||||||
|
|
||||||
|
self.chatInputPositionLayout.addWidget(self.chatInputPositionLabel)
|
||||||
|
self.chatInputPositionLayout.addWidget(self.chatInputTopOption)
|
||||||
|
self.chatInputPositionLayout.addWidget(self.chatInputMiddleOption)
|
||||||
|
self.chatInputPositionLayout.addWidget(self.chatInputBottomOption)
|
||||||
|
self.chatInputLayout.addWidget(self.chatInputPositionFrame)
|
||||||
|
|
||||||
|
self.subitems['chatInputEnabled'] = [self.chatInputPositionLabel.objectName(), self.chatInputTopOption.objectName(),
|
||||||
|
self.chatInputMiddleOption.objectName(), self.chatInputBottomOption.objectName(),
|
||||||
|
self.chatInputFontButton.objectName(), self.chatFontLabel.objectName(),
|
||||||
|
self.chatInputColourButton.objectName(), self.chatDirectInputCheckbox.objectName()]
|
||||||
|
# Output
|
||||||
|
self.chatOutputGroup = QtWidgets.QGroupBox(u"Chat message output")
|
||||||
|
self.chatOutputLayout = QtWidgets.QGridLayout()
|
||||||
|
self.chatLayout.addWidget(self.chatOutputGroup)
|
||||||
|
self.chatOutputGroup.setLayout(self.chatOutputLayout)
|
||||||
|
self.chatOutputEnabledCheckbox = QCheckBox(getMessage("chatoutputenabled-label"))
|
||||||
|
self.chatOutputEnabledCheckbox.setObjectName("chatOutputEnabled")
|
||||||
|
self.chatOutputLayout.addWidget(self.chatOutputEnabledCheckbox, 1, 0, 1,1, Qt.AlignLeft)
|
||||||
|
|
||||||
|
self.outputFontLayout = QtWidgets.QHBoxLayout()
|
||||||
|
self.outputFontLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.outputFontFrame = QtWidgets.QFrame()
|
||||||
|
self.outputFontFrame.setLayout(self.outputFontLayout)
|
||||||
|
self.outputFontFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
|
||||||
|
self.chatOutputFontLabel = QLabel(getMessage("chatoutputfont-label"), self)
|
||||||
|
self.chatOutputFontLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||||
|
self.chatOutputFontLabel.setObjectName("font-output-label")
|
||||||
|
self.chatOutputFontButton = QtWidgets.QPushButton(getMessage("chatfont-label"))
|
||||||
|
self.chatOutputFontButton.setObjectName("set-output-font")
|
||||||
|
self.chatOutputFontButtonGroup = QtWidgets.QButtonGroup()
|
||||||
|
self.chatOutputFontButtonGroup.addButton(self.chatOutputFontButton)
|
||||||
|
self.chatOutputFontButton.released.connect(lambda: self.fontDialog("chatOutput"))
|
||||||
|
self.chatOutputColourButton = QtWidgets.QPushButton(getMessage("chatcolour-label"))
|
||||||
|
self.outputFontLayout.addWidget(self.chatOutputFontLabel, Qt.AlignLeft)
|
||||||
|
self.outputFontLayout.addWidget(self.chatOutputFontButton, Qt.AlignLeft)
|
||||||
|
self.chatOutputLayout.addWidget(self.outputFontFrame, 2, 0, 1, 3, Qt.AlignLeft)
|
||||||
|
|
||||||
|
self.chatOutputModeLabel = QLabel(getMessage("chatoutputposition-label"), self)
|
||||||
|
self.chatOutputModeLabel.setStyleSheet(constants.STYLE_SUBCHECKBOX.format(self.posixresourcespath + u"chevrons_right.png"))
|
||||||
|
self.chatOutputModeGroup = QButtonGroup()
|
||||||
|
self.chatOutputChatroomOption = QRadioButton(getMessage("chat-chatroom-option"))
|
||||||
|
self.chatOutputScrollingOption = QRadioButton(getMessage("chat-scrolling-option"))
|
||||||
|
self.chatOutputModeGroup.addButton(self.chatOutputChatroomOption)
|
||||||
|
self.chatOutputModeGroup.addButton(self.chatOutputScrollingOption)
|
||||||
|
|
||||||
|
self.chatOutputModeLabel.setObjectName("chatoutputmode")
|
||||||
|
self.chatOutputChatroomOption.setObjectName("chatoutputmode-chatroom" + constants.CONFIG_NAME_MARKER + "chatOutputMode" + constants.CONFIG_VALUE_MARKER + constants.CHATROOM_MODE)
|
||||||
|
self.chatOutputScrollingOption.setObjectName("chatoutputmode-scrolling" + constants.CONFIG_NAME_MARKER + "chatOutputMode" + constants.CONFIG_VALUE_MARKER + constants.SCROLLING_MODE)
|
||||||
|
|
||||||
|
self.chatOutputModeFrame = QtWidgets.QFrame()
|
||||||
|
self.chatOutputModeLayout = QtWidgets.QHBoxLayout()
|
||||||
|
self.chatOutputModeLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.chatOutputModeFrame.setLayout(self.chatOutputModeLayout)
|
||||||
|
self.chatOutputModeFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
|
||||||
|
self.chatOutputModeLayout.addWidget(self.chatOutputModeLabel)
|
||||||
|
self.chatOutputModeLayout.addWidget(self.chatOutputChatroomOption)
|
||||||
|
self.chatOutputModeLayout.addWidget(self.chatOutputScrollingOption)
|
||||||
|
self.chatOutputLayout.addWidget(self.chatOutputModeFrame)
|
||||||
|
|
||||||
|
self.subitems['chatOutputEnabled'] = [self.chatOutputModeLabel.objectName(), self.chatOutputChatroomOption.objectName(),
|
||||||
|
self.chatOutputScrollingOption.objectName(),self.chatOutputFontButton.objectName(),
|
||||||
|
self.chatOutputFontLabel.objectName()]
|
||||||
|
# chatFrame
|
||||||
|
self.chatFrame.setLayout(self.chatLayout)
|
||||||
|
self.stackedLayout.addWidget(self.chatFrame)
|
||||||
|
|
||||||
|
def fontDialog(self, configName):
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily(self.config[configName+ u"FontFamily"])
|
||||||
|
font.setPointSize(self.config[configName + u"RelativeFontSize"])
|
||||||
|
font.setWeight(self.config[configName + u"FontWeight"])
|
||||||
|
font.setUnderline(self.config[configName + u"FontUnderline"])
|
||||||
|
value, ok = QtWidgets.QFontDialog.getFont(font)
|
||||||
|
if ok:
|
||||||
|
self.config[configName + u"FontFamily"] = value.family()
|
||||||
|
self.config[configName + u"RelativeFontSize"] = value.pointSize()
|
||||||
|
self.config[configName + u"FontWeight"] = value.weight()
|
||||||
|
self.config[configName + u"FontUnderline"] = value.underline()
|
||||||
|
|
||||||
|
def colourDialog(self, configName):
|
||||||
|
oldColour = QtGui.QColor()
|
||||||
|
oldColour.setNamedColor(self.config[configName+ u"FontColor"])
|
||||||
|
colour = QtWidgets.QColorDialog.getColor(oldColour, self)
|
||||||
|
if colour.isValid():
|
||||||
|
self.config[configName + u"FontColor"] = colour.name()
|
||||||
|
|
||||||
def addMessageTab(self):
|
def addMessageTab(self):
|
||||||
self.messageFrame = QtWidgets.QFrame()
|
self.messageFrame = QtWidgets.QFrame()
|
||||||
self.messageLayout = QtWidgets.QVBoxLayout()
|
self.messageLayout = QtWidgets.QVBoxLayout()
|
||||||
@ -970,18 +1119,20 @@ class ConfigDialog(QtWidgets.QDialog):
|
|||||||
self.helpButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'help.png'), getMessage("help-label"))
|
self.helpButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'help.png'), getMessage("help-label"))
|
||||||
self.helpButton.setObjectName("help")
|
self.helpButton.setObjectName("help")
|
||||||
self.helpButton.setMaximumSize(self.helpButton.sizeHint())
|
self.helpButton.setMaximumSize(self.helpButton.sizeHint())
|
||||||
self.helpButton.pressed.connect(self.openHelp)
|
self.helpButton.released.connect(self.openHelp)
|
||||||
|
|
||||||
self.resetButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'cog_delete.png'),getMessage("reset-label"))
|
self.resetButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'cog_delete.png'),getMessage("reset-label"))
|
||||||
self.resetButton.setMaximumSize(self.resetButton.sizeHint())
|
self.resetButton.setMaximumSize(self.resetButton.sizeHint())
|
||||||
self.resetButton.setObjectName("reset")
|
self.resetButton.setObjectName("reset")
|
||||||
self.resetButton.pressed.connect(self.resetSettings)
|
self.resetButton.released.connect(self.resetSettings)
|
||||||
|
|
||||||
|
self.runButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label"))
|
||||||
|
self.runButton.released.connect(self._runWithoutStoringConfig)
|
||||||
self.runButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label"))
|
self.runButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("run-label"))
|
||||||
self.runButton.pressed.connect(self._runWithoutStoringConfig)
|
self.runButton.pressed.connect(self._runWithoutStoringConfig)
|
||||||
self.runButton.setToolTip(getMessage("nostore-tooltip"))
|
self.runButton.setToolTip(getMessage("nostore-tooltip"))
|
||||||
self.storeAndRunButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("storeandrun-label"))
|
self.storeAndRunButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + u'accept.png'), getMessage("storeandrun-label"))
|
||||||
self.storeAndRunButton.pressed.connect(self._saveDataAndLeave)
|
self.storeAndRunButton.released.connect(self._saveDataAndLeave)
|
||||||
self.bottomButtonLayout.addWidget(self.helpButton)
|
self.bottomButtonLayout.addWidget(self.helpButton)
|
||||||
self.bottomButtonLayout.addWidget(self.resetButton)
|
self.bottomButtonLayout.addWidget(self.resetButton)
|
||||||
self.bottomButtonLayout.addWidget(self.runButton)
|
self.bottomButtonLayout.addWidget(self.runButton)
|
||||||
@ -1011,7 +1162,8 @@ class ConfigDialog(QtWidgets.QDialog):
|
|||||||
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"house.png"),getMessage("basics-label")))
|
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"house.png"),getMessage("basics-label")))
|
||||||
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"control_pause_blue.png"),getMessage("readiness-label")))
|
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"control_pause_blue.png"),getMessage("readiness-label")))
|
||||||
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"film_link.png"),getMessage("sync-label")))
|
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"film_link.png"),getMessage("sync-label")))
|
||||||
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"comments.png"),getMessage("messages-label")))
|
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"user_comment.png"), getMessage("chat-label")))
|
||||||
|
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"error.png"),getMessage("messages-label")))
|
||||||
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"cog.png"),getMessage("misc-label")))
|
self.tabListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(resourcespath + u"cog.png"),getMessage("misc-label")))
|
||||||
self.tabListLayout.addWidget(self.tabListWidget)
|
self.tabListLayout.addWidget(self.tabListWidget)
|
||||||
self.tabListFrame.setLayout(self.tabListLayout)
|
self.tabListFrame.setLayout(self.tabListLayout)
|
||||||
@ -1111,6 +1263,12 @@ class ConfigDialog(QtWidgets.QDialog):
|
|||||||
self.QtWidgets = QtWidgets
|
self.QtWidgets = QtWidgets
|
||||||
self.QtGui = QtGui
|
self.QtGui = QtGui
|
||||||
self.error = error
|
self.error = error
|
||||||
|
if isWindows():
|
||||||
|
resourcespath = utils.findWorkingDir() + "\\resources\\"
|
||||||
|
else:
|
||||||
|
resourcespath = utils.findWorkingDir() + u"/resources/"
|
||||||
|
self.posixresourcespath = utils.findWorkingDir().replace(u"\\","/") + u"/resources/"
|
||||||
|
self.resourcespath = resourcespath
|
||||||
|
|
||||||
super(ConfigDialog, self).__init__()
|
super(ConfigDialog, self).__init__()
|
||||||
|
|
||||||
@ -1130,6 +1288,7 @@ class ConfigDialog(QtWidgets.QDialog):
|
|||||||
self.addBasicTab()
|
self.addBasicTab()
|
||||||
self.addReadinessTab()
|
self.addReadinessTab()
|
||||||
self.addSyncTab()
|
self.addSyncTab()
|
||||||
|
self.addChatTab()
|
||||||
self.addMessageTab()
|
self.addMessageTab()
|
||||||
self.addMiscTab()
|
self.addMiscTab()
|
||||||
self.tabList()
|
self.tabList()
|
||||||
|
|||||||
@ -38,7 +38,7 @@ class ConsoleUI(threading.Thread):
|
|||||||
self.PromptResult = data
|
self.PromptResult = data
|
||||||
self.promptMode.set()
|
self.promptMode.set()
|
||||||
elif self._syncplayClient:
|
elif self._syncplayClient:
|
||||||
self._executeCommand(data)
|
self.executeCommand(data)
|
||||||
except EOFError:
|
except EOFError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ class ConsoleUI(threading.Thread):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _executeCommand(self, data):
|
def executeCommand(self, data):
|
||||||
command = re.match(constants.UI_COMMAND_REGEX, data)
|
command = re.match(constants.UI_COMMAND_REGEX, data)
|
||||||
if not command:
|
if not command:
|
||||||
return
|
return
|
||||||
@ -145,7 +145,7 @@ class ConsoleUI(threading.Thread):
|
|||||||
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
||||||
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
||||||
elif command.group('command') in constants.COMMANDS_LIST:
|
elif command.group('command') in constants.COMMANDS_LIST:
|
||||||
self._syncplayClient.getUserList()
|
self.getUserlist()
|
||||||
elif command.group('command') in constants.COMMANDS_CHAT:
|
elif command.group('command') in constants.COMMANDS_CHAT:
|
||||||
message= command.group('parameter')
|
message= command.group('parameter')
|
||||||
self._syncplayClient.sendChat(message)
|
self._syncplayClient.sendChat(message)
|
||||||
@ -158,8 +158,8 @@ class ConsoleUI(threading.Thread):
|
|||||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||||
else:
|
else:
|
||||||
room = self._syncplayClient.defaultRoom
|
room = self._syncplayClient.defaultRoom
|
||||||
|
|
||||||
self._syncplayClient.setRoom(room, resetAutoplay=True)
|
self._syncplayClient.setRoom(room, resetAutoplay=True)
|
||||||
|
self._syncplayClient.ui.updateRoomName(room)
|
||||||
self._syncplayClient.sendRoom()
|
self._syncplayClient.sendRoom()
|
||||||
elif command.group('command') in constants.COMMANDS_CREATE:
|
elif command.group('command') in constants.COMMANDS_CREATE:
|
||||||
roombasename = command.group('parameter')
|
roombasename = command.group('parameter')
|
||||||
@ -191,3 +191,5 @@ class ConsoleUI(threading.Thread):
|
|||||||
self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True)
|
self.showMessage(getMessage("syncplay-version-notification").format(syncplay.version), True)
|
||||||
self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True)
|
self.showMessage(getMessage("more-info-notification").format(syncplay.projectURL), True)
|
||||||
|
|
||||||
|
def getUserlist(self):
|
||||||
|
self._syncplayClient.getUserList()
|
||||||
@ -16,10 +16,27 @@ import os
|
|||||||
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
|
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
|
from syncplay.ui.consoleUI import ConsoleUI
|
||||||
if isMacOS() and IsPySide:
|
if isMacOS() and IsPySide:
|
||||||
from Foundation import NSURL
|
from Foundation import NSURL
|
||||||
lastCheckedForUpdates = None
|
lastCheckedForUpdates = None
|
||||||
|
|
||||||
|
class ConsoleInGUI(ConsoleUI):
|
||||||
|
def showMessage(self, message, noTimestamp=False):
|
||||||
|
self._syncplayClient.ui.showMessage(message, True)
|
||||||
|
|
||||||
|
def showDebugMessage(self, message):
|
||||||
|
self._syncplayClient.ui.showDebugMessage(message)
|
||||||
|
|
||||||
|
def showErrorMessage(self, message, criticalerror=False):
|
||||||
|
self._syncplayClient.ui.showErrorMessage(message, criticalerror)
|
||||||
|
|
||||||
|
def updateRoomName(self, room=""):
|
||||||
|
self._syncplayClient.ui.updateRoomName(room)
|
||||||
|
|
||||||
|
def getUserlist(self):
|
||||||
|
self._syncplayClient.showUserList(self)
|
||||||
|
|
||||||
class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
|
class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QStyledItemDelegate.__init__(self)
|
QtWidgets.QStyledItemDelegate.__init__(self)
|
||||||
@ -84,8 +101,7 @@ class UserlistItemDelegate(QtWidgets.QStyledItemDelegate):
|
|||||||
QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
|
QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex)
|
||||||
|
|
||||||
class AboutDialog(QtWidgets.QDialog):
|
class AboutDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
def __init__(self, parent=None):
|
|
||||||
super(AboutDialog, self).__init__(parent)
|
super(AboutDialog, self).__init__(parent)
|
||||||
if isMacOS():
|
if isMacOS():
|
||||||
self.setWindowTitle("")
|
self.setWindowTitle("")
|
||||||
@ -120,17 +136,17 @@ class AboutDialog(QtWidgets.QDialog):
|
|||||||
self.setSizeGripEnabled(False)
|
self.setSizeGripEnabled(False)
|
||||||
self.setLayout(aboutLayout)
|
self.setLayout(aboutLayout)
|
||||||
|
|
||||||
def openLicense(self):
|
def openLicense(self):
|
||||||
if isWindows():
|
if isWindows():
|
||||||
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"license.rtf"))
|
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"license.rtf"))
|
||||||
else:
|
else:
|
||||||
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"license.rtf"))
|
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"license.rtf"))
|
||||||
|
|
||||||
def openDependencies(self):
|
def openDependencies(self):
|
||||||
if isWindows():
|
if isWindows():
|
||||||
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"third-party-notices.rtf"))
|
QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + u"third-party-notices.rtf"))
|
||||||
else:
|
else:
|
||||||
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"third-party-notices.rtf"))
|
QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + u"third-party-notices.rtf"))
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow):
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
insertPosition = None
|
insertPosition = None
|
||||||
@ -347,6 +363,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
def addClient(self, client):
|
def addClient(self, client):
|
||||||
self._syncplayClient = client
|
self._syncplayClient = client
|
||||||
|
if self.console:
|
||||||
|
self.console.addClient(client)
|
||||||
self.roomInput.setText(self._syncplayClient.getRoom())
|
self.roomInput.setText(self._syncplayClient.getRoom())
|
||||||
self.config = self._syncplayClient.getConfig()
|
self.config = self._syncplayClient.getConfig()
|
||||||
try:
|
try:
|
||||||
@ -385,6 +403,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.chatInput.setReadOnly(True)
|
self.chatInput.setReadOnly(True)
|
||||||
if not featureList["sharedPlaylists"]:
|
if not featureList["sharedPlaylists"]:
|
||||||
self.playlistGroup.setEnabled(False)
|
self.playlistGroup.setEnabled(False)
|
||||||
|
self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH)
|
||||||
|
self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH)
|
||||||
|
|
||||||
def showMessage(self, message, noTimestamp=False):
|
def showMessage(self, message, noTimestamp=False):
|
||||||
message = unicode(message)
|
message = unicode(message)
|
||||||
@ -1126,10 +1146,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self._syncplayClient.playlist.changePlaylist(newPlaylist)
|
self._syncplayClient.playlist.changePlaylist(newPlaylist)
|
||||||
self._syncplayClient.fileSwitch.updateInfo()
|
self._syncplayClient.fileSwitch.updateInfo()
|
||||||
|
|
||||||
|
def executeCommand(self, command):
|
||||||
|
self.showMessage(u"/{}".format(command))
|
||||||
|
self.console.executeCommand(command)
|
||||||
|
|
||||||
def sendChatMessage(self):
|
def sendChatMessage(self):
|
||||||
if self.chatInput.text() <> "":
|
chatText = self.chatInput.text()
|
||||||
self._syncplayClient.sendChat(self.chatInput.text())
|
self.chatInput.setText("")
|
||||||
self.chatInput.setText("")
|
if chatText <> "":
|
||||||
|
if chatText[:1] == "/" and chatText <> "/":
|
||||||
|
command = chatText[1:]
|
||||||
|
if command and command[:1] == "/":
|
||||||
|
chatText = chatText[1:]
|
||||||
|
else:
|
||||||
|
self.executeCommand(command)
|
||||||
|
return
|
||||||
|
self._syncplayClient.sendChat(chatText)
|
||||||
|
|
||||||
def addTopLayout(self, window):
|
def addTopLayout(self, window):
|
||||||
window.topSplit = self.topSplitter(Qt.Horizontal, self)
|
window.topSplit = self.topSplitter(Qt.Horizontal, self)
|
||||||
@ -1705,6 +1737,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(MainWindow, self).__init__()
|
super(MainWindow, self).__init__()
|
||||||
|
self.console = ConsoleInGUI()
|
||||||
|
self.console.setDaemon(True)
|
||||||
self.newWatchlist = []
|
self.newWatchlist = []
|
||||||
self.publicServerList = []
|
self.publicServerList = []
|
||||||
self.lastCheckedForUpdates = None
|
self.lastCheckedForUpdates = None
|
||||||
|
|||||||
@ -130,6 +130,13 @@ def formatSize (bytes, precise=False):
|
|||||||
def isASCII(s):
|
def isASCII(s):
|
||||||
return all(ord(c) < 128 for c in s)
|
return all(ord(c) < 128 for c in s)
|
||||||
|
|
||||||
|
def findResourcePath(resourceName):
|
||||||
|
if resourceName == "syncplay.lua":
|
||||||
|
resourcePath = os.path.join(findWorkingDir(), "lua", "intf" , "resources", resourceName)
|
||||||
|
else:
|
||||||
|
resourcePath = os.path.join(findWorkingDir(),"resources", resourceName)
|
||||||
|
return resourcePath
|
||||||
|
|
||||||
def findWorkingDir():
|
def findWorkingDir():
|
||||||
frozen = getattr(sys, 'frozen', '')
|
frozen = getattr(sys, 'frozen', '')
|
||||||
if not frozen:
|
if not frozen:
|
||||||
@ -152,6 +159,14 @@ def getResourcesPath():
|
|||||||
resourcespath = getResourcesPath()
|
resourcespath = getResourcesPath()
|
||||||
posixresourcespath = findWorkingDir().replace(u"\\","/") + u"/resources/"
|
posixresourcespath = findWorkingDir().replace(u"\\","/") + u"/resources/"
|
||||||
|
|
||||||
|
def getDefaultMonospaceFont():
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
return constants.DEFAULT_WINDOWS_MONOSPACE_FONT
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
return constants.DEFAULT_OSX_MONOSPACE_FONT
|
||||||
|
else:
|
||||||
|
return constants.FALLBACK_MONOSPACE_FONT
|
||||||
|
|
||||||
def limitedPowerset(s, minLength):
|
def limitedPowerset(s, minLength):
|
||||||
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in xrange(len(s), minLength, -1))
|
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in xrange(len(s), minLength, -1))
|
||||||
|
|
||||||
@ -189,20 +204,28 @@ def blackholeStdoutForFrozenWindow():
|
|||||||
|
|
||||||
def truncateText(unicodeText, maxLength):
|
def truncateText(unicodeText, maxLength):
|
||||||
try:
|
try:
|
||||||
unicodeText = unicodedata.normalize('NFC', unicodeText)
|
unicodeText = unicodeText.decode('utf-8')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
maxSaneLength= maxLength*5
|
return(unicode(unicodeText.encode("utf-8"), "utf-8", errors="ignore")[:maxLength])
|
||||||
if len(unicodeText) > maxSaneLength:
|
|
||||||
unicodeText = unicode(unicodeText.encode("utf-8")[:maxSaneLength], "utf-8", errors="ignore")
|
|
||||||
while len(unicodeText) > maxLength:
|
|
||||||
unicodeText = unicode(unicodeText.encode("utf-8")[:-1], "utf-8", errors="ignore")
|
|
||||||
return unicodeText
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def splitText(unicodeText, maxLength):
|
||||||
|
try:
|
||||||
|
unicodeText = unicodeText.decode('utf-8')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
unicodeText = unicode(unicodeText.encode("utf-8"), "utf-8", errors="ignore")
|
||||||
|
unicodeArray = [unicodeText[i:i + maxLength] for i in range(0, len(unicodeText), maxLength)]
|
||||||
|
return(unicodeArray)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return [""]
|
||||||
|
|
||||||
# Relate to file hashing / difference checking:
|
# Relate to file hashing / difference checking:
|
||||||
|
|
||||||
def stripfilename(filename, stripURL):
|
def stripfilename(filename, stripURL):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user