├── .gitignore ├── .gitmodules ├── .qmake.conf ├── LICENSE ├── README.md ├── TODO ├── compositor ├── compositor.pro ├── fonts │ ├── FontAwesome.otf │ └── manzanit.pfb ├── grefsen.qrc ├── images │ ├── background.jpg │ ├── gee.svg │ ├── grefsen-logo-on-silhouette.png │ ├── grefsen-logo.png │ └── grefsenkollen-silhouette.svg ├── main.cpp ├── processlauncher.cpp ├── processlauncher.h ├── qml │ ├── Chrome.qml │ ├── Keyboard.qml │ ├── Output.qml │ └── main.qml ├── stackableitem.cpp └── stackableitem.h ├── example-config ├── example-config.pro ├── grefsen.conf └── screen.qml ├── grefsen.pro ├── imports ├── Grefsen │ ├── ConnmanPopover.qml │ ├── Grefsen.pro │ ├── LauncherIcon.qml │ ├── LauncherMenu.qml │ ├── LauncherMenuIcon.qml │ ├── LeftSlidePanel.qml │ ├── PanelClock.qml │ ├── Popover.qml │ ├── PopoverPanelItem.qml │ ├── PopoverTrayIcon.qml │ ├── QuitButton.qml │ ├── RightSlidePanel.qml │ ├── TuioTouchPanel.qml │ ├── iconprovider.cpp │ ├── iconprovider.h │ ├── launchermodel.cpp │ ├── launchermodel.h │ ├── plugin.cpp │ └── qmldir └── imports.pro ├── screenshot.jpg └── thirdparty ├── freefont ├── README ├── manzanit.license └── manzanit.pfb └── thirdparty.pro /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *-build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/Font-Awesome"] 2 | path = thirdparty/Font-Awesome 3 | url = https://github.com/FortAwesome/Font-Awesome.git 4 | [submodule "thirdparty/qt-tuio-sender"] 5 | path = thirdparty/qt-tuio-sender 6 | url = https://github.com/ec1oud/qt-tuio-sender.git 7 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | load(qt_build_config) 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Grefsen is a Qt/Wayland compositor providing a minimal desktop environment. 2 | 3 | ![a screenshot](screenshot.jpg "screenshot") 4 | 5 | # Requirements 6 | 7 | * Qt 6 (if you need 5.x, use the 5.x branch) 8 | - qtbase, qtdeclarative, qtwayland 9 | - QtQuick.Controls 10 | * libQtXdg 11 | - build from [github](https://github.com/lxqt/libqtxdg/tree/wip_qt6) with Qt 6 and cmake 12 | * for the Connman network manager popover (optional): [libconnman-qt](https://git.merproject.org/mer-core/libconnman-qt) 13 | or from [github](https://github.com/sailfishos/libconnman-qt) 14 | * FontAwesome, but it's a submodule here 15 | * recommended: [freefonts](http://ibiblio.org/pub/linux/X11/fonts/freefonts-0.10.tar.gz) but hopefully your distro has that as a package 16 | - the clock uses Manzanita, which is also installed as a resource in the executable, just in case 17 | - WindsorDemi is another nice font, but it will fall back to others if that is not installed 18 | 19 | # Building 20 | 21 | ``` 22 | git submodule update --init --recursive 23 | qmake 24 | make 25 | ``` 26 | 27 | # Installation 28 | 29 | ``` 30 | mkdir ~/.config/grefsen 31 | cp example-config/*.qml ~/.config/grefsen 32 | ``` 33 | 34 | Then modify to taste. If you want the wallpaper shown in the screenshot, download 35 | the full-resolution version from 36 | [wikipedia](https://commons.wikimedia.org/wiki/File:Oslo_mot_Grefsentoppen_fra_Ekeberg.jpg) 37 | to your ~/.config/grefsen directory. 38 | 39 | # Running 40 | 41 | It can run as a window inside an X11 session. 42 | 43 | If you want to run it on the Linux console without an X11 session 44 | (which is much more efficient), you can use a startup script like this: 45 | 46 | ``` 47 | #!/bin/sh 48 | export QT_QPA_PLATFORM=eglfs 49 | export QT_AUTO_SCREEN_SCALE_FACTOR=0 # don't embiggen stuff on "high-res" displays 50 | #export QT_QPA_EGLFS_PHYSICAL_WIDTH=480 # in case it's not detected 51 | #export QT_QPA_EGLFS_PHYSICAL_HEIGHT=270 # or you wish to override 52 | # try to restart if it crashes; write a log file 53 | ~/src/grefsen/grefsen -r -l /tmp/grefsen.log 54 | ``` 55 | 56 | If you are on the console and have the problem that the keyboard, mouse etc. 57 | don't work (which should be fixed in Qt 5.6 and above, theoretically) you can 58 | try various input plugins (after rebooting via ssh, or the power button ;-) by adding 59 | ```-plugin EvdevTouch -plugin EvdevMouse -plugin EvdevTablet -plugin EvdevKeyboard``` 60 | or 61 | ```-plugin libinput``` 62 | 63 | The set of applications you can run inside is mostly limited to those 64 | that are built with Qt 5, so far. That includes a lot of KDE applications. 65 | It is intended to eventually be able to run weston and GTK3 apps too; 66 | that's mainly a matter of qtwayland having the XDG shell support finished. 67 | Grefsen does not include an embedded X server yet, but it might be possible. 68 | 69 | The application menu uses libqtxdg to find .desktop files and .menu files 70 | in a way which is compliant with the 71 | [XDG menu spec](https://specifications.freedesktop.org/menu-spec/menu-spec-1.0.html), 72 | which means that you have some control over the contents of the menu, if you 73 | don't mind editing XML files. But grefsen does not try to prevent you from 74 | running incompatible applications: they will simply fail to launch. So you 75 | might want a custom menu just for grefsen, whereas by default you will probably 76 | see all the same applications as you do in your usual desktop environment. 77 | 78 | [YAT](https://github.com/jorgen/yat) is another terminal alternative. 79 | It's planned to have better touchscreen support soon (flicking with your 80 | finger, text selection when you drag a mouse), whereas konsole still doesn't. 81 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | eglfs: touch doesn't work 2 | raise/lower 3 | way to get in/out of fullscreen mode 4 | window decoration theme rather than server-side decorations 5 | because access to the QWindow is easier than dealing with limitations of Wayland protocol for window management 6 | alt-tab 7 | virtual desktops 8 | window flinging onto other display 9 | better hover handling 10 | other 3-finger window gestures? 11 | ctrl-alt-f key to get back to other consoles (chvt) 12 | map caps lock to control (QTBUG-53183) 13 | touch window drag/resize 14 | long-press menu 15 | dbus menubar & tray 16 | xwayland: root window transparent, or under the grefsen desktop background? or rootless? 17 | probably hard: decorate X windows (like Weston does) 18 | device hotplugging? 19 | show single-ended-arrow cursor over the resize edge if the window can only be enlarged, not shrunken (idea from MS Teams on macOS) 20 | use new calendar control (submodule?) https://code.qt.io/cgit/qt-extensions/qtquickcalendar.git 21 | 22 | to fix 23 | ------ 24 | redundant cursor problem: 25 | - QT_QPA_EGLFS_HIDECURSOR env var works, but 26 | - WaylandCursorItem shows only the entered window's cursor, and doesn't change on leave 27 | - cursors set on items within the compositor are only shown via the EGLFS cursor, not WaylandCursorItem 28 | connman configuration - can't always turn technologies on and off 29 | the crash when killing applications via the titlebar close button 30 | click outside popups to close 31 | can't run grefsen inside grefsen (or is it impossible?) 32 | 33 | done 34 | ---- 35 | basic sidebar with a basic launcher for konsole 36 | some custom window decorations 37 | launcher menu based on desktop files 38 | launchers for more apps: qtcreator and a browser at least 39 | -c for config 40 | clock and calendar 41 | xdg_shell protocol (weston and gtk apps don't run without it) 42 | 3-finger pinch to resize and move windows 43 | alt-drag, meta-drag 44 | 45 | -------------------------------------------------------------------------------- /compositor/compositor.pro: -------------------------------------------------------------------------------- 1 | QT += gui qml quick 2 | CONFIG += link_pkgconfig 3 | QMAKE_CXXFLAGS += -std=c++17 4 | TARGET = ../grefsen 5 | 6 | SOURCES += *.cpp 7 | 8 | HEADERS += *.h 9 | 10 | INCLUDEPATH += /usr/include/glib-2.0 /usr/lib/glib-2.0/include 11 | 12 | OTHER_FILES = \ 13 | qml/main.qml \ 14 | qml/Output.qml \ 15 | qml/Chrome.qml 16 | 17 | RESOURCES += grefsen.qrc 18 | 19 | OBJECTS_DIR = .obj 20 | MOC_DIR = .moc 21 | RCC_DIR = .rcc 22 | 23 | PKGCONFIG += glib-2.0 24 | 25 | sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS grefsen.pro 26 | -------------------------------------------------------------------------------- /compositor/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- 1 | ../../thirdparty/Font-Awesome/otfs/Font Awesome 5 Free-Regular-400.otf -------------------------------------------------------------------------------- /compositor/fonts/manzanit.pfb: -------------------------------------------------------------------------------- 1 | ../../thirdparty/freefont/manzanit.pfb -------------------------------------------------------------------------------- /compositor/grefsen.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/background.jpg 4 | qml/main.qml 5 | qml/Keyboard.qml 6 | qml/Output.qml 7 | qml/Chrome.qml 8 | fonts/FontAwesome.otf 9 | images/grefsen-logo-on-silhouette.png 10 | fonts/manzanit.pfb 11 | 12 | 13 | -------------------------------------------------------------------------------- /compositor/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ec1oud/grefsen/05a6203a2210b57c366f36297b82c548e5653ddb/compositor/images/background.jpg -------------------------------------------------------------------------------- /compositor/images/gee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 25 | 27 | image/svg+xml 28 | 30 | 31 | 32 | 33 | 34 | 36 | 57 | 60 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /compositor/images/grefsen-logo-on-silhouette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ec1oud/grefsen/05a6203a2210b57c366f36297b82c548e5653ddb/compositor/images/grefsen-logo-on-silhouette.png -------------------------------------------------------------------------------- /compositor/images/grefsen-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ec1oud/grefsen/05a6203a2210b57c366f36297b82c548e5653ddb/compositor/images/grefsen-logo.png -------------------------------------------------------------------------------- /compositor/images/grefsenkollen-silhouette.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 53 | 59 | G 71 | 77 | 78 | -------------------------------------------------------------------------------- /compositor/main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 Shawn Rutledge 4 | ** 5 | ** This file is free software; you can redistribute it and/or 6 | ** modify it under the terms of the GNU Lesser General Public 7 | ** License version 3 as published by the Free Software Foundation 8 | ** and appearing in the file LICENSE included in the packaging 9 | ** of this file. 10 | ** 11 | ** This code is distributed in the hope that it will be useful, 12 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ** GNU Lesser General Public License for more details. 15 | ** 16 | ****************************************************************************/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "processlauncher.h" 35 | #include "stackableitem.h" 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | static QLatin1String glassPaneName("glassPane"); 46 | static QByteArray grefsonExecutablePath; 47 | static qint64 grefsonPID; 48 | static void *signalHandlerStack; 49 | static QString logFilePath; 50 | static QElapsedTimer sinceStartup; 51 | 52 | QString grefsenConfigDirPath(QDir::homePath() + "/.config/grefsen/"); 53 | 54 | extern "C" void signalHandler(int signal) 55 | { 56 | #ifdef Q_WS_X11 57 | // Kill window since it's frozen anyway. 58 | if (QX11Info::display()) 59 | close(ConnectionNumber(QX11Info::display())); 60 | #endif 61 | pid_t pid = fork(); 62 | switch (pid) { 63 | case -1: // error 64 | break; 65 | case 0: // child 66 | kill(grefsonPID, 9); 67 | fprintf(stderr, "crashed (PID %lld SIG %d): respawn %s\n", grefsonPID, signal, grefsonExecutablePath.constData()); 68 | execl(grefsonExecutablePath.constData(), grefsonExecutablePath.constData(), (char *) 0); 69 | _exit(EXIT_FAILURE); 70 | default: // parent 71 | prctl(PR_SET_PTRACER, pid, 0, 0, 0); 72 | waitpid(pid, 0, 0); 73 | _exit(EXIT_FAILURE); 74 | break; 75 | } 76 | } 77 | 78 | static void setupSignalHandler() 79 | { 80 | // Setup an alternative stack for the signal handler. This way we are able to handle SIGSEGV 81 | // even if the normal process stack is exhausted. 82 | stack_t ss; 83 | ss.ss_sp = signalHandlerStack = malloc(SIGSTKSZ); // Usual requirements for alternative signal stack. 84 | if (ss.ss_sp == 0) { 85 | qWarning("Warning: Could not allocate space for alternative signal stack (%s).", Q_FUNC_INFO); 86 | return; 87 | } 88 | ss.ss_size = SIGSTKSZ; 89 | ss.ss_flags = 0; 90 | if (sigaltstack(&ss, 0) == -1) { 91 | qWarning("Warning: Failed to set alternative signal stack (%s).", Q_FUNC_INFO); 92 | return; 93 | } 94 | 95 | // Install signal handler. 96 | struct sigaction sa; 97 | if (sigemptyset(&sa.sa_mask) == -1) { 98 | qWarning("Warning: Failed to empty signal set (%s).", Q_FUNC_INFO); 99 | return; 100 | } 101 | sa.sa_handler = &signalHandler; 102 | // SA_RESETHAND - Restore signal action to default after signal handler has been called. 103 | // SA_NODEFER - Don't block the signal after it was triggered (otherwise blocked signals get 104 | // inherited via fork() and execve()). Without this the signal will not be delivered to the 105 | // restarted Qt Creator. 106 | // SA_ONSTACK - Use alternative stack. 107 | sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_ONSTACK; 108 | // See "man 7 signal" for an overview of signals. 109 | // Do not add SIGPIPE here, QProcess and QTcpSocket use it. 110 | const int signalsToHandle[] = { SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGBUS, 0 }; 111 | for (int i = 0; signalsToHandle[i]; ++i) 112 | if (sigaction(signalsToHandle[i], &sa, 0) == -1) 113 | qWarning("Failed to install signal handler for signal \"%s\"", strsignal(signalsToHandle[i])); 114 | } 115 | 116 | void qtMsgLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) 117 | { 118 | static QFile log(logFilePath); 119 | if (!log.isOpen()) 120 | log.open(QIODevice::WriteOnly | QIODevice::Text); 121 | char typeChar = ' '; 122 | switch (type) { 123 | case QtDebugMsg: 124 | typeChar = 'd'; 125 | break; 126 | case QtInfoMsg: 127 | typeChar = 'i'; 128 | break; 129 | case QtWarningMsg: 130 | typeChar = 'W'; 131 | break; 132 | case QtCriticalMsg: 133 | typeChar = '!'; 134 | break; 135 | case QtFatalMsg: 136 | typeChar = 'F'; 137 | } 138 | char buf[64]; 139 | qint64 ts = sinceStartup.elapsed(); 140 | snprintf(buf, 64, "[%6lld.%03lld %c] ", ts / 1000, ts % 1000, typeChar); 141 | log.write(buf); 142 | if (context.function) { 143 | snprintf(buf, 64, "%s:%d: ", context.function, context.line); 144 | log.write(buf); 145 | } 146 | log.write(msg.toUtf8()); 147 | log.write("\n"); 148 | if (type == QtFatalMsg) 149 | abort(); 150 | } 151 | 152 | static void registerTypes() 153 | { 154 | qmlRegisterType("com.theqtcompany.wlprocesslauncher", 1, 0, "ProcessLauncher"); 155 | qmlRegisterType("com.theqtcompany.wlcompositor", 1, 0, "StackableItem"); 156 | } 157 | 158 | static qreal highestDPR(QList &screens) 159 | { 160 | qreal ret = 0; 161 | for (const QScreen *scr : screens) { 162 | qDebug() << "Screen" << scr->name() << scr->geometry() << scr->physicalSize() 163 | << "DPI: log" << scr->logicalDotsPerInch() << "phys" << scr->physicalDotsPerInch() 164 | << "DPR" << scr->devicePixelRatio(); 165 | ret = qMax(ret, scr->devicePixelRatio()); 166 | } 167 | return ret; 168 | } 169 | 170 | int main(int argc, char *argv[]) 171 | { 172 | sinceStartup.start(); 173 | if (!qEnvironmentVariableIsSet("QT_XCB_GL_INTEGRATION")) 174 | qputenv("QT_XCB_GL_INTEGRATION", "xcb_egl"); // use xcomposite-glx if no EGL 175 | if (!qEnvironmentVariableIsSet("QT_WAYLAND_DISABLE_WINDOWDECORATION")) 176 | qputenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1"); 177 | if (!qEnvironmentVariableIsSet("QT_LABS_CONTROLS_STYLE")) 178 | qputenv("QT_LABS_CONTROLS_STYLE", "Universal"); 179 | if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME")) 180 | qputenv("QT_QPA_PLATFORMTHEME", "generic"); 181 | QGuiApplication app(argc, argv); 182 | //QCoreApplication::setApplicationName("grefsen"); // defaults to name of the executable 183 | QCoreApplication::setOrganizationName("grefsen"); 184 | QCoreApplication::setApplicationVersion("0.1"); 185 | // app.setAttribute(Qt::AA_DisableHighDpiScaling); // better use the env variable... but that's not enough on eglfs 186 | grefsonExecutablePath = app.applicationFilePath().toLocal8Bit(); 187 | grefsonPID = QCoreApplication::applicationPid(); 188 | bool windowed = false; 189 | 190 | QList screens = QGuiApplication::screens(); 191 | { 192 | QCommandLineParser parser; 193 | parser.setApplicationDescription("Grefsen Qt/Wayland compositor"); 194 | parser.addHelpOption(); 195 | parser.addVersionOption(); 196 | 197 | QCommandLineOption respawnOption(QStringList() << "r" << "respawn", 198 | QCoreApplication::translate("main", "respawn grefsen after a crash")); 199 | parser.addOption(respawnOption); 200 | 201 | QCommandLineOption logFileOption(QStringList() << "l" << "log", 202 | QCoreApplication::translate("main", "redirect all debug/warning/error output to a log file"), 203 | QCoreApplication::translate("main", "file path")); 204 | parser.addOption(logFileOption); 205 | 206 | QCommandLineOption configDirOption(QStringList() << "c" << "config", 207 | QCoreApplication::translate("main", "load config files from the given directory (default is ~/.config/grefsen)"), 208 | QCoreApplication::translate("main", "directory path")); 209 | parser.addOption(configDirOption); 210 | 211 | QCommandLineOption screenOption(QStringList() << "s" << "screen", 212 | QCoreApplication::translate("main", "send output to the given screen"), 213 | QCoreApplication::translate("main", "screen")); 214 | parser.addOption(screenOption); 215 | 216 | QCommandLineOption windowOption(QStringList() << "w" << "window", 217 | QCoreApplication::translate("main", "run in a window rather than fullscreen")); 218 | parser.addOption(windowOption); 219 | 220 | parser.process(app); 221 | if (parser.isSet(respawnOption)) 222 | setupSignalHandler(); 223 | if (parser.isSet(configDirOption)) 224 | grefsenConfigDirPath = parser.value(configDirOption); 225 | if (parser.isSet(logFileOption)) { 226 | logFilePath = parser.value(logFileOption); 227 | qInstallMessageHandler(qtMsgLog); 228 | } 229 | if (parser.isSet(screenOption)) { 230 | QStringList scrNames = parser.values(screenOption); 231 | QList keepers; 232 | foreach (QScreen *scr, screens) 233 | if (scrNames.contains(scr->name(), Qt::CaseInsensitive)) 234 | keepers << scr; 235 | if (keepers.isEmpty()) { 236 | qWarning() << "None of the screens" << scrNames << "exist; available screens:"; 237 | foreach (QScreen *scr, screens) 238 | qWarning() << " " << scr->name() << scr->geometry(); 239 | return -1; 240 | } 241 | screens = keepers; 242 | } 243 | if (parser.isSet(windowOption)) 244 | windowed = true; 245 | 246 | qreal dpr = highestDPR(screens); 247 | if (!qEnvironmentVariableIsSet("XCURSOR_SIZE")) { 248 | // QTBUG-67579: we can't have different cursor sizes on different screens 249 | // or different windows yet, but we can override globally 250 | int cursorSize = int(32 * dpr); 251 | qDebug() << "highest DPR" << dpr << "-> cursor size" << cursorSize; 252 | qputenv("XCURSOR_SIZE", QByteArray::number(cursorSize)); 253 | } 254 | 255 | QFontDatabase fd; 256 | if (!fd.families().contains(QLatin1String("FontAwesome"))) 257 | if (QFontDatabase::addApplicationFont(":/fonts/FontAwesome.otf")) 258 | qWarning("failed to load FontAwesome from resources"); 259 | if (!fd.families().contains(QLatin1String("Manzanita"))) 260 | if (QFontDatabase::addApplicationFont(":/fonts/manzanit.pfb")) 261 | qWarning("failed to load Manzanita font from resources"); 262 | } 263 | 264 | registerTypes(); 265 | qputenv("QT_QPA_PLATFORM", "wayland"); // not for grefsen but for child processes 266 | 267 | QQmlApplicationEngine appEngine; 268 | appEngine.addImportPath(app.applicationDirPath() + QLatin1String("/imports")); 269 | appEngine.load(QUrl("qrc:///qml/main.qml")); 270 | QObject *root = appEngine.rootObjects().first(); 271 | root->setProperty("fullscreenAllowed", !windowed); 272 | appEngine.rootContext()->setContextProperty(glassPaneName, 273 | root->findChild(glassPaneName)); 274 | QList windows = root->findChildren(); 275 | auto windowIter = windows.begin(); 276 | auto screenIter = screens.begin(); 277 | while (windowIter != windows.end() && screenIter != screens.end()) { 278 | QWindow * window = *windowIter; 279 | QScreen * screen = *screenIter; 280 | window->setScreen(screen); 281 | if (windowed) { 282 | window->resize(1920, 1080); 283 | window->showNormal(); 284 | } else { 285 | window->setGeometry(screen->geometry()); 286 | window->showFullScreen(); 287 | } 288 | ++windowIter; 289 | ++screenIter; 290 | if (windowed) 291 | break; 292 | } 293 | 294 | return app.exec(); 295 | } 296 | -------------------------------------------------------------------------------- /compositor/processlauncher.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is free software; you can redistribute it and/or 7 | ** modify it under the terms of the GNU Lesser General Public 8 | ** License version 3 as published by the Free Software Foundation 9 | ** and appearing in the file LICENSE included in the packaging 10 | ** of this file. 11 | ** 12 | ** This code is distributed in the hope that it will be useful, 13 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ** GNU Lesser General Public License for more details. 16 | ** 17 | ****************************************************************************/ 18 | 19 | #include "processlauncher.h" 20 | #include 21 | 22 | WaylandProcessLauncher::WaylandProcessLauncher(QObject *parent) 23 | : QObject(parent) 24 | { 25 | } 26 | 27 | WaylandProcessLauncher::~WaylandProcessLauncher() 28 | { 29 | } 30 | 31 | void WaylandProcessLauncher::launch(const QString &program) 32 | { 33 | QProcess *process = new QProcess(this); 34 | 35 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 36 | process->setProcessEnvironment(env); 37 | 38 | connect(process, static_cast(&QProcess::finished), 39 | process, &QProcess::deleteLater); 40 | connect(process, &QProcess::errorOccurred, &QProcess::deleteLater); 41 | connect(process, &QProcess::errorOccurred, this, &WaylandProcessLauncher::onError); 42 | connect(process, &QProcess::stateChanged, this, &WaylandProcessLauncher::onStateChanged); 43 | 44 | QStringList arguments; 45 | arguments << "-platform" << "wayland"; 46 | process->start(program, arguments); 47 | 48 | } 49 | 50 | void WaylandProcessLauncher::onError(QProcess::ProcessError error) 51 | { 52 | QProcess *proc = qobject_cast(sender()); 53 | qWarning() << error << "from" << proc; 54 | if (proc) 55 | qWarning() << proc->readAllStandardError(); 56 | } 57 | 58 | void WaylandProcessLauncher::onStateChanged(QProcess::ProcessState state) 59 | { 60 | QProcess *proc = qobject_cast(sender()); 61 | qDebug() << proc << "state changed" << state; 62 | if (proc) 63 | qDebug() << proc->readAllStandardError(); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /compositor/processlauncher.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is free software; you can redistribute it and/or 7 | ** modify it under the terms of the GNU Lesser General Public 8 | ** License version 3 as published by the Free Software Foundation 9 | ** and appearing in the file LICENSE included in the packaging 10 | ** of this file. 11 | ** 12 | ** This code is distributed in the hope that it will be useful, 13 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ** GNU Lesser General Public License for more details. 16 | ** 17 | ****************************************************************************/ 18 | 19 | #ifndef PROCESSLAUNCHER_H 20 | #define PROCESSLAUNCHER_H 21 | 22 | #include 23 | #include 24 | 25 | 26 | class WaylandProcessLauncher : public QObject 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | explicit WaylandProcessLauncher(QObject *parent = 0); 32 | ~WaylandProcessLauncher(); 33 | Q_INVOKABLE void launch(const QString &program); 34 | protected slots: 35 | void onError(QProcess::ProcessError error); 36 | void onStateChanged(QProcess::ProcessState state); 37 | }; 38 | 39 | #endif // PROCESSLAUNCHER_H 40 | -------------------------------------------------------------------------------- /compositor/qml/Chrome.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Copyright (C) 2020 Shawn Rutledge 5 | ** 6 | ** This file is free software; you can redistribute it and/or 7 | ** modify it under the terms of the GNU Lesser General Public 8 | ** License version 3 as published by the Free Software Foundation 9 | ** and appearing in the file LICENSE included in the packaging 10 | ** of this file. 11 | ** 12 | ** This code is distributed in the hope that it will be useful, 13 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ** GNU Lesser General Public License for more details. 16 | ** 17 | ****************************************************************************/ 18 | 19 | import QtQuick 20 | import QtQuick.Window 21 | import QtWayland.Compositor 22 | import QtWayland.Compositor.XdgShell 23 | import Qt5Compat.GraphicalEffects 24 | import com.theqtcompany.wlcompositor 25 | 26 | StackableItem { 27 | id: rootChrome 28 | property alias shellSurface: surfaceItem.shellSurface 29 | property var topLevel 30 | property alias moveItem: surfaceItem.moveItem 31 | property bool decorationVisible: false 32 | property bool moving: surfaceItem.moveItem ? surfaceItem.moveItem.moving : false 33 | property alias destroyAnimation : destroyAnimationImpl 34 | 35 | property int marginWidth : surfaceItem.isFullscreen ? 0 : (surfaceItem.isPopup ? 1 : 6) 36 | property int titlebarHeight : surfaceItem.isPopup || surfaceItem.isFullscreen ? 0 : 25 37 | property string screenName: "" 38 | 39 | property real resizeAreaWidth: 12 40 | 41 | x: surfaceItem.moveItem.x - surfaceItem.output.geometry.x 42 | y: surfaceItem.moveItem.y - surfaceItem.output.geometry.y 43 | height: surfaceItem.height + marginWidth + titlebarHeight 44 | width: surfaceItem.width + 2 * marginWidth 45 | visible: surfaceItem.valid 46 | 47 | Rectangle { 48 | id: decoration 49 | anchors.fill: parent 50 | border.width: 1 51 | radius: marginWidth 52 | border.color: (rightEdgeHover.hovered || bottomEdgeHover.hovered) ? "#ffc02020" :"#305070a0" 53 | color: "#50ffffff" 54 | visible: rootChrome.decorationVisible && !surfaceItem.isFullscreen && 55 | !topLevel || topLevel.decorationMode === XdgToplevel.ServerSideDecoration 56 | 57 | // TODO write a ResizeHandler for this purpose? otherwise there are up to 8 components for edges and corners 58 | Item { 59 | id: rightEdgeResizeArea 60 | x: parent.width - resizeAreaWidth / 2; width: resizeAreaWidth; height: parent.height - resizeAreaWidth 61 | onXChanged: 62 | if (horzDragHandler.active) { 63 | var size = topLevel.sizeForResize(horzDragHandler.initialSize, 64 | Qt.point(horzDragHandler.translation.x, horzDragHandler.translation.y), 65 | Qt.RightEdge); 66 | topLevel.sendConfigure(size, [3] /*XdgShellToplevel.ResizingState*/ ) 67 | } 68 | DragHandler { 69 | id: horzDragHandler 70 | property size initialSize 71 | onActiveChanged: if (active) initialSize = Qt.size(rootChrome.width, rootChrome.height) 72 | yAxis.enabled: false 73 | } 74 | HoverHandler { 75 | id: rightEdgeHover 76 | cursorShape: Qt.SizeHorCursor // problem: this so far only sets the EGLFS cursor, not WaylandCursorItem 77 | } 78 | } 79 | Item { 80 | id: bottomEdgeResizeArea 81 | y: parent.height - resizeAreaWidth / 2; height: resizeAreaWidth; width: parent.width - resizeAreaWidth 82 | onYChanged: 83 | if (vertDragHandler.active) { 84 | var size = topLevel.sizeForResize(vertDragHandler.initialSize, 85 | Qt.point(vertDragHandler.translation.x, vertDragHandler.translation.y), 86 | Qt.BottomEdge); 87 | topLevel.sendConfigure(size, [3] /*XdgShellToplevel.ResizingState*/ ) 88 | } 89 | DragHandler { 90 | id: vertDragHandler 91 | property size initialSize 92 | onActiveChanged: if (active) initialSize = Qt.size(rootChrome.width, rootChrome.height) 93 | xAxis.enabled: false 94 | } 95 | HoverHandler { 96 | id: bottomEdgeHover 97 | cursorShape: Qt.SizeVerCursor 98 | } 99 | } 100 | Item { 101 | id: bottomRightResizeArea 102 | x: parent.width - resizeAreaWidth / 2; y: parent.height - resizeAreaWidth / 2 103 | width: resizeAreaWidth; height: parent.height - resizeAreaWidth 104 | onXChanged: resize() 105 | onYChanged: resize() 106 | function resize() { 107 | if (bottomRightDragHandler.active) { 108 | var size = topLevel.sizeForResize(bottomRightDragHandler.initialSize, 109 | Qt.point(bottomRightDragHandler.translation.x, bottomRightDragHandler.translation.y), 110 | Qt.BottomEdge | Qt.RightEdge); 111 | topLevel.sendConfigure(size, [3] /*XdgShellToplevel.ResizingState*/ ) 112 | } 113 | } 114 | DragHandler { 115 | id: bottomRightDragHandler 116 | property size initialSize 117 | onActiveChanged: if (active) initialSize = Qt.size(rootChrome.width, rootChrome.height) 118 | } 119 | HoverHandler { 120 | id: bottomRightHover 121 | cursorShape: Qt.SizeFDiagCursor 122 | } 123 | } 124 | // end of resizing components 125 | 126 | Item { 127 | id: titlebar 128 | anchors.margins: marginWidth 129 | anchors.top: parent.top 130 | anchors.left: parent.left 131 | anchors.right: parent.right 132 | height: titlebarHeight - marginWidth 133 | visible: !surfaceItem.isPopup 134 | 135 | LinearGradient { 136 | anchors.fill: parent 137 | start: Qt.point(0, 0) 138 | end: Qt.point(0, height) 139 | gradient: Gradient { 140 | GradientStop { position: 0.0; color: "#50ffffff" } 141 | GradientStop { position: 1.0; color: "#e0ffffff" } 142 | } 143 | } 144 | 145 | Text { 146 | color: "gray" 147 | text: surfaceItem.shellSurface.title !== undefined ? surfaceItem.shellSurface.title : "" 148 | anchors.margins: marginWidth 149 | 150 | anchors.left: parent.left 151 | anchors.verticalCenter: parent.verticalCenter 152 | } 153 | 154 | DragHandler { 155 | id: titlebarDrag 156 | target: rootChrome 157 | cursorShape: Qt.ClosedHandCursor 158 | property var movingBinding: Binding { 159 | target: surfaceItem.moveItem 160 | property: "moving" 161 | value: titlebarDrag.active 162 | } 163 | } 164 | 165 | HoverHandler { 166 | cursorShape: Qt.OpenHandCursor 167 | } 168 | 169 | TapHandler { 170 | acceptedButtons: Qt.LeftButton 171 | gesturePolicy: TapHandler.DragThreshold 172 | onTapped: rootChrome.raise() 173 | } 174 | 175 | TapHandler { 176 | acceptedButtons: Qt.MiddleButton 177 | gesturePolicy: TapHandler.DragThreshold 178 | onTapped: rootChrome.lower() 179 | } 180 | 181 | RectangularGlow { 182 | id: closeButton 183 | visible: !surfaceItem.isTransient 184 | height: 8 185 | width: 8 186 | anchors.margins: marginWidth 187 | anchors.right: parent.right 188 | anchors.verticalCenter: parent.verticalCenter 189 | glowRadius: 8 190 | cornerRadius: glowRadius 191 | spread: 0.4 192 | color: closeButtonHover.hovered ? "#88FF0000" : "transparent" 193 | Text { 194 | id: closeIcon 195 | anchors.centerIn: parent 196 | font.pixelSize: parent.height + parent.glowRadius 197 | font.family: "FontAwesome" 198 | text: "\uf00d" 199 | } 200 | HoverHandler { 201 | id: closeButtonHover 202 | } 203 | TapHandler { 204 | onTapped: topLevel.sendClose() 205 | } 206 | } 207 | } 208 | } 209 | 210 | SequentialAnimation { 211 | id: destroyAnimationImpl 212 | ParallelAnimation { 213 | NumberAnimation { target: scaleTransform; property: "yScale"; to: 2/height; duration: 150 } 214 | NumberAnimation { target: scaleTransform; property: "xScale"; to: 0.4; duration: 150 } 215 | } 216 | NumberAnimation { target: scaleTransform; property: "xScale"; to: 0; duration: 150 } 217 | ScriptAction { script: { rootChrome.destroy(); } } 218 | } 219 | 220 | ParallelAnimation { 221 | id: createAnimationImpl 222 | NumberAnimation { target: scaleTransform; property: "yScale"; from: 0; to: 1; duration: 150 } 223 | NumberAnimation { target: scaleTransform; property: "xScale"; from: 0; to: 1; duration: 150 } 224 | } 225 | 226 | SequentialAnimation { 227 | id: receivedFocusAnimation 228 | ParallelAnimation { 229 | NumberAnimation { target: scaleTransform; property: "yScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad } 230 | NumberAnimation { target: scaleTransform; property: "xScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad } 231 | } 232 | ParallelAnimation { 233 | NumberAnimation { target: scaleTransform; property: "yScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad } 234 | NumberAnimation { target: scaleTransform; property: "xScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad } 235 | } 236 | } 237 | 238 | transform: [ 239 | Scale { 240 | id:scaleTransform 241 | origin.x: rootChrome.width / 2 242 | origin.y: rootChrome.height / 2 243 | 244 | } 245 | ] 246 | 247 | ShellSurfaceItem { 248 | id: surfaceItem 249 | property bool valid: false 250 | property bool isPopup: false 251 | property bool isTransient: false 252 | property bool isFullscreen: false 253 | 254 | opacity: moving ? 0.5 : 1.0 255 | inputEventsEnabled: !pinch3.active && !metaDragHandler.active && !altDragHandler.active 256 | 257 | x: marginWidth 258 | y: titlebarHeight 259 | 260 | DragHandler { 261 | id: metaDragHandler 262 | acceptedModifiers: Qt.MetaModifier 263 | target: surfaceItem.moveItem 264 | property var movingBinding: Binding { 265 | target: surfaceItem.moveItem 266 | property: "moving" 267 | value: metaDragHandler.active 268 | } 269 | } 270 | 271 | DragHandler { 272 | id: altDragHandler 273 | acceptedModifiers: Qt.AltModifier 274 | target: surfaceItem.moveItem 275 | property var movingBinding: Binding { 276 | target: surfaceItem.moveItem 277 | property: "moving" 278 | value: altDragHandler.active 279 | } 280 | } 281 | 282 | Connections { 283 | target: shellSurface 284 | ignoreUnknownSignals: true 285 | 286 | onActivatedChanged: { // xdg_shell only 287 | if (shellSurface.activated) 288 | receivedFocusAnimation.start(); 289 | } 290 | onSetPopup: { 291 | surfaceItem.isPopup = true 292 | decoration.visible = false 293 | } 294 | onSetTransient: { 295 | surfaceItem.isTransient = true 296 | } 297 | onSetFullScreen: { 298 | surfaceItem.isFullscreen = true 299 | rootChrome.x = 0 300 | rootChrome.y = 0 301 | } 302 | } 303 | 304 | onSurfaceDestroyed: { 305 | destroyAnimationImpl.start(); 306 | } 307 | 308 | onWidthChanged: { 309 | valid = !surface.cursorSurface && surface.bufferSize.width > 0 && surface.bufferSize.height > 0 310 | } 311 | 312 | onValidChanged: if (valid) { 313 | if (isFullscreen) { 314 | topLevel.sendFullscreen(output.geometry) 315 | } else if (decorationVisible) { 316 | createAnimationImpl.start() 317 | } 318 | } 319 | } 320 | 321 | PinchHandler { 322 | id: pinch3 323 | objectName: "3-finger pinch" 324 | minimumPointCount: 3 325 | maximumPointCount: 3 326 | minimumScale: 0.1 327 | minimumRotation: 0 328 | maximumRotation: 0 329 | onActiveChanged: if (!active) { 330 | // just a silly way of getting a QSize for sendConfigure() 331 | var size = topLevel.sizeForResize(Qt.size(width * scale, height * scale), Qt.point(0, 0), 0); 332 | topLevel.sendConfigure(size, [3] /*XdgShellToplevel.ResizingState*/); 333 | rootChrome.scale = 1 334 | } 335 | } 336 | 337 | Rectangle { 338 | visible: moving || metaDragHandler.active || altDragHandler.active 339 | border.color: "white" 340 | color: "black" 341 | radius: 5 342 | anchors.centerIn: parent 343 | width: height * 10 344 | height: moveGeometryText.implicitHeight * 1.5 345 | Text { 346 | id: moveGeometryText 347 | color: "white" 348 | anchors.centerIn: parent 349 | text: Math.round(rootChrome.x) + "," + Math.round(rootChrome.y) + " on " + rootChrome.screenName 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /compositor/qml/Keyboard.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is free software; you can redistribute it and/or 7 | ** modify it under the terms of the GNU Lesser General Public 8 | ** License version 3 as published by the Free Software Foundation 9 | ** and appearing in the file LICENSE included in the packaging 10 | ** of this file. 11 | ** 12 | ** This code is distributed in the hope that it will be useful, 13 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ** GNU Lesser General Public License for more details. 16 | ** 17 | ****************************************************************************/ 18 | 19 | import QtQuick 2.5 20 | import QtQuick.VirtualKeyboard 2.1 21 | 22 | InputPanel { 23 | id: inputPanel 24 | visible: active 25 | y: active ? parent.height - inputPanel.height : parent.height 26 | anchors.left: parent.left 27 | anchors.right: parent.right 28 | } 29 | 30 | -------------------------------------------------------------------------------- /compositor/qml/Output.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is free software; you can redistribute it and/or 7 | ** modify it under the terms of the GNU Lesser General Public 8 | ** License version 3 as published by the Free Software Foundation 9 | ** and appearing in the file LICENSE included in the packaging 10 | ** of this file. 11 | ** 12 | ** This code is distributed in the hope that it will be useful, 13 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ** GNU Lesser General Public License for more details. 16 | ** 17 | ****************************************************************************/ 18 | 19 | import QtQuick 2.8 20 | import QtQuick.Window 2.3 21 | import QtWayland.Compositor 1.0 22 | import Grefsen 1.0 23 | 24 | WaylandOutput { 25 | id: output 26 | property variant viewsBySurface: ({}) 27 | property alias surfaceArea: compositorArea // Chrome instances are parented to compositorArea 28 | property alias targetScreen: win.screen 29 | sizeFollowsWindow: true 30 | 31 | window: Window { 32 | id: win 33 | property Item customizedBackground: desktopLoader.item 34 | color: "black" 35 | title: "Grefsen on " + Screen.name 36 | 37 | WaylandMouseTracker { 38 | id: mouseTracker 39 | objectName: "wmt on " + Screen.name 40 | anchors.fill: parent 41 | windowSystemCursorEnabled: false 42 | 43 | Item { 44 | id: background 45 | anchors.fill: parent 46 | Loader { 47 | id: desktopLoader 48 | anchors.fill: parent 49 | source: "file://" + Env.grefsenconfig + "screen.qml" 50 | } 51 | } 52 | Item { 53 | id: compositorArea 54 | anchors.fill: parent 55 | } 56 | /* 57 | Loader { 58 | anchors.fill: parent 59 | source: "Keyboard.qml" 60 | } 61 | */ 62 | Item { 63 | id: glassPane 64 | objectName: "glassPane" 65 | anchors.fill: parent 66 | } 67 | WaylandCursorItem { 68 | id: cursor 69 | x: mouseTracker.mouseX 70 | y: mouseTracker.mouseY 71 | seat: output.compositor.defaultSeat 72 | visible: mouseTracker.containsMouse 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /compositor/qml/main.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Copyright (C) 2020 Shawn Rutledge 5 | ** 6 | ** This file is free software; you can redistribute it and/or 7 | ** modify it under the terms of the GNU Lesser General Public 8 | ** License version 3 as published by the Free Software Foundation 9 | ** and appearing in the file LICENSE included in the packaging 10 | ** of this file. 11 | ** 12 | ** This code is distributed in the hope that it will be useful, 13 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ** GNU Lesser General Public License for more details. 16 | ** 17 | ****************************************************************************/ 18 | 19 | import QtQuick 20 | import QtWayland.Compositor 21 | import QtWayland.Compositor.XdgShell 22 | import QtWayland.Compositor.WlShell 23 | import Qt.labs.settings 24 | 25 | WaylandCompositor { 26 | id: comp 27 | 28 | Instantiator { 29 | id: screens 30 | model: Qt.application.screens 31 | 32 | delegate: Output { 33 | compositor: comp 34 | targetScreen: modelData 35 | Component.onCompleted: if (!comp.defaultOutput) comp.defaultOutput = this 36 | position: Qt.point(virtualX, virtualY) 37 | } 38 | } 39 | 40 | Component { 41 | id: chromeComponent 42 | Chrome { 43 | } 44 | } 45 | 46 | Component { 47 | id: moveItemComponent 48 | Item { 49 | property bool moving: false 50 | } 51 | } 52 | 53 | QtWindowManager { 54 | id: qtWindowManager 55 | onShowIsFullScreenChanged: console.debug("Show is fullscreen hint for Qt applications:", showIsFullScreen) 56 | } 57 | 58 | WlShell { 59 | onWlShellSurfaceCreated: handleShellSurfaceCreated(shellSurface, shellSurface, true) 60 | } 61 | 62 | XdgShell { 63 | // void toplevelCreated(QWaylandXdgToplevel *toplevel, QWaylandXdgSurface *xdgSurface); 64 | onToplevelCreated: handleShellSurfaceCreated(xdgSurface, toplevel, true) 65 | } 66 | 67 | XdgDecorationManagerV1 { 68 | preferredMode: XdgToplevel.ServerSideDecoration 69 | } 70 | 71 | TextInputManager { 72 | } 73 | 74 | defaultSeat.keymap { 75 | layout: keymapSettings.layout 76 | variant: keymapSettings.variant 77 | options: keymapSettings.options 78 | rules: keymapSettings.rules 79 | model: keymapSettings.model 80 | } 81 | Settings { 82 | id: keymapSettings 83 | category: "keymap" 84 | property string layout: "us" 85 | property string variant: "intl" 86 | property string options: "grp:shifts_toggle,compose:ralt,ctrl:nocaps" 87 | property string rules: "" 88 | property string model: "" 89 | } 90 | 91 | function createShellSurfaceItem(shellSurface, topLevel, moveItem, output, decorate) { 92 | var parentSurfaceItem = output.viewsBySurface[shellSurface.parentSurface]; 93 | var parent = parentSurfaceItem || output.surfaceArea; 94 | var item = chromeComponent.createObject(parent, { 95 | "shellSurface": shellSurface, 96 | "topLevel": topLevel, 97 | "moveItem": moveItem, 98 | "output": output, 99 | "screenName": output.targetScreen.name, 100 | "decorationVisible": decorate 101 | }); 102 | if (parentSurfaceItem !== undefined) { 103 | item.x += output.position.x; 104 | item.y += output.position.y; 105 | } 106 | output.viewsBySurface[shellSurface.surface] = item; 107 | console.log(lcComp, "shellSurface:", shellSurface, "topLevel:", topLevel, "moveItem:", moveItem, "output:", output, 108 | "decorate:", decorate, "parentSurfaceItem:", parentSurfaceItem, "new surface item:", output.viewsBySurface[shellSurface.surface]) 109 | } 110 | 111 | function handleShellSurfaceCreated(shellSurface, topLevel, decorate) { 112 | var moveItem = moveItemComponent.createObject(defaultOutput.surfaceArea, { 113 | "x": screens.objectAt(0).position.x, 114 | "y": screens.objectAt(0).position.y, 115 | "width": Qt.binding(function() { return shellSurface.surface.width; }), 116 | "height": Qt.binding(function() { return shellSurface.surface.height; }) 117 | }); 118 | for (var i = 0; i < screens.count; ++i) 119 | createShellSurfaceItem(shellSurface, topLevel, moveItem, screens.objectAt(i), decorate); 120 | } 121 | 122 | LoggingCategory { 123 | id: lcComp 124 | name: "grefsen.compositor" 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /compositor/stackableitem.cpp: -------------------------------------------------------------------------------- 1 | #include "stackableitem.h" 2 | 3 | StackableItem::StackableItem() 4 | { 5 | 6 | } 7 | 8 | 9 | void StackableItem::lower() 10 | { 11 | QQuickItem *parent = parentItem(); 12 | Q_ASSERT(parent); 13 | QQuickItem *bottom = parent->childItems().first(); 14 | if (this != bottom) 15 | stackBefore(bottom); 16 | } 17 | 18 | void StackableItem::raise() 19 | { 20 | QQuickItem *parent = parentItem(); 21 | Q_ASSERT(parent); 22 | QQuickItem *top = parent->childItems().last(); 23 | if (this != top) 24 | stackAfter(top); 25 | } 26 | -------------------------------------------------------------------------------- /compositor/stackableitem.h: -------------------------------------------------------------------------------- 1 | #ifndef STACKABLEITEM_H 2 | #define STACKABLEITEM_H 3 | 4 | #include 5 | 6 | class StackableItem : public QQuickItem 7 | { 8 | Q_OBJECT 9 | public: 10 | StackableItem(); 11 | 12 | public Q_SLOTS: 13 | void raise(); 14 | void lower(); 15 | }; 16 | 17 | #endif // STACKABLEITEM_H 18 | -------------------------------------------------------------------------------- /example-config/example-config.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | OTHER_FILES = *.qml *.conf 3 | -------------------------------------------------------------------------------- /example-config/grefsen.conf: -------------------------------------------------------------------------------- 1 | [keymap] 2 | layout="us,ru,gr" 3 | model= 4 | options="grp:shifts_toggle,compose:ralt,ctrl:nocaps" 5 | rules= 6 | variant="intl,phonetic" 7 | -------------------------------------------------------------------------------- /example-config/screen.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.6 2 | import Grefsen 1.0 3 | 4 | Image { 5 | fillMode: Image.PreserveAspectCrop 6 | // download from https://commons.wikimedia.org/wiki/File:Oslo_mot_Grefsentoppen_fra_Ekeberg.jpg 7 | source: Env.grefsenconfig + "Oslo_mot_Grefsentoppen_fra_Ekeberg.jpg" 8 | 9 | // TODO set the icon theme 10 | 11 | LeftSlidePanel { 12 | id: leftPanel 13 | 14 | LauncherMenuIcon { } 15 | 16 | LauncherIcon { 17 | path: "/usr/bin/konsole" 18 | icon: "utilities-terminal" 19 | } 20 | 21 | LauncherIcon { 22 | path: "/usr/bin/qtcreator" 23 | icon: "QtProject-qtcreator" 24 | } 25 | 26 | LauncherIcon { 27 | // the Qt WebKit example; replace with your favorite Wayland-compatible browser 28 | path: "fancybrowser" 29 | icon: "internet-web-browser" 30 | } 31 | 32 | LauncherIcon { 33 | path: "quasselclient" 34 | icon: "quassel" 35 | } 36 | 37 | /* 38 | // depends on https://git.merproject.org/mer-core/libconnman-qt 39 | PopoverTrayIcon { 40 | popover: ConnmanPopover { } 41 | icon: "preferences-system-network" 42 | } 43 | */ 44 | 45 | PanelClock { } 46 | 47 | QuitButton { } 48 | 49 | Shortcut { 50 | sequence: "Meta+A" // maybe not the best one... or maybe we don't need it at all 51 | onActivated: leftPanel.toggle() 52 | } 53 | } 54 | 55 | Shortcut { 56 | sequence: "Ctrl+Alt+Backspace" 57 | onActivated: Qt.quit() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /grefsen.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += compositor imports example-config thirdparty 4 | -------------------------------------------------------------------------------- /imports/Grefsen/ConnmanPopover.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.6 2 | import QtQuick.Controls 2.0 3 | import MeeGo.Connman 0.2 4 | 5 | Popover { 6 | width: 320 7 | height: 480 8 | 9 | NetworkManager { 10 | id: manager 11 | function checkTechnologies() { 12 | var tl = manager.technologiesList() 13 | if (tl.length > 0) 14 | techList.model = tl 15 | console.log("state " + state + " available? " + available + " enabled svcs " + servicesEnabled + " techs " + technologiesEnabled) 16 | console.log(" list " + tl) 17 | } 18 | 19 | onAvailabilityChanged: checkTechnologies() 20 | onStateChanged: checkTechnologies() 21 | onOfflineModeChanged: checkTechnologies() 22 | onTechnologiesChanged: checkTechnologies() 23 | onTechnologiesEnabledChanged: checkTechnologies() 24 | } 25 | 26 | SwipeView { 27 | id: swipeView 28 | 29 | currentIndex: 0 30 | anchors.fill: parent 31 | 32 | ListView { 33 | id: techList 34 | spacing: 2 35 | delegate: Rectangle { 36 | width: parent.width 37 | height: 40 38 | color: "#222" 39 | property NetworkTechnology tech: manager.getTechnology(modelData) 40 | Text { 41 | text: tech.name 42 | color: "white" 43 | anchors.verticalCenter: parent.verticalCenter 44 | x: 6 45 | } 46 | MouseArea { 47 | anchors.fill: parent 48 | enabled: modelData === "wifi" && techEnableSwitch.checked 49 | onClicked: swipeView.currentIndex = 1 50 | } 51 | Switch { 52 | id: techEnableSwitch 53 | checked: tech.powered 54 | anchors { 55 | right: parent.right 56 | margins: 6 57 | verticalCenter: parent.verticalCenter 58 | } 59 | onReleased: { 60 | console.log("setting " + modelData + " " + !checked) 61 | tech.powered = !checked 62 | } 63 | } 64 | } 65 | } 66 | ListView { 67 | id: wifiList 68 | spacing: 2 69 | model: TechnologyModel { 70 | id: techModel 71 | name: "wifi" 72 | onAvailabilityChanged: { 73 | console.log(name + " powered? " + powered + " available? " + available) 74 | } 75 | } 76 | delegate: Rectangle { 77 | width: parent.width 78 | height: 40 79 | color: "#222" 80 | Text { 81 | text: networkService.name 82 | color: "white" 83 | anchors.verticalCenter: parent.verticalCenter 84 | x: 6 85 | } 86 | Text { 87 | anchors { 88 | right: parent.right 89 | margins: 6 90 | verticalCenter: parent.verticalCenter 91 | } 92 | color: "white" 93 | font.pixelSize: 30 94 | text: networkService.connected ? "✔" : "" 95 | } 96 | MouseArea { 97 | anchors.fill: parent 98 | enabled: !networkService.connected 99 | onClicked: { 100 | console.log("trying to connect to " + networkService.name) 101 | networkService.requestConnect() 102 | } 103 | } 104 | } 105 | } 106 | Item { 107 | id: wifiConfigPage 108 | Rectangle { 109 | anchors.fill: parent 110 | anchors.margins: 6 111 | anchors.bottomMargin: indicator.height 112 | radius: 8 113 | color: "#444" 114 | } 115 | } 116 | } 117 | 118 | PageIndicator { 119 | id: indicator 120 | 121 | count: swipeView.count 122 | currentIndex: swipeView.currentIndex 123 | 124 | anchors.bottom: swipeView.bottom 125 | anchors.horizontalCenter: parent.horizontalCenter 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /imports/Grefsen/Grefsen.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | TARGET = grefsenplugin 3 | TARGETPATH = Grefsen 4 | QT += qml quick xml 5 | CONFIG += link_pkgconfig 6 | QMAKE_CXXFLAGS += -std=c++17 7 | PKGCONFIG += glib-2.0 Qt6Xdg 8 | 9 | SOURCES += \ 10 | plugin.cpp \ 11 | iconprovider.cpp \ 12 | launchermodel.cpp 13 | 14 | HEADERS += \ 15 | iconprovider.h \ 16 | launchermodel.h 17 | 18 | OTHER_FILES += *.qml 19 | -------------------------------------------------------------------------------- /imports/Grefsen/LauncherIcon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import com.theqtcompany.wlprocesslauncher 1.0 3 | 4 | MouseArea { 5 | width: parent.width 6 | height: width 7 | 8 | ProcessLauncher { 9 | id: launcher 10 | } 11 | 12 | property string path: "" 13 | property string icon: "" 14 | Image { 15 | source: "image://icon/" + icon 16 | sourceSize.width: 64 17 | sourceSize.height: 64 18 | anchors.centerIn: parent 19 | } 20 | onClicked: if (path) launcher.launch(path); else console.log("nothing to launch") 21 | } 22 | -------------------------------------------------------------------------------- /imports/Grefsen/LauncherMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 2.15 3 | import Grefsen 1.0 4 | 5 | Item { 6 | id: root 7 | width: 200 8 | // height: Math.min(1000, list.implicitHeight) 9 | height: 500 10 | signal close 11 | 12 | HoverHandler { 13 | id: hover 14 | onHoveredChanged: if (!hovered) root.close() 15 | } 16 | 17 | Rectangle { 18 | anchors.fill: parent 19 | border.color: "black" 20 | color: "#555" 21 | radius: 10 22 | opacity: 0.5 23 | } 24 | 25 | TextField { 26 | id: searchField 27 | width: parent.width - 12 28 | x: 6; y: 6 29 | focus: true 30 | onTextChanged: LauncherModel.substringFilter = text 31 | MouseArea { 32 | height: searchField.height 33 | width: height 34 | anchors.right: parent.right 35 | anchors.verticalCenter: parent.verticalCenter 36 | onClicked: { 37 | LauncherModel.reset() 38 | searchField.text = "" 39 | } 40 | Text { 41 | anchors.centerIn: parent 42 | font.family: "FontAwesome" 43 | text: "\uf05c" // TODO more specific clear-text-field icon 44 | font.pointSize: searchField.font.pointSize * 1.25 45 | } 46 | } 47 | 48 | } 49 | ListView { 50 | id: list 51 | anchors.fill: parent 52 | anchors.topMargin: searchField.height + 6 53 | anchors.bottomMargin: 0 54 | anchors.margins: 6 55 | clip: true 56 | model: LauncherModel.applicationMenu 57 | delegate: MouseArea { 58 | width: parent.width 59 | height: 32 60 | onClicked: LauncherModel.select(modelData) 61 | 62 | Rectangle { 63 | radius: 2 64 | anchors.fill: parent 65 | anchors.margins: 1 66 | opacity: 0.35 67 | } 68 | Image { 69 | source: "image://icon/" + modelData.icon 70 | sourceSize.width: 22 71 | sourceSize.height: 22 72 | anchors.verticalCenter: parent.verticalCenter 73 | x: 4 74 | } 75 | Text { 76 | anchors.verticalCenter: parent.verticalCenter 77 | x: 30 78 | elide: Text.ElideRight 79 | text: modelData.title 80 | // Component.onCompleted: console.log(" "+ JSON.stringify(modelData)) 81 | width: parent.width - x - 4 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /imports/Grefsen/LauncherMenuIcon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import Grefsen 1.0 3 | 4 | MouseArea { 5 | id: root 6 | width: parent.width 7 | height: width 8 | property bool checked: false 9 | 10 | Image { 11 | source: "qrc:///images/grefsen-logo-on-silhouette.png" 12 | anchors.centerIn: parent 13 | } 14 | onClicked: root.checked = !checked 15 | 16 | LauncherMenu { 17 | anchors.left: parent.right 18 | visible: root.checked 19 | onClose: root.checked = false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /imports/Grefsen/LeftSlidePanel.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import Qt5Compat.GraphicalEffects 3 | import Grefsen 4 | 5 | /*! 6 | A panel which slides out from the left edge of the screen. 7 | 8 | You can do that with your finger on the touchscreen, by mouseover, 9 | or by activating one of the open/close/toggle functions. 10 | 11 | Intended to be Fitts Law compliant: the content Item goes just to the 12 | screen edges, so that a button whose MouseArea goes to the top and/or left 13 | edge can be activated by slamming the mouse cursor into the edge and 14 | clicking. 15 | */ 16 | Flickable { 17 | id: root 18 | width: 72 19 | height: contentContainer.implicitHeight + 20 20 | anchors.left: parent.left 21 | y: -10 22 | parent: glassPane 23 | contentWidth: width * 2 24 | contentHeight: height 25 | 26 | function open() { 27 | contentX = 0 28 | } 29 | 30 | function close() { 31 | contentX = width 32 | } 33 | 34 | function toggle() { 35 | if (contentX < width / 2) 36 | contentX = width 37 | else 38 | contentX = 0 39 | } 40 | 41 | Component.onCompleted: close() 42 | 43 | default property alias __content: contentContainer.data 44 | onDraggingChanged: if (!dragging) { 45 | if (horizontalVelocity > 0) 46 | flick(-1500, 0) 47 | else 48 | flick(1500, 0) 49 | } 50 | flickableDirection: Flickable.HorizontalFlick 51 | rebound: Transition { 52 | NumberAnimation { 53 | properties: "x" 54 | duration: 300 55 | easing.type: Easing.OutBounce 56 | } 57 | } 58 | 59 | RectangularGlow { 60 | id: effect 61 | width: root.width 62 | height: root.height 63 | glowRadius: 10 64 | spread: 0.2 65 | color: "#eaffa0" 66 | cornerRadius: rect.radius + glowRadius 67 | 68 | Rectangle { 69 | id: rect 70 | anchors.fill: parent 71 | anchors.leftMargin: -200 72 | color: "#394d65" 73 | radius: 10 74 | antialiasing: true 75 | 76 | Column { 77 | id: contentContainer 78 | anchors.fill: parent 79 | anchors.leftMargin: 200 80 | anchors.topMargin: 10 81 | anchors.margins: 6 82 | spacing: 6 83 | } 84 | } 85 | HoverHandler { 86 | margin: 10 87 | onHoveredChanged: if (hovered) root.open(); else root.close() 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /imports/Grefsen/PanelClock.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import QtQuick.Controls 1.4 3 | import QtQuick.Controls.Styles 1.4 4 | 5 | PopoverPanelItem { 6 | 7 | Item { 8 | id: clock 9 | height: time.implicitHeight - 8 10 | Text { 11 | x: 2 12 | id: time 13 | font.family: "Manzanita" 14 | font.pixelSize: 46 15 | color: "lightgreen" 16 | } 17 | Text { 18 | x: 48 19 | id: seconds 20 | font.family: "Manzanita" 21 | font.pixelSize: 30 22 | color: "lightgreen" 23 | anchors.baseline: time.baseline 24 | } 25 | function update() { 26 | var now = new Date(); 27 | time.text = ("0" + now.getUTCHours()).slice(-2) + ":" 28 | + ("0" + now.getUTCMinutes()).slice(-2) 29 | seconds.text = ":" + ("0" + now.getUTCSeconds()).slice(-2); 30 | date.text = now.getFullYear() + "." + 31 | ("0" + (now.getMonth() + 1)).slice(-2) + "." + 32 | ("0" + now.getDate()).slice(-2) 33 | } 34 | } 35 | Text { 36 | id: date 37 | font.pixelSize: 11 38 | anchors.top: clock.bottom 39 | anchors.horizontalCenter: parent.horizontalCenter 40 | anchors.horizontalCenterOffset: 2 41 | color: "beige" 42 | } 43 | Timer { 44 | interval: 1000; running: true; repeat: true 45 | onTriggered: clock.update() 46 | } 47 | Component.onCompleted: clock.update() 48 | 49 | popover: Popover { 50 | width: 480 51 | height: 320 52 | 53 | Calendar { 54 | anchors.fill: parent 55 | frameVisible: false 56 | style: CalendarStyle { 57 | gridVisible: false 58 | background: Item { } 59 | dayDelegate: Rectangle { 60 | color: styleData.selected ? "#111" : 61 | (styleData.visibleMonth && styleData.valid ? "#444" : "#666"); 62 | 63 | Text { 64 | text: styleData.date.getDate() 65 | anchors.centerIn: parent 66 | color: styleData.valid ? "beige" : "brown" 67 | font.pointSize: 14 68 | } 69 | 70 | Rectangle { 71 | width: parent.width 72 | height: 1 73 | color: "#555" 74 | anchors.bottom: parent.bottom 75 | } 76 | 77 | Rectangle { 78 | width: 1 79 | height: parent.height 80 | color: "#555" 81 | anchors.right: parent.right 82 | } 83 | } 84 | navigationBar: Rectangle { 85 | color: "beige" 86 | implicitHeight: row.implicitHeight 87 | radius: 6 88 | antialiasing: true 89 | Row { 90 | id: row 91 | anchors.fill: parent 92 | ToolButton { 93 | id: prevButton 94 | text: "\u25c0" // BLACK LEFT-POINTING TRIANGLE 95 | onClicked: control.showPreviousMonth() 96 | } 97 | Text { 98 | id: navigationBar 99 | text: styleData.title 100 | width: parent.width - prevButton.width - nextButton.width 101 | horizontalAlignment: Text.AlignHCenter 102 | anchors.verticalCenter: parent.verticalCenter 103 | font.family: "WindsorDemi" 104 | font.pointSize: 18 105 | color: "#420" 106 | } 107 | ToolButton { 108 | id: nextButton 109 | text: "\u25b6" // BLACK RIGHT-POINTING TRIANGLE 110 | onClicked: control.showNextMonth() 111 | } 112 | } 113 | } 114 | dayOfWeekDelegate: Text { 115 | id: dayLabel 116 | text: control.__locale.dayName(styleData.dayOfWeek, control.dayOfWeekFormat) 117 | horizontalAlignment: Text.AlignHCenter 118 | color: "beige" 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /imports/Grefsen/Popover.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.6 2 | 3 | Item { 4 | id: wrapper 5 | property real pointAboveToX: -1 6 | property real pointBelowToX: -1 7 | property real pointLeftToY: -1 8 | property real pointRightToY: -1 9 | width: 100 10 | height: 100 11 | 12 | default property alias __content: contentContainer.data 13 | property var __popoverPos: mapToItem(glassPane, x, y) 14 | Component.onCompleted: popover.parent = glassPane 15 | 16 | Item { 17 | id: popover 18 | x: __popoverPos.x 19 | y: __popoverPos.y 20 | width: wrapper.width 21 | height: wrapper.height 22 | 23 | Rectangle { 24 | id: outerBackground 25 | anchors.fill: parent 26 | color: "#555" 27 | radius: 10 28 | antialiasing: true 29 | opacity: 0.5 30 | } 31 | 32 | Rectangle { 33 | id: pointyBitBorder 34 | color: "black" 35 | border.color: innerBackground.border.color 36 | border.width: 2 37 | anchors.fill: pointyBit 38 | anchors.margins: -2 39 | rotation: 45 40 | antialiasing: true 41 | } 42 | 43 | Rectangle { 44 | id: innerBackground 45 | anchors.fill: parent 46 | anchors.margins: 4 47 | color: "#333" 48 | border.width: 2 49 | border.color: "#FF6600" 50 | radius: 10 51 | antialiasing: true 52 | } 53 | 54 | Rectangle { 55 | id: pointyBit 56 | property real pointToX: __popoverPos.x + Math.max(pointAboveToX, pointBelowToX) 57 | x: pointToX > 0 ? pointToX - popover.x - 2 : pointRightToY > 0 ? popover.width - 12 : -2 58 | property real pointToY: __popoverPos.y + Math.max(pointLeftToY, pointRightToY) 59 | y: pointToY > 0 ? pointToY - popover.y - 2 : pointBelowToX > 0 ? popover.height - 12 : -5 60 | width: 14 61 | height: 14 62 | rotation: 45 63 | color: innerBackground.color 64 | antialiasing: true 65 | visible: pointToX > 0 || pointToY > 0 66 | } 67 | 68 | Rectangle { 69 | id: contentContainer 70 | radius: 8 71 | anchors.fill: innerBackground 72 | anchors.margins: 6 73 | color: "transparent" 74 | clip: true // maybe expensive, but done because SwipeView lets neighbor panels stay visible sometimes 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /imports/Grefsen/PopoverPanelItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | MouseArea { 4 | id: root 5 | width: parent.width 6 | height: width 7 | 8 | property Component popover: null 9 | 10 | Loader { 11 | id: loader 12 | anchors.left: root.right 13 | anchors.top: root.top 14 | onItemChanged: if (item) item.pointLeftToY = root.height / 2 15 | } 16 | 17 | onClicked: { 18 | if (loader.sourceComponent == undefined) 19 | loader.sourceComponent = popover 20 | else 21 | loader.sourceComponent = undefined 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /imports/Grefsen/PopoverTrayIcon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | PopoverPanelItem { 4 | property string icon: "" 5 | Image { 6 | source: "image://icon/" + icon 7 | sourceSize.width: 64 8 | sourceSize.height: 64 9 | anchors.centerIn: parent 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /imports/Grefsen/QuitButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import com.theqtcompany.wlprocesslauncher 1.0 3 | 4 | MouseArea { 5 | width: parent.width 6 | height: background.height 7 | 8 | Rectangle { 9 | id: background 10 | color: "red" 11 | radius: width 12 | width: height 13 | height: closeIcon.font.pixelSize * 1.1 14 | anchors.centerIn: parent 15 | } 16 | 17 | Text { 18 | id: closeIcon 19 | anchors.centerIn: parent 20 | anchors.verticalCenterOffset: -1 21 | font.pixelSize: 36 22 | font.family: "FontAwesome" 23 | text: "\uf00d" 24 | color: "white" 25 | } 26 | 27 | onClicked: Qt.quit() 28 | } 29 | -------------------------------------------------------------------------------- /imports/Grefsen/RightSlidePanel.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import Qt5Compat.GraphicalEffects 3 | 4 | Flickable { 5 | id: root 6 | width: 100 7 | height: 480 8 | anchors.right: parent.right 9 | y: -10 10 | z: 10000 11 | contentWidth: width * 2 12 | contentHeight: height 13 | 14 | function open() { 15 | contentX = width 16 | } 17 | 18 | default property alias __content: rect.data 19 | onDraggingChanged: if (!dragging) { 20 | if (horizontalVelocity > 0) 21 | flick(-1500, 0) 22 | else 23 | flick(1500, 0) 24 | } 25 | flickableDirection: Flickable.HorizontalFlick 26 | rebound: Transition { 27 | NumberAnimation { 28 | properties: "x" 29 | duration: 300 30 | easing.type: Easing.OutBounce 31 | } 32 | } 33 | 34 | RectangularGlow { 35 | id: effect 36 | x: width 37 | width: root.width 38 | height: root.height 39 | glowRadius: 10 40 | spread: 0.2 41 | color: "cyan" 42 | cornerRadius: rect.radius + glowRadius 43 | 44 | Rectangle { 45 | id: rect 46 | anchors.fill: parent 47 | anchors.rightMargin: -200 48 | color: "black" 49 | radius: 10 50 | antialiasing: true 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /imports/Grefsen/TuioTouchPanel.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | 3 | Rectangle { 4 | color: "black" 5 | opacity: 0.8 6 | radius: 15 7 | antialiasing: true 8 | visible: false 9 | 10 | Shortcut { 11 | sequence: "Meta+T" 12 | onActivated: visible = !visible 13 | } 14 | 15 | MultiPointTouchArea { 16 | id: mpta 17 | anchors.fill: parent 18 | touchPoints: [ 19 | TouchPoint { property color color: "red" }, 20 | TouchPoint { property color color: "orange" }, 21 | TouchPoint { property color color: "lightsteelblue" }, 22 | TouchPoint { property color color: "green" }, 23 | TouchPoint { property color color: "blue" }, 24 | TouchPoint { property color color: "violet" }, 25 | TouchPoint { property color color: "steelblue" }, 26 | TouchPoint { property color color: "magenta" }, 27 | TouchPoint { property color color: "goldenrod" }, 28 | TouchPoint { property color color: "darkgray" } 29 | ] } 30 | 31 | Repeater { 32 | model: 10 33 | 34 | Item { 35 | id: crosshairs 36 | property TouchPoint touchPoint 37 | x: touchPoint.x - width / 2 38 | y: touchPoint.y - height / 2 39 | width: 300; height: 300 40 | visible: touchPoint.pressed 41 | rotation: touchPoint.rotation 42 | 43 | Rectangle { 44 | color: touchPoint.color 45 | anchors.centerIn: parent 46 | width: 2; height: parent.height 47 | antialiasing: true 48 | } 49 | Rectangle { 50 | color: touchPoint.color 51 | anchors.centerIn: parent 52 | width: parent.width; height: 2 53 | antialiasing: true 54 | } 55 | Rectangle { 56 | color: touchPoint.color 57 | implicitWidth: label.implicitWidth + 8 58 | implicitHeight: label.implicitHeight + 16 59 | radius: width / 2 60 | anchors.centerIn: parent 61 | antialiasing: true 62 | Rectangle { 63 | color: "black" 64 | opacity: 0.35 65 | width: (parent.width - 8) * touchPoint.pressure 66 | height: width 67 | radius: width / 2 68 | anchors.centerIn: parent 69 | antialiasing: true 70 | } 71 | Rectangle { 72 | color: "transparent" 73 | border.color: "white" 74 | border.width: 2 75 | opacity: 0.75 76 | visible: width > 0 77 | width: touchPoint.ellipseDiameters.width 78 | height: touchPoint.ellipseDiameters.height 79 | radius: Math.min(width, height) / 2 80 | anchors.centerIn: parent 81 | antialiasing: true 82 | } 83 | Text { 84 | id: label 85 | anchors.centerIn: parent 86 | color: "white" 87 | horizontalAlignment: Text.AlignHCenter 88 | verticalAlignment: Text.AlignVCenter 89 | property string uid: touchPoint.uniqueId === undefined || touchPoint.uniqueId.numericId === -1 ? 90 | "" : "\nUID " + touchPoint.uniqueId.numericId 91 | text: "x " + touchPoint.x.toFixed(1) + 92 | "\ny " + touchPoint.y.toFixed(1) + uid + 93 | "\nID " + touchPoint.pointId.toString(16) + 94 | "\n∡" + touchPoint.rotation.toFixed(1) + "°" 95 | } 96 | } 97 | Rectangle { 98 | id: velocityVector 99 | visible: width > 0 100 | width: touchPoint.velocity.length() 101 | height: 4 102 | Behavior on width { SmoothedAnimation { duration: 200 } } 103 | radius: height / 2 104 | antialiasing: true 105 | color: "gray" 106 | x: crosshairs.width / 2 107 | y: crosshairs.height / 2 108 | rotation: width > 0 ? Math.atan2(touchPoint.velocity.y, touchPoint.velocity.x) * 180 / Math.PI - crosshairs.rotation : 0 109 | Behavior on rotation { SmoothedAnimation { duration: 20 } } 110 | transformOrigin: Item.BottomLeft 111 | } 112 | 113 | Component.onCompleted: touchPoint = mpta.touchPoints[index] 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /imports/Grefsen/iconprovider.cpp: -------------------------------------------------------------------------------- 1 | #include "iconprovider.h" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static QDir UsrSharePixmaps("/usr/share/pixmaps"); 9 | 10 | IconProvider::IconProvider() 11 | : QQuickImageProvider(QQuickImageProvider::Pixmap) 12 | { 13 | XdgIcon::setThemeName(QStringLiteral("oxygen")); // TODO make configurable 14 | qDebug() << "theme is" << QIcon::themeName() << "paths" << QIcon::themeSearchPaths() 15 | << "default icon" << XdgIcon::defaultApplicationIconName(); 16 | } 17 | 18 | QPixmap IconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) 19 | { 20 | //qDebug() << id << requestedSize; 21 | // absolute path: just load the image 22 | if (id.startsWith('/')) { 23 | QPixmap ret(id); 24 | if (ret.width() > requestedSize.width() || ret.height() > requestedSize.height()) 25 | ret = ret.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); 26 | if (size) 27 | *size = ret.size(); 28 | return ret; 29 | } 30 | // main strategy: QIcon usually knows how to find it 31 | QIcon icon = QIcon::fromTheme(id); 32 | // QIcon icon = XdgIcon::fromTheme(id, XdgIcon::defaultApplicationIcon()); // often not working well 33 | // fall back to /usr/share/pixmaps if nothing found yet 34 | if (icon.isNull()) { 35 | QStringList p = UsrSharePixmaps.entryList({id + "*"}, QDir::Files); 36 | if (!p.isEmpty()) { 37 | QPixmap ret(UsrSharePixmaps.filePath(p.first())); 38 | if (ret.width() > requestedSize.width() || ret.height() > requestedSize.height()) 39 | ret = ret.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); 40 | if (size) 41 | *size = ret.size(); 42 | return ret; 43 | } 44 | } 45 | // default app icon if all else fails 46 | if (icon.isNull()) { 47 | qWarning() << "failed to find icon" << id; 48 | icon = QIcon::fromTheme(XdgIcon::defaultApplicationIconName()); 49 | } 50 | QPixmap ret = icon.pixmap(requestedSize); 51 | if (size) 52 | *size = ret.size(); 53 | return ret; 54 | } 55 | -------------------------------------------------------------------------------- /imports/Grefsen/iconprovider.h: -------------------------------------------------------------------------------- 1 | #ifndef ICONPROVIDER_H 2 | #define ICONPROVIDER_H 3 | 4 | #include 5 | #include 6 | 7 | class IconProvider : public QQuickImageProvider 8 | { 9 | public: 10 | IconProvider(); 11 | 12 | QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) Q_DECL_OVERRIDE; 13 | protected: 14 | // QDir m_usrSharePixmaps = QDir("/usr/share/pixmaps"); 15 | }; 16 | 17 | #endif // ICONPROVIDER_H 18 | -------------------------------------------------------------------------------- /imports/Grefsen/launchermodel.cpp: -------------------------------------------------------------------------------- 1 | #include "launchermodel.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | LauncherModel::LauncherModel(QJSEngine *engine, QObject *parent) 9 | : QObject(parent) 10 | , m_engine(engine) 11 | , m_root(engine->newObject()) 12 | , m_allApps(engine->newArray()) 13 | { 14 | QString menuFile = XdgMenu::getMenuFileName(); 15 | XdgMenu xdgMenu; 16 | xdgMenu.setLogDir("/tmp"); 17 | xdgMenu.setEnvironments(QStringList() << "X-GREFSEN" << "Grefsen"); // TODO what's that for? 18 | // xdgMenu.setEnvironments(QStringList() << "X-LXQT" << "LXQt"); 19 | bool res = xdgMenu.read(menuFile); 20 | if (!res) 21 | qWarning() << "Parse error" << xdgMenu.errorString(); 22 | m_dom = xdgMenu.xml().documentElement(); 23 | qDebug() << "read XML:" << menuFile << xdgMenu.menuFileName() << m_dom.tagName(); 24 | 25 | build(m_root, m_dom); 26 | m_list = m_root; 27 | } 28 | 29 | void LauncherModel::setSubstringFilter(QString substringFilter) 30 | { 31 | if (m_substringFilter == substringFilter) 32 | return; 33 | 34 | m_substringFilter = substringFilter; 35 | emit substringFilterChanged(); 36 | 37 | if (m_substringFilter.isEmpty()) { 38 | reset(); 39 | return; 40 | } 41 | m_list = m_engine->newObject(); 42 | m_list.setProperty(QStringLiteral("items"), findSubstring(QStringLiteral("title"), m_substringFilter)); 43 | emit applicationsChanged(); 44 | } 45 | 46 | void LauncherModel::reset() 47 | { 48 | m_list = m_root; 49 | emit applicationsChanged(); 50 | } 51 | 52 | void LauncherModel::select(QJSValue sel) 53 | { 54 | QString title = sel.property(QStringLiteral("title")).toString(); 55 | qDebug() << title; 56 | if (sel.hasOwnProperty(QStringLiteral("exec"))) { 57 | //qDebug() << "exec" << sel.property(QStringLiteral("exec")).toString(); 58 | exec(sel.property(QStringLiteral("desktopFile")).toString()); 59 | reset(); 60 | } else { 61 | openSubmenu(title); 62 | } 63 | } 64 | 65 | void LauncherModel::exec(QString desktopFilePath) 66 | { 67 | XdgDesktopFile dtf; 68 | dtf.load(desktopFilePath); 69 | //qDebug() << desktopFilePath << dtf; 70 | if (dtf.isValid()) { 71 | bool ok = dtf.startDetached(); 72 | if (Q_UNLIKELY(!ok)) 73 | emit execFailed(tr("failed to exec '%s'", dtf.value(QStringLiteral("exec")).toString().toLocal8Bit().constData())); 74 | } else { 75 | emit execFailed(tr("failed to load desktop file '%s'", desktopFilePath.toLocal8Bit().constData())); 76 | } 77 | } 78 | 79 | void LauncherModel::openSubmenu(QString title) 80 | { 81 | m_list = findFirst(QStringLiteral("title"), title, applicationMenu()); 82 | emit applicationsChanged(); 83 | } 84 | 85 | void LauncherModel::build(QJSValue in, const QDomElement &xml) 86 | { 87 | QJSValue array = m_engine->newArray(); 88 | in.setProperty(QStringLiteral("items"), array); 89 | DomElementIterator it(xml, QString()); 90 | while(it.hasNext()) 91 | { 92 | QDomElement xml = it.next(); 93 | 94 | if (xml.tagName() == "Menu") 95 | appendMenu(array, xml); 96 | 97 | else if (xml.tagName() == "AppLink") 98 | appendApp(array, xml); 99 | 100 | else if (xml.tagName() == "Separator") 101 | qDebug() << "separator"; 102 | } 103 | } 104 | 105 | void LauncherModel::appendMenu(QJSValue in, const QDomElement &xml) 106 | { 107 | QString title = xml.attribute(QStringLiteral("title")); 108 | QString icon = xml.attribute(QStringLiteral("icon")); 109 | int idx = in.property(QStringLiteral("length")).toInt(); 110 | // qDebug() << in.property(QStringLiteral("title")).toString() << ":" << title << icon << idx; 111 | QJSValue menu = m_engine->newObject(); 112 | menu.setProperty(QStringLiteral("title"), title); 113 | menu.setProperty(QStringLiteral("icon"), icon); 114 | in.setProperty(idx, menu); 115 | build(menu, xml); 116 | } 117 | 118 | void LauncherModel::appendApp(QJSValue in, const QDomElement &xml) 119 | { 120 | QString title = xml.attribute(QStringLiteral("title")); 121 | QString icon = xml.attribute(QStringLiteral("icon")); 122 | int idx = in.property(QStringLiteral("length")).toInt(); 123 | // qDebug() << in.property(QStringLiteral("title")).toString() << ":" << title << icon << idx; 124 | QJSValue item = m_engine->newObject(); 125 | item.setProperty(QStringLiteral("title"), title); 126 | item.setProperty(QStringLiteral("icon"), icon); 127 | item.setProperty(QStringLiteral("exec"), xml.attribute(QStringLiteral("exec"))); 128 | item.setProperty(QStringLiteral("desktopFile"), xml.attribute(QStringLiteral("desktopFile"))); 129 | in.setProperty(idx, item); 130 | m_allApps.setProperty(m_allAppsCount++, item); 131 | } 132 | 133 | QJSValue LauncherModel::findFirst(QString key, QString value, QJSValue array) 134 | { 135 | int i = 0; 136 | QJSValue next = array.property(i); 137 | while (!next.isUndefined()) { 138 | if (next.property(key).toString() == value) 139 | return next; 140 | next = array.property(++i); 141 | } 142 | return QJSValue(); 143 | } 144 | 145 | QJSValue LauncherModel::findSubstring(QString key, QString substr) 146 | { 147 | QJSValue ret = m_engine->newArray(); 148 | int i = 0, ri = 0; 149 | QJSValue next = m_allApps.property(i); 150 | while (!next.isUndefined()) { 151 | if (next.property(key).toString().contains(substr, Qt::CaseInsensitive)) 152 | ret.setProperty(ri++, next); 153 | next = m_allApps.property(++i); 154 | } 155 | return ret; 156 | } 157 | -------------------------------------------------------------------------------- /imports/Grefsen/launchermodel.h: -------------------------------------------------------------------------------- 1 | #ifndef LAUNCHERMODEL_H 2 | #define LAUNCHERMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class XdgDesktopFile; 9 | 10 | class LauncherModel : public QObject 11 | { 12 | Q_OBJECT 13 | Q_PROPERTY(QJSValue allApplications READ allApplications NOTIFY applicationsChanged) 14 | Q_PROPERTY(QJSValue applicationMenu READ applicationMenu NOTIFY applicationsChanged) 15 | Q_PROPERTY(QString substringFilter READ substringFilter WRITE setSubstringFilter NOTIFY substringFilterChanged) 16 | 17 | 18 | public: 19 | explicit LauncherModel(QJSEngine *engine, QObject *parent = 0); 20 | QJSValue allApplications() { return m_allApps; } 21 | QJSValue applicationMenu() { return m_list.property(QStringLiteral("items")); } 22 | 23 | QString substringFilter() const { return m_substringFilter; } 24 | void setSubstringFilter(QString substringFilter); 25 | 26 | signals: 27 | void applicationsChanged(); 28 | void substringFilterChanged(); 29 | void execFailed(QString error); 30 | 31 | public slots: 32 | void reset(); 33 | void select(QJSValue sel); 34 | void exec(QString desktopFilePath); 35 | void openSubmenu(QString title); 36 | 37 | 38 | protected: 39 | void build(QJSValue in, const QDomElement& xml); 40 | void appendMenu(QJSValue in, const QDomElement& xml); 41 | void appendApp(QJSValue in, const QDomElement &xml); 42 | QJSValue findFirst(QString key, QString value, QJSValue array); 43 | QJSValue findSubstring(QString key, QString substr); 44 | 45 | protected: 46 | // static QList m_allFiles; 47 | QJSEngine *m_engine; 48 | QDomElement m_dom; 49 | QJSValue m_root; 50 | QJSValue m_list; 51 | QJSValue m_allApps; 52 | int m_allAppsCount = 0; 53 | QString m_substringFilter; 54 | }; 55 | 56 | #endif // LAUNCHERMODEL_H 57 | -------------------------------------------------------------------------------- /imports/Grefsen/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "iconprovider.h" 7 | #include "launchermodel.h" 8 | 9 | Q_LOGGING_CATEGORY(lcRegistration, "grefsen.registration") 10 | 11 | static const char *ModuleName = "Grefsen"; 12 | static QJSValue launcherModelSingleton; 13 | 14 | static QString ensureFinalSlash(const QString &path) 15 | { 16 | if (path.endsWith('/')) 17 | return path; 18 | return path + QLatin1String("/"); 19 | } 20 | 21 | static QJSValue environmentSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine) 22 | { 23 | Q_UNUSED(engine) 24 | 25 | QJSValue v = scriptEngine->newObject(); 26 | v.setProperty(QLatin1String("home"), QDir::homePath() + "/"); 27 | const QStringList &args = QCoreApplication::arguments(); 28 | int argi = args.indexOf(QLatin1String("-c")); 29 | if (argi > 0 && args.length() > argi + 1) 30 | v.setProperty(QLatin1String("grefsenconfig"), ensureFinalSlash(args[argi + 1])); 31 | else 32 | v.setProperty(QLatin1String("grefsenconfig"), QDir::homePath() + "/.config/grefsen/"); 33 | return v; 34 | } 35 | 36 | static QJSValue launcherModelSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine) 37 | { 38 | Q_UNUSED(engine) 39 | 40 | if (launcherModelSingleton.isUndefined()) 41 | launcherModelSingleton = scriptEngine->newQObject(new LauncherModel(scriptEngine)); 42 | return launcherModelSingleton; 43 | } 44 | 45 | class GrefsenPlugin : public QQmlExtensionPlugin 46 | { 47 | Q_OBJECT 48 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0") 49 | 50 | public: 51 | GrefsenPlugin() : QQmlExtensionPlugin() { } 52 | 53 | virtual void initializeEngine(QQmlEngine *engine, const char * uri) { 54 | Q_UNUSED(engine) 55 | qCDebug(lcRegistration) << uri; 56 | engine->addImageProvider(QLatin1String("icon"), new IconProvider); 57 | } 58 | 59 | virtual void registerTypes(const char *uri) { 60 | qCDebug(lcRegistration) << uri; 61 | Q_ASSERT(uri == QLatin1String(ModuleName)); 62 | qmlRegisterSingletonType(ModuleName, 1, 0, "Env", environmentSingletonProvider); 63 | qmlRegisterSingletonType(ModuleName, 1, 0, "LauncherModel", launcherModelSingletonProvider); 64 | } 65 | }; 66 | 67 | QT_END_NAMESPACE 68 | 69 | #include "plugin.moc" 70 | -------------------------------------------------------------------------------- /imports/Grefsen/qmldir: -------------------------------------------------------------------------------- 1 | module Grefsen 2 | plugin grefsenplugin 3 | classname GrefsenPlugin 4 | typeinfo plugins.qmltypes 5 | ConnmanPopover 1.0 ConnmanPopover.qml 6 | LauncherIcon 1.0 LauncherIcon.qml 7 | LauncherMenu 1.0 LauncherMenu.qml 8 | LauncherMenuIcon 1.0 LauncherMenuIcon.qml 9 | LeftSlidePanel 1.0 LeftSlidePanel.qml 10 | PanelClock 1.0 PanelClock.qml 11 | Popover 1.0 Popover.qml 12 | PopoverTrayIcon 1.0 PopoverTrayIcon.qml 13 | PopoverPanelItem 1.0 PopoverPanelItem.qml 14 | QuitButton 1.0 QuitButton.qml 15 | RightSlidePanel 1.0 RightSlidePanel.qml 16 | TuioTouchPanel 1.0 TuioTouchPanel.qml 17 | -------------------------------------------------------------------------------- /imports/imports.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += Grefsen 4 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ec1oud/grefsen/05a6203a2210b57c366f36297b82c548e5653ddb/screenshot.jpg -------------------------------------------------------------------------------- /thirdparty/freefont/README: -------------------------------------------------------------------------------- 1 | These are from a very old sunsite package which can now be found at 2 | ftp://ftp.ibiblio.org/pub/linux/X11/fonts/freefonts-0.10.tar.gz 3 | -------------------------------------------------------------------------------- /thirdparty/freefont/manzanit.license: -------------------------------------------------------------------------------- 1 | Manzanita is a font with a victorian wood type feel Type 1 and ATM 2 | compatible. This one is free and a gift! I enjoy the design process. Hope 3 | you will look for more fonts The price will be $25.00 each.Look for more 4 | or write for a catalog. Stuffit classic file. 5 | Marty Bee, Rt. 5 Box 1720, Sulphur, LA 70663 6 | -------------------------------------------------------------------------------- /thirdparty/freefont/manzanit.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ec1oud/grefsen/05a6203a2210b57c366f36297b82c548e5653ddb/thirdparty/freefont/manzanit.pfb -------------------------------------------------------------------------------- /thirdparty/thirdparty.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += qt-tuio-sender 4 | --------------------------------------------------------------------------------