.',
+ list[i]
+ );
+ }
+ }
+ addAttr(el, name, JSON.stringify(value), list[i]);
+ // #6887 firefox doesn't update muted state if set via attribute
+ // even immediately after element creation
+ if (!el.component &&
+ name === 'muted' &&
+ platformMustUseProp(el.tag, el.attrsMap.type, name)) {
+ addProp(el, name, 'true', list[i]);
+ }
+ }
+ }
+ }
+
+ function checkInFor (el) {
+ var parent = el;
+ while (parent) {
+ if (parent.for !== undefined) {
+ return true
+ }
+ parent = parent.parent;
+ }
+ return false
+ }
+
+ function parseModifiers (name) {
+ var match = name.match(modifierRE);
+ if (match) {
+ var ret = {};
+ match.forEach(function (m) { ret[m.slice(1)] = true; });
+ return ret
+ }
+ }
+
+ function makeAttrsMap (attrs) {
+ var map = {};
+ for (var i = 0, l = attrs.length; i < l; i++) {
+ if (
+ map[attrs[i].name] && !isIE && !isEdge
+ ) {
+ warn$2('duplicate attribute: ' + attrs[i].name, attrs[i]);
+ }
+ map[attrs[i].name] = attrs[i].value;
+ }
+ return map
+ }
+
+ // for script (e.g. type="x/template") or style, do not decode content
+ function isTextTag (el) {
+ return el.tag === 'script' || el.tag === 'style'
+ }
+
+ function isForbiddenTag (el) {
+ return (
+ el.tag === 'style' ||
+ (el.tag === 'script' && (
+ !el.attrsMap.type ||
+ el.attrsMap.type === 'text/javascript'
+ ))
+ )
+ }
+
+ var ieNSBug = /^xmlns:NS\d+/;
+ var ieNSPrefix = /^NS\d+:/;
+
+ /* istanbul ignore next */
+ function guardIESVGBug (attrs) {
+ var res = [];
+ for (var i = 0; i < attrs.length; i++) {
+ var attr = attrs[i];
+ if (!ieNSBug.test(attr.name)) {
+ attr.name = attr.name.replace(ieNSPrefix, '');
+ res.push(attr);
+ }
+ }
+ return res
+ }
+
+ function checkForAliasModel (el, value) {
+ var _el = el;
+ while (_el) {
+ if (_el.for && _el.alias === value) {
+ warn$2(
+ "<" + (el.tag) + " v-model=\"" + value + "\">: " +
+ "You are binding v-model directly to a v-for iteration alias. " +
+ "This will not be able to modify the v-for source array because " +
+ "writing to the alias is like modifying a function local variable. " +
+ "Consider using an array of objects and use v-model on an object property instead.",
+ el.rawAttrsMap['v-model']
+ );
+ }
+ _el = _el.parent;
+ }
+ }
+
+ /* */
+
+ function preTransformNode (el, options) {
+ if (el.tag === 'input') {
+ var map = el.attrsMap;
+ if (!map['v-model']) {
+ return
+ }
+
+ var typeBinding;
+ if (map[':type'] || map['v-bind:type']) {
+ typeBinding = getBindingAttr(el, 'type');
+ }
+ if (!map.type && !typeBinding && map['v-bind']) {
+ typeBinding = "(" + (map['v-bind']) + ").type";
+ }
+
+ if (typeBinding) {
+ var ifCondition = getAndRemoveAttr(el, 'v-if', true);
+ var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : "";
+ var hasElse = getAndRemoveAttr(el, 'v-else', true) != null;
+ var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true);
+ // 1. checkbox
+ var branch0 = cloneASTElement(el);
+ // process for on the main node
+ processFor(branch0);
+ addRawAttr(branch0, 'type', 'checkbox');
+ processElement(branch0, options);
+ branch0.processed = true; // prevent it from double-processed
+ branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra;
+ addIfCondition(branch0, {
+ exp: branch0.if,
+ block: branch0
+ });
+ // 2. add radio else-if condition
+ var branch1 = cloneASTElement(el);
+ getAndRemoveAttr(branch1, 'v-for', true);
+ addRawAttr(branch1, 'type', 'radio');
+ processElement(branch1, options);
+ addIfCondition(branch0, {
+ exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
+ block: branch1
+ });
+ // 3. other
+ var branch2 = cloneASTElement(el);
+ getAndRemoveAttr(branch2, 'v-for', true);
+ addRawAttr(branch2, ':type', typeBinding);
+ processElement(branch2, options);
+ addIfCondition(branch0, {
+ exp: ifCondition,
+ block: branch2
+ });
+
+ if (hasElse) {
+ branch0.else = true;
+ } else if (elseIfCondition) {
+ branch0.elseif = elseIfCondition;
+ }
+
+ return branch0
+ }
+ }
+ }
+
+ function cloneASTElement (el) {
+ return createASTElement(el.tag, el.attrsList.slice(), el.parent)
+ }
+
+ var model$1 = {
+ preTransformNode: preTransformNode
+ };
+
+ var modules$1 = [
+ klass$1,
+ style$1,
+ model$1
+ ];
+
+ /* */
+
+ function text (el, dir) {
+ if (dir.value) {
+ addProp(el, 'textContent', ("_s(" + (dir.value) + ")"), dir);
+ }
+ }
+
+ /* */
+
+ function html (el, dir) {
+ if (dir.value) {
+ addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"), dir);
+ }
+ }
+
+ var directives$1 = {
+ model: model,
+ text: text,
+ html: html
+ };
+
+ /* */
+
+ var baseOptions = {
+ expectHTML: true,
+ modules: modules$1,
+ directives: directives$1,
+ isPreTag: isPreTag,
+ isUnaryTag: isUnaryTag,
+ mustUseProp: mustUseProp,
+ canBeLeftOpenTag: canBeLeftOpenTag,
+ isReservedTag: isReservedTag,
+ getTagNamespace: getTagNamespace,
+ staticKeys: genStaticKeys(modules$1)
+ };
+
+ /* */
+
+ var isStaticKey;
+ var isPlatformReservedTag;
+
+ var genStaticKeysCached = cached(genStaticKeys$1);
+
+ /**
+ * Goal of the optimizer: walk the generated template AST tree
+ * and detect sub-trees that are purely static, i.e. parts of
+ * the DOM that never needs to change.
+ *
+ * Once we detect these sub-trees, we can:
+ *
+ * 1. Hoist them into constants, so that we no longer need to
+ * create fresh nodes for them on each re-render;
+ * 2. Completely skip them in the patching process.
+ */
+ function optimize (root, options) {
+ if (!root) { return }
+ isStaticKey = genStaticKeysCached(options.staticKeys || '');
+ isPlatformReservedTag = options.isReservedTag || no;
+ // first pass: mark all non-static nodes.
+ markStatic$1(root);
+ // second pass: mark static roots.
+ markStaticRoots(root, false);
+ }
+
+ function genStaticKeys$1 (keys) {
+ return makeMap(
+ 'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
+ (keys ? ',' + keys : '')
+ )
+ }
+
+ function markStatic$1 (node) {
+ node.static = isStatic(node);
+ if (node.type === 1) {
+ // do not make component slot content static. this avoids
+ // 1. components not able to mutate slot nodes
+ // 2. static slot content fails for hot-reloading
+ if (
+ !isPlatformReservedTag(node.tag) &&
+ node.tag !== 'slot' &&
+ node.attrsMap['inline-template'] == null
+ ) {
+ return
+ }
+ for (var i = 0, l = node.children.length; i < l; i++) {
+ var child = node.children[i];
+ markStatic$1(child);
+ if (!child.static) {
+ node.static = false;
+ }
+ }
+ if (node.ifConditions) {
+ for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
+ var block = node.ifConditions[i$1].block;
+ markStatic$1(block);
+ if (!block.static) {
+ node.static = false;
+ }
+ }
+ }
+ }
+ }
+
+ function markStaticRoots (node, isInFor) {
+ if (node.type === 1) {
+ if (node.static || node.once) {
+ node.staticInFor = isInFor;
+ }
+ // For a node to qualify as a static root, it should have children that
+ // are not just static text. Otherwise the cost of hoisting out will
+ // outweigh the benefits and it's better off to just always render it fresh.
+ if (node.static && node.children.length && !(
+ node.children.length === 1 &&
+ node.children[0].type === 3
+ )) {
+ node.staticRoot = true;
+ return
+ } else {
+ node.staticRoot = false;
+ }
+ if (node.children) {
+ for (var i = 0, l = node.children.length; i < l; i++) {
+ markStaticRoots(node.children[i], isInFor || !!node.for);
+ }
+ }
+ if (node.ifConditions) {
+ for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
+ markStaticRoots(node.ifConditions[i$1].block, isInFor);
+ }
+ }
+ }
+ }
+
+ function isStatic (node) {
+ if (node.type === 2) { // expression
+ return false
+ }
+ if (node.type === 3) { // text
+ return true
+ }
+ return !!(node.pre || (
+ !node.hasBindings && // no dynamic bindings
+ !node.if && !node.for && // not v-if or v-for or v-else
+ !isBuiltInTag(node.tag) && // not a built-in
+ isPlatformReservedTag(node.tag) && // not a component
+ !isDirectChildOfTemplateFor(node) &&
+ Object.keys(node).every(isStaticKey)
+ ))
+ }
+
+ function isDirectChildOfTemplateFor (node) {
+ while (node.parent) {
+ node = node.parent;
+ if (node.tag !== 'template') {
+ return false
+ }
+ if (node.for) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /* */
+
+ var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/;
+ var fnInvokeRE = /\([^)]*?\);*$/;
+ var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
+
+ // KeyboardEvent.keyCode aliases
+ var keyCodes = {
+ esc: 27,
+ tab: 9,
+ enter: 13,
+ space: 32,
+ up: 38,
+ left: 37,
+ right: 39,
+ down: 40,
+ 'delete': [8, 46]
+ };
+
+ // KeyboardEvent.key aliases
+ var keyNames = {
+ // #7880: IE11 and Edge use `Esc` for Escape key name.
+ esc: ['Esc', 'Escape'],
+ tab: 'Tab',
+ enter: 'Enter',
+ // #9112: IE11 uses `Spacebar` for Space key name.
+ space: [' ', 'Spacebar'],
+ // #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
+ up: ['Up', 'ArrowUp'],
+ left: ['Left', 'ArrowLeft'],
+ right: ['Right', 'ArrowRight'],
+ down: ['Down', 'ArrowDown'],
+ // #9112: IE11 uses `Del` for Delete key name.
+ 'delete': ['Backspace', 'Delete', 'Del']
+ };
+
+ // #4868: modifiers that prevent the execution of the listener
+ // need to explicitly return null so that we can determine whether to remove
+ // the listener for .once
+ var genGuard = function (condition) { return ("if(" + condition + ")return null;"); };
+
+ var modifierCode = {
+ stop: '$event.stopPropagation();',
+ prevent: '$event.preventDefault();',
+ self: genGuard("$event.target !== $event.currentTarget"),
+ ctrl: genGuard("!$event.ctrlKey"),
+ shift: genGuard("!$event.shiftKey"),
+ alt: genGuard("!$event.altKey"),
+ meta: genGuard("!$event.metaKey"),
+ left: genGuard("'button' in $event && $event.button !== 0"),
+ middle: genGuard("'button' in $event && $event.button !== 1"),
+ right: genGuard("'button' in $event && $event.button !== 2")
+ };
+
+ function genHandlers (
+ events,
+ isNative
+ ) {
+ var prefix = isNative ? 'nativeOn:' : 'on:';
+ var staticHandlers = "";
+ var dynamicHandlers = "";
+ for (var name in events) {
+ var handlerCode = genHandler(events[name]);
+ if (events[name] && events[name].dynamic) {
+ dynamicHandlers += name + "," + handlerCode + ",";
+ } else {
+ staticHandlers += "\"" + name + "\":" + handlerCode + ",";
+ }
+ }
+ staticHandlers = "{" + (staticHandlers.slice(0, -1)) + "}";
+ if (dynamicHandlers) {
+ return prefix + "_d(" + staticHandlers + ",[" + (dynamicHandlers.slice(0, -1)) + "])"
+ } else {
+ return prefix + staticHandlers
+ }
+ }
+
+ function genHandler (handler) {
+ if (!handler) {
+ return 'function(){}'
+ }
+
+ if (Array.isArray(handler)) {
+ return ("[" + (handler.map(function (handler) { return genHandler(handler); }).join(',')) + "]")
+ }
+
+ var isMethodPath = simplePathRE.test(handler.value);
+ var isFunctionExpression = fnExpRE.test(handler.value);
+ var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, ''));
+
+ if (!handler.modifiers) {
+ if (isMethodPath || isFunctionExpression) {
+ return handler.value
+ }
+ return ("function($event){" + (isFunctionInvocation ? ("return " + (handler.value)) : handler.value) + "}") // inline statement
+ } else {
+ var code = '';
+ var genModifierCode = '';
+ var keys = [];
+ for (var key in handler.modifiers) {
+ if (modifierCode[key]) {
+ genModifierCode += modifierCode[key];
+ // left/right
+ if (keyCodes[key]) {
+ keys.push(key);
+ }
+ } else if (key === 'exact') {
+ var modifiers = (handler.modifiers);
+ genModifierCode += genGuard(
+ ['ctrl', 'shift', 'alt', 'meta']
+ .filter(function (keyModifier) { return !modifiers[keyModifier]; })
+ .map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
+ .join('||')
+ );
+ } else {
+ keys.push(key);
+ }
+ }
+ if (keys.length) {
+ code += genKeyFilter(keys);
+ }
+ // Make sure modifiers like prevent and stop get executed after key filtering
+ if (genModifierCode) {
+ code += genModifierCode;
+ }
+ var handlerCode = isMethodPath
+ ? ("return " + (handler.value) + "($event)")
+ : isFunctionExpression
+ ? ("return (" + (handler.value) + ")($event)")
+ : isFunctionInvocation
+ ? ("return " + (handler.value))
+ : handler.value;
+ return ("function($event){" + code + handlerCode + "}")
+ }
+ }
+
+ function genKeyFilter (keys) {
+ return (
+ // make sure the key filters only apply to KeyboardEvents
+ // #9441: can't use 'keyCode' in $event because Chrome autofill fires fake
+ // key events that do not have keyCode property...
+ "if(!$event.type.indexOf('key')&&" +
+ (keys.map(genFilterCode).join('&&')) + ")return null;"
+ )
+ }
+
+ function genFilterCode (key) {
+ var keyVal = parseInt(key, 10);
+ if (keyVal) {
+ return ("$event.keyCode!==" + keyVal)
+ }
+ var keyCode = keyCodes[key];
+ var keyName = keyNames[key];
+ return (
+ "_k($event.keyCode," +
+ (JSON.stringify(key)) + "," +
+ (JSON.stringify(keyCode)) + "," +
+ "$event.key," +
+ "" + (JSON.stringify(keyName)) +
+ ")"
+ )
+ }
+
+ /* */
+
+ function on (el, dir) {
+ if (dir.modifiers) {
+ warn("v-on without argument does not support modifiers.");
+ }
+ el.wrapListeners = function (code) { return ("_g(" + code + "," + (dir.value) + ")"); };
+ }
+
+ /* */
+
+ function bind$1 (el, dir) {
+ el.wrapData = function (code) {
+ return ("_b(" + code + ",'" + (el.tag) + "'," + (dir.value) + "," + (dir.modifiers && dir.modifiers.prop ? 'true' : 'false') + (dir.modifiers && dir.modifiers.sync ? ',true' : '') + ")")
+ };
+ }
+
+ /* */
+
+ var baseDirectives = {
+ on: on,
+ bind: bind$1,
+ cloak: noop
+ };
+
+ /* */
+
+
+
+
+
+ var CodegenState = function CodegenState (options) {
+ this.options = options;
+ this.warn = options.warn || baseWarn;
+ this.transforms = pluckModuleFunction(options.modules, 'transformCode');
+ this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
+ this.directives = extend(extend({}, baseDirectives), options.directives);
+ var isReservedTag = options.isReservedTag || no;
+ this.maybeComponent = function (el) { return !!el.component || !isReservedTag(el.tag); };
+ this.onceId = 0;
+ this.staticRenderFns = [];
+ this.pre = false;
+ };
+
+
+
+ function generate (
+ ast,
+ options
+ ) {
+ var state = new CodegenState(options);
+ var code = ast ? genElement(ast, state) : '_c("div")';
+ return {
+ render: ("with(this){return " + code + "}"),
+ staticRenderFns: state.staticRenderFns
+ }
+ }
+
+ function genElement (el, state) {
+ if (el.parent) {
+ el.pre = el.pre || el.parent.pre;
+ }
+
+ if (el.staticRoot && !el.staticProcessed) {
+ return genStatic(el, state)
+ } else if (el.once && !el.onceProcessed) {
+ return genOnce(el, state)
+ } else if (el.for && !el.forProcessed) {
+ return genFor(el, state)
+ } else if (el.if && !el.ifProcessed) {
+ return genIf(el, state)
+ } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
+ return genChildren(el, state) || 'void 0'
+ } else if (el.tag === 'slot') {
+ return genSlot(el, state)
+ } else {
+ // component or element
+ var code;
+ if (el.component) {
+ code = genComponent(el.component, el, state);
+ } else {
+ var data;
+ if (!el.plain || (el.pre && state.maybeComponent(el))) {
+ data = genData$2(el, state);
+ }
+
+ var children = el.inlineTemplate ? null : genChildren(el, state, true);
+ code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
+ }
+ // module transforms
+ for (var i = 0; i < state.transforms.length; i++) {
+ code = state.transforms[i](el, code);
+ }
+ return code
+ }
+ }
+
+ // hoist static sub-trees out
+ function genStatic (el, state) {
+ el.staticProcessed = true;
+ // Some elements (templates) need to behave differently inside of a v-pre
+ // node. All pre nodes are static roots, so we can use this as a location to
+ // wrap a state change and reset it upon exiting the pre node.
+ var originalPreState = state.pre;
+ if (el.pre) {
+ state.pre = el.pre;
+ }
+ state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
+ state.pre = originalPreState;
+ return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
+ }
+
+ // v-once
+ function genOnce (el, state) {
+ el.onceProcessed = true;
+ if (el.if && !el.ifProcessed) {
+ return genIf(el, state)
+ } else if (el.staticInFor) {
+ var key = '';
+ var parent = el.parent;
+ while (parent) {
+ if (parent.for) {
+ key = parent.key;
+ break
+ }
+ parent = parent.parent;
+ }
+ if (!key) {
+ state.warn(
+ "v-once can only be used inside v-for that is keyed. ",
+ el.rawAttrsMap['v-once']
+ );
+ return genElement(el, state)
+ }
+ return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")
+ } else {
+ return genStatic(el, state)
+ }
+ }
+
+ function genIf (
+ el,
+ state,
+ altGen,
+ altEmpty
+ ) {
+ el.ifProcessed = true; // avoid recursion
+ return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
+ }
+
+ function genIfConditions (
+ conditions,
+ state,
+ altGen,
+ altEmpty
+ ) {
+ if (!conditions.length) {
+ return altEmpty || '_e()'
+ }
+
+ var condition = conditions.shift();
+ if (condition.exp) {
+ return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
+ } else {
+ return ("" + (genTernaryExp(condition.block)))
+ }
+
+ // v-if with v-once should generate code like (a)?_m(0):_m(1)
+ function genTernaryExp (el) {
+ return altGen
+ ? altGen(el, state)
+ : el.once
+ ? genOnce(el, state)
+ : genElement(el, state)
+ }
+ }
+
+ function genFor (
+ el,
+ state,
+ altGen,
+ altHelper
+ ) {
+ var exp = el.for;
+ var alias = el.alias;
+ var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
+ var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
+
+ if (state.maybeComponent(el) &&
+ el.tag !== 'slot' &&
+ el.tag !== 'template' &&
+ !el.key
+ ) {
+ state.warn(
+ "<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " +
+ "v-for should have explicit keys. " +
+ "See https://vuejs.org/guide/list.html#key for more info.",
+ el.rawAttrsMap['v-for'],
+ true /* tip */
+ );
+ }
+
+ el.forProcessed = true; // avoid recursion
+ return (altHelper || '_l') + "((" + exp + ")," +
+ "function(" + alias + iterator1 + iterator2 + "){" +
+ "return " + ((altGen || genElement)(el, state)) +
+ '})'
+ }
+
+ function genData$2 (el, state) {
+ var data = '{';
+
+ // directives first.
+ // directives may mutate the el's other properties before they are generated.
+ var dirs = genDirectives(el, state);
+ if (dirs) { data += dirs + ','; }
+
+ // key
+ if (el.key) {
+ data += "key:" + (el.key) + ",";
+ }
+ // ref
+ if (el.ref) {
+ data += "ref:" + (el.ref) + ",";
+ }
+ if (el.refInFor) {
+ data += "refInFor:true,";
+ }
+ // pre
+ if (el.pre) {
+ data += "pre:true,";
+ }
+ // record original tag name for components using "is" attribute
+ if (el.component) {
+ data += "tag:\"" + (el.tag) + "\",";
+ }
+ // module data generation functions
+ for (var i = 0; i < state.dataGenFns.length; i++) {
+ data += state.dataGenFns[i](el);
+ }
+ // attributes
+ if (el.attrs) {
+ data += "attrs:" + (genProps(el.attrs)) + ",";
+ }
+ // DOM props
+ if (el.props) {
+ data += "domProps:" + (genProps(el.props)) + ",";
+ }
+ // event handlers
+ if (el.events) {
+ data += (genHandlers(el.events, false)) + ",";
+ }
+ if (el.nativeEvents) {
+ data += (genHandlers(el.nativeEvents, true)) + ",";
+ }
+ // slot target
+ // only for non-scoped slots
+ if (el.slotTarget && !el.slotScope) {
+ data += "slot:" + (el.slotTarget) + ",";
+ }
+ // scoped slots
+ if (el.scopedSlots) {
+ data += (genScopedSlots(el, el.scopedSlots, state)) + ",";
+ }
+ // component v-model
+ if (el.model) {
+ data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
+ }
+ // inline-template
+ if (el.inlineTemplate) {
+ var inlineTemplate = genInlineTemplate(el, state);
+ if (inlineTemplate) {
+ data += inlineTemplate + ",";
+ }
+ }
+ data = data.replace(/,$/, '') + '}';
+ // v-bind dynamic argument wrap
+ // v-bind with dynamic arguments must be applied using the same v-bind object
+ // merge helper so that class/style/mustUseProp attrs are handled correctly.
+ if (el.dynamicAttrs) {
+ data = "_b(" + data + ",\"" + (el.tag) + "\"," + (genProps(el.dynamicAttrs)) + ")";
+ }
+ // v-bind data wrap
+ if (el.wrapData) {
+ data = el.wrapData(data);
+ }
+ // v-on data wrap
+ if (el.wrapListeners) {
+ data = el.wrapListeners(data);
+ }
+ return data
+ }
+
+ function genDirectives (el, state) {
+ var dirs = el.directives;
+ if (!dirs) { return }
+ var res = 'directives:[';
+ var hasRuntime = false;
+ var i, l, dir, needRuntime;
+ for (i = 0, l = dirs.length; i < l; i++) {
+ dir = dirs[i];
+ needRuntime = true;
+ var gen = state.directives[dir.name];
+ if (gen) {
+ // compile-time directive that manipulates AST.
+ // returns true if it also needs a runtime counterpart.
+ needRuntime = !!gen(el, dir, state.warn);
+ }
+ if (needRuntime) {
+ hasRuntime = true;
+ res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:" + (dir.isDynamicArg ? dir.arg : ("\"" + (dir.arg) + "\""))) : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
+ }
+ }
+ if (hasRuntime) {
+ return res.slice(0, -1) + ']'
+ }
+ }
+
+ function genInlineTemplate (el, state) {
+ var ast = el.children[0];
+ if (el.children.length !== 1 || ast.type !== 1) {
+ state.warn(
+ 'Inline-template components must have exactly one child element.',
+ { start: el.start }
+ );
+ }
+ if (ast && ast.type === 1) {
+ var inlineRenderFns = generate(ast, state.options);
+ return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}")
+ }
+ }
+
+ function genScopedSlots (
+ el,
+ slots,
+ state
+ ) {
+ // by default scoped slots are considered "stable", this allows child
+ // components with only scoped slots to skip forced updates from parent.
+ // but in some cases we have to bail-out of this optimization
+ // for example if the slot contains dynamic names, has v-if or v-for on them...
+ var needsForceUpdate = el.for || Object.keys(slots).some(function (key) {
+ var slot = slots[key];
+ return (
+ slot.slotTargetDynamic ||
+ slot.if ||
+ slot.for ||
+ containsSlotChild(slot) // is passing down slot from parent which may be dynamic
+ )
+ });
+
+ // #9534: if a component with scoped slots is inside a conditional branch,
+ // it's possible for the same component to be reused but with different
+ // compiled slot content. To avoid that, we generate a unique key based on
+ // the generated code of all the slot contents.
+ var needsKey = !!el.if;
+
+ // OR when it is inside another scoped slot or v-for (the reactivity may be
+ // disconnected due to the intermediate scope variable)
+ // #9438, #9506
+ // TODO: this can be further optimized by properly analyzing in-scope bindings
+ // and skip force updating ones that do not actually use scope variables.
+ if (!needsForceUpdate) {
+ var parent = el.parent;
+ while (parent) {
+ if (
+ (parent.slotScope && parent.slotScope !== emptySlotScopeToken) ||
+ parent.for
+ ) {
+ needsForceUpdate = true;
+ break
+ }
+ if (parent.if) {
+ needsKey = true;
+ }
+ parent = parent.parent;
+ }
+ }
+
+ var generatedSlots = Object.keys(slots)
+ .map(function (key) { return genScopedSlot(slots[key], state); })
+ .join(',');
+
+ return ("scopedSlots:_u([" + generatedSlots + "]" + (needsForceUpdate ? ",null,true" : "") + (!needsForceUpdate && needsKey ? (",null,false," + (hash(generatedSlots))) : "") + ")")
+ }
+
+ function hash(str) {
+ var hash = 5381;
+ var i = str.length;
+ while(i) {
+ hash = (hash * 33) ^ str.charCodeAt(--i);
+ }
+ return hash >>> 0
+ }
+
+ function containsSlotChild (el) {
+ if (el.type === 1) {
+ if (el.tag === 'slot') {
+ return true
+ }
+ return el.children.some(containsSlotChild)
+ }
+ return false
+ }
+
+ function genScopedSlot (
+ el,
+ state
+ ) {
+ var isLegacySyntax = el.attrsMap['slot-scope'];
+ if (el.if && !el.ifProcessed && !isLegacySyntax) {
+ return genIf(el, state, genScopedSlot, "null")
+ }
+ if (el.for && !el.forProcessed) {
+ return genFor(el, state, genScopedSlot)
+ }
+ var slotScope = el.slotScope === emptySlotScopeToken
+ ? ""
+ : String(el.slotScope);
+ var fn = "function(" + slotScope + "){" +
+ "return " + (el.tag === 'template'
+ ? el.if && isLegacySyntax
+ ? ("(" + (el.if) + ")?" + (genChildren(el, state) || 'undefined') + ":undefined")
+ : genChildren(el, state) || 'undefined'
+ : genElement(el, state)) + "}";
+ // reverse proxy v-slot without scope on this.$slots
+ var reverseProxy = slotScope ? "" : ",proxy:true";
+ return ("{key:" + (el.slotTarget || "\"default\"") + ",fn:" + fn + reverseProxy + "}")
+ }
+
+ function genChildren (
+ el,
+ state,
+ checkSkip,
+ altGenElement,
+ altGenNode
+ ) {
+ var children = el.children;
+ if (children.length) {
+ var el$1 = children[0];
+ // optimize single v-for
+ if (children.length === 1 &&
+ el$1.for &&
+ el$1.tag !== 'template' &&
+ el$1.tag !== 'slot'
+ ) {
+ var normalizationType = checkSkip
+ ? state.maybeComponent(el$1) ? ",1" : ",0"
+ : "";
+ return ("" + ((altGenElement || genElement)(el$1, state)) + normalizationType)
+ }
+ var normalizationType$1 = checkSkip
+ ? getNormalizationType(children, state.maybeComponent)
+ : 0;
+ var gen = altGenNode || genNode;
+ return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType$1 ? ("," + normalizationType$1) : ''))
+ }
+ }
+
+ // determine the normalization needed for the children array.
+ // 0: no normalization needed
+ // 1: simple normalization needed (possible 1-level deep nested array)
+ // 2: full normalization needed
+ function getNormalizationType (
+ children,
+ maybeComponent
+ ) {
+ var res = 0;
+ for (var i = 0; i < children.length; i++) {
+ var el = children[i];
+ if (el.type !== 1) {
+ continue
+ }
+ if (needsNormalization(el) ||
+ (el.ifConditions && el.ifConditions.some(function (c) { return needsNormalization(c.block); }))) {
+ res = 2;
+ break
+ }
+ if (maybeComponent(el) ||
+ (el.ifConditions && el.ifConditions.some(function (c) { return maybeComponent(c.block); }))) {
+ res = 1;
+ }
+ }
+ return res
+ }
+
+ function needsNormalization (el) {
+ return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
+ }
+
+ function genNode (node, state) {
+ if (node.type === 1) {
+ return genElement(node, state)
+ } else if (node.type === 3 && node.isComment) {
+ return genComment(node)
+ } else {
+ return genText(node)
+ }
+ }
+
+ function genText (text) {
+ return ("_v(" + (text.type === 2
+ ? text.expression // no need for () because already wrapped in _s()
+ : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
+ }
+
+ function genComment (comment) {
+ return ("_e(" + (JSON.stringify(comment.text)) + ")")
+ }
+
+ function genSlot (el, state) {
+ var slotName = el.slotName || '"default"';
+ var children = genChildren(el, state);
+ var res = "_t(" + slotName + (children ? ("," + children) : '');
+ var attrs = el.attrs || el.dynamicAttrs
+ ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(function (attr) { return ({
+ // slot props are camelized
+ name: camelize(attr.name),
+ value: attr.value,
+ dynamic: attr.dynamic
+ }); }))
+ : null;
+ var bind$$1 = el.attrsMap['v-bind'];
+ if ((attrs || bind$$1) && !children) {
+ res += ",null";
+ }
+ if (attrs) {
+ res += "," + attrs;
+ }
+ if (bind$$1) {
+ res += (attrs ? '' : ',null') + "," + bind$$1;
+ }
+ return res + ')'
+ }
+
+ // componentName is el.component, take it as argument to shun flow's pessimistic refinement
+ function genComponent (
+ componentName,
+ el,
+ state
+ ) {
+ var children = el.inlineTemplate ? null : genChildren(el, state, true);
+ return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
+ }
+
+ function genProps (props) {
+ var staticProps = "";
+ var dynamicProps = "";
+ for (var i = 0; i < props.length; i++) {
+ var prop = props[i];
+ var value = transformSpecialNewlines(prop.value);
+ if (prop.dynamic) {
+ dynamicProps += (prop.name) + "," + value + ",";
+ } else {
+ staticProps += "\"" + (prop.name) + "\":" + value + ",";
+ }
+ }
+ staticProps = "{" + (staticProps.slice(0, -1)) + "}";
+ if (dynamicProps) {
+ return ("_d(" + staticProps + ",[" + (dynamicProps.slice(0, -1)) + "])")
+ } else {
+ return staticProps
+ }
+ }
+
+ // #3895, #4268
+ function transformSpecialNewlines (text) {
+ return text
+ .replace(/\u2028/g, '\\u2028')
+ .replace(/\u2029/g, '\\u2029')
+ }
+
+ /* */
+
+
+
+ // these keywords should not appear inside expressions, but operators like
+ // typeof, instanceof and in are allowed
+ var prohibitedKeywordRE = new RegExp('\\b' + (
+ 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
+ 'super,throw,while,yield,delete,export,import,return,switch,default,' +
+ 'extends,finally,continue,debugger,function,arguments'
+ ).split(',').join('\\b|\\b') + '\\b');
+
+ // these unary operators should not be used as property/method names
+ var unaryOperatorsRE = new RegExp('\\b' + (
+ 'delete,typeof,void'
+ ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
+
+ // strip strings in expressions
+ var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
+
+ // detect problematic expressions in a template
+ function detectErrors (ast, warn) {
+ if (ast) {
+ checkNode(ast, warn);
+ }
+ }
+
+ function checkNode (node, warn) {
+ if (node.type === 1) {
+ for (var name in node.attrsMap) {
+ if (dirRE.test(name)) {
+ var value = node.attrsMap[name];
+ if (value) {
+ var range = node.rawAttrsMap[name];
+ if (name === 'v-for') {
+ checkFor(node, ("v-for=\"" + value + "\""), warn, range);
+ } else if (name === 'v-slot' || name[0] === '#') {
+ checkFunctionParameterExpression(value, (name + "=\"" + value + "\""), warn, range);
+ } else if (onRE.test(name)) {
+ checkEvent(value, (name + "=\"" + value + "\""), warn, range);
+ } else {
+ checkExpression(value, (name + "=\"" + value + "\""), warn, range);
+ }
+ }
+ }
+ }
+ if (node.children) {
+ for (var i = 0; i < node.children.length; i++) {
+ checkNode(node.children[i], warn);
+ }
+ }
+ } else if (node.type === 2) {
+ checkExpression(node.expression, node.text, warn, node);
+ }
+ }
+
+ function checkEvent (exp, text, warn, range) {
+ var stripped = exp.replace(stripStringRE, '');
+ var keywordMatch = stripped.match(unaryOperatorsRE);
+ if (keywordMatch && stripped.charAt(keywordMatch.index - 1) !== '$') {
+ warn(
+ "avoid using JavaScript unary operator as property name: " +
+ "\"" + (keywordMatch[0]) + "\" in expression " + (text.trim()),
+ range
+ );
+ }
+ checkExpression(exp, text, warn, range);
+ }
+
+ function checkFor (node, text, warn, range) {
+ checkExpression(node.for || '', text, warn, range);
+ checkIdentifier(node.alias, 'v-for alias', text, warn, range);
+ checkIdentifier(node.iterator1, 'v-for iterator', text, warn, range);
+ checkIdentifier(node.iterator2, 'v-for iterator', text, warn, range);
+ }
+
+ function checkIdentifier (
+ ident,
+ type,
+ text,
+ warn,
+ range
+ ) {
+ if (typeof ident === 'string') {
+ try {
+ new Function(("var " + ident + "=_"));
+ } catch (e) {
+ warn(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim())), range);
+ }
+ }
+ }
+
+ function checkExpression (exp, text, warn, range) {
+ try {
+ new Function(("return " + exp));
+ } catch (e) {
+ var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
+ if (keywordMatch) {
+ warn(
+ "avoid using JavaScript keyword as property name: " +
+ "\"" + (keywordMatch[0]) + "\"\n Raw expression: " + (text.trim()),
+ range
+ );
+ } else {
+ warn(
+ "invalid expression: " + (e.message) + " in\n\n" +
+ " " + exp + "\n\n" +
+ " Raw expression: " + (text.trim()) + "\n",
+ range
+ );
+ }
+ }
+ }
+
+ function checkFunctionParameterExpression (exp, text, warn, range) {
+ try {
+ new Function(exp, '');
+ } catch (e) {
+ warn(
+ "invalid function parameter expression: " + (e.message) + " in\n\n" +
+ " " + exp + "\n\n" +
+ " Raw expression: " + (text.trim()) + "\n",
+ range
+ );
+ }
+ }
+
+ /* */
+
+ var range = 2;
+
+ function generateCodeFrame (
+ source,
+ start,
+ end
+ ) {
+ if ( start === void 0 ) start = 0;
+ if ( end === void 0 ) end = source.length;
+
+ var lines = source.split(/\r?\n/);
+ var count = 0;
+ var res = [];
+ for (var i = 0; i < lines.length; i++) {
+ count += lines[i].length + 1;
+ if (count >= start) {
+ for (var j = i - range; j <= i + range || end > count; j++) {
+ if (j < 0 || j >= lines.length) { continue }
+ res.push(("" + (j + 1) + (repeat$1(" ", 3 - String(j + 1).length)) + "| " + (lines[j])));
+ var lineLength = lines[j].length;
+ if (j === i) {
+ // push underline
+ var pad = start - (count - lineLength) + 1;
+ var length = end > count ? lineLength - pad : end - start;
+ res.push(" | " + repeat$1(" ", pad) + repeat$1("^", length));
+ } else if (j > i) {
+ if (end > count) {
+ var length$1 = Math.min(end - count, lineLength);
+ res.push(" | " + repeat$1("^", length$1));
+ }
+ count += lineLength + 1;
+ }
+ }
+ break
+ }
+ }
+ return res.join('\n')
+ }
+
+ function repeat$1 (str, n) {
+ var result = '';
+ if (n > 0) {
+ while (true) { // eslint-disable-line
+ if (n & 1) { result += str; }
+ n >>>= 1;
+ if (n <= 0) { break }
+ str += str;
+ }
+ }
+ return result
+ }
+
+ /* */
+
+
+
+ function createFunction (code, errors) {
+ try {
+ return new Function(code)
+ } catch (err) {
+ errors.push({ err: err, code: code });
+ return noop
+ }
+ }
+
+ function createCompileToFunctionFn (compile) {
+ var cache = Object.create(null);
+
+ return function compileToFunctions (
+ template,
+ options,
+ vm
+ ) {
+ options = extend({}, options);
+ var warn$$1 = options.warn || warn;
+ delete options.warn;
+
+ /* istanbul ignore if */
+ {
+ // detect possible CSP restriction
+ try {
+ new Function('return 1');
+ } catch (e) {
+ if (e.toString().match(/unsafe-eval|CSP/)) {
+ warn$$1(
+ 'It seems you are using the standalone build of Vue.js in an ' +
+ 'environment with Content Security Policy that prohibits unsafe-eval. ' +
+ 'The template compiler cannot work in this environment. Consider ' +
+ 'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
+ 'templates into render functions.'
+ );
+ }
+ }
+ }
+
+ // check cache
+ var key = options.delimiters
+ ? String(options.delimiters) + template
+ : template;
+ if (cache[key]) {
+ return cache[key]
+ }
+
+ // compile
+ var compiled = compile(template, options);
+
+ // check compilation errors/tips
+ {
+ if (compiled.errors && compiled.errors.length) {
+ if (options.outputSourceRange) {
+ compiled.errors.forEach(function (e) {
+ warn$$1(
+ "Error compiling template:\n\n" + (e.msg) + "\n\n" +
+ generateCodeFrame(template, e.start, e.end),
+ vm
+ );
+ });
+ } else {
+ warn$$1(
+ "Error compiling template:\n\n" + template + "\n\n" +
+ compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
+ vm
+ );
+ }
+ }
+ if (compiled.tips && compiled.tips.length) {
+ if (options.outputSourceRange) {
+ compiled.tips.forEach(function (e) { return tip(e.msg, vm); });
+ } else {
+ compiled.tips.forEach(function (msg) { return tip(msg, vm); });
+ }
+ }
+ }
+
+ // turn code into functions
+ var res = {};
+ var fnGenErrors = [];
+ res.render = createFunction(compiled.render, fnGenErrors);
+ res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
+ return createFunction(code, fnGenErrors)
+ });
+
+ // check function generation errors.
+ // this should only happen if there is a bug in the compiler itself.
+ // mostly for codegen development use
+ /* istanbul ignore if */
+ {
+ if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
+ warn$$1(
+ "Failed to generate render function:\n\n" +
+ fnGenErrors.map(function (ref) {
+ var err = ref.err;
+ var code = ref.code;
+
+ return ((err.toString()) + " in\n\n" + code + "\n");
+ }).join('\n'),
+ vm
+ );
+ }
+ }
+
+ return (cache[key] = res)
+ }
+ }
+
+ /* */
+
+ function createCompilerCreator (baseCompile) {
+ return function createCompiler (baseOptions) {
+ function compile (
+ template,
+ options
+ ) {
+ var finalOptions = Object.create(baseOptions);
+ var errors = [];
+ var tips = [];
+
+ var warn = function (msg, range, tip) {
+ (tip ? tips : errors).push(msg);
+ };
+
+ if (options) {
+ if (options.outputSourceRange) {
+ // $flow-disable-line
+ var leadingSpaceLength = template.match(/^\s*/)[0].length;
+
+ warn = function (msg, range, tip) {
+ var data = { msg: msg };
+ if (range) {
+ if (range.start != null) {
+ data.start = range.start + leadingSpaceLength;
+ }
+ if (range.end != null) {
+ data.end = range.end + leadingSpaceLength;
+ }
+ }
+ (tip ? tips : errors).push(data);
+ };
+ }
+ // merge custom modules
+ if (options.modules) {
+ finalOptions.modules =
+ (baseOptions.modules || []).concat(options.modules);
+ }
+ // merge custom directives
+ if (options.directives) {
+ finalOptions.directives = extend(
+ Object.create(baseOptions.directives || null),
+ options.directives
+ );
+ }
+ // copy other options
+ for (var key in options) {
+ if (key !== 'modules' && key !== 'directives') {
+ finalOptions[key] = options[key];
+ }
+ }
+ }
+
+ finalOptions.warn = warn;
+
+ var compiled = baseCompile(template.trim(), finalOptions);
+ {
+ detectErrors(compiled.ast, warn);
+ }
+ compiled.errors = errors;
+ compiled.tips = tips;
+ return compiled
+ }
+
+ return {
+ compile: compile,
+ compileToFunctions: createCompileToFunctionFn(compile)
+ }
+ }
+ }
+
+ /* */
+
+ // `createCompilerCreator` allows creating compilers that use alternative
+ // parser/optimizer/codegen, e.g the SSR optimizing compiler.
+ // Here we just export a default compiler using the default parts.
+ var createCompiler = createCompilerCreator(function baseCompile (
+ template,
+ options
+ ) {
+ var ast = parse(template.trim(), options);
+ if (options.optimize !== false) {
+ optimize(ast, options);
+ }
+ var code = generate(ast, options);
+ return {
+ ast: ast,
+ render: code.render,
+ staticRenderFns: code.staticRenderFns
+ }
+ });
+
+ /* */
+
+ var ref$1 = createCompiler(baseOptions);
+ var compile = ref$1.compile;
+ var compileToFunctions = ref$1.compileToFunctions;
+
+ /* */
+
+ // check whether current browser encodes a char inside attribute values
+ var div;
+ function getShouldDecode (href) {
+ div = div || document.createElement('div');
+ div.innerHTML = href ? "
" : "";
+ return div.innerHTML.indexOf('
') > 0
+ }
+
+ // #3663: IE encodes newlines inside attribute values while other browsers don't
+ var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
+ // #6828: chrome encodes content in a[href]
+ var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
+
+ /* */
+
+ var idToTemplate = cached(function (id) {
+ var el = query(id);
+ return el && el.innerHTML
+ });
+
+ var mount = Vue.prototype.$mount;
+ Vue.prototype.$mount = function (
+ el,
+ hydrating
+ ) {
+ el = el && query(el);
+
+ /* istanbul ignore if */
+ if (el === document.body || el === document.documentElement) {
+ warn(
+ "Do not mount Vue to or - mount to normal elements instead."
+ );
+ return this
+ }
+
+ var options = this.$options;
+ // resolve template/el and convert to render function
+ if (!options.render) {
+ var template = options.template;
+ if (template) {
+ if (typeof template === 'string') {
+ if (template.charAt(0) === '#') {
+ template = idToTemplate(template);
+ /* istanbul ignore if */
+ if (!template) {
+ warn(
+ ("Template element not found or is empty: " + (options.template)),
+ this
+ );
+ }
+ }
+ } else if (template.nodeType) {
+ template = template.innerHTML;
+ } else {
+ {
+ warn('invalid template option:' + template, this);
+ }
+ return this
+ }
+ } else if (el) {
+ template = getOuterHTML(el);
+ }
+ if (template) {
+ /* istanbul ignore if */
+ if (config.performance && mark) {
+ mark('compile');
+ }
+
+ var ref = compileToFunctions(template, {
+ outputSourceRange: "development" !== 'production',
+ shouldDecodeNewlines: shouldDecodeNewlines,
+ shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
+ delimiters: options.delimiters,
+ comments: options.comments
+ }, this);
+ var render = ref.render;
+ var staticRenderFns = ref.staticRenderFns;
+ options.render = render;
+ options.staticRenderFns = staticRenderFns;
+
+ /* istanbul ignore if */
+ if (config.performance && mark) {
+ mark('compile end');
+ measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
+ }
+ }
+ }
+ return mount.call(this, el, hydrating)
+ };
+
+ /**
+ * Get outerHTML of elements, taking care
+ * of SVG elements in IE as well.
+ */
+ function getOuterHTML (el) {
+ if (el.outerHTML) {
+ return el.outerHTML
+ } else {
+ var container = document.createElement('div');
+ container.appendChild(el.cloneNode(true));
+ return container.innerHTML
+ }
+ }
+
+ Vue.compile = compileToFunctions;
+
+ return Vue;
+
+}));
diff --git a/gen-deb.in b/gen-deb.in
index 8abb4dc2..be81bcff 100755
--- a/gen-deb.in
+++ b/gen-deb.in
@@ -37,7 +37,7 @@ Package: sunshine
Architecture: amd64
Maintainer: @loki
Priority: optional
-Version: 0.10.1
+Version: 0.10.2
Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0, libboost-log1.67.0 | libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2
Description: Gamestream host for Moonlight
EOF
diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp
index 3e624809..71465958 100644
--- a/sunshine/confighttp.cpp
+++ b/sunshine/confighttp.cpp
@@ -211,6 +211,27 @@ void getWelcomePage(resp_https_t response, req_https_t request) {
response->write(header + content);
}
+void getBootstrapCss(resp_https_t response, req_https_t request) {
+ print_req(request);
+
+ std::string content = read_file(WEB_DIR "third_party/bootstrap.min.css");
+ response->write(content);
+}
+
+void getBootstrapJs(resp_https_t response, req_https_t request) {
+ print_req(request);
+
+ std::string content = read_file(WEB_DIR "third_party/bootstrap.bundle.min.js");
+ response->write(content);
+}
+
+void getVueJs(resp_https_t response, req_https_t request) {
+ print_req(request);
+
+ std::string content = read_file(WEB_DIR "third_party/vue.js");
+ response->write(content);
+}
+
void getApps(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
@@ -490,24 +511,27 @@ void start() {
ctx->use_certificate_chain_file(config::nvhttp.cert);
ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem);
https_server_t server { ctx, 0 };
- server.default_resource = not_found;
- server.resource["^/$"]["GET"] = getIndexPage;
- server.resource["^/pin$"]["GET"] = getPinPage;
- server.resource["^/apps$"]["GET"] = getAppsPage;
- server.resource["^/clients$"]["GET"] = getClientsPage;
- server.resource["^/config$"]["GET"] = getConfigPage;
- server.resource["^/password$"]["GET"] = getPasswordPage;
- server.resource["^/welcome$"]["GET"] = getWelcomePage;
- server.resource["^/api/pin"]["POST"] = savePin;
- server.resource["^/api/apps$"]["GET"] = getApps;
- server.resource["^/api/apps$"]["POST"] = saveApp;
- server.resource["^/api/config$"]["GET"] = getConfig;
- server.resource["^/api/config$"]["POST"] = saveConfig;
- server.resource["^/api/password$"]["POST"] = savePassword;
- server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
- server.config.reuse_address = true;
- server.config.address = "0.0.0.0"s;
- server.config.port = port_https;
+ server.default_resource = not_found;
+ server.resource["^/$"]["GET"] = getIndexPage;
+ server.resource["^/pin$"]["GET"] = getPinPage;
+ server.resource["^/apps$"]["GET"] = getAppsPage;
+ server.resource["^/clients$"]["GET"] = getClientsPage;
+ server.resource["^/config$"]["GET"] = getConfigPage;
+ server.resource["^/password$"]["GET"] = getPasswordPage;
+ server.resource["^/welcome$"]["GET"] = getWelcomePage;
+ server.resource["^/api/pin"]["POST"] = savePin;
+ server.resource["^/api/apps$"]["GET"] = getApps;
+ server.resource["^/api/apps$"]["POST"] = saveApp;
+ server.resource["^/api/config$"]["GET"] = getConfig;
+ server.resource["^/api/config$"]["POST"] = saveConfig;
+ server.resource["^/api/password$"]["POST"] = savePassword;
+ server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
+ server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss;
+ server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs;
+ server.resource["^/third_party/vue.js$"]["GET"] = getVueJs;
+ server.config.reuse_address = true;
+ server.config.address = "0.0.0.0"s;
+ server.config.port = port_https;
try {
server.bind();
diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp
index 1b0a2cad..2905db0d 100644
--- a/sunshine/platform/linux/graphics.cpp
+++ b/sunshine/platform/linux/graphics.cpp
@@ -9,12 +9,13 @@
// They aren't likely to change any time soon.
#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \
((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
+#define fourcc_mod_code(vendor, val) ((((uint64_t)vendor) << 56) | ((val)&0x00ffffffffffffffULL))
#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */
#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */
#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
#define DRM_FORMAT_XBGR8888 fourcc_code('X', 'B', '2', '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */
-
+#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1))
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl"
@@ -278,11 +279,28 @@ int init() {
} // namespace gbm
namespace egl {
-constexpr auto EGL_LINUX_DMA_BUF_EXT = 0x3270;
-constexpr auto EGL_LINUX_DRM_FOURCC_EXT = 0x3271;
-constexpr auto EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272;
-constexpr auto EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273;
-constexpr auto EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274;
+constexpr auto EGL_LINUX_DMA_BUF_EXT = 0x3270;
+constexpr auto EGL_LINUX_DRM_FOURCC_EXT = 0x3271;
+constexpr auto EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272;
+constexpr auto EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273;
+constexpr auto EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274;
+constexpr auto EGL_DMA_BUF_PLANE1_FD_EXT = 0x3275;
+constexpr auto EGL_DMA_BUF_PLANE1_OFFSET_EXT = 0x3276;
+constexpr auto EGL_DMA_BUF_PLANE1_PITCH_EXT = 0x3277;
+constexpr auto EGL_DMA_BUF_PLANE2_FD_EXT = 0x3278;
+constexpr auto EGL_DMA_BUF_PLANE2_OFFSET_EXT = 0x3279;
+constexpr auto EGL_DMA_BUF_PLANE2_PITCH_EXT = 0x327A;
+constexpr auto EGL_DMA_BUF_PLANE3_FD_EXT = 0x3440;
+constexpr auto EGL_DMA_BUF_PLANE3_OFFSET_EXT = 0x3441;
+constexpr auto EGL_DMA_BUF_PLANE3_PITCH_EXT = 0x3442;
+constexpr auto EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT = 0x3443;
+constexpr auto EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT = 0x3444;
+constexpr auto EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT = 0x3445;
+constexpr auto EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT = 0x3446;
+constexpr auto EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT = 0x3447;
+constexpr auto EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT = 0x3448;
+constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT = 0x3449;
+constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT = 0x344A;
bool fail() {
return eglGetError() != EGL_SUCCESS;
@@ -329,7 +347,6 @@ display_t make_display(util::Either
native_di
"EGL_KHR_create_context",
"EGL_KHR_surfaceless_context",
"EGL_EXT_image_dma_buf_import",
- // "EGL_KHR_image_pixmap"
};
for(auto ext : extensions) {
@@ -389,22 +406,87 @@ std::optional make_ctx(display_t::pointer display) {
return ctx;
}
-std::optional import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
- gl_drain_errors;
- EGLAttrib img_attr_planes[13] {
- EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_XRGB8888,
- EGL_WIDTH, xrgb.width,
- EGL_HEIGHT, xrgb.height,
- EGL_DMA_BUF_PLANE0_FD_EXT, xrgb.fd,
- EGL_DMA_BUF_PLANE0_OFFSET_EXT, xrgb.offset,
- EGL_DMA_BUF_PLANE0_PITCH_EXT, xrgb.pitch,
- EGL_NONE
- };
+struct plane_attr_t {
+ EGLAttrib fd;
+ EGLAttrib offset;
+ EGLAttrib pitch;
+ EGLAttrib lo;
+ EGLAttrib hi;
+};
+
+inline plane_attr_t get_plane(std::uint32_t plane_indice) {
+ switch(plane_indice) {
+ case 0:
+ return {
+ EGL_DMA_BUF_PLANE0_FD_EXT,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
+ };
+ case 1:
+ return {
+ EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
+ };
+ case 2:
+ return {
+ EGL_DMA_BUF_PLANE2_FD_EXT,
+ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE2_PITCH_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
+ };
+ case 3:
+ return {
+ EGL_DMA_BUF_PLANE3_FD_EXT,
+ EGL_DMA_BUF_PLANE3_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE3_PITCH_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT,
+ };
+ }
+
+ // Avoid warning
+ return {};
+}
+
+std::optional import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
+ EGLAttrib attribs[47];
+ int atti = 0;
+ attribs[atti++] = EGL_WIDTH;
+ attribs[atti++] = xrgb.width;
+ attribs[atti++] = EGL_HEIGHT;
+ attribs[atti++] = xrgb.height;
+ attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+ attribs[atti++] = xrgb.fourcc;
+
+ for(auto x = 0; x < xrgb.obj_count; ++x) {
+ auto plane_attr = get_plane(xrgb.plane_indices[x]);
+
+ attribs[atti++] = plane_attr.fd;
+ attribs[atti++] = xrgb.fds[x];
+ attribs[atti++] = plane_attr.offset;
+ attribs[atti++] = xrgb.offsets[x];
+ attribs[atti++] = plane_attr.pitch;
+ attribs[atti++] = xrgb.pitches[x];
+
+ if(xrgb.modifier && xrgb.modifier != DRM_FORMAT_MOD_INVALID) {
+ attribs[atti++] = plane_attr.lo;
+ attribs[atti++] = xrgb.modifier & 0xFFFFFFFF;
+ attribs[atti++] = plane_attr.hi;
+ attribs[atti++] = xrgb.modifier >> 32;
+ }
+ }
+ attribs[atti++] = EGL_NONE;
rgb_t rgb {
egl_display,
- eglCreateImage(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes),
+ eglCreateImage(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs),
gl::tex_t::make(1)
};
@@ -429,17 +511,17 @@ std::optional import_target(display_t::pointer egl_display, std::array), , {
});
struct surface_descriptor_t {
- int fd;
+ int obj_count;
int width;
int height;
- int offset;
- int pitch;
+ int fds[4];
+ std::uint32_t fourcc;
+ std::uint64_t modifier;
+ std::uint32_t pitches[4];
+ std::uint32_t offsets[4];
+ std::uint32_t plane_indices[4];
};
display_t make_display(util::Either native_display);
@@ -245,14 +249,19 @@ public:
// Allow cursor and the underlying image to be kept together
class img_descriptor_t : public cursor_t {
public:
- std::uint32_t format;
- std::uint32_t img_width, img_height;
- std::uint32_t obj_count;
- std::uint32_t strides[4];
- // std::uint32_t sizes[4];
- std::int32_t fds[4];
- std::uint32_t offsets[4];
- // std::uint32_t plane_indices[4];
+ ~img_descriptor_t() {
+ reset();
+ }
+
+ void reset() {
+ std::for_each_n(sd.fds, sd.obj_count, [](int fd) {
+ close(fd);
+ });
+
+ sd.obj_count = 0;
+ }
+
+ surface_descriptor_t sd;
// Increment sequence when new rgb_t needs to be created
std::uint64_t sequence;
@@ -295,4 +304,4 @@ public:
bool fail();
} // namespace egl
-#endif
\ No newline at end of file
+#endif
diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp
index 4984709b..1d1e1d1a 100644
--- a/sunshine/platform/linux/kmsgrab.cpp
+++ b/sunshine/platform/linux/kmsgrab.cpp
@@ -24,12 +24,56 @@ namespace fs = std::filesystem;
namespace platf {
namespace kms {
+
+class wrapper_fb {
+public:
+ wrapper_fb(drmModeFB *fb)
+ : fb { fb }, fb_id { fb->fb_id }, width { fb->width }, height { fb->height } {
+ pixel_format = DRM_FORMAT_XRGB8888;
+ modifier = DRM_FORMAT_MOD_INVALID;
+ std::fill_n(handles, 4, 0);
+ std::fill_n(pitches, 4, 0);
+ std::fill_n(offsets, 4, 0);
+ handles[0] = fb->handle;
+ pitches[0] = fb->pitch;
+ }
+
+ wrapper_fb(drmModeFB2 *fb2)
+ : fb2 { fb2 }, fb_id { fb2->fb_id }, width { fb2->width }, height { fb2->height } {
+ pixel_format = fb2->pixel_format;
+ modifier = fb2->modifier;
+
+ memcpy(handles, fb2->handles, sizeof(handles));
+ memcpy(pitches, fb2->pitches, sizeof(pitches));
+ memcpy(offsets, fb2->offsets, sizeof(offsets));
+ }
+
+ ~wrapper_fb() {
+ if(fb) {
+ drmModeFreeFB(fb);
+ }
+ else if(fb2) {
+ drmModeFreeFB2(fb2);
+ }
+ }
+
+ drmModeFB *fb = nullptr;
+ drmModeFB2 *fb2 = nullptr;
+ uint32_t fb_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t pixel_format;
+ uint64_t modifier;
+ uint32_t handles[4];
+ uint32_t pitches[4];
+ uint32_t offsets[4];
+};
+
using plane_res_t = util::safe_ptr;
using encoder_t = util::safe_ptr;
using res_t = util::safe_ptr;
using plane_t = util::safe_ptr;
-using fb_t = util::safe_ptr;
-using fb2_t = util::safe_ptr;
+using fb_t = std::unique_ptr;
using crtc_t = util::safe_ptr;
using obj_prop_t = util::safe_ptr;
using prop_t = util::safe_ptr;
@@ -182,11 +226,11 @@ public:
}
fb_t fb(plane_t::pointer plane) {
- return drmModeGetFB(fd.el, plane->fb_id);
- }
-
- fb2_t fb2(plane_t::pointer plane) {
- return drmModeGetFB2(fd.el, plane->fb_id);
+ auto fb = drmModeGetFB2(fd.el, plane->fb_id);
+ if(fb) {
+ return std::make_unique(fb);
+ }
+ return std::make_unique(drmModeGetFB(fd.el, plane->fb_id));
}
crtc_t crtc(std::uint32_t id) {
@@ -332,11 +376,7 @@ struct kms_img_t : public img_t {
}
};
-struct kms_vram_img_t : public egl::img_descriptor_t {
- file_t fb_fd;
-};
-
-void print(plane_t::pointer plane, fb2_t::pointer fb, crtc_t::pointer crtc) {
+void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
if(crtc) {
BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')';
BOOST_LOG(debug) << "crtc("sv << crtc->width << ", "sv << crtc->height << ')';
@@ -403,7 +443,7 @@ public:
continue;
}
- auto fb = card.fb2(plane.get());
+ auto fb = card.fb(plane.get());
if(!fb) {
BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno);
return -1;
@@ -415,10 +455,16 @@ public:
return -1;
}
- file_t fb_fd = card.handleFD(fb->handles[0]);
- if(fb_fd.el < 0) {
- BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno);
- return -1;
+ for(int i = 0; i < 4; ++i) {
+ if(!fb->handles[i]) {
+ break;
+ }
+
+ auto fb_fd = card.handleFD(fb->handles[i]);
+ if(fb_fd.el < 0) {
+ BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno);
+ continue;
+ }
}
BOOST_LOG(info) << "Found monitor for DRM screencasting"sv;
@@ -435,6 +481,8 @@ public:
return -1;
}
+ //TODO: surf_sd = fb->to_sd();
+
auto crct = card.crtc(plane->crtc_id);
kms::print(plane.get(), fb.get(), crct.get());
@@ -486,10 +534,10 @@ public:
return 0;
}
- inline capture_e refresh(file_t *file) {
+ inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd) {
plane_t plane = drmModeGetPlane(card.fd.el, plane_id);
- auto fb = card.fb2(plane.get());
+ auto fb = card.fb(plane.get());
if(!fb) {
BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno);
return capture_e::error;
@@ -501,20 +549,43 @@ public:
return capture_e::error;
}
- *file = card.handleFD(fb->handles[0]);
- if(file->el < 0) {
- BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno);
- return capture_e::error;
+ auto obj_count = 4;
+
+ int x = 0;
+ for(int y = 0; y < 4; ++y) {
+ if(!fb->handles[y]) {
+ // It's not clear wheter there could still be valid handles left.
+ // So, continue anyway.
+ // TODO: Is this redundent?
+ --obj_count;
+ continue;
+ }
+
+ file[x] = card.handleFD(fb->handles[x]);
+ if(file[x].el < 0) {
+ BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno);
+ return capture_e::error;
+ }
+
+ sd->fds[x] = file[x].el;
+ sd->offsets[x] = fb->offsets[y];
+ sd->pitches[x] = fb->pitches[y];
+ sd->plane_indices[x] = y;
}
+ sd->width = fb->width;
+ sd->height = fb->height;
+ sd->modifier = fb->modifier;
+ sd->fourcc = fb->pixel_format;
+ sd->obj_count = obj_count;
+
if(
fb->width != img_width ||
fb->height != img_height) {
return capture_e::reinit;
}
- pitch = fb->pitches[0];
- offset = fb->offsets[0];
+ ++x;
return capture_e::ok;
}
@@ -526,8 +597,6 @@ public:
int img_width, img_height;
int img_offset_x, img_offset_y;
- int pitch;
- int offset;
int plane_id;
card_t card;
@@ -613,21 +682,16 @@ public:
}
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
- file_t fb_fd;
+ file_t fb_fd[4];
- auto status = refresh(&fb_fd);
+ egl::surface_descriptor_t sd;
+
+ auto status = refresh(fb_fd, &sd);
if(status != capture_e::ok) {
return status;
}
- auto rgb_opt = egl::import_source(display.get(),
- {
- fb_fd.el,
- img_width,
- img_height,
- offset,
- pitch,
- });
+ auto rgb_opt = egl::import_source(display.get(), sd);
if(!rgb_opt) {
return capture_e::error;
@@ -679,24 +743,20 @@ public:
}
std::shared_ptr alloc_img() override {
- auto img = std::make_shared();
+ auto img = std::make_shared();
img->serial = std::numeric_limitsserial)>::max();
img->data = nullptr;
img->pixel_pitch = 4;
- img->sequence = 0;
-
- img->obj_count = 1;
- img->img_width = img_width;
- img->img_height = img_height;
+ img->sequence = 0;
+ img->sd.obj_count = 0;
return img;
}
int dummy_img(platf::img_t *img) override {
- snapshot(img, 1s, false);
- return 0;
+ return snapshot(img, 1s, false) != capture_e::ok;
}
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) {
@@ -734,25 +794,24 @@ public:
}
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds /* timeout */, bool cursor) {
- file_t fb_fd;
+ file_t fb_fd[4];
- auto status = refresh(&fb_fd);
+ auto img = (egl::img_descriptor_t *)img_out_base;
+ img->reset();
+
+ auto status = refresh(fb_fd, &img->sd);
if(status != capture_e::ok) {
return status;
}
- auto img = (kms_vram_img_t *)img_out_base;
-
- img->fb_fd = std::move(fb_fd);
- img->sequence = ++sequence;
- img->fds[0] = img->fb_fd.el;
- img->offsets[0] = offset;
- img->strides[0] = pitch;
+ img->sequence = ++sequence;
if(!cursor || !cursor_opt) {
img_out_base->data = nullptr;
-
+ for(auto x = 0; x < img->sd.obj_count; ++x) {
+ fb_fd[x].release();
+ }
return capture_e::ok;
}
@@ -761,6 +820,9 @@ public:
img->x -= offset_x;
img->y -= offset_y;
+ for(auto x = 0; x < img->sd.obj_count; ++x) {
+ fb_fd[x].release();
+ }
return capture_e::ok;
}
@@ -897,7 +959,7 @@ std::vector kms_display_names() {
auto end = std::end(card);
for(auto plane = std::begin(card); plane != end; ++plane) {
- auto fb = card.fb2(plane.get());
+ auto fb = card.fb(plane.get());
if(!fb) {
BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno);
continue;
@@ -970,4 +1032,4 @@ std::vector kms_display_names() {
return display_names;
}
-} // namespace platf
\ No newline at end of file
+} // namespace platf
diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp
index 98d94a96..28876f40 100644
--- a/sunshine/platform/linux/vaapi.cpp
+++ b/sunshine/platform/linux/vaapi.cpp
@@ -350,20 +350,22 @@ public:
auto nv12_opt = egl::import_target(
display.get(),
std::move(fds),
- {
- prime.objects[prime.layers[0].object_index[0]].fd,
+ { 1,
(int)prime.width,
(int)prime.height,
- (int)prime.layers[0].offset[0],
- (int)prime.layers[0].pitch[0],
- },
- {
- prime.objects[prime.layers[0].object_index[1]].fd,
+ { prime.objects[prime.layers[0].object_index[0]].fd, -1, -1, -1 },
+ 0,
+ 0,
+ { prime.layers[0].pitch[0] },
+ { prime.layers[0].offset[0] } },
+ { 1,
(int)prime.width / 2,
(int)prime.height / 2,
- (int)prime.layers[0].offset[1],
- (int)prime.layers[0].pitch[1],
- });
+ { prime.objects[prime.layers[0].object_index[1]].fd, -1, -1, -1 },
+ 0,
+ 0,
+ { prime.layers[0].pitch[1] },
+ { prime.layers[0].offset[1] } });
if(!nv12_opt) {
return -1;
@@ -419,14 +421,7 @@ public:
rgb = egl::rgb_t {};
- auto rgb_opt = egl::import_source(display.get(),
- {
- descriptor.fds[0],
- (int)descriptor.img_width,
- (int)descriptor.img_height,
- (int)descriptor.offsets[0],
- (int)descriptor.strides[0],
- });
+ auto rgb_opt = egl::import_source(display.get(), descriptor.sd);
if(!rgb_opt) {
return -1;
@@ -657,4 +652,4 @@ std::shared_ptr make_hwdevice(int width, int height, int offs
std::shared_ptr make_hwdevice(int width, int height, bool vram) {
return make_hwdevice(width, height, 0, 0, vram);
}
-} // namespace va
\ No newline at end of file
+} // namespace va
diff --git a/sunshine/platform/linux/wayland.cpp b/sunshine/platform/linux/wayland.cpp
index 9da8718c..5b93c829 100644
--- a/sunshine/platform/linux/wayland.cpp
+++ b/sunshine/platform/linux/wayland.cpp
@@ -164,10 +164,15 @@ void dmabuf_t::frame(
std::uint32_t obj_count) {
auto next_frame = get_next_frame();
- next_frame->format = format;
- next_frame->width = width;
- next_frame->height = height;
- next_frame->obj_count = obj_count;
+ next_frame->sd.fourcc = format;
+ next_frame->sd.width = width;
+ next_frame->sd.height = height;
+ next_frame->sd.modifier = (((std::uint64_t)high) << 32) | low;
+ next_frame->obj_count = obj_count;
+
+ std::fill_n(next_frame->sd.fds + obj_count, 4 - obj_count, -1);
+ std::fill_n(next_frame->sd.pitches + obj_count, 4 - obj_count, 0);
+ std::fill_n(next_frame->sd.offsets + obj_count, 4 - obj_count, 0);
}
void dmabuf_t::object(
@@ -180,11 +185,10 @@ void dmabuf_t::object(
std::uint32_t plane_index) {
auto next_frame = get_next_frame();
- next_frame->fds[index] = fd;
- next_frame->sizes[index] = size;
- next_frame->strides[index] = stride;
- next_frame->offsets[index] = offset;
- next_frame->plane_indices[index] = plane_index;
+ next_frame->sd.fds[index] = fd;
+ next_frame->sd.pitches[index] = stride;
+ next_frame->sd.offsets[index] = offset;
+ next_frame->sd.plane_indices[index] = plane_index;
}
void dmabuf_t::ready(
@@ -213,7 +217,7 @@ void dmabuf_t::cancel(
void frame_t::destroy() {
for(auto x = 0; x < obj_count; ++x) {
- close(fds[x]);
+ close(sd.fds[x]);
}
obj_count = 0;
diff --git a/sunshine/platform/linux/wayland.h b/sunshine/platform/linux/wayland.h
index 3b961349..34d7a365 100644
--- a/sunshine/platform/linux/wayland.h
+++ b/sunshine/platform/linux/wayland.h
@@ -21,14 +21,9 @@ using display_internal_t = util::safe_ptr;
class frame_t {
public:
- std::uint32_t format;
- std::uint32_t width, height;
+ egl::surface_descriptor_t sd;
+
std::uint32_t obj_count;
- std::uint32_t strides[4];
- std::uint32_t sizes[4];
- std::int32_t fds[4];
- std::uint32_t offsets[4];
- std::uint32_t plane_indices[4];
void destroy();
};
diff --git a/sunshine/platform/linux/wlgrab.cpp b/sunshine/platform/linux/wlgrab.cpp
index 6b7ba492..592594ae 100644
--- a/sunshine/platform/linux/wlgrab.cpp
+++ b/sunshine/platform/linux/wlgrab.cpp
@@ -16,21 +16,6 @@ struct img_t : public platf::img_t {
}
};
-class frame_descriptor_t : public egl::img_descriptor_t {
-public:
- ~frame_descriptor_t() {
- reset();
- }
-
- void reset() {
- std::for_each_n(fds, obj_count, [](int fd) {
- close(fd);
- });
-
- obj_count = 0;
- }
-};
-
class wlr_t : public platf::display_t {
public:
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
@@ -107,8 +92,8 @@ public:
if(
dmabuf.status == dmabuf_t::REINIT ||
- current_frame->width != width ||
- current_frame->height != height) {
+ current_frame->sd.width != width ||
+ current_frame->sd.height != height) {
return platf::capture_e::reinit;
}
@@ -170,14 +155,7 @@ public:
auto current_frame = dmabuf.current_frame;
- auto rgb_opt = egl::import_source(egl_display.get(),
- {
- current_frame->fds[0],
- (int)current_frame->width,
- (int)current_frame->height,
- (int)current_frame->offsets[0],
- (int)current_frame->strides[0],
- });
+ auto rgb_opt = egl::import_source(egl_display.get(), current_frame->sd);
if(!rgb_opt) {
return platf::capture_e::reinit;
@@ -274,7 +252,7 @@ public:
return status;
}
- auto img = (frame_descriptor_t *)img_out_base;
+ auto img = (egl::img_descriptor_t *)img_out_base;
img->reset();
auto current_frame = dmabuf.current_frame;
@@ -282,14 +260,8 @@ public:
++sequence;
img->sequence = sequence;
- img->obj_count = 1;
- img->img_width = current_frame->width;
- img->img_height = current_frame->height;
- img->obj_count = current_frame->obj_count;
-
- std::copy_n(std::begin(current_frame->fds), current_frame->obj_count, img->fds);
- std::copy_n(std::begin(current_frame->offsets), current_frame->obj_count, img->offsets);
- std::copy_n(std::begin(current_frame->strides), current_frame->obj_count, img->strides);
+ img->sd = current_frame->sd;
+ img->sd.obj_count = current_frame->obj_count;
// Prevent dmabuf from closing the file descriptors.
current_frame->obj_count = 0;
@@ -298,13 +270,11 @@ public:
}
std::shared_ptr alloc_img() override {
- auto img = std::make_shared();
+ auto img = std::make_shared();
- img->img_width = width;
- img->img_height = height;
- img->sequence = 0;
- img->serial = std::numeric_limitsserial)>::max();
- img->data = nullptr;
+ img->sequence = 0;
+ img->serial = std::numeric_limitsserial)>::max();
+ img->data = nullptr;
return img;
}
diff --git a/sunshine/process.cpp b/sunshine/process.cpp
index 1d8ece13..f68593c0 100644
--- a/sunshine/process.cpp
+++ b/sunshine/process.cpp
@@ -11,6 +11,7 @@
#include
#include
+#include
#include "main.h"
#include "utility.h"
@@ -109,14 +110,17 @@ int proc_t::execute(int app_id) {
if(proc.cmd.empty()) {
BOOST_LOG(debug) << "Executing [Desktop]"sv;
placebo = true;
- }
- else if(proc.output.empty() || proc.output == "null"sv) {
- BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
- _process = bp::child(_process_handle, proc.cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
- }
- else {
- BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
- _process = bp::child(_process_handle, proc.cmd, _env, bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
+ } else {
+ boost::filesystem::path working_dir = proc.working_dir.empty() ?
+ boost::filesystem::path(proc.cmd).parent_path() : boost::filesystem::path(proc.working_dir);
+ if(proc.output.empty() || proc.output == "null"sv) {
+ BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
+ _process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
+ }
+ else {
+ BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
+ _process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
+ }
}
if(ec) {
@@ -275,6 +279,7 @@ std::optional parse(const std::string &file_name) {
auto output = app_node.get_optional("output"s);
auto name = parse_env_val(this_env, app_node.get("name"s));
auto cmd = app_node.get_optional("cmd"s);
+ auto working_dir = app_node.get_optional("working-dir"s);
std::vector prep_cmds;
if(prep_nodes_opt) {
@@ -312,6 +317,10 @@ std::optional parse(const std::string &file_name) {
ctx.cmd = parse_env_val(this_env, *cmd);
}
+ if(working_dir) {
+ ctx.working_dir = parse_env_val(this_env, *working_dir);
+ }
+
ctx.name = std::move(name);
ctx.prep_cmds = std::move(prep_cmds);
ctx.detached = std::move(detached);
diff --git a/sunshine/process.h b/sunshine/process.h
index 1339b76b..86016b64 100644
--- a/sunshine/process.h
+++ b/sunshine/process.h
@@ -34,6 +34,7 @@ struct cmd_t {
* cmd -- Runs indefinitely until:
* No session is running and a different set of commands it to be executed
* Command exits
+ * working_dir -- the process working directory. This is required for some games to run properly.
* cmd_output --
* empty -- The output of the commands are appended to the output of sunshine
* "null" -- The output of the commands are discarded
@@ -52,6 +53,7 @@ struct ctx_t {
std::string name;
std::string cmd;
+ std::string working_dir;
std::string output;
};
diff --git a/sunshine/utility.h b/sunshine/utility.h
index af2da53e..90435696 100644
--- a/sunshine/utility.h
+++ b/sunshine/utility.h
@@ -46,6 +46,12 @@ struct argument_type { typedef U type; };
element_type *operator->() { return ⪙ } \
const element_type *operator->() const { return ⪙ } \
\
+ inline element_type release() { \
+ element_type val = std::move(el); \
+ el = element_type { init_val }; \
+ return val; \
+ } \
+ \
~move_t() z \
\
element_type el; \