Canonical Voices

UbuntuTouch

[原]Ubnutu手机中的Theme颜色

在我们设计应用时,我们可能很想知道所有在Ubuntu手机设计中的Theme颜色.在今天的例程中,我们将动态地检测我们系统里所有的Theme的颜色,并显示它们.开发者可以充分利用这些颜色在自己的设计中.在我们的设计中,开发者可以通过向左或向右滑动手势来导航列表中的内容.这个项目的设计有点像是一个Property browser.可以扩展到其它的项目中.关于Ubuntu手机中的Theme更多的知识,请阅读https://developer.ubuntu.com/en/phone/apps/qml/tutorials/ubuntu-ui-toolkit-palette/.该应用可以在Ubuntu商店中下载"ThemeViewer".


   


   


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import Qt.labs.settings 1.0

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "themeviewer.liu-xiao-guo"

    width: units.gu(60)
    height: units.gu(85)

    property var props: [theme]
    property var propnames : ["theme"]

    ListModel {
        id: mymodel
    }

    Settings {
        id: settings
        property bool selectedAmbiance: true
    }

    function goUp() {
        if ( propnames.length > 1) {
            // We only do it when there is more than one item
            propnames.pop();
            header.text = propnames.join(".");
            props.pop();
            getProperties(props[props.length -1])
            console.log("swipe left: " + propnames)
        }
    }

    function getProperties(obj) {
        mymodel.clear();

        var keys = Object.keys(obj);
        for(var i = 0; i < keys.length; i++) {
            var key = keys[i];
            var type = typeof obj[key];
            console.log(key + ' : ' + obj[key] + " " + type);

            if ( type === 'object' && obj[key] ) {
                if ( propnames.length === 3 ) {
                    mymodel.append({"name": key, "value": ""+obj[key] })
                } else {
                    mymodel.append({"name": key, "value": "white" })
                }
            }
        }
    }

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("Theme Viewer")
            trailingActionBar.actions: [
                Action {
                    iconSource: settings.selectedAmbiance ?
                                "images/ambiance.png" : "images/dark.png"
                    text: "Ambiance"
                    onTriggered: {
                        settings.selectedAmbiance = !settings.selectedAmbiance

                        theme.name = (settings.selectedAmbiance ?
                                          "Ubuntu.Components.Themes.Ambiance" :
                                          "Ubuntu.Components.Themes.SuruDark" )

                        // We need to do it from the very beginning
                        props = [theme]
                        propnames = ["theme"]
                        header.text = propnames.join(".")
                        getProperties(props[props.length -1])
                    }
                }
            ]

            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        SystemPalette { id: __palette }

        Component {
            id: delegate
            Rectangle {
                width: parent.width
                height: propname.height * 1.7
                color: "transparent"

                Rectangle {
                    height: parent.height
                    width: parent.width*.2
                    anchors {
                        right: parent.right
                        top: parent.top
                    }
                    color: value
                    visible: propnames.length === 3
                }

                Label {
                    id: propname
                    text: name
                    fontSize: "x-large"
                    anchors.verticalCenter: parent.verticalCenter
                }

                MouseArea {
                    id: mouseRegion
                    anchors.fill: parent

                    onDoubleClicked: {
                        props.push(props[props.length -1][propname.text])
                        propnames.push(propname.text)
                        header.text = propnames.join(".")
                        getProperties(props[props.length -1])
                    }

                    onClicked: {
                        list.currentIndex = index;
                    }
                }

                SwipeArea {
                    id: swiperight
                    anchors.fill: parent
                    direction: SwipeArea.Rightwards

                    onDraggingChanged: {
                        if ( dragging ) {
                            console.log("swipe right: " + propname.text)
                            props.push(props[props.length -1][propname.text])
                            propnames.push(propname.text)
                            header.text = propnames.join(".")
                            getProperties(props[props.length -1])
                        }
                    }
                }
            }
        }

        Column {
            anchors {
                left: parent.left
                right: parent.right
                bottom: parent.bottom
                top: page.header.bottom
            }

            Row {
                width: parent.width
                height: header.height

                Button {
                    id: upButton
                    height: parent.height
                    width: units.gu(5)
                    iconSource: "qrc:///images/up.png"
                    onClicked:  {
                        goUp()
                    }
                }

                Label {
                    id: header
                    text: { return propnames.join(".") }
                    fontSize: "large"
                }

            }

            UbuntuListView {
                id: list
                clip:true
                width: page.width
                height: page.height - header.height
                focus: true
                model: mymodel
                highlight: Rectangle {
                    color: __palette.midlight
                    border.color: Qt.darker(__palette.window, 1.3)
                }
                highlightMoveDuration: -1
                highlightMoveVelocity: -1
                highlightFollowsCurrentItem: true
                delegate: delegate
            }

            Component.onCompleted: {
                getProperties(props[props.length -1])
            }
        }
    }

    SwipeArea {
        id: swipeleft
        direction:  SwipeArea.Leftwards
        anchors.fill: parent

        onDraggingChanged: {
            if ( dragging ) {
                goUp()
            }
        }
    }
}

整个项目的源码在:https://github.com/liu-xiao-guo/themeviewer
作者:UbuntuTouch 发表于2016/4/28 15:03:32 原文链接
阅读:139 评论:0 查看评论

Read more
UbuntuTouch

很多开发者可能希望能够在不改变现有的Ubuntu Toolkit中的控件的情况下,有个性地来改变一些属性.这样使得自己的界面更加个性化.目前我们在我们的官方网址可以查看到我们已经有的一些Style的一些控件.这个列表并不完善.更加详细的列表在地址可以看到.


下面,我们来用一个具体的例子来说明如何是用这些API来个性化我们的应用的:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Themes 1.3
import Ubuntu.Components.Styles 1.3

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "style.liu-xiao-guo"

    width: units.gu(60)
    height: units.gu(85)

    Action {
        id: action1
        text: "action 1"
        iconName: "compose"
        onTriggered: print("one!")
    }

    Page {
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("pageheaderstyle")

            style: PageHeaderStyle {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
                contentHeight: units.gu(10)
                Label {
                    anchors.centerIn: parent
                    fontSize: "x-large"
                    text: styledItem.title
                }
                implicitHeight: contentHeight
            }
        }

        Image {
            id: pressed
            source: "images/pressed.jpg"
            visible: false
        }

        Image {
            id: unpressed
            source: "images/unpressed.jpg"
            visible: false
        }

        Column {
            anchors.centerIn: parent
            spacing: units.gu(5)

            Button {
                id: button1
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    defaultColor: button1.pressed ? "blue" : "red"
                }
            }

            Button {
                id: button2
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    backgroundSource: button2.pressed ? pressed : unpressed
                }
            }
        }
    }
}

在上面的代码中,我们使用了PageHeaderStyle来个性化我们的page header.我们它的高度设置为units.gu(10),同时我们也让我们的文字居中.

在下面的代码中,我们利用StyleHints来修改我们已经在使用的style的一些属性.比如:

            Button {
                id: button1
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    defaultColor: button1.pressed ? "blue" : "red"
                }
            }

在上面的代码中,当我们的按钮按下时,颜色会变为蓝色而不是通常状态下的红色:

 

另外,如下的代码可以使得我们的按钮在按下,和不按下的情况下显示不同的图片:

            Button {
                id: button2
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    backgroundSource: button2.pressed ? pressed : unpressed
                }
            }


 

关于ButtonStyle的更多介绍请参阅连接

整个项目的源码在:https://github.com/liu-xiao-guo/style


作者:UbuntuTouch 发表于2016/5/3 11:50:27 原文链接
阅读:64 评论:0 查看评论

Read more
UbuntuTouch

在这里,我来展示一个使用QML来做单位转换的例程:


Main.qml


/*
 * Copyright 2012 Canonical Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import QtQuick 2.4
import Ubuntu.Components 1.3
import "conversion.js" as Converter

/*!
  \brief Example unit converter application.

  The application demonstrates the usage of
    - i18n
    - units
    - Label
    - Tabs
    - Tab
    - Page
    - ToolbarActions
    - Action
    - TextField and
    - Button components

  The application converts length and weight units between several metrics.
  Related units are grouped in the same page (Tab) and conversion happens when
  pressing Enter/Return after entering a number in one of the input fields (i.e.
  accepting the entered text), or by pressing the "Convert" button.

  The navigation between converter pages is provided by the Tabs component.
*/

MainView {
    id: root
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"
    
    // Note! applicationName needs to match the .desktop filename
    applicationName: "unit-converter"
    
    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    automaticOrientation: true
    
    width: units.gu(100)
    height: units.gu(75)

    property real margins: units.gu(2)
    property real labelWidth: units.gu(12)

    // Length conversion model; the unit is Mile
    property var lengthModel: [
        {"unit": "Inch", "rate": 63360.0},
        {"unit": "Meter", "rate": 1609.344},
        {"unit": "Miles", "rate": 1.0},
        {"unit": "Feets", "rate": 5280.0},
        {"unit": "Yards", "rate": 1760.0},
        {"unit": "Kilometers", "rate": 1.609344},
    ]

    // Weight conversion model; the base unit is Pound
    property var weightModel: [
        {"unit": "Pounds", "rate": 1.0},
        {"unit": "Kilograms", "rate": 0.45359237},
        {"unit": "Ounces", "rate": 16},
        {"unit": "Stones", "rate": 0.0714285714},
        {"unit": "US Tons", "rate": 0.0005},
        {"unit": "UK Tons", "rate": 0.000446428571},
    ]

    // converter page template
    Component {
        id: pageContent
        Page {
            // expose Repeater's model for reusability, so we can set it from
            // outside, when we build the tabs
            property alias model: converter.model

            // remove the input panel when pressed outside of any text input
            MouseArea {
                anchors.fill: parent
                onPressed: Qt.inputMethod.hide();
            }

            Flickable {
                id: flickable
                anchors {
                    fill: parent
                    margins: root.margins
                }
                flickableDirection: Flickable.VerticalFlick
                contentWidth: pageLayout.width
                contentHeight: pageLayout.height
                Column {
                    id: pageLayout
                    width: flickable.width
                    height: childrenRect.height

                    spacing: units.gu(1.2)
                    // show as many lines as many units we have in the model
                    // it is assumed that the model has "unit" and "rate" roles
                    Repeater {
                        id: converter
                        Row {
                            spacing: units.gu(1)
                            Label {
                                text: i18n.tr(modelData.unit)
                                textSize: Label.Large
                                width: root.labelWidth
                                height: input.height
                                verticalAlignment: Text.AlignVCenter
                            }
                            // input field performing conversion
                            TextField {
                                id: input
                                //errorHighlight: false
                                validator: DoubleValidator {notation: DoubleValidator.StandardNotation}
                                width: pageLayout.width - root.labelWidth - spacing
                                text: "0.0"
                                font.pixelSize: FontUtils.sizeToPixels("large")
                                height: units.gu(4)
                                // on-the-fly conversion
                                onTextChanged: if (activeFocus) Converter.convert(input, converter, index)
                                onAccepted: Qt.inputMethod.hide()
                            }
                        }
                    }
                }
            }

            head.actions: Action {
                objectName: "action"
                iconName: "clear"
                text: i18n.tr("Clear")
                onTriggered: Converter.clear(converter)
            }
        }
    }
    
    Tabs {
        id: tabs
        
        // First tab begins here
        Tab {
            objectName: "Tab1"
            
            title: i18n.tr("Lengths")
            
            // Tab content begins here
            page: Loader {
                sourceComponent: pageContent
                onStatusChanged: {
                    if (status === Loader.Ready && item) {
                        item.parent = parent;
                        item.model = lengthModel;
                    }
                }
            }
        }
        
        // Second tab begins here
        Tab {
            objectName: "Tab2"
            
            title: i18n.tr("Weights")
            page: Loader {
                sourceComponent: pageContent
                onStatusChanged: {
                    if (status === Loader.Ready && item) {
                        item.parent = parent;
                        item.model = weightModel;
                    }
                }
            }
        }
    }
}

运行我们的例程:

  

作者:UbuntuTouch 发表于2016/5/3 13:58:11 原文链接
阅读:56 评论:0 查看评论

Read more
UbuntuTouch

最近发现一个新的API叫做LayoutMirroring.运用这个API,我们可以在水平方向把我们的布局进行进行镜像显示.

我们还是先来看一个例子:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

MainView {
    id: main
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "layoutmirroring.liu-xiao-guo"

    width: units.gu(60)
    height: units.gu(85)

    LayoutMirroring.enabled: rtl
    LayoutMirroring.childrenInherit: true
    property bool rtl: Qt.application.layoutDirection == Qt.RightToLeft

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("LayoutMirroring")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }

            trailingActionBar.actions: [
                Action {
                    text: i18n.tr('Right to Left')
                    iconName: 'flash-on'
                    visible: !rtl
                    onTriggered: rtl = !rtl
                },
                Action {
                    text: i18n.tr('Left to Right')
                    iconName: 'flash-off'
                    visible: rtl
                    onTriggered: rtl = !rtl
                }
            ]
        }

        Row {
            anchors {
                left: parent.left
                right: parent.Right
                top: page.header.bottom
                bottom: parent.bottom
            }
            spacing: units.gu(1)

            Repeater {
                model: 5

                Rectangle {
                    color: "red"
                    opacity: (5 - index) / 5
                    width: units.gu(5); height: units.gu(5)

                    Text {
                        text: index + 1
                        anchors.centerIn: parent
                    }
                }
            }
        }

    }
}

在上面的例程中,我们在我们的MainView中使用了一个attached property: LayoutMirroring.当它的属性LayoutMirroring.enabled为真时,它使得我们的界面的UI布局将在在水平方向上从右向左布局.


运行我们的例程的效果:

 

当我们点击上面图中的闪电的图标时,我们会看见布局在水平方向发生改变,并变为从右侧开始的.当我们点击禁止闪电标志的图标时,就会恢复以前的布局.

作者:UbuntuTouch 发表于2016/5/4 10:08:01 原文链接
阅读:53 评论:0 查看评论

Read more
UbuntuTouch

细心的开发者可能在我先前的应用"Ubuntu文件浏览器 - 开发Scope/应用利器"已经看到过在我们应用的header的有上角位置上已经显示了一个ActionBar用来显示一排图标按钮供我们做出我们的选择.



事实上,我们也可以在我们的应用的其它位置也可以使用ActionBar.在今天的例程中,我们来展示如何利用ActionBar来实现我们所需要的一些功能.事实上,我们上面的Browser应用中,也可以在我们的"/"位置提供一个这样的ActionBar来选择不同的drive.当然这目前不在我们的考虑范围之内.

我们还是来看一下我们的例子:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "actionbar.liu-xiao-guo"

    width: units.gu(60)
    height: units.gu(85)

    property list<Action> shortActionList: [
        Action {
            iconName: "share"
            text: "Share"
            onTriggered: {
                console.log("share is clicked")
            }
        },
        Action {
            iconName: "starred"
            text: "Favorite"
            onTriggered: {
                console.log("starred is clicked")
            }
        }
    ]

    property list<Action> actionList:  [
        Action {
            iconName: "alarm-clock"
            text: "Tick tock"
            onTriggered: {
                console.log("alarm-clock is clicked")
            }
        },
        Action {
            iconName: "appointment"
            text: "Date"
            onTriggered: {
                console.log("appointment is clicked")
            }
        },
        Action {
            iconName: "attachment"
            text: "Attach"
            onTriggered: {
                console.log("attachment is clicked")
            }
        },
        Action {
            iconName: "contact"
            text: "Contact"
            onTriggered: {
                console.log("contact is clicked")
            }
        },
        Action {
            iconName: "like"
            text: "Like"
            onTriggered: {
                console.log("like is clicked")
            }
        },
        Action {
            iconName: "lock"
            text: "Lock"
            onTriggered: {
                console.log("lock is clicked")
            }
        }
    ]

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("actionbar")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        Column {
            anchors.centerIn: parent
            spacing: units.gu(3)

            ActionBar {
                // no numberOfSlots specified. Using default value.
                id: shortBar
                actions: shortActionList
            }

            ActionBar {
                id: bar
                numberOfSlots: 3
                actions: actionList
            }

            ActionBar {
                id: bar1
                actions: actionList

                StyleHints {
                    overflowIconName: "grip-large"
                    overflowText: "More"
                    defaultNumberOfSlots: 2
                }
            }
        }
    }
}
 
在这里我创建了三个ActionBar.第一个没有什么特别的.它显示连个并排的图标按钮.第二个ActionBar在图标比较多的情况下,它把多余的图标显示在一个Popup的菜单中.我们可以在ActionBar中定义可以最少显示的图标个数(numberOfSlots).在第三个ActionBar中,我们展示了如何去通过style的方式来修改/定制一个ActaionBar.运行我们的应用:

   


作者:UbuntuTouch 发表于2016/5/4 15:40:37 原文链接
阅读:60 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu SDK 安装介绍视频

最近我做了一个Ubuntu SDK的安装视频的介绍.如果有兴趣的朋友,请观看地址:


http://v.youku.com/v_show/id_XMTU1Nzg0NTI2NA==.html?from=s1.8-1-1.2

Ubuntu SDK安装

作者:UbuntuTouch 发表于2016/5/5 9:46:13 原文链接
阅读:70 评论:0 查看评论

Read more
UbuntuTouch

通常我们在Scope的设计中,我们可以通过搜寻在query中发生一个请求.事实上,我们也可以在我们的preview中通过按钮的方式来发生一个query请求,并生成显示结果.在今天的文章中,我们将讲述如何在preview中发生一个请求.


1)请求的种类


我们可以通过"CannedQuery"中如下的两个API来生产我们需要的query请求:
  • set_query_string (std::string const &query_str)
  • set_user_data (Variant const &value)
上面的这两个API的区别在于前面一个API可以在搜索框里输入我们所需要的字符串再进行搜索.对于set_user_data来说,它的搜索的字符串已经在传人的参数中,我们可以直接进行解析,并使解析的参数成为我们API请求的一部分.对于这种情况,通常我们不需要再进行搜索.

下面我们通过实际的例子来展示如果使用.


2)set_query_string


在我们的preview中,我们加入如下的代码:

preview.cpp


    sc::CannedQuery new_query(SCOPE_INSTALL_NAME);
    std::string str("carousel");
    new_query.set_query_string(str);
    builder1.add_tuple({
        {"id", Variant("user_videos")},
        {"label", Variant("Show query string")},
        {"uri", Variant(new_query.to_uri())}
    });

从上面的代码中可以看出来我们在query中预先设置了一个字符串"carousel".在我们的query中,我们通过如下的代码来识别这种情况:


query.cpp


void Query::run(sc::SearchReplyProxy const& reply) {
    try {

        // Let's first check the query string
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        // Get the query string
        string query_string = query.query_string();

        QString queryStr = QString::fromStdString(query_string);
        qDebug() << "query: " << queryStr;

        if ( queryStr.startsWith("carousel") ) {
            qDebug() << "it is from the preview";

            int count = images_.count();
            auto cat = reply->register_category( "Carousel", "Carousel" ,
                                                 "", sc::CategoryRenderer(CAT_RENDERER9) );

            for ( int i = 0; i < count; i ++ ) {
                pushResult( reply, &cat, i);
            }

        } 

  ....
}

在上面的代码中,我们可以对有carousel字符串进行匹配,如果请求德尔是以这个为开始的字符串,我们做分别的处理.运行我们的Scope:

 

当我们在preivew中点击"Show query string"的时候,我们的query显示的图片如右图所示.在搜寻框中显示"carousel"字样.并显示我们所需要的carousel图片.我们也可以在输入框中接着输入我们所需要的任何一个字符串,只要前面的几个字符串是"carousel"即可.


3)set_user_data


在我们的preview中,我们写上如下的代码:

preview.cpp


    sc::CannedQuery new_query1(SCOPE_INSTALL_NAME);
    new_query1.set_user_data(Variant("num=" + std::to_string(4)));
    builder1.add_tuple({
        {"id", Variant("user_videos")},
        {"label", Variant("User data")},
        {"uri", Variant(new_query1.to_uri())}
    });

在这里,我们把搜寻的代码中直接写入所需要搜索的参数,我们可以在query中进行解析,并展示结果:

query.cpp


void Query::run(sc::SearchReplyProxy const& reply) {
    try {

        // Let's first check the query string
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        // Get the query string
        string query_string = query.query_string();

        QString queryStr = QString::fromStdString(query_string);
        qDebug() << "query: " << queryStr;

      if (sc::SearchQueryBase::query().has_user_data()) {
            string userdata = sc::SearchQueryBase::query().user_data().get_string();
            qDebug() << "it has user data in it";
            cerr << "user_data: " << userdata << endl;
            const std::string prefix("num=");
            if (!userdata.compare(0, prefix.size(), prefix)) {
                qDebug() << "prefix is found!";
                std::string num = userdata.substr(prefix.size());
                cerr << "num: " << num << endl;
                int number = num.back() - '0';
                qDebug() << "number: " << number;

                // Let's display a vertical-journal according to the number
                auto cat = reply->register_category( "vertical-journal", "vertical-journal" ,
                                                "", sc::CategoryRenderer(CAT_RENDERER12) );

                for ( int i = 0; i < number; i ++ ) {
                    pushResult( reply, &cat, i);
                }
            }
        }

 ...
}

在上面,我们通过检测有没有user_data.如果有,我们也做分别的处理.这种情况和上面的那种情况不之处在于我们不需要再输入任何额外的参数给我们的搜索,因为我们可以通过解析user_data来得到搜寻所需要的所有的参数.运行我们的scope:

   

在上面,我们可以点击"User data"按钮来直接进入到我们的query界面.我们不需要输入任何的参数.当前显示我们输入的4个grid图片.

作者:UbuntuTouch 发表于2016/5/17 13:39:00 原文链接
阅读:54 评论:0 查看评论

Read more
UbuntuTouch

在实际的Scope中,如果我们的搜寻的结果多过一个屏幕,这个时候,我们可以通过创建一些按钮来帮我们进行翻页.当然我们也可以使用按钮来做一些其它的功能,这完全依赖于我们对我们的Scope的具体的设计.比如,在下面的Scope的画面中,我们创建了"Next","Previous"及"Top".在这篇文章中,我们将介绍如何创建这些按钮,并捕获这些按钮的事件.




下面,我们来介绍如何创建这些按钮.


1)在query中创建按钮


query.cpp


我们定义如下的函数:

void Query::showNext(sc::SearchReplyProxy const& reply, sc::CategorisedResult res, int next_breadcrumb)
{
    res.set_uri("http://ubuntu.com");
    //Translators: this means show the next page of results
    std::string show_more =  _("Next");
    res.set_title("<b>" + show_more + "</b>");
    res["get_next"] = "true";
    res.set_intercept_activation();
    res["api_page"] = sc::Variant(next_breadcrumb);
    if (!reply->push(res)) {
        return;
    }
}

void Query::showPrevious(sc::SearchReplyProxy const& reply, sc::CategorisedResult res)
{
    res.set_uri("http://ubuntu.com");
    //Translators: this means show the previous page of results
    std::string show_prev =  _("Previous");
    res.set_title("<b>" + show_prev + "</b>");
    res["get_previous"] = "true";
    res.set_intercept_activation();
    if (!reply->push(res)) {
        return;
    }
}

void Query::showTop(sc::SearchReplyProxy const& reply, sc::CategorisedResult res)
{
    res.set_uri("http://ubuntu.com");
    //Translators: this means return to the top page (the first page) of results
    std::string top = _("Top");
    res.set_title("<b>" + top + "</b>");
    res["go_to_top"] = "true";
    res.set_intercept_activation();
    if (!reply->push(res)) {
        return;
    }
}

void Query::createButtons(const unity::scopes::SearchReplyProxy &reply) {
    auto buttons_cat = reply->register_category("buttons", "", "",
                                                sc::CategoryRenderer(BUTTONS_TEMPLATE));
    // Show the next button
    sc::CategorisedResult res(buttons_cat);
    showNext(reply, res, 1);

    // show the preview button
    sc::CategorisedResult prev_res(buttons_cat);
    showPrevious(reply, prev_res);

    // show the top button
    sc::CategorisedResult top_res(buttons_cat);
    showTop(reply, top_res);
}

它们被用来创建我们所需要的按钮.注意上面的"res.set_intercept_activation()".它可以让我们在我们下面的Action中捕获这个事件..在这里,BUTTONS_TEMPLATE的定义如下:

const static string BUTTONS_TEMPLATE =
        R"(
{
        "schema-version": 1,
        "template": {
        "category-layout": "vertical-journal",
        "card-size": "small",
        "card-background":"color:///#12a3d8"
        },
        "components": {
        "title": "title"
        }
        }
        )";

我们在query的run方法中调用我们上面创建的createButtons:

void Query::run(sc::SearchReplyProxy const& reply) {
    try {

        // We can play a two buttons on the top
        createButtons(reply);
        ...

 } catch (domain_error &e) {
        // Handle exceptions being thrown by the client API
        cerr << e.what() << endl;
        reply->error(current_exception());
    }
}

这样我们就可以在最上面显示我们所需要的按钮.事实上,就是通过push的方法来生产一个我们喜欢颜色的方块.


2)捕获按钮事件


按照如下的方法来捕获我们的按钮事件.我们必须在scope.cpp中创建如下的方法:

scope.cpp


sc::ActivationQueryBase::UPtr Scope::activate(sc::Result const& result,
                                              sc::ActionMetadata const& metadata)
{
    cerr << "activate" << endl;
    return sc::ActivationQueryBase::UPtr(new Action(result, metadata, *this));
}

action.cpp


创建我们所需要的构造函数:

Action::Action(us::Result const& result,
               us::ActionMetadata const& metadata,
               scope::Scope & scope):
    result_(result),
    scope_(scope),
    ActivationQueryBase(result, metadata)
{
}

在activate()方法中处理我们所需要的按键事件.

sc::ActivationResponse Action::activate()
{
    qDebug() << "in activate()";

    try {
        std::string val = result_["get_previous"].get_string();
        qDebug() << "PREVIOUS button is clicked!";
        us::CannedQuery cq("scopetemplates.xiaoguo_scopetemplates");
        // restore the previous search. We can save the state in query
        // and save the states into the members of scope.
        //        cq.set_filter_state(scope_.filter_state);
        //        cq.set_query_string(scope_.previous_query);
        return us::ActivationResponse(cq);
    }
    catch ( unity::LogicException &e){
    }

    try {
        std::string val = result_["get_next"].get_string();
        qDebug() << "NEXT button is clicked!";
        us::CannedQuery cq("scopetemplates.xiaoguo_scopetemplates");
        // restore the previous search
        //        cq.set_filter_state(scope_.filter_state);
        //        if (scope_.previous_query.empty())
        //            cq.set_department_id(scope_.previous_dept_id);
        //        cq.set_query_string("");
        return us::ActivationResponse(cq);
    }
    catch ( unity::LogicException &e){
    }

    ...
}

我们可以在Activation中捕获这些按钮的事件.当我们返回的时候,我们可以参照文章"如果在Scope中的Preview中发起一个query请求"及文章"运用link query特性query自己的Scope中department或其它scope中的department"调到我们需要的department中或通过set_query_string生产一个新的query.

在Desktop下运行,我们可以看到我们在按下按钮时的事件:



作者:UbuntuTouch 发表于2016/5/18 11:28:51 原文链接
阅读:44 评论:1 查看评论

Read more
UbuntuTouch

在QML的设计中,在很多的情况下,我们可以把我们的逻辑代码通过Javascript来书写,而且可以把我们的JS代码掩埋在我们的QML代码中,比如典型的代码如下:

            Button {
                text: "Calculate"
                onClicked: {
                    console.log("Change button is clicked")
                    console.log(Method.factorial(10));
                    console.log(Method.factorialCallCount())
                }
            }


在上面的代码中,我们定义了一个Button按钮.每当我们的按钮的触碰事件click发生时,我们可以在我们的QML文件中直接按照上面的方法来执行我们的Javascript代码.在onClicked后面的""及""中间其实就是我们的Javascript代码来实现我们的逻辑处理.

事实上,如果我们的逻辑比较复杂,并且有时,我们想把我们的逻辑及UI (由QML语言描述)分开的话,这时候,我们可以直接把我们的逻辑代码放入到一个叫做Javascript的文件之中.然后再在我们的QML文件中进行直接import.这样的好处是界面和逻辑分开,也可以使得我们的QML代码简单明了.

在QML中引入Javascript有两种方式.有兴趣的开发者可以参阅Qt的官方文档.在该文档中,如果不是自己亲手去实验,其实也很晦涩难懂.简单地说,这两种方式是:

  • stateful:在这种方式下,js模块中所定义的变量在每次被import后,都会被拷贝一份.所以如果有被多次import就有多个拷贝,而且每个的值都可能会有不同. 在这种过情况下,js文件可以直接访问在我们QML文件中所定义的object
  • statelss:在这种情况下,js所定义的模块就像一个公共分享的库一样.它里面的方法都可以被使用,并且模块里定义的变量在所以被import的QML代码中独一份,无论被import多少次.另外,它不可以直接访问QML文件中的object尽管可以通过参数的传人对所要求的object进行修改.

1)stateful


我们特意在我们的例子中加入我们一个特有的MyButton.qml:

MyButton.qml


// MyButton.qml
import QtQuick 2.0
import "my_button_impl.js" as Logic // a new instance of this JavaScript resource is loaded for each instance of Button.qml
import "factorial.js" as Method

Rectangle {
    id: rect
    width: 200
    height: 100
    color: "red"
    property int count: 0
    property alias text: mytext.text
    signal clicked()

    Text {
        id: mytext
        anchors.centerIn: parent
        font.pixelSize: units.gu(3)
    }

    MouseArea {
        id: mousearea
        anchors.fill: parent
        onClicked: {
            rect.clicked()
            count = Logic.onClicked(rect)
            console.log(Method.factorialCallCount())
        }
    }
}

my_button_impl.js


// this state is separate for each instance of MyButton
var clickCount = 0;

function onClicked(obj) {
    clickCount += 1;
    if ((clickCount % 5) == 0) {
        obj.color = Qt.rgba(1,0,0,1);
    } else {
        obj.color = Qt.rgba(0,1,0,1);
    }

    return clickCount;
}

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

在我们的MyButton.qml中,我们import了my_button_impl.js这个文件.由于这个js文件的最前面没有".pragma library",所以它是一个stateful的Javascript.如果我们有多个MyButton的实例,那么每个按钮都分别有自己的clickCount变量,并且它们之间毫无关系.

我们在我们的Main.qml中加入两个我们设计的MyButton按钮:

Main.qml


        Column {
            anchors.centerIn: parent
            spacing: units.gu(5)

            MyButton {
                anchors.horizontalCenter: parent.horizontalCenter
                text: "Button " + count
                onClicked: {
                    console.log("Button 1 is clicked!")
                }
            }

            MyButton {
                anchors.horizontalCenter: parent.horizontalCenter
                width: parent.width
                text: "Button " + count
                onClicked: {
                    console.log("Button 2 is clicked!")
                }
            }
         }

运行我们的代码:



从上面的输出可以看出,两个按钮的count可以是完全不同的.它们之间完全独立.

另外,我们也在我们的my_button_impl.js中设计了如下的函数:

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

显然它可以直接访问我们在Main.qml中的object Main.运行我们应用,点击"Change color"按钮:



可以看见我们的MainView的背景颜色已经发生改变了.

2)stateless



针对这种情况,在Javascript文件的开头部分必须是一下的语句:

.pragma library

我们一个完整的factorial.js文件如下:

.pragma library

var factorialCount = 0;

function factorial(a) {
    // a = parseInt(a);

    // factorial recursion
    if (a > 0)
        return a * factorial(a - 1);

    // shared state
    factorialCount += 1;

    // recursion base-case.
    return 1;
}

function factorialCallCount() {
    return factorialCount;
}

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

function changeBackground(obj) {
    obj.backgroundColor = "green"
}


在这个模块中,我们定义了一个factorialCount的变量.由于是stateless,所有这个变量对于所有的import文件来说,只有一个.有点类似于C++类中static变量.在我们的Main.qml中,我们定义了一个按钮:

Main.qml


            Button {
                text: "Calculate"
                onClicked: {
                    console.log("Calculate button is clicked")
                    console.log(Method.factorial(10));
                    console.log(Method.factorialCallCount())
                }
            }

按下这个按钮,factorial将会帮我们计算我们的结果,同时会显示factorial被调用多少次.在我们的MyButton.qml中,我们也在点击的地方展示这个数据:

MyButton.qml


    MouseArea {
        id: mousearea
        anchors.fill: parent
        onClicked: {
            rect.clicked()
            count = Logic.onClicked(rect)
            console.log(Method.factorialCallCount())
        }
    }

如果我们点击"Calculate"按钮,在点击MyButton按钮,我们将可以看到同样的factorialCount值.



同样的,如果在我们的factorial.js中直接访问object main来修改它的属性,就像:

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

这样是不可以的.我们必须通过如下的方法来修改:

function changeBackground(obj) {
    obj.backgroundColor = "green"
}

我们在我们的Main.qml中使用如下的代码来实现:

            Button {
                text: "Change color via stateless "
                onClicked: {
                    Method.changeBackground(main)
                }
            }

整个项目的源码:https://github.com/liu-xiao-guo/JsInQml



作者:UbuntuTouch 发表于2016/5/19 11:12:00 原文链接
阅读:26 评论:0 查看评论

Read more
UbuntuTouch

在先前的文章" 如何在Ubuntu手机中使得一个应用是全屏的应用 - Ubuntu.Components 1.3",我们介绍了如何实现一个全屏的应用.但是在那里的文章中,我们的方法不能完全覆盖手机的状态显示区域.比如:


  


从上面的两张图中,我们可以看出来一些差别.左边的一个图里,还是有手机状态的显示.究其原因,还是因为MainView不让我们完全覆盖所有的区域.那么我们怎么实现一个完全的全屏应用呢?


在我们的下面的例子中,我们完全抛弃MainView.我们选用Window来完成我们的界面:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import QtQuick.Window 2.2

//Rectangle {
//    width: Screen.width
//    height: Screen.height

//    color:"red"
//}

Window {
    id: main
    width: Screen.width
    height: Screen.height
    // special flag only supported by Unity8/MIR so far that hides the shell's
    // top panel in Staged mode
    flags: Qt.Window | 0x00800000

    Image {
        anchors.fill: parent
        source: "images/pic.jpg"
    }

    Label {
        anchors.centerIn: parent
        text: "This a full screen app"
        fontSize: "x-large"
    }

    Component.onCompleted: {
        console.log("visibility: " + main.visibility )
        console.log("width: " + Screen.width + " height: " + Screen.height )
    }
}

在上面的代码中,我们同时也设置如下的flags:

    flags: Qt.Window | 0x00800000

这样我们就可以完全实现一个全屏的应用了.

作者:UbuntuTouch 发表于2016/5/24 13:37:49 原文链接
阅读:17 评论:0 查看评论

Read more
liam zheng

我们与我们的物联网业务主管 Maarten 进行了一次有趣的聊天。大家畅谈了投影仪与支持应用的软件无线电 (limeSDR.org) 相遇会发生什么。下面就是它们的结合带来的一些很酷的玩法,包括通过手势与无人机交流、进入无线电对讲机系统以及开辟实际上并不存在的覆盖区域。

我们与我们的物联网业务主管 Maarten 进行了一次有趣的聊天。大家畅谈了投影仪与支持应用的软件无线电 (limeSDR.org) 相遇会发生什么。下面就是它们的结合带来的一些很酷的玩法,包括通过手势与无人机交流、进入无线电对讲机系统以及开辟实际上并不存在的覆盖区域。

  • 使用软件无线电 (SDR) 接收电视广播,并使用投影仪作为电视屏幕

  • 添加 WiFi 应用但不必多交一部设备的电费

  • 添加 4G 应用,通过 REST API 获取电信提供商的频谱,并为建筑物内没有信号的地方提供信号覆盖

  • 探测进入房间的人是否随身携带了手机并开启投影仪,在周围没有人时关闭投影仪。如果照明、供暖等设备可通过 API 来控制,那么这些设备也包括在内

  • 创建你自己的物联网无线协议,从而可以连接房子周围的感应器,并将带有实时数据的仪表板投影到屏幕上

  • 使用支持蓝牙的手势感应器探测手的动作,通过手势与无人机或玩具车交流并控制它们,将无人机或玩具车看到的东西投影到屏幕上观看

  • 使用 3 台投影仪,根据每个人的蓝牙或手机信号以三角法定位每个人在办公室内的行动位置。连接他们的在线日历,按照他们的日程安排自动打开 Webex 或 Google Hangout。连接室内的无线监控摄像头,将监控画面传送给其他会议邀请。使用内置麦克风收听会议所讲的全部内容

  • 通过港口的 AIS 信号传输设备跟踪船舶航行或通过 Mode S 跟踪飞机飞行,并通过投影仪显示在 Google Map 上绘制的船舶和飞机航线

  • 通过直接式胎压监测系统 (TPMS) 将车辆状态的实时数据投影到屏幕上

  • 与诸如无线电对讲机的各类 2.4GHz 设备通话,以及通过投影仪连接婴儿监视器来显示婴儿当时的活动

  • 构建你自己的低功率 WAN 接收器解决方案(例如 LoRA),并在屏幕上显示接收到的内容

 

Read more
Grazina Borosko

April marks the release of Xerus 16.4 and with it we bring a new design of our iconic wallpaper. This post will take you through our design process and how we have integrated our Suru visual language.

Evolution

The foundation of our recent designs are based on our Suru visual language, which encompasses our core brand values, bringing consistency across the Ubuntu brand.

Our Suru language is influenced by the minimalist nature of Japanese culture. We have taken elements of their Zen culture that give us a precise yet simplistic rhythm and used it in our designs. Working with paper metaphors we have drawn inspiration from the art of origami that provides us with a solid and tangible foundation to work from. Paper is also transferable, meaning it can be used in all areas of our brand in two and three dimensional forms.

Design process

We started by looking at previously released wallpapers across Ubuntu to see how each has evolved from each other. After seeing the previous designs we started to apply our new Suru patterns, which inspired us to move in a new direction.

Ubuntu 14.10 ‘Utopic Unicorn’’

wallpaper_unicorn

Ubuntu 15.04 ‘Vivid Vervet’

suru-desktop-wallpaper-ubuntu-vivid (1)

Ubuntu 15.10 ‘Wily Werewolf’

ubuntu-1510-wily-werewolf-wallpaper

Step-by-step process

Step 1. Origami animal

Since every new Ubuntu release is named after animal, the Design Team wanted to bring this idea closer to the wallpaper and the Suru language. The folds are part of the origami animal and become the base of which we start our design process.

Origarmi

To make your own origami Xerus squirrel, you can find the instructions here.

Step.2 Searching for the shape

We started to look at different patterns by using various techniques with origami paper. We zoomed into particular folds of the paper, experimented with different light sources, photography, and used various effects to enhance the design.

The idea was to bring actual origami to the wallpaper as much as possible. We had to think about composition that would work across all screen sizes, especially desktop. As the wallpaper is a prominent feature in a desktop environment, we wanted to make sure that it was user friendly, allowing users to find documents and folders located on the computer screen easily. The main priority was to not let the design get in the way of everyday usage, but enhance it aesthetically and provide a great user experience.

After all the experiments with fold patterns and light sources, we started to look at colour. We wanted to integrate both the Ubuntu orange and Canonical aubergine to balance the brightness and played with gradient levels.

We balanced the contrast of the wallpaper color palette by using a long and subtle gradient that kept the bright and feel look. This made the wallpaper became brighter and more colorful.

Step.3 Final product

The result was successful. The new concept and usage of Suru language helped to create a brighter wallpaper that fitted into our overall visual aesthetic. We created a three-dimensional look and feel that gives the feeling of actual origami appearance. The wallpaper is still recognizable as Ubuntu, but at the same time looks fresh and different.

Ubuntu 16.04 Xenial Xerus

Xerus - purple

Ubuntu 16.04 Xenial Xerus ( light version)

Xerus - Grey

What is next?

The Design Team is now looking at ways to bring the Suru language into animation and fold usage. The idea is to bring an overall seamless and consistent experience to the user, whilst reflecting our tone of voice and visual identity.

Read more
liam zheng

大会来了!三星年度大型开发者峰会即将于4月27日至28日在旧金山揭幕,旧金山是一个极具吸引力的地方,很多开发者、创新者聚集在这里讨论最新的技术和未来创新。

大会来了!三星年度大型开发者峰会即将于4月27日至28日在旧金山揭幕,旧金山是一个极具吸引力的地方,很多开发者、创新者聚集在这里讨论最新的技术和未来创新。

今年,物联网(IoT)将是一个巨大的焦点,特别是智能家庭和针对物联网设计的芯片——ARTIK,届时我们的同事Didier也会出现在大会现场做演示,也会发表关于ARTIK的演讲。

我们的演示将是一个可以让你方便在家庭网关(Home gateway)上部署其他应用的app。让你直观感受智能手机和家庭机器人之间是如何交流、通信从而为你服务的。当你到家时,机器人将用摄像头来识别是你回来了,同时麦克风也可以提供语音交互。

除此,你还可以部署其他app在家庭网关上,包括一个接入点(家庭WiFi),视频服务,本地的服务器(比如Skype和Google Hangout的数据都存储在本地),自动化家用设备等。

并且,我们的往期博客也有关于本次活动的内容,比如这里

Read more
liam zheng

空中移动基站来到英国

英国最大的移动运营商 EE(现已归属 BT 旗下)近日宣布,将与新一轮开源移动网络技术大潮领导者之一的 Lime Micro 以及 Canonical (Ubuntu) 合作,共同确保英国实现更好的移动网络覆盖。

英国最大的移动运营商 EE(现已归属 BT 旗下)近日宣布,将与新一轮开源移动网络技术大潮领导者之一的 Lime Micro 以及 Canonical (Ubuntu) 合作,共同确保英国实现更好的移动网络覆盖。

EE 正在大力投资网络建设,争取到 2020 年实现 95% 的 4G 网络地理覆盖率。但是,并不是所有地区都可以或者都适合建造新的大型信号塔。他们想充分利用现有基础设施,例如灯塔、高层建筑、山岭等等。另外的一大难题是,依靠现有手段覆盖偏远地区在经济上或技术上是不可行的。正因为如此,EE 才选择与技术创新公司合作开发成本更低、体积更小、弹性更高、效果更好的解决方案。

Lime Micro 即将以众筹形式推出首款支持应用的开源软件无线电,即 LimeSDR。通过 4G 应用,LimeSDR 将构成一个功能完善的基站的基础。将这个基站安装到热气球或无人机上,可以覆盖通过其他方式难以到达的地区。另外,将这些基站嵌入自动售货机、银行设施、智能灯杆、数字标牌等其他用途的设备内部,还将降低部署连接的成本。

EE 希望偏远社区也能加入网络建设当中。这些社区可以提出他们需要的功能,甚至可以参与网络的维护。这可以让社区与运营商以全新的方式合作。并且有了当地人的支持,甚至可以减少受过培训的技术人员长途跋涉解决问题的需要。例如,如果一个基站只是需要重新启动,那么派遣一位工程师从爱丁堡跋涉 300 英里前往诸岛将是一件很不经济(或者不切实际)的事情。

EE 将在英国多所大学举办挑战赛,鼓励大家集思广益,就如何连接未联网地区和降低运营成本提出更多创新和开源的想法。任何有好创意的人都可以参加。Ubuntu Core 具有开源、支持应用和随时可用于生产的特点。你只要购买一个 LimeSDR,下载 Ubuntu Core,就能向世界展示你的应用或设备可以怎样降低网络运营成本。帮助未联网地区实现网络覆盖将促进经济发展,因此你的努力将会造福我们的社会。我们非常期待看到你如何将无线网络的未来变得更加美好.

LimeSDR Crowd Supply 宣传视频(优酷)

Read more
Tim Penhey (thumper)

It has been too long

Well, it has certainly been a lot longer since I wrote a post than I thought.

My work at Canonical still has me on the Juju team. Juju has come a long way in the last few years, and we are on the final push for the 2.0 version. This was initially intended to come out with the Xenial release, but unfortunately was not ready. Xenial has 2.0-beta4 right now, soon to be beta 6. Hoping that real soon now we'll step through the release candidates to a final release. This will be SRU'ed into both Xenial and Trusty.

I plan to do some more detailed posts on some of the Go utility libraries that have come out of the Juju work. In particular, talking again about loggo which I moved under the "github.com/juju" banner, and the errors package.

Recent work has had me look at the database agnostic model representations for migrating models from one controller to another, and also at gomaasapi - the Go library for talking with MAAS. Perhaps more on that later.

Read more
Jamie Young

Ubuntu orange update

Recently, you may have seen our new colour palette update in the SDK. One notable change is the new hex code we’ve assigned to Ubuntu Orange for screen use. The new colour is #E95420.

We have a post coming soon that will delve deeper into our new palette but for now we just wanted to make sure this change is reflected on our website while at the same time touching on it through our blog. Our Suru visual language has evolved to have a lighter feel and we’ve adjusted the hex value in order to fit in with the palette as a whole.

We’ve updated our brand colour guidelines to take into account this change as well. You can find the new hex as well as all the tints of this colour that we recommend using in your design work.

Read more
April Wang

Ubuntu 手机平台的快速发展及其向物联网IoT和各种设备的广泛延伸,要求我们必须解决安全性和可靠性的实际问题。这些挑战同样存在于台式机和服务器系统平台,并令想使用长期支持版本来运行较新软件的用户和开发者感到格外烦恼。这促使我们开发了 snap 打包格式和工具。

在 Ubuntu 16.04 LTS 中,用户将可以同时安装 snap 包和传统的 deb 包。这两种打包格式可以和谐共存,并使我们能够保持现有的操作系统开发和升级流程。这种做法巩固了我们与 Debian 社区的关系,同时也使广大开发者和社区可以根据需要为 Ubuntu 用户发布 deb 应用或 snap 应用。

利用 snap 软件包,开发者可以为 Ubuntu 16.04 LTS 带来更新版本的应用。较新版本的 KDE、GNOME、浏览器或其他桌面环境应用通常很容易在较旧版本的 LTS 上构建,但是打包和提供更新的复杂程度令我们过去无法提供这类应用。

snap 包的安全机制让我们可以开放平台并在各个分支版本间更快地迭代,这是因为 snap 应用与系统的其余部分是隔离的。用户可以放心安装 snap 而不必担心会对系统或其他应用造成影响。同样,开发者可以自己决定为其应用打包的库的具体版本,从而更好地掌控更新周期。事务性更新使 snap 软件包的部署更加稳定和可靠。

通过在 Ubuntu 16.04 LTS 中加入 snap 软件包支持,我们使 Ubuntu 开发者拥有统一的体验,无论他们是为 PC、服务器、手机还是物联网设备开发软件。Snapcraft 是面向 snap 开发推出的一款工具,可帮助开发者轻松打包应用和依赖项。以 snap 软件包为目标的开发者将获得一个极佳的环境来直接在桌面上编写和测试他们的应用,而不必使用某个设备或虚拟机。

付费应用的开发者通常不得不管理各种库之间的依赖关系和兼容性,特别是在较旧版本的 Ubuntu 上,这令他们颇感烦恼。出于这个原因,这些应用将于 2016 年秋季从 deb 迁移到 snap。Canonical 将与开发者社区一同努力,在未来几个月内提供工具、培训和文档来支持大家顺利完成过渡。

虽然这对于 Ubuntu 社区来说一项重大的新功能,但这并不会使我们丧失传统。16.04 及后续版本仍将继续支持数以万计的所有 .deb 格式的应用和软件包,特别是将会继续提供 deb 档案文件,以供所有人使用和分发软件。

如果您想进一步了解在经典 Ubuntu 上使用 snap 的更多信息或对此有任何疑问,请访问 ubuntuonair.com 参加我们近期举办的以下活动:

Snaps on classic Ubuntu Q&A with Olli Ries,4 月 14 日星期四 15:00(UTC 时间)

Snappy Clinic,4 月 26 日星期二 15:00(UTC 时间)

像往常一样,您可以通过 snappy-app-devel@lists.ubuntu.com 邮件列表参与讨论,或在 Ask Ubuntu 上提出和解答有关 snappy 的问题。

原文作者:Olli Ries  最早发布时间:2016 年 4 月 13 日,原文网址

Read more
Stéphane Graber

This is the ninth blog post in this series about LXD 2.0.

LXD logo

Introduction

One of the very exciting feature of LXD 2.0, albeit experimental, is the support for container checkpoint and restore.

Simply put, checkpoint/restore means that the running container state can be serialized down to disk and then restored, either on the same host as a stateful snapshot of the container or on another host which equates to live migration.

Requirements

To have access to container live migration and stateful snapshots, you need the following:

  • A very recent Linux kernel, 4.4 or higher.
  • CRIU 2.0, possibly with some cherry-picked commits depending on your exact kernel configuration.
  • Run LXD directly on the host. It’s not possible to use those features with container nesting.
  • For migration, the target machine must at least implement the instruction set of the source, the target kernel must at least offer the same syscalls as the source and any kernel filesystem which was mounted on the source must also be mountable on the target.

All the needed dependencies are provided by Ubuntu 16.04 LTS, in which case, all you need to do is install CRIU itself:

apt install criu

Using the thing

Stateful snapshots

A normal container snapshot looks like:

stgraber@dakara:~$ lxc snapshot c1 first
stgraber@dakara:~$ lxc info c1 | grep first
 first (taken at 2016/04/25 19:35 UTC) (stateless)

A stateful snapshot instead looks like:

stgraber@dakara:~$ lxc snapshot c1 second --stateful
stgraber@dakara:~$ lxc info c1 | grep second
 second (taken at 2016/04/25 19:36 UTC) (stateful)

This means that all the container runtime state was serialized to disk and included as part of the snapshot. Restoring one such snapshot is done as you would a stateless one:

stgraber@dakara:~$ lxc restore c1 second
stgraber@dakara:~$

Stateful stop/start

Say you want to reboot your server for a kernel update or similar maintenance. Rather than have to wait for all the containers to start from scratch after reboot, you can do:

stgraber@dakara:~$ lxc stop c1 --stateful

The container state will be written to disk and then picked up the next time you start it.

You can even look at what the state looks like:

root@dakara:~# tree /var/lib/lxd/containers/c1/rootfs/state/
/var/lib/lxd/containers/c1/rootfs/state/
├── cgroup.img
├── core-101.img
├── core-102.img
├── core-107.img
├── core-108.img
├── core-109.img
├── core-113.img
├── core-114.img
├── core-122.img
├── core-125.img
├── core-126.img
├── core-127.img
├── core-183.img
├── core-1.img
├── core-245.img
├── core-246.img
├── core-50.img
├── core-52.img
├── core-95.img
├── core-96.img
├── core-97.img
├── core-98.img
├── dump.log
├── eventfd.img
├── eventpoll.img
├── fdinfo-10.img
├── fdinfo-11.img
├── fdinfo-12.img
├── fdinfo-13.img
├── fdinfo-14.img
├── fdinfo-2.img
├── fdinfo-3.img
├── fdinfo-4.img
├── fdinfo-5.img
├── fdinfo-6.img
├── fdinfo-7.img
├── fdinfo-8.img
├── fdinfo-9.img
├── fifo-data.img
├── fifo.img
├── filelocks.img
├── fs-101.img
├── fs-113.img
├── fs-122.img
├── fs-183.img
├── fs-1.img
├── fs-245.img
├── fs-246.img
├── fs-50.img
├── fs-52.img
├── fs-95.img
├── fs-96.img
├── fs-97.img
├── fs-98.img
├── ids-101.img
├── ids-113.img
├── ids-122.img
├── ids-183.img
├── ids-1.img
├── ids-245.img
├── ids-246.img
├── ids-50.img
├── ids-52.img
├── ids-95.img
├── ids-96.img
├── ids-97.img
├── ids-98.img
├── ifaddr-9.img
├── inetsk.img
├── inotify.img
├── inventory.img
├── ip6tables-9.img
├── ipcns-var-10.img
├── iptables-9.img
├── mm-101.img
├── mm-113.img
├── mm-122.img
├── mm-183.img
├── mm-1.img
├── mm-245.img
├── mm-246.img
├── mm-50.img
├── mm-52.img
├── mm-95.img
├── mm-96.img
├── mm-97.img
├── mm-98.img
├── mountpoints-12.img
├── netdev-9.img
├── netlinksk.img
├── netns-9.img
├── netns-ct-9.img
├── netns-exp-9.img
├── packetsk.img
├── pagemap-101.img
├── pagemap-113.img
├── pagemap-122.img
├── pagemap-183.img
├── pagemap-1.img
├── pagemap-245.img
├── pagemap-246.img
├── pagemap-50.img
├── pagemap-52.img
├── pagemap-95.img
├── pagemap-96.img
├── pagemap-97.img
├── pagemap-98.img
├── pages-10.img
├── pages-11.img
├── pages-12.img
├── pages-13.img
├── pages-1.img
├── pages-2.img
├── pages-3.img
├── pages-4.img
├── pages-5.img
├── pages-6.img
├── pages-7.img
├── pages-8.img
├── pages-9.img
├── pipes-data.img
├── pipes.img
├── pstree.img
├── reg-files.img
├── remap-fpath.img
├── route6-9.img
├── route-9.img
├── rule-9.img
├── seccomp.img
├── sigacts-101.img
├── sigacts-113.img
├── sigacts-122.img
├── sigacts-183.img
├── sigacts-1.img
├── sigacts-245.img
├── sigacts-246.img
├── sigacts-50.img
├── sigacts-52.img
├── sigacts-95.img
├── sigacts-96.img
├── sigacts-97.img
├── sigacts-98.img
├── signalfd.img
├── stats-dump
├── timerfd.img
├── tmpfs-dev-104.tar.gz.img
├── tmpfs-dev-109.tar.gz.img
├── tmpfs-dev-110.tar.gz.img
├── tmpfs-dev-112.tar.gz.img
├── tmpfs-dev-114.tar.gz.img
├── tty.info
├── unixsk.img
├── userns-13.img
└── utsns-11.img

0 directories, 154 files

Restoring the container can be done with a simple:

stgraber@dakara:~$ lxc start c1

Live migration

Live migration is basically the same as the stateful stop/start above, except that the container directory and configuration happens to be moved to another machine too.

stgraber@dakara:~$ lxc list c1
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |          IPV4         |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| c1   | RUNNING | 10.178.150.197 (eth0) | 2001:470:b368:4242:216:3eff:fe19:27b0 (eth0) | PERSISTENT | 2         |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+

stgraber@dakara:~$ lxc list s-tollana:
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

stgraber@dakara:~$ lxc move c1 s-tollana:

stgraber@dakara:~$ lxc list c1
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

stgraber@dakara:~$ lxc list s-tollana:
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |          IPV4         |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| c1   | RUNNING | 10.178.150.197 (eth0) | 2001:470:b368:4242:216:3eff:fe19:27b0 (eth0) | PERSISTENT | 2         |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+

Limitations

As I said before, checkpoint/restore of containers is still pretty new and we’re still very much working on this feature, fixing issues as we are made aware of them. We do need more people trying this feature and sending us feedback, I would however not recommend using this in production just yet.

The current list of issues we’re tracking is available on Launchpad.

We expect a basic Ubuntu container with a few services to work properly with CRIU in Ubuntu 16.04. However more complex containers, using device passthrough, complex network services or special storage configurations are likely to fail.

Whenever possible, CRIU will fail at dump time, rather than at restore time. In such cases, the source container will keep running, the snapshot or migration will simply fail and a log file will be generated for debugging.

In rare cases, CRIU fails to restore the container, in which case the source container will still be around but will be stopped and will have to be manually restarted.

Sending bug reports

We’re tracking bugs related to checkpoint/restore against the CRIU Ubuntu package on Launchpad. Most of the work to fix those bugs will then happen upstream either on CRIU itself or the Linux kernel, but it’s easier for us to track things this way.

To file a new bug report, head here.

Please make sure to include:

  • The command you ran and the error message as displayed to you
  • Output of “lxc info” (*)
  • Output of “lxc info <container name>”
  • Output of “lxc config show –expanded <container name>”
  • Output of “dmesg” (*)
  • Output of “/proc/self/mountinfo” (*)
  • Output of “lxc exec <container name> — cat /proc/self/mountinfo”
  • Output of “uname -a” (*)
  • The content of /var/log/lxd.log (*)
  • The content of /etc/default/lxd-bridge (*)
  • A tarball of /var/log/lxd/<container name>/ (*)

If reporting a migration bug as opposed to a stateful snapshot or stateful stop bug, please include the data for both the source and target for any of the above which has been marked with a (*).

Extra information

The CRIU website can be found at: https://criu.org

The main LXD website is at: https://linuxcontainers.org/lxd
Development happens on Github at: https://github.com/lxc/lxd
Mailing-list support happens on: https://lists.linuxcontainers.org
IRC support happens in: #lxcontainers on irc.freenode.net
Try LXD online: https://linuxcontainers.org/lxd/try-it

Read more
facundo

Algunos te pedimos perdón


Si el agua que tomo se pudre, se pudre, me pudro por dentro también.
Si el aire que respiro se pudre, se pudre, se pudre mi forma de ser.

Agoniza montaña vacía de su mineral, de su corazón.
La represa que linda energía se muere otro río, se muere la vida.

Pachamama, Madre Tierra, Madre de todos los colores.
Pachamama, Madre Tierra, Madre de todos los sabores.

Hay bosques que daban oxígeno y sombra y ahora ya ni se ven.
La Tierra se retuerce por dentro y hay tantas flores que ya no crecen.

Pachamama, Madre Tierra, Madre de todos los colores.
Pachamama, Madre Tierra, Madre de todos los sabores.

Algunos te pedimos perdón.

("Pachamama", Arbolito)

Read more
niemeyer

As much anticipated, this week Ubuntu 16.04 LTS was released with integrated support for snaps on classic Ubuntu.

Snappy 2.0 is a modern software platform, that includes the ability to define rich interfaces between snaps that control their security and confinement, comprehensive observation and control of system changes, completion and undoing of partial system changes across restarts/reboots/crashes, macaroon-based authentication for local access and store access, preliminary development mode, a polished filesystem layout and CLI experience, modern sequencing of revisions, and so forth.

The previous post in this series described the reassuring details behind how snappy does system changes. This post will now cover Snappy interfaces, the mechanism that controls the confinement and integration of snaps with other snaps and with the system itself.

A snap interface gives one snap the ability to use resources provided by another snap, including the operating system snap (ubuntu-core is itself a snap!). That’s quite vague, and intentionally so. Software interacts with other software for many reasons and in diverse ways, and Snappy is a platform that has to mediate all of that according to user needs.

In practice, though, the mechanism is straightforward and pleasant to deal with. Without any snaps in the system, there are no interfaces available:

% sudo snap interfaces
error: no interfaces found

If we install the ubuntu-core snap alone (done implicitly when the first snap is installed), we can already see some interface slots being provided by it, but no plugs connected to them:

% sudo snap install ubuntu-core
75.88 MB / 75.88 MB [=====================] 100.00 % 355.56 KB/s 

% snap interfaces
Slot                 Plug
:firewall-control    -
:home                -
:locale-control      -
(...)
:opengl              -
:timeserver-control  -
:timezone-control    -
:unity7              -
:x11                 -

The syntax is <snap>:<slot> and <snap>:<plug>. The lack of a snap name is a shorthand notation for slots and plugs on the operating system snap.

Now let’s install an application:

% sudo snap install ubuntu-calculator-app
120.01 MB / 120.01 MB [=====================] 100.00 % 328.88 KB/s 

% snap interfaces
Slot                 Plug
:firewall-control    -
:home                -
:locale-control      -
(...)
:opengl              ubuntu-calculator-app
:timeserver-control  -
:timezone-control    -
:unity7              ubuntu-calculator-app
:x11                 -

At this point the application should work fine. But let’s instead see what happens if we take away one of these interfaces:

% sudo snap disconnect \
             ubuntu-calculator-app:unity7 ubuntu-core:unity7 

% /snap/bin/ubuntu-calculator-app.calculator
QXcbConnection: Could not connect to display :0

The application installed depends on unity7 to be able to display itself properly, which is itself based on X11. When we disconnected the interface that gave it permission to be accessing these resources, the application was unable to touch them.

The security minded will observe that X11 is not in fact a secure protocol. A number of system abuses are possible when we hand an application this permission. Other interfaces such as home would give the snap access to every non-hidden file in the user’s $HOME directory (those that do not start with a dot), which means a malicious application might steal personal information and send it over the network (assuming it also defines a network plug).

Some might be surprised that this is the case, but this is a misunderstanding about the role of snaps and Snappy as a software platform. When you install software from the Ubuntu archive, that’s a statement of trust in the Ubuntu and Debian developers. When you install Google’s Chrome or MongoDB binaries from their respective archives, that’s a statement of trust in those developers (these have root on your system!). Snappy is not eliminating the need for that trust, as once you give a piece of software access to your personal files, web camera, microphone, etc, you need to believe that it won’t be using those allowances maliciously.

The point of Snappy’s confinement in that picture is to enable a software ecosystem that can control exactly what is allowed and to whom in a clear and observable way, in addition to the same procedural care that we’ve all learned to appreciate in the Linux world, not instead of it. Preventing people from using all relevant resources in the system would simply force them to use that same software over less secure mechanisms instead of fixing the problem.

And what we have today is just the beginning. These interfaces will soon become much richer and more fine grained, including resource selection (e.g. which serial port?), and some of them will disappear completely in favor of more secure choices (Unity 8, for instance).

These are exciting times for Ubuntu and the software world.

@gniemeyer

Read more