summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabien Proriol <fabien.proriol@kazoe.org>2025-05-25 17:58:09 +0200
committerFabien Proriol <fabien.proriol@kazoe.org>2025-05-26 18:01:49 +0200
commit49daa163530ceabc9eaa8911ab96b5f799cfb552 (patch)
tree080d0b31eafd138cd8d47d5c2a52b75d3cfa6f28
Initial Commit
-rw-r--r--.gitignore2
-rw-r--r--CMakeLists.txt67
-rw-r--r--LICENSE21
-rw-r--r--README.md35
-rw-r--r--api/org.kazoe.desktop.applications.xml31
-rw-r--r--conf/kms.conf11
-rw-r--r--conf/kzdesktop.conf3
-rw-r--r--conf/kzdesktop.service21
-rw-r--r--conf/kzdesktop.sh3
-rw-r--r--protocols/vnc-keyboard-unstable-v1.xml60
-rw-r--r--protocols/wlr-screencopy-unstable-v1.xml232
-rw-r--r--protocols/wlr-virtual-pointer-unstable-v1.xml152
-rw-r--r--qml/DesktopGridView.qml116
-rw-r--r--qml/HomePage.qml55
-rw-r--r--qml/Main.qml167
-rw-r--r--qml/StatusBar.qml149
-rw-r--r--qml/SystrayView.qml32
-rw-r--r--qml/VKeyboard.qml14
-rw-r--r--src/applicationmanager.cpp127
-rw-r--r--src/applicationmanager.h43
-rw-r--r--src/debug.cpp6
-rw-r--r--src/debug.h11
-rw-r--r--src/main.cpp70
-rw-r--r--src/org.freedesktop.DBus.xml17
-rw-r--r--src/vnc-keyboard-unstable-v1.cpp146
-rw-r--r--src/vnc-keyboard-unstable-v1.h46
-rw-r--r--src/waylandentries.cpp159
-rw-r--r--src/waylandentries.h53
-rw-r--r--src/waylandentriesfiltered.cpp63
-rw-r--r--src/waylandentriesfiltered.h41
-rw-r--r--tools/cli/main.cpp98
31 files changed, 2051 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e7f8a12
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+build
+CMakeLists.txt.user
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c388041
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,67 @@
+cmake_minimum_required(VERSION 3.16)
+
+project(KzDesktop VERSION 1.0.0 LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(Qt6 COMPONENTS Quick DBus WaylandCompositor REQUIRED)
+
+qt_standard_project_setup(REQUIRES 6.5)
+
+find_package(KzXdg REQUIRED)
+include_directories(${KZXDG_INCLUDE_DIR})
+
+set(DBUS_APIS
+ api/org.kazoe.desktop.applications.xml
+)
+
+set(PROJECT_QMLS
+ qml/Main.qml
+ qml/HomePage.qml
+ qml/DesktopGridView.qml
+ qml/StatusBar.qml
+ qml/SystrayView.qml
+ qml/VKeyboard.qml
+)
+
+set(PROJECT_SOURCES
+ src/main.cpp
+ src/applicationmanager.h src/applicationmanager.cpp
+ src/waylandentries.h src/waylandentries.cpp
+ src/waylandentriesfiltered.h src/waylandentriesfiltered.cpp
+ src/debug.cpp src/debug.h
+)
+
+set(OTHER_FILES
+ conf/kzdesktop.sh
+ conf/kzdesktop.service
+ ${WAYLAND_PROTOCOLS}
+ ${DBUS_APIS}
+)
+
+set(CLI_SOURCES
+ tools/cli/main.cpp
+)
+
+qt_add_dbus_adaptor(ApplicationManager_SRCS api/org.kazoe.desktop.applications.xml src/applicationmanager.h ApplicationManager)
+qt_add_dbus_interface(ApplicationManagerIface_SRCS api/org.kazoe.desktop.applications.xml ApplicationManagerIface)
+qt_add_executable(kzdesktop ${PROJECT_SOURCES} ${PROJECT_QMLS} ${OTHER_FILES} ${ApplicationManager_SRCS} ${APPMANAGER_SRCS})
+qt_add_executable(desktopcli ${CLI_SOURCES} ${ApplicationManagerIface_SRCS} ${DBUS_APIS})
+qt_add_qml_module(kzdesktop URI org.kazoe.desktop VERSION 1.0 QML_FILES ${PROJECT_QMLS} )
+
+target_compile_definitions(kzdesktop PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
+target_link_libraries(kzdesktop PRIVATE ${KZXDG_LIBRARIES} Qt6::Quick Qt6::DBus Qt6::WaylandCompositor systemd )
+target_link_libraries(desktopcli PRIVATE Qt6::Core Qt6::DBus)
+
+
+include(GNUInstallDirs)
+install(TARGETS kzdesktop DESTINATION ${CMAKE_INSTALL_BIN})
+install(TARGETS desktopcli DESTINATION ${CMAKE_INSTALL_BIN})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/conf/kzdesktop.service DESTINATION /usr/lib/systemd/user)
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/conf/kzdesktop.sh DESTINATION /etc/profile.d/)
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/conf/kzdesktop.conf DESTINATION /etc/system.conf.d/)
+install(FILES ${DBUS_APIS} DESTINATION /usr/share/dbus-1/interfaces/)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..529eb62
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Proriol Fabien
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bd564d3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+# KaZoe Desktop
+This project provide a desktop environement base on "Qt Wayland Compositor".
+
+It combine features:
+
+- Wayland compositor
+- Window manager
+- customizable Task bar
+- customizable systray
+- homepage provided by an external application
+- VNC server managed by a plugin
+
+
+# Build and run
+```sh
+git clone https://kazoe.org/cgit/kazoe/kazoe-desktop.git
+cd kazoe-desktop
+mkdir build
+cmake ..
+make
+./kzdesktop
+```
+
+DesktopManager use [standard XDG paths](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
+This paths can be override by setting [XDG_DATA_HOME and XDG_DATA_DIRS](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
+
+
+## Design
+
+
+
+
+
+## Protocol
+https://specifications.freedesktop.org/menu-spec/menu-spec-1.0.html#desktop-entry-extensions-examples
diff --git a/api/org.kazoe.desktop.applications.xml b/api/org.kazoe.desktop.applications.xml
new file mode 100644
index 0000000..b32a062
--- /dev/null
+++ b/api/org.kazoe.desktop.applications.xml
@@ -0,0 +1,31 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kazoe.desktop.applications">
+ <method name="backHome" />
+ <method name="count">
+ <arg type="i" direction="out"/>
+ </method>
+ <method name="currentApplicationPid">
+ <arg type="i" direction="out"/>
+ </method>
+ <method name="applicationName">
+ <arg type="s" direction="out"/>
+ <arg name="id" type="i" direction="in"/>
+ </method>
+ <method name="applicationPid">
+ <arg type="i" direction="out"/>
+ <arg name="id" type="i" direction="in"/>
+ </method>
+ <method name="applicationFocus">
+ <arg type="i" direction="out"/>
+ <arg name="id" type="i" direction="in"/>
+ </method>
+ <method name="hideApplicationPid">
+ <arg name="pid" type="x" direction="in"/>
+ </method>
+ <method name="raiseApplicationPid">
+ <arg name="pid" type="x" direction="in"/>
+ </method>
+ </interface>
+</node>
diff --git a/conf/kms.conf b/conf/kms.conf
new file mode 100644
index 0000000..6629859
--- /dev/null
+++ b/conf/kms.conf
@@ -0,0 +1,11 @@
+{
+ "hwcursor" : false,
+ "pbuffers": true,
+ "device": "/dev/dri/card1",
+ "outputs": [
+ {
+ "name": "DP1",
+ "format":"argb8888"
+ }
+ ]
+}
diff --git a/conf/kzdesktop.conf b/conf/kzdesktop.conf
new file mode 100644
index 0000000..be38d38
--- /dev/null
+++ b/conf/kzdesktop.conf
@@ -0,0 +1,3 @@
+[desktop]
+orientation="horizontal"
+theme="light"
diff --git a/conf/kzdesktop.service b/conf/kzdesktop.service
new file mode 100644
index 0000000..dc7c6bb
--- /dev/null
+++ b/conf/kzdesktop.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=KaZoe desktop compositor
+Wants=dbus.service
+
+[Service]
+Restart=always
+RestartSec=1s
+Type=notify
+NotifyAccess=main
+ExecStart=/usr/bin/kzdesktop
+StandardOutput=journal+console
+WatchdogSec=5
+
+Environment="QT_QPA_PLATFORM=eglfs"
+Environment="QT_WAYLAND_SERVER_BUFFER_INTEGRATION=dmabuf-server"
+Environment="QT_WAYLAND_CLIENT_BUFFER_INTEGRATION=linux-dmabuf-unstable-v1"
+Environment="QT_IM_MODULE=qtvirtualkeyboard"
+Environment="XDG_DATA_DIRS=/var/volatile:/usr/share"
+
+[Install]
+WantedBy=default.target
diff --git a/conf/kzdesktop.sh b/conf/kzdesktop.sh
new file mode 100644
index 0000000..9463922
--- /dev/null
+++ b/conf/kzdesktop.sh
@@ -0,0 +1,3 @@
+# Profile for desktopmgr /etc/profile.d/desktopmgr.sh
+export QT_QPA_PLATFORM=wayland-egl
+export WAYLAND_DISPLAY=wayland-0
diff --git a/protocols/vnc-keyboard-unstable-v1.xml b/protocols/vnc-keyboard-unstable-v1.xml
new file mode 100644
index 0000000..e870731
--- /dev/null
+++ b/protocols/vnc-keyboard-unstable-v1.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="vnc_keyboard_unstable_v1">
+
+ <interface name="vnc_keyboard_v1" version="1">
+ <description summary="vnc keyboard">
+ The vnc keyboard provides an application with requests which maps
+ wit the vnc protocol. It's not a keyboard interface in that it
+ doesn't know anything about keymaps.
+
+ </description>
+
+ <request name="key">
+ <description summary="key event">
+ A key was pressed or released.
+ The time argument is a timestamp with millisecond granularity, with an
+ undefined base. All requests regarding a single object must share the
+ same clock.
+ State carries a value from the key_state enumeration.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="keysym" type="uint" summary="key symbol"/>
+ <arg name="pressed" type="uint" summary="physical state of the key"/>
+ </request>
+
+ <request name="keycode">
+ <description summary="key code event">
+ A key was pressed or released.
+ The time argument is a timestamp with millisecond granularity, with an
+ undefined base. All requests regarding a single object must share the
+ same clock.
+ State carries a value from the key_state enumeration.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="code" type="uint" summary="key code"/>
+ <arg name="pressed" type="uint" summary="physical state of the key"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual keyboard keyboard object"/>
+ </request>
+ </interface>
+
+ <interface name="vnc_virtual_keyboard_manager_v1" version="1">
+ <description summary="virtual keyboard manager">
+ A virtual keyboard manager allows an application to provide key
+ input events (using RFB key encoding)
+ </description>
+
+ <enum name="error">
+ <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
+ </enum>
+
+ <request name="create_virtual_keyboard">
+ <description summary="Create a new virtual keyboard">
+ Creates a new vnc keyboard.
+ </description>
+ <arg name="id" type="new_id" interface="vnc_keyboard_v1"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocols/wlr-screencopy-unstable-v1.xml b/protocols/wlr-screencopy-unstable-v1.xml
new file mode 100644
index 0000000..50b1b7d
--- /dev/null
+++ b/protocols/wlr-screencopy-unstable-v1.xml
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_screencopy_unstable_v1">
+ <copyright>
+ Copyright © 2018 Simon Ser
+ Copyright © 2019 Andri Yngvason
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="screen content capturing on client buffers">
+ This protocol allows clients to ask the compositor to copy part of the
+ screen content to a client buffer.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <interface name="zwlr_screencopy_manager_v1" version="3">
+ <description summary="manager to inform clients and begin capturing">
+ This object is a manager which offers requests to start capturing from a
+ source.
+ </description>
+
+ <request name="capture_output">
+ <description summary="capture an output">
+ Capture the next frame of an entire output.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="composite cursor onto the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="capture_output_region">
+ <description summary="capture an output's region">
+ Capture the next frame of an output's region.
+
+ The region is given in output logical coordinates, see
+ xdg_output.logical_size. The region will be clipped to the output's
+ extents.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="composite cursor onto the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_screencopy_frame_v1" version="3">
+ <description summary="a frame ready for copy">
+ This object represents a single frame.
+
+ When created, a series of buffer events will be sent, each representing a
+ supported buffer type. The "buffer_done" event is sent afterwards to
+ indicate that all supported buffer types have been enumerated. The client
+ will then be able to send a "copy" request. If the capture is successful,
+ the compositor will send a "flags" followed by a "ready" event.
+
+ For objects version 2 or lower, wl_shm buffers are always supported, ie.
+ the "buffer" event is guaranteed to be sent.
+
+ If the capture failed, the "failed" event is sent. This can happen anytime
+ before the "ready" event.
+
+ Once either a "ready" or a "failed" event is received, the client should
+ destroy the frame.
+ </description>
+
+ <event name="buffer">
+ <description summary="wl_shm buffer information">
+ Provides information about wl_shm buffer parameters that need to be
+ used for this frame. This event is sent once after the frame is created
+ if wl_shm buffers are supported.
+ </description>
+ <arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
+ <arg name="width" type="uint" summary="buffer width"/>
+ <arg name="height" type="uint" summary="buffer height"/>
+ <arg name="stride" type="uint" summary="buffer stride"/>
+ </event>
+
+ <request name="copy">
+ <description summary="copy the frame">
+ Copy the frame to the supplied buffer. The buffer must have a the
+ correct size, see zwlr_screencopy_frame_v1.buffer and
+ zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
+ supported format.
+
+ If the frame is successfully copied, a "flags" and a "ready" events are
+ sent. Otherwise, a "failed" event is sent.
+ </description>
+ <arg name="buffer" type="object" interface="wl_buffer"/>
+ </request>
+
+ <enum name="error">
+ <entry name="already_used" value="0"
+ summary="the object has already been used to copy a wl_buffer"/>
+ <entry name="invalid_buffer" value="1"
+ summary="buffer attributes are invalid"/>
+ </enum>
+
+ <enum name="flags" bitfield="true">
+ <entry name="y_invert" value="1" summary="contents are y-inverted"/>
+ </enum>
+
+ <event name="flags">
+ <description summary="frame flags">
+ Provides flags about the frame. This event is sent once before the
+ "ready" event.
+ </description>
+ <arg name="flags" type="uint" enum="flags" summary="frame flags"/>
+ </event>
+
+ <event name="ready">
+ <description summary="indicates frame is available for reading">
+ Called as soon as the frame is copied, indicating it is available
+ for reading. This event includes the time at which presentation happened
+ at.
+
+ The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
+ each component being an unsigned 32-bit value. Whole seconds are in
+ tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
+ and the additional fractional part in tv_nsec as nanoseconds. Hence,
+ for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
+ may have an arbitrary offset at start.
+
+ After receiving this event, the client should destroy the object.
+ </description>
+ <arg name="tv_sec_hi" type="uint"
+ summary="high 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_sec_lo" type="uint"
+ summary="low 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_nsec" type="uint"
+ summary="nanoseconds part of the timestamp"/>
+ </event>
+
+ <event name="failed">
+ <description summary="frame copy failed">
+ This event indicates that the attempted frame copy has failed.
+
+ After receiving this event, the client should destroy the object.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete this object, used or not">
+ Destroys the frame. This request can be sent at any time by the client.
+ </description>
+ </request>
+
+ <!-- Version 2 additions -->
+ <request name="copy_with_damage" since="2">
+ <description summary="copy the frame when it's damaged">
+ Same as copy, except it waits until there is damage to copy.
+ </description>
+ <arg name="buffer" type="object" interface="wl_buffer"/>
+ </request>
+
+ <event name="damage" since="2">
+ <description summary="carries the coordinates of the damaged region">
+ This event is sent right before the ready event when copy_with_damage is
+ requested. It may be generated multiple times for each copy_with_damage
+ request.
+
+ The arguments describe a box around an area that has changed since the
+ last copy request that was derived from the current screencopy manager
+ instance.
+
+ The union of all regions received between the call to copy_with_damage
+ and a ready event is the total damage since the prior ready event.
+ </description>
+ <arg name="x" type="uint" summary="damaged x coordinates"/>
+ <arg name="y" type="uint" summary="damaged y coordinates"/>
+ <arg name="width" type="uint" summary="current width"/>
+ <arg name="height" type="uint" summary="current height"/>
+ </event>
+
+ <!-- Version 3 additions -->
+ <event name="linux_dmabuf" since="3">
+ <description summary="linux-dmabuf buffer information">
+ Provides information about linux-dmabuf buffer parameters that need to
+ be used for this frame. This event is sent once after the frame is
+ created if linux-dmabuf buffers are supported.
+ </description>
+ <arg name="format" type="uint" summary="fourcc pixel format"/>
+ <arg name="width" type="uint" summary="buffer width"/>
+ <arg name="height" type="uint" summary="buffer height"/>
+ </event>
+
+ <event name="buffer_done" since="3">
+ <description summary="all buffer types reported">
+ This event is sent once after all buffer events have been sent.
+
+ The client should proceed to create a buffer of one of the supported
+ types, and send a "copy" request.
+ </description>
+ </event>
+ </interface>
+</protocol>
diff --git a/protocols/wlr-virtual-pointer-unstable-v1.xml b/protocols/wlr-virtual-pointer-unstable-v1.xml
new file mode 100644
index 0000000..ea243e7
--- /dev/null
+++ b/protocols/wlr-virtual-pointer-unstable-v1.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_virtual_pointer_unstable_v1">
+ <copyright>
+ Copyright © 2019 Josef Gajdusek
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_virtual_pointer_v1" version="2">
+ <description summary="virtual pointer">
+ This protocol allows clients to emulate a physical pointer device. The
+ requests are mostly mirror opposites of those specified in wl_pointer.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_axis" value="0"
+ summary="client sent invalid axis enumeration value" />
+ <entry name="invalid_axis_source" value="1"
+ summary="client sent invalid axis source enumeration value" />
+ </enum>
+
+ <request name="motion">
+ <description summary="pointer relative motion event">
+ The pointer has moved by a relative amount to the previous request.
+
+ Values are in the global compositor space.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="dx" type="fixed" summary="displacement on the x-axis"/>
+ <arg name="dy" type="fixed" summary="displacement on the y-axis"/>
+ </request>
+
+ <request name="motion_absolute">
+ <description summary="pointer absolute motion event">
+ The pointer has moved in an absolute coordinate frame.
+
+ Value of x can range from 0 to x_extent, value of y can range from 0
+ to y_extent.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="x" type="uint" summary="position on the x-axis"/>
+ <arg name="y" type="uint" summary="position on the y-axis"/>
+ <arg name="x_extent" type="uint" summary="extent of the x-axis"/>
+ <arg name="y_extent" type="uint" summary="extent of the y-axis"/>
+ </request>
+
+ <request name="button">
+ <description summary="button event">
+ A button was pressed or released.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="button" type="uint" summary="button that produced the event"/>
+ <arg name="state" type="uint" enum="wl_pointer.button_state" summary="physical state of the button"/>
+ </request>
+
+ <request name="axis">
+ <description summary="axis event">
+ Scroll and other axis requests.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
+ <arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
+ </request>
+
+ <request name="frame">
+ <description summary="end of a pointer event sequence">
+ Indicates the set of events that logically belong together.
+ </description>
+ </request>
+
+ <request name="axis_source">
+ <description summary="axis source event">
+ Source information for scroll and other axis.
+ </description>
+ <arg name="axis_source" type="uint" enum="wl_pointer.axis_source" summary="source of the axis event"/>
+ </request>
+
+ <request name="axis_stop">
+ <description summary="axis stop event">
+ Stop notification for scroll and other axes.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="wl_pointer.axis" summary="the axis stopped with this event"/>
+ </request>
+
+ <request name="axis_discrete">
+ <description summary="axis click event">
+ Discrete step information for scroll and other axes.
+
+ This event allows the client to extend data normally sent using the axis
+ event with discrete value.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
+ <arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
+ <arg name="discrete" type="int" summary="number of steps"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual pointer object"/>
+ </request>
+ </interface>
+
+ <interface name="zwlr_virtual_pointer_manager_v1" version="2">
+ <description summary="virtual pointer manager">
+ This object allows clients to create individual virtual pointer objects.
+ </description>
+
+ <request name="create_virtual_pointer">
+ <description summary="Create a new virtual pointer">
+ Creates a new virtual pointer. The optional seat is a suggestion to the
+ compositor.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
+ <arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual pointer manager"/>
+ </request>
+
+ <!-- Version 2 additions -->
+ <request name="create_virtual_pointer_with_output" since="2">
+ <description summary="Create a new virtual pointer">
+ Creates a new virtual pointer. The seat and the output arguments are
+ optional. If the seat argument is set, the compositor should assign the
+ input device to the requested seat. If the output argument is set, the
+ compositor should map the input device to the requested output.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/qml/DesktopGridView.qml b/qml/DesktopGridView.qml
new file mode 100644
index 0000000..c5bf588
--- /dev/null
+++ b/qml/DesktopGridView.qml
@@ -0,0 +1,116 @@
+import QtQuick
+import QtQuick.Controls
+import org.kazoe.xdg
+
+ListView {
+ id: desktopgridview
+ property string category
+ property int nbrow
+ clip: true
+ orientation: ListView.Horizontal
+ anchors.margins: 5
+ height: 150
+ spacing: 15
+
+
+ model: DesktopFilter {
+ id: filtermodel
+ model: xdgEntries
+ category: desktopgridview.category
+ onRowsInserted: {
+ desktopgridview.nbrow = rowCount()
+ }
+
+ onRowsRemoved: {
+ desktopgridview.nbrow = rowCount()
+ }
+
+ Component.onCompleted: {
+ desktopgridview.nbrow = rowCount()
+ }
+ }
+
+ delegate: Rectangle {
+ property string applicationIcon: appIcon
+ property string applicationTitle: name
+ property string xdgPath: path
+ property alias highlighted: btn.highlighted
+ id: entryItem
+ width: desktopgridview.height * 0.8
+ height: desktopgridview.height * 0.8
+ highlighted: keywords.includes("running")
+ color: (btn.highlighted)?(currentTheme.highlight):(currentTheme.window)
+ radius: 4
+
+ Button {
+ id: btn
+ icon.source: "file://" + entryItem.applicationIcon
+ icon.name: entryItem.applicationIcon
+ icon.width: btn.width
+ icon.height: btn.height
+
+ font.pixelSize: 12
+ font.bold: true
+ height: entryItem.height * 0.5
+ width: entryItem.width * 0.5
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ background: Rectangle {
+ color: "transparent"
+ }
+ }
+
+ Text {
+ anchors.top: btn.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: entryItem.height * 0.4
+ text: entryItem.applicationTitle
+ font: btn.font
+ fontSizeMode: Text.Fit
+ opacity: enabled ? 1.0 : 0.3
+ color: (btn.highlighted)?("white"):("grey")
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ MouseArea {
+ id: zone
+ anchors.fill: parent
+ enabled: parent.enabled
+ property bool locked: false
+
+ onClicked: {
+ if(zone.locked)
+ zone.locked = false
+ else
+ xdgEntries.start(entryItem.xdgPath)
+ }
+
+
+ Timer {
+ id: longPressTimer
+
+ interval: 1000
+ repeat: false
+ running: false
+
+ onTriggered: {
+ xdgEntries.stop(entryItem.xdgPath)
+ zone.locked = true
+ }
+ }
+
+ onPressedChanged: {
+ if ( pressed ) {
+ longPressTimer.running = true;
+ } else {
+ longPressTimer.running = false;
+ }
+ }
+ }
+ }
+}
diff --git a/qml/HomePage.qml b/qml/HomePage.qml
new file mode 100644
index 0000000..fbcfd70
--- /dev/null
+++ b/qml/HomePage.qml
@@ -0,0 +1,55 @@
+import QtQuick
+
+Rectangle {
+ id: homepage
+ clip: true
+ property string selected: "tests"
+
+ ListView {
+ id: category
+ anchors.fill: parent
+ model: xdgEntries.categories
+ delegate: Rectangle {
+ Component.onCompleted: {
+ console.debug("MANAGE CATEGORY [" + modelData + "]")
+ }
+
+ property bool isshow: modelData !== "System" && modelData !== "Debug"
+ width: homepage.width
+ height: (isshow) ? (entriesView.nbrow > 0 ? entriesView.height + categoryTitle.height : 0) : 0
+ color: currentTheme.base
+ visible: isshow && entriesView.nbrow > 0
+
+ Rectangle {
+ id: categoryTitle
+ height: 20
+ width: parent.width - 2
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: 4
+ anchors.topMargin: 4
+ color: currentTheme.mid
+
+
+ Text {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ text: modelData
+ font.pixelSize: 15
+ font.bold: true
+ color: currentTheme.windowText
+ }
+ }
+
+ DesktopGridView {
+ id: entriesView
+ category: modelData
+ anchors.top: categoryTitle.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+ }
+ }
+}
diff --git a/qml/Main.qml b/qml/Main.qml
new file mode 100644
index 0000000..c3470fc
--- /dev/null
+++ b/qml/Main.qml
@@ -0,0 +1,167 @@
+import QtQuick
+import QtWayland.Compositor
+import QtWayland.Compositor.XdgShell
+import QtQuick.Window
+import QtQuick.Controls
+import org.kazoe.desktop
+import org.kazoe.xdg
+
+WaylandCompositor {
+ id: comp
+ property string defaultTitle: ""
+ property int appHeight: win.height - statusBar.height
+ property int appWidth: win.width
+ property bool showHomepage: true
+ property string statusBarModel: "StatusBar"
+ property alias statusBarHeight: statusBar.height
+
+ SystemPalette {
+ id: currentTheme
+ colorGroup: SystemPalette.Active
+ }
+
+ DesktopEntries {
+ id: xdgEntries
+
+ onRaiseProcess: {
+ manager.currentSurface = waylandEntries.getSurface(pid)
+ }
+ }
+
+ AutoStart {
+ id: autostarter
+ Component.onCompleted: {
+ autostarter.start("Desktop Entry/X-DESKTOPMGR-AUTOSTART", "true")
+ }
+ }
+
+ DesktopFilter {
+ id: sysbar
+ section: "X-DESKTOPMGR-SYSBAR"
+ showAll: true
+ model: xdgEntries
+ }
+
+ DesktopFilter {
+ id: systray
+ section: "X-DESKTOPMGR-SYSTRAY"
+ showAll: true
+ model: xdgEntries
+ }
+
+ WaylandEntries {
+ id: entries
+ }
+
+ Connections {
+ target: manager
+ function onCurrentSurfaceChanged() {
+ comp.showHomepage = false
+ }
+
+ function onGotoHome() {
+ comp.showHomepage = true
+ }
+ }
+
+
+ WaylandOutput {
+ id: output
+ sizeFollowsWindow: true
+ transform: WaylandOutput.Transform90
+ scaleFactor: 4
+ window: Window {
+ id: win
+ visible: true
+ width: 800
+ height: 480
+
+ VKeyboard {
+ id: keyboard
+ anchors.fill: parent
+ }
+
+ Flickable {
+ anchors.fill: parent
+ id: appZoneView
+ clip: true
+ contentWidth: win.width
+ contentHeight: win.height
+ flickableDirection: Flickable.VerticalFlick
+ interactive: false
+
+ property int textInputY: keyboard.textInputY
+ property int textInputHeight: keyboard.textInputHeight
+ property bool keyboardActive: keyboard.active
+ property int keyboardHeight: keyboard.implicitHeight
+ property int wantOffset: (appZoneView.height - keyboardHeight) / 2
+
+ onTextInputYChanged: {
+ if(appZoneView.keyboardActive && appZoneView.contentY == 0)
+ {
+ if(textInputY > (appZoneView.height - keyboardHeight))
+ {
+ appZoneView.contentY = textInputY - textInputHeight - wantOffset
+ }
+ }
+ }
+
+ onKeyboardActiveChanged: {
+ appZoneView.contentY = 0
+ }
+
+ StatusBar {
+ id: statusBar
+ width: parent.width
+ z: 20
+ }
+
+ HomePage {
+ id: homepageview
+ anchors.top: statusBar.bottom
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ visible: comp.showHomepage
+ }
+
+ ShellSurfaceItem {
+ id: appZone
+ visible: !comp.showHomepage
+ anchors.top: statusBar.bottom
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ shellSurface: manager.currentSurface
+ autoCreatePopupItems: true
+
+ onWidthChanged: {
+ if(manager.currentSurface)
+ manager.currentSurface.toplevel.sendResizing(Qt.size(appZone.width, appZone.height))
+ }
+
+ onHeightChanged: {
+ if(manager.currentSurface)
+ manager.currentSurface.toplevel.sendResizing(Qt.size(appZone.width, appZone.height))
+ }
+ }
+ }
+ }
+ }
+
+ XdgShell {
+ onToplevelCreated: function(toplevel, xdgSurface) {
+ toplevel.sendResizing(Qt.size(appZone.width, appZone.height));
+ entries.addSurface(xdgSurface)
+ manager.currentSurface = xdgSurface
+ }
+ }
+
+ XdgDecorationManagerV1 {
+ preferredMode: XdgToplevel.ServerSideDecoration
+ }
+
+ // Extension for Input Method (QT_IM_MODULE) support at compositor-side
+ TextInputManager {}
+}
diff --git a/qml/StatusBar.qml b/qml/StatusBar.qml
new file mode 100644
index 0000000..3758657
--- /dev/null
+++ b/qml/StatusBar.qml
@@ -0,0 +1,149 @@
+import QtQuick
+import QtQuick.Controls
+import org.kazoe.desktop
+import org.kazoe.xdg
+
+Rectangle {
+ id: statusBar
+ height: 46
+ property int batteryLevelWarning: 15
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: currentTheme.window }
+ GradientStop { position: 1.0; color: currentTheme.window }
+ }
+
+ Rectangle {
+ id: bottomline
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 1
+ width: undefined
+ color: "black"
+ }
+
+ Button {
+ id: backHome
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ height: comp.statusBarHeight
+ anchors.margins: 4
+ palette.buttonText: "white"
+ font.pointSize: 9
+ highlighted: comp.showHomepage
+ display: AbstractButton.IconOnly
+
+ icon.name: "home"
+
+ background: Rectangle {
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: backHome.width
+ height: backHome.height
+ border.color: backHome.highlighted ? currentTheme.highlight : currentTheme.alternateBase
+ border.width: backHome.highlighted ? 2 : 1
+ radius: 4
+ color: currentTheme.mid
+ }
+
+ onClicked: {
+ comp.showHomepage = true
+ }
+ }
+
+ ListView {
+ id: taskbar
+ anchors.left: backHome.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: systrayview.left
+ anchors.leftMargin: 4
+ anchors.topMargin: 4
+ clip: true
+ model: entries
+ orientation: Qt.Horizontal
+ spacing: 4
+
+ delegate: Button {
+ id: taskapp
+ palette.buttonText: "white"
+ font.pointSize: 9
+ highlighted: (comp.showHomepage === false) && (manager.currentSurface === wl_surface)
+
+ height: taskbar.height - 4
+ display: AbstractButton.TextBesideIcon
+ /*
+ * TODO: Find app icon with wayland
+ icon.name: wl_surface.surface
+ icon.color: "#0aa4da"
+ */
+ Entry {
+ id: xdgapp
+ desktopFile: "/usr/share/applications/" + appId + ".desktop"
+ }
+ icon.name: xdgapp.icon
+ icon.color: "transparent"
+
+ background: Rectangle {
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: taskapp.width
+ height: taskapp.height
+ border.color: taskapp.highlighted ? currentTheme.highlight : currentTheme.alternateBase
+ border.width: taskapp.highlighted ? 2 : 1
+ radius: 4
+ color: currentTheme.mid
+ }
+ text: title
+
+ onClicked: {
+ manager.currentSurface = wl_surface
+ comp.showHomepage = false
+ }
+
+ Timer {
+ id: longPressTimer
+
+ interval: 1000
+ repeat: false
+ running: false
+
+ onTriggered: {
+ if(manager.currentSurface == wl_surface)
+ {
+ comp.showHomepage = true
+ }
+
+ entries.stop(pid)
+ }
+ }
+
+ onPressedChanged: {
+ if ( pressed ) {
+ longPressTimer.running = true;
+ } else {
+ longPressTimer.running = false;
+ }
+ }
+ }
+ }
+
+ SystrayView {
+ id: systrayview
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.topMargin: 2
+ anchors.bottomMargin: 2
+ anchors.rightMargin: 2
+
+ width: systrayview.contentWidth
+ spacing: 1
+ }
+}
diff --git a/qml/SystrayView.qml b/qml/SystrayView.qml
new file mode 100644
index 0000000..3b21152
--- /dev/null
+++ b/qml/SystrayView.qml
@@ -0,0 +1,32 @@
+import QtQuick
+import QtQuick.Controls
+import QtWayland.Compositor
+import org.kazoe.desktop
+import org.kazoe.xdg
+
+ListView {
+ id: systrayView
+ width: systrayView.contentWidth
+ orientation: ListView.Horizontal
+ model: systray
+ delegate: Item {
+ id: trayitem
+
+ Component.onCompleted: {
+ var qmlcode
+ var c
+ if(exec.startsWith("qml://")) {
+ var ur = exec.split("/")
+ qmlcode = "import " + ur[2] + " " + ur[3] + "; " + ur[4] + " {}"
+ c = Qt.createQmlObject(qmlcode, trayitem, ur[4])
+ }
+ else
+ {
+ console.debug("Item not supported for systray:" + exec)
+ }
+
+ trayitem.width = Qt.binding(() => {return c.width})
+ trayitem.height = Qt.binding(() => {return c.height})
+ }
+ }
+}
diff --git a/qml/VKeyboard.qml b/qml/VKeyboard.qml
new file mode 100644
index 0000000..c97e120
--- /dev/null
+++ b/qml/VKeyboard.qml
@@ -0,0 +1,14 @@
+import QtQuick
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Settings
+import QtQuick.Window
+
+InputPanel {
+ id: panel
+ visible: active
+ y: active ? 0 : parent.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ property int textInputHeight: InputContext.cursorRectangle.height
+ property int textInputY: InputContext.cursorRectangle.y
+}
diff --git a/src/applicationmanager.cpp b/src/applicationmanager.cpp
new file mode 100644
index 0000000..13158f3
--- /dev/null
+++ b/src/applicationmanager.cpp
@@ -0,0 +1,127 @@
+#include "applicationmanager.h"
+#include "waylandentries.h"
+#include <QWaylandXdgSurface>
+#include <QWaylandXdgToplevel>
+
+ApplicationManager::ApplicationManager(QObject *parent)
+ : QObject(parent)
+{
+
+}
+
+bool ApplicationManager::isHome() const
+{
+ return m_isHome;
+}
+
+QObject *ApplicationManager::currentSurface() const
+{
+ return m_currentSurface;
+}
+
+void ApplicationManager::setCurrentSurface(QObject *newCurrentSurface)
+{
+ if (m_currentSurface == newCurrentSurface)
+ {
+ emit currentSurfaceChanged();
+ return;
+ }
+ m_currentSurface = newCurrentSurface;
+ emit currentSurfaceChanged();
+}
+
+int ApplicationManager::count()
+{
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ if(!entries)
+ return 0;
+ return entries->rowCount();
+}
+
+QString ApplicationManager::applicationName(int id)
+{
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ if(!entries)
+ return "";
+ return entries->data(entries->index(id), WaylandEntries::TitleRole).toString();
+}
+
+int ApplicationManager::applicationPid(int id)
+{
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ if(!entries)
+ return 0;
+ return entries->data(entries->index(id), WaylandEntries::PidRole).toInt();
+}
+
+int ApplicationManager::applicationFocus(int id)
+{
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ if(!entries)
+ return -1;
+ if(id >= entries->rowCount())
+ return -2;
+ QVariant d = entries->data(entries->index(id), WaylandEntries::SurfaceRole);
+ if(!d.isValid())
+ return -3;
+ QWaylandXdgSurface *surface = qvariant_cast<QWaylandXdgSurface *>(d);
+ if(surface)
+ {
+ setCurrentSurface(surface);
+ return 0;
+ }
+ return -4;
+}
+
+int ApplicationManager::currentApplicationPid()
+{
+ if(isHome())
+ return -1;
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ for(int i = 0; i < entries->rowCount(); i++)
+ {
+ QVariant d = entries->data(entries->index(i), WaylandEntries::SurfaceRole);
+ QWaylandXdgSurface *surface = qvariant_cast<QWaylandXdgSurface *>(d);
+ QWaylandXdgSurface *current = qobject_cast<QWaylandXdgSurface*>(currentSurface());
+ if(surface != current)
+ continue;
+ return entries->data(entries->index(i), WaylandEntries::PidRole).toInt();
+ }
+ return -2;
+}
+
+void ApplicationManager::hideApplicationPid(qint64 pid)
+{
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ entries->addHidePid(pid);
+}
+
+void ApplicationManager::raiseApplicationPid(qint64 pid)
+{
+ WaylandEntries *entries = WaylandEntries::getInstance();
+ QObject *d = entries->getSurface(pid);
+ QWaylandXdgSurface *surface = qobject_cast<QWaylandXdgSurface *>(d);
+ if(surface)
+ {
+ setCurrentSurface(surface);
+ }
+}
+
+void ApplicationManager::backHome()
+{
+ emit gotoHome();
+}
+
+void ApplicationManager::setIsHome(bool isHome)
+{
+ if (m_isHome == isHome)
+ return;
+
+ m_isHome = isHome;
+ emit isHomeChanged(m_isHome);
+}
+
+QString ApplicationManager::virtualkeyboard()
+{
+ return qgetenv("QT_IM_MODULE");
+}
diff --git a/src/applicationmanager.h b/src/applicationmanager.h
new file mode 100644
index 0000000..b4568f0
--- /dev/null
+++ b/src/applicationmanager.h
@@ -0,0 +1,43 @@
+#ifndef APPLICATIONMANAGER_H
+#define APPLICATIONMANAGER_H
+
+#include <QObject>
+
+class ApplicationManager : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QObject *currentSurface READ currentSurface WRITE setCurrentSurface NOTIFY currentSurfaceChanged)
+ Q_PROPERTY(bool isHome READ isHome WRITE setIsHome NOTIFY isHomeChanged)
+ Q_PROPERTY(QString virtualkeyboard READ virtualkeyboard CONSTANT)
+
+ QObject *m_currentSurface {nullptr};
+ bool m_isHome;
+
+public:
+ explicit ApplicationManager(QObject *parent = nullptr);
+
+ bool isHome() const;
+
+public slots:
+ QObject *currentSurface() const;
+ void setCurrentSurface(QObject *newCurrentSurface);
+
+ int count();
+ QString applicationName(int id);
+ int applicationPid(int id);
+ int applicationFocus(int id);
+ int currentApplicationPid();
+ void hideApplicationPid(qint64 pid);
+ void raiseApplicationPid(qint64 pid);
+ void backHome();
+ void setIsHome(bool isHome);
+
+ QString virtualkeyboard();
+
+signals:
+ void currentSurfaceChanged();
+ void gotoHome();
+ void isHomeChanged(bool isHome);
+};
+
+#endif // APPLICATIONMANAGER_H
diff --git a/src/debug.cpp b/src/debug.cpp
new file mode 100644
index 0000000..a07d815
--- /dev/null
+++ b/src/debug.cpp
@@ -0,0 +1,6 @@
+#include "debug.h"
+
+Q_LOGGING_CATEGORY(DESKTOP_MGR_CORE, "desktopmgr.core")
+Q_LOGGING_CATEGORY(DESKTOP_MGR_SCREEN_COPY, "desktopmgr.screencopy")
+Q_LOGGING_CATEGORY(DESKTOP_MGR_VNC_KEYBOARD, "desktopmgr.vnckeyboard")
+Q_LOGGING_CATEGORY(DESKTOP_MGR_VIRTUAL_POINTER, "desktopmgr.virtualpointer")
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..803192d
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,11 @@
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#include <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(DESKTOP_MGR_CORE)
+Q_DECLARE_LOGGING_CATEGORY(DESKTOP_MGR_SCREEN_COPY)
+Q_DECLARE_LOGGING_CATEGORY(DESKTOP_MGR_VNC_KEYBOARD)
+Q_DECLARE_LOGGING_CATEGORY(DESKTOP_MGR_VIRTUAL_POINTER)
+
+#endif // DEBUG_H
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..ca19b05
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,70 @@
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QDBusConnection>
+#include <QIcon>
+#include <QTimer>
+#include <QThread>
+#include <systemd/sd-daemon.h>
+#include <QDir>
+#include <QFileInfo>
+#include <xdgstatusnotifierwatcher.h>
+#include "applicationmanager.h"
+#include "applicationsadaptor.h"
+#include "waylandentries.h"
+#include "waylandentriesfiltered.h"
+
+int main(int argc, char *argv[])
+{
+ qputenv("QT_IM_MODULE", "qtvirtualkeyboard");
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QGuiApplication app(argc, argv);
+ ApplicationManager manager;
+ QTimer watchdog;
+ uint64_t watchdogPeriodUSec;
+
+ QFileInfo volatilapps("/var/volatile/applications");
+ if(!volatilapps.exists())
+ {
+ QDir().mkdir("/var/volatile/applications");
+ }
+
+ qmlRegisterType<WaylandEntries>("org.kazoe.desktop", 1, 0, "WaylandEntries");
+ qmlRegisterType<WaylandEntriesFiltered>("org.kazoe.desktop", 1, 0, "WaylandEntriesFiltered");
+
+ if (!bus.isConnected()) {
+ qWarning("Cannot connect to the D-Bus session bus.\n"
+ "Please check your system settings and try again.\n");
+ return 1;
+ }
+
+ QQmlApplicationEngine engine;
+ new ApplicationsAdaptor(&manager);
+ bus.registerObject("/applications", &manager);
+
+
+ bool conres = bus.registerService("org.kazoe.desktop");
+ if(!conres) qWarning() << "Can't register dbus service org.kazoe.desktop";
+
+ KaZoe::StatusNotifierWatcher notifierWatcher;
+
+ engine.rootContext()->setContextProperty("manager", &manager);
+ engine.loadFromModule("org.kazoe.desktop", "Main");
+
+ // signal systemd that the app the ready
+ QTimer::singleShot(0, [] () {
+ sd_notify(0, "READY=1");
+ }
+ );
+
+ // setup a timer to refresh the systemd'd watchdog if needed
+ if (sd_watchdog_enabled(0,&watchdogPeriodUSec) > 0) {
+ QObject::connect(&watchdog, &QTimer::timeout, [] () {
+ sd_notify(0, "WATCHDOG=1");
+ }
+ );
+ }
+ // configure the period of the QT timer for a half period of the watchdog timer
+ watchdog.start(watchdogPeriodUSec / 2000);
+ return app.exec();
+}
diff --git a/src/org.freedesktop.DBus.xml b/src/org.freedesktop.DBus.xml
new file mode 100644
index 0000000..68d5262
--- /dev/null
+++ b/src/org.freedesktop.DBus.xml
@@ -0,0 +1,17 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.freedesktop.DBus.Properties">
+ <method name="GetAll">
+ <arg name="interface_name" type="s" direction="in"/>
+ <arg name="properties" type="a{sv}" direction="out"/>
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
+ </method>
+ <signal name="PropertiesChanged">
+ <arg name="interface_name" type="s" direction="out"/>
+ <arg name="changed_properties" type="a{sv}" direction="out"/>
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
+ <arg name="invalidated_properties" type="as" direction="out"/>
+ </signal>
+ </interface>
+</node>
diff --git a/src/vnc-keyboard-unstable-v1.cpp b/src/vnc-keyboard-unstable-v1.cpp
new file mode 100644
index 0000000..5dc8dd7
--- /dev/null
+++ b/src/vnc-keyboard-unstable-v1.cpp
@@ -0,0 +1,146 @@
+#include <QDebug>
+#include <QGuiApplication>
+#include <wayland-server-protocol.h>
+#include <linux/input-event-codes.h>
+#include "vnc-keyboard-unstable-v1.h"
+
+
+void toQtKey(uint32_t key, uint32_t *pkeycode, uint32_t *punicode)
+{
+ static const QMap<int, int> keyMap {
+ { 0xff08, Qt::Key_Backspace },
+ { 0xff09, Qt::Key_Tab },
+ { 0xff0d, Qt::Key_Return },
+ { 0xff1b, Qt::Key_Escape },
+ { 0xff63, Qt::Key_Insert },
+ { 0xffff, Qt::Key_Delete },
+ { 0xff50, Qt::Key_Home },
+ { 0xff57, Qt::Key_End },
+ { 0xff55, Qt::Key_PageUp },
+ { 0xff56, Qt::Key_PageDown },
+ { 0xff51, Qt::Key_Left },
+ { 0xff52, Qt::Key_Up },
+ { 0xff53, Qt::Key_Right },
+ { 0xff54, Qt::Key_Down },
+ { 0xffbe, Qt::Key_F1 },
+ { 0xffbf, Qt::Key_F2 },
+ { 0xffc0, Qt::Key_F3 },
+ { 0xffc1, Qt::Key_F4 },
+ { 0xffc2, Qt::Key_F5 },
+ { 0xffc3, Qt::Key_F6 },
+ { 0xffc4, Qt::Key_F7 },
+ { 0xffc5, Qt::Key_F8 },
+ { 0xffc6, Qt::Key_F9 },
+ { 0xffc7, Qt::Key_F10 },
+ { 0xffc8, Qt::Key_F11 },
+ { 0xffc9, Qt::Key_F12 },
+ { 0xffe1, Qt::Key_Shift },
+ { 0xffe2, Qt::Key_Shift },
+ { 0xffe3, Qt::Key_Control },
+ { 0xffe4, Qt::Key_Control },
+ { 0xffe7, Qt::Key_Meta },
+ { 0xffe8, Qt::Key_Meta },
+ { 0xffe9, Qt::Key_Alt },
+ { 0xffea, Qt::Key_Alt },
+
+ { 0xffb0, Qt::Key_0 },
+ { 0xffb1, Qt::Key_1 },
+ { 0xffb2, Qt::Key_2 },
+ { 0xffb3, Qt::Key_3 },
+ { 0xffb4, Qt::Key_4 },
+ { 0xffb5, Qt::Key_5 },
+ { 0xffb6, Qt::Key_6 },
+ { 0xffb7, Qt::Key_7 },
+ { 0xffb8, Qt::Key_8 },
+ { 0xffb9, Qt::Key_9 },
+
+ { 0xff8d, Qt::Key_Return },
+ { 0xffaa, Qt::Key_Asterisk },
+ { 0xffab, Qt::Key_Plus },
+ { 0xffad, Qt::Key_Minus },
+ { 0xffae, Qt::Key_Period },
+ { 0xffaf, Qt::Key_Slash },
+
+ { 0xff95, Qt::Key_Home },
+ { 0xff96, Qt::Key_Left },
+ { 0xff97, Qt::Key_Up },
+ { 0xff98, Qt::Key_Right },
+ { 0xff99, Qt::Key_Down },
+ { 0xff9a, Qt::Key_PageUp },
+ { 0xff9b, Qt::Key_PageDown },
+ { 0xff9c, Qt::Key_End },
+ { 0xff9e, Qt::Key_Insert },
+ { 0xff9f, Qt::Key_Delete },
+ };
+ uint32_t unicode = 0;
+ uint32_t keycode = keyMap.value(key, 0);
+
+ if (keycode >= ' ' && keycode <= '~')
+ unicode = keycode;
+
+ if (!keycode) {
+ if (key <= 0xff) {
+ unicode = key;
+ if (key >= 'a' && key <= 'z')
+ keycode = Qt::Key_A + key - 'a';
+ else if (key >= ' ' && key <= '~')
+ keycode = Qt::Key_Space + key - ' ';
+ }
+ }
+ *pkeycode = keycode;
+ *punicode = unicode;
+}
+
+
+VncKeyboardManager::VncKeyboardManager(QWaylandCompositor *compositor)
+ :QWaylandCompositorExtensionTemplate(compositor)
+{
+}
+
+void VncKeyboardManager::initialize()
+{
+ QWaylandCompositorExtensionTemplate::initialize();
+ QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer());
+ init(compositor->display(), 1);
+}
+
+void VncKeyboardManager::vnc_virtual_keyboard_manager_v1_create_virtual_keyboard(Resource *resource, uint32_t id)
+{
+ new VncKeyboard(m_window, resource->client(), id, 1);
+}
+
+VncKeyboard::VncKeyboard(QQuickWindow *window, struct ::wl_client *client, int id, int version):
+ QtWaylandServer::vnc_keyboard_v1(client, id, version),
+ m_window(window),
+ m_keymod(Qt::NoModifier)
+{
+}
+
+void VncKeyboard::vnc_keyboard_v1_key(Resource *resource, uint32_t time, uint32_t key, uint32_t state)
+{
+ bool down = !!state;
+ uint32_t keycode, unicode;
+
+ toQtKey(key, &keycode, &unicode);
+
+ if (keycode == Qt::Key_Shift)
+ m_keymod = down ? m_keymod | Qt::ShiftModifier :
+ m_keymod & ~Qt::ShiftModifier;
+ else if (keycode == Qt::Key_Control)
+ m_keymod = down ? m_keymod | Qt::ControlModifier :
+ m_keymod & ~Qt::ControlModifier;
+ else if (keycode == Qt::Key_Alt)
+ m_keymod = down ? m_keymod | Qt::AltModifier :
+ m_keymod & ~Qt::AltModifier;
+ if (unicode || keycode) {
+ QKeyEvent event(down ? QEvent::KeyPress : QEvent::KeyRelease, keycode, m_keymod, QString(QChar(unicode)));
+ QGuiApplication::sendEvent(m_window, &event);
+ } else {
+ qDebug() << __func__ << " no unicode or keycode. keysym is " << key << ". state is " << state;
+ }
+}
+
+void VncKeyboard::vnc_keyboard_v1_destroy(Resource *)
+{
+ delete this;
+}
diff --git a/src/vnc-keyboard-unstable-v1.h b/src/vnc-keyboard-unstable-v1.h
new file mode 100644
index 0000000..3728b3f
--- /dev/null
+++ b/src/vnc-keyboard-unstable-v1.h
@@ -0,0 +1,46 @@
+#ifndef VIRTUALKEYBOARD_H
+#define VIRTUALKEYBOARD_H
+
+#include "wayland-util.h"
+#include <QQuickWindow>
+#include <QtWaylandCompositor/QWaylandCompositorExtensionTemplate>
+#include <QtWaylandCompositor/QWaylandQuickExtension>
+#include <QtWaylandCompositor/QWaylandCompositor>
+#include "qwayland-server-vnc-keyboard-unstable-v1.h"
+
+class VncKeyboardManager : public QWaylandCompositorExtensionTemplate<VncKeyboardManager>
+ , public QtWaylandServer::vnc_virtual_keyboard_manager_v1
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickWindow *window MEMBER m_window);
+
+public:
+ VncKeyboardManager(QWaylandCompositor *compositor = nullptr);
+ void initialize() override;
+
+protected:
+ void vnc_virtual_keyboard_manager_v1_create_virtual_keyboard(Resource *resource, uint32_t id) override;
+
+private:
+ QQuickWindow *m_window;
+};
+
+class VncKeyboard : public QWaylandCompositorExtensionTemplate<VncKeyboard>
+ , public QtWaylandServer::vnc_keyboard_v1
+{
+ Q_OBJECT
+public:
+ VncKeyboard(QQuickWindow *window, struct ::wl_client *client, int id, int version);
+
+protected:
+ void vnc_keyboard_v1_key(Resource *resource, uint32_t time, uint32_t key, uint32_t state) override;
+ void vnc_keyboard_v1_destroy(Resource *resource) override;
+
+private:
+ QQuickWindow *m_window;
+ Qt::KeyboardModifiers m_keymod;
+};
+
+Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(VncKeyboardManager)
+
+#endif // VIRTUALKEYBOARD_H
diff --git a/src/waylandentries.cpp b/src/waylandentries.cpp
new file mode 100644
index 0000000..42808bb
--- /dev/null
+++ b/src/waylandentries.cpp
@@ -0,0 +1,159 @@
+#include "waylandentries.h"
+#include <QWaylandXdgSurface>
+#include <QWaylandSurface>
+#include <QMutexLocker>
+#include <qdebug.h>
+
+WaylandEntries *m_instance = nullptr;
+
+WaylandEntries::WaylandEntries(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ if(m_instance != nullptr)
+ {
+ qWarning() << "Many instance of WaylandEntries";
+ }
+ m_instance = this;
+}
+
+int WaylandEntries::rowCount(const QModelIndex &parent) const
+{
+ QMutexLocker locker(&m_lock);
+ return m_surfaces.count();
+}
+
+QVariant WaylandEntries::data(const QModelIndex &index, int role) const
+{
+ QMutexLocker locker(&m_lock);
+ if(index.row() > m_surfaces.count())
+ return QVariant();
+
+ QWaylandXdgSurface *surface = m_surfaces[index.row()];
+ if(surface == nullptr)
+ return QVariant();
+
+ switch(role) {
+ case Qt::DisplayRole:
+ case TitleRole:
+ return surface->toplevel()->title();
+ case AppIdRole:
+ return surface->toplevel()->appId();
+ case SurfaceRole:
+ return QVariant::fromValue<QWaylandXdgSurface*>(surface);
+ case PidRole:
+ return surface->surface()->client()->processId();
+ }
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> WaylandEntries::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
+ roles[SurfaceRole] = "wl_surface";
+ roles[TitleRole] = "title";
+ roles[PidRole] = "pid";
+ roles[AppIdRole] = "appId";
+ return roles;
+}
+
+// Need only for application manager interface for cli
+WaylandEntries *WaylandEntries::getInstance()
+{
+ return m_instance;
+}
+
+void WaylandEntries::addSurface(QObject *obj)
+{
+ QMutexLocker locker(&m_lock);
+ QWaylandXdgSurface *s = qobject_cast<QWaylandXdgSurface*>(obj);
+ if(s)
+ {
+ QObject::connect(s, &QWaylandXdgSurface::destroyed, this, &WaylandEntries::surfaceDestroyed);
+ if(m_hidden.contains(s->surface()->client()->processId()))
+ {
+ m_surfaces_hidden.append(s);
+ }
+ else
+ {
+ int id = m_surfaces.count();
+ beginInsertRows(QModelIndex(), m_surfaces.count(), m_surfaces.count());
+ QObject::connect(s->toplevel(), &QWaylandXdgToplevel::titleChanged, this, [this, id](){
+ const QVector<int> roles = {TitleRole};
+ emit dataChanged(index(id), index(id), roles);
+ });
+ QObject::connect(s->toplevel(), &QWaylandXdgToplevel::appIdChanged, this, [this, id, s](){
+ const QVector<int> roles = {AppIdRole};
+
+ emit topWindowRegistered(s, s->toplevel()->appId());
+ emit dataChanged(index(id), index(id), roles);
+ });
+ m_surfaces.append(s);
+ endInsertRows();
+ emit nbAppsChanged();
+ }
+ }
+}
+
+void WaylandEntries::addHidePid(qint64 pid)
+{
+ QMutexLocker locker(&m_lock);
+ m_hidden.append(pid);
+ QWaylandXdgSurface *surfaceToHide = nullptr;
+
+ for(QWaylandXdgSurface *surface: m_surfaces)
+ {
+ if(surface->surface()->client()->processId() == pid)
+ {
+ surfaceToHide = surface;
+ break;
+ }
+ }
+ if(surfaceToHide)
+ {
+ beginRemoveRows(QModelIndex(), m_surfaces.indexOf(surfaceToHide), m_surfaces.indexOf(surfaceToHide));
+ m_surfaces.removeAll(surfaceToHide);
+ endRemoveRows();
+ m_surfaces_hidden.append(surfaceToHide);
+ emit nbAppsChanged();
+ }
+}
+
+void WaylandEntries::surfaceDestroyed()
+{
+ QObject *obj = QObject::sender();
+ QWaylandXdgSurface *s = static_cast<QWaylandXdgSurface*>(obj);
+ emit topWindowDestroyed(s, s->toplevel()->appId());
+ QMutexLocker locker(&m_lock);
+ beginRemoveRows(QModelIndex(), m_surfaces.indexOf(s), m_surfaces.indexOf(s));
+ m_surfaces.removeAll(s);
+ endRemoveRows();
+ emit nbAppsChanged();
+}
+
+void WaylandEntries::stop(int pid)
+{
+ kill(pid, SIGTERM);
+}
+
+QObject *WaylandEntries::getSurface(int pid)
+{
+ QMutexLocker locker(&m_lock);
+ for(QWaylandXdgSurface *surface: m_surfaces)
+ {
+ if(surface->surface()->client()->processId() == pid)
+ return surface;
+ }
+ for(QWaylandXdgSurface *surface: m_surfaces_hidden)
+ {
+ if(surface->surface()->client()->processId() == pid)
+ return surface;
+ }
+ return nullptr;
+}
+
+int WaylandEntries::nbApps() const
+{
+ QMutexLocker locker(&m_lock);
+ return m_surfaces.count();
+}
diff --git a/src/waylandentries.h b/src/waylandentries.h
new file mode 100644
index 0000000..ce98ade
--- /dev/null
+++ b/src/waylandentries.h
@@ -0,0 +1,53 @@
+#ifndef WAYLANDENTRIES_H
+#define WAYLANDENTRIES_H
+
+#include <QAbstractListModel>
+#include <QRecursiveMutex>
+#include <QObject>
+
+class QWaylandXdgSurface;
+
+class WaylandEntries : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int nbApps READ nbApps NOTIFY nbAppsChanged)
+
+
+ QList<QWaylandXdgSurface *> m_surfaces;
+ QList<QWaylandXdgSurface *> m_surfaces_hidden;
+ QList<qint64> m_hidden;
+ mutable QRecursiveMutex m_lock;
+
+public:
+ explicit WaylandEntries(QObject *parent = nullptr);
+
+ enum WaylandEntryRoles {
+ SurfaceRole = Qt::UserRole + 1,
+ TitleRole,
+ PidRole,
+ AppIdRole
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ static WaylandEntries* getInstance();
+
+ int nbApps() const;
+ void setNbApps(int newNbApps);
+
+public slots:
+ void addSurface(QObject *obj);
+ void addHidePid(qint64 pid);
+ void surfaceDestroyed();
+ void stop(int pid);
+ QObject *getSurface(int pid);
+
+signals:
+ void nbAppsChanged();
+ void topWindowRegistered(QObject *surface, QString appid);
+ void topWindowDestroyed(QObject *surface, QString appid);
+};
+
+#endif // WAYLANDENTRIES_H
diff --git a/src/waylandentriesfiltered.cpp b/src/waylandentriesfiltered.cpp
new file mode 100644
index 0000000..f1f2ebc
--- /dev/null
+++ b/src/waylandentriesfiltered.cpp
@@ -0,0 +1,63 @@
+#include "waylandentriesfiltered.h"
+#include "waylandentries.h"
+#include "xdgdesktopentries.h"
+#include <QDebug>
+
+WaylandEntriesFiltered::WaylandEntriesFiltered(QObject *parent)
+ : QSortFilterProxyModel{parent}
+{
+ m_entries = new KaZoe::DesktopEntries(this);
+ QObject::connect(this, &WaylandEntriesFiltered::rowsInserted, this, &WaylandEntriesFiltered::lengthChanged);
+ QObject::connect(this, &WaylandEntriesFiltered::rowsRemoved, this, &WaylandEntriesFiltered::lengthChanged);
+}
+
+QString WaylandEntriesFiltered::roleFilter() const
+{
+ return m_roleFilter.join(";");
+}
+
+void WaylandEntriesFiltered::setRoleFilter(const QString &roles)
+{
+ QStringList newlist = roles.split(";");
+ if (m_roleFilter == newlist)
+ return;
+ m_roleFilter = newlist;
+ emit roleFilterChanged();
+}
+
+bool WaylandEntriesFiltered::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ QVariant appid = sourceModel()->data(index, WaylandEntries::AppIdRole);
+ QString role = m_entries->appIdData(appid.toString(), "Desktop Entry/X-DESKTOPMGR-ROLE");
+
+ if(appid.isNull())
+ return false;
+
+ if(m_roleFilter.contains(role))
+ {
+ return false;
+ }
+ return true;
+}
+
+QObject *WaylandEntriesFiltered::model() const
+{
+ return static_cast<QObject*>(m_model);
+}
+
+void WaylandEntriesFiltered::setModel(QObject *newModel)
+{
+ WaylandEntries *entries = qobject_cast<WaylandEntries*>(newModel);
+ if (entries)
+ {
+ m_model = entries;
+ setSourceModel(m_model);
+ emit modelChanged(m_model);
+ }
+}
+
+int WaylandEntriesFiltered::length()
+{
+ return rowCount();
+}
diff --git a/src/waylandentriesfiltered.h b/src/waylandentriesfiltered.h
new file mode 100644
index 0000000..e562a7d
--- /dev/null
+++ b/src/waylandentriesfiltered.h
@@ -0,0 +1,41 @@
+#ifndef WAYLANDENTIRESFILTERED_H
+#define WAYLANDENTIRESFILTERED_H
+
+#include <QSortFilterProxyModel>
+#include <QObject>
+
+class WaylandEntries;
+namespace KaZoe {
+ class DesktopEntries;
+}
+
+class WaylandEntriesFiltered : public QSortFilterProxyModel
+{
+ Q_OBJECT
+ Q_PROPERTY(QObject *model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(QString roleFilter READ roleFilter WRITE setRoleFilter NOTIFY roleFilterChanged)
+ Q_PROPERTY(int length READ length NOTIFY lengthChanged)
+ KaZoe::DesktopEntries *m_entries {nullptr};
+
+public:
+ explicit WaylandEntriesFiltered(QObject *parent = nullptr);
+ QString roleFilter() const;
+ void setRoleFilter(const QString &roles);
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+
+ QObject *model() const;
+ void setModel(QObject *newModel);
+
+ int length();
+
+signals:
+ void roleFilterChanged();
+ void modelChanged(QObject * model);
+ void lengthChanged();
+
+private:
+ QStringList m_roleFilter;
+ WaylandEntries *m_model = nullptr;
+};
+
+#endif // WAYLANDENTIRESFILTERED_H
diff --git a/tools/cli/main.cpp b/tools/cli/main.cpp
new file mode 100644
index 0000000..caf4463
--- /dev/null
+++ b/tools/cli/main.cpp
@@ -0,0 +1,98 @@
+#include <QCoreApplication>
+#include <QDBusConnection>
+#include <QTimer>
+#include "ApplicationManagerIface.h"
+
+static int query(OrgKazoeDesktopApplicationsInterface &iface, int argc, char *argv[])
+{
+ if(argc < 2 || QString(argv[1]) == "list")
+ {
+ int current = iface.currentApplicationPid().value();
+ printf(" ______________________________________________________\n");
+ printf("| ID | Name | PID |\n");
+ printf("|--------|----------------------------------|----------|\n");
+ if(current == -1)
+ {
+ printf("|> HOME | Homepage | |\n");
+ }
+ else
+ {
+ printf("| HOME | Homepage | |\n");
+ }
+
+ for(int i = 0; i < iface.count(); i++)
+ {
+ int pid = iface.applicationPid(i).value();
+ if(current == pid)
+ {
+ printf("|> @%04i | ", i);
+ }
+ else
+ {
+ printf("| @%04i | ", i);
+ }
+ printf("%-32s |", QString(iface.applicationName(i)).mid(0,32).toStdString().c_str());
+ if(pid) printf("% 9i | ", iface.applicationPid(i).value());
+ else printf(" |");
+ printf("\n");
+ }
+ printf("--------------------------------------------------------\n");
+ return 0;
+ }
+
+ if(QString(argv[1]) == "focus" && argc == 3)
+ {
+ int id = -1;
+ QString ids = QString(argv[2]);
+ if(ids.startsWith("@"))
+ {
+ id = ids.mid(1).toInt();
+ return iface.applicationFocus(id).value();
+ }
+ for(int i = 0; i < iface.count(); i++)
+ {
+ QString name = iface.applicationName(i);
+ if(name == ids)
+ {
+ return iface.applicationFocus(i).value();
+ }
+ }
+ return -1;
+ }
+
+ if(QString(argv[1]) == "home")
+ {
+ iface.backHome();
+ return 0;
+ }
+
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ QDBusConnection connection = QDBusConnection::sessionBus();
+
+ if (!connection.isConnected()) {
+ qWarning("Cannot connect to the D-Bus session bus.\n"
+ "Please check your system settings and try again.\n");
+ return 1;
+ }
+ const QString service = "org.kazoe.desktop.applications";
+ const QString path = "/applications";
+ OrgKazoeDesktopApplicationsInterface iface(service, path, connection);
+
+ QTimer::singleShot(0, [argc, argv, &iface](){
+ if(query(iface, argc, argv) != 0)
+ {
+ qInfo() << "Usage:";
+ qInfo() << "> desktopcli list -> List all views";
+ qInfo() << "> desktopcli home -> Go to home view";
+ qInfo() << "> desktopcli focus <ID | Name> -> Go to application view, by ID or Name";
+ }
+ exit(0);
+ });
+
+ return app.exec();
+}