Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
87a468c
feat(keyboard): add MARATHON_FORCE_VKB env flag for desktop testing
patrickjquinn Jan 2, 2026
7d34da7
feat(keyboard): implement iOS/Android-style auto-show/hide on focus
patrickjquinn Jan 2, 2026
5ecff82
refactor(keyboard): apply Deepak's Rule - remove all comments
patrickjquinn Jan 2, 2026
236eb6a
feat(keyboard): improve word predictions with Hunspell backend
patrickjquinn Jan 2, 2026
dcb4cc5
feat(keyboard): add dynamic multi-language layout system
patrickjquinn Jan 2, 2026
237e405
feat(settings): add keyboard language settings page
patrickjquinn Jan 2, 2026
f716a34
refactor(keyboard): clean InputMethodEngine and build integration
patrickjquinn Jan 2, 2026
a47222d
Style: Apply automated formatting (clang-format, qmlformat)
patrickjquinn Jan 2, 2026
9248882
Refactor: Strip comments from MarathonShell.qml for code hygiene
patrickjquinn Jan 2, 2026
f932f0e
feat(wayland): add custom text-input-v3 protocol handler
patrickjquinn Jan 2, 2026
2f6ce46
build(wayland): add text-input-v3 protocol generation to CMake
patrickjquinn Jan 2, 2026
9f92efb
feat(wayland): integrate text-input-v3 with virtual keyboard
patrickjquinn Jan 2, 2026
cc9fce8
build: enable C language for wayland protocol code
patrickjquinn Jan 2, 2026
a3fef94
fix(calendar): add .pragma library to CalendarWorker.js
patrickjquinn Jan 2, 2026
e641790
refactor(calendar): instantiate CalendarStorage as local object
patrickjquinn Jan 2, 2026
dbd7c8d
refactor(keyboard): unify virtual keyboard focus handling
patrickjquinn Jan 2, 2026
927065a
chore: clean up run.sh debug flags
patrickjquinn Jan 2, 2026
001d3f8
chore: add CMakeFiles to gitignore
patrickjquinn Jan 2, 2026
43be1b3
fix(keyboard): repair auto-suggest interaction and async updates
patrickjquinn Jan 3, 2026
037abf4
feat(keyboard): implement Trie-based word completion
patrickjquinn Jan 3, 2026
429ca7e
test(keyboard): add unit and integration tests for WordTrie
patrickjquinn Jan 3, 2026
594fc0a
feat(keyboard): add frequency-based prediction ranking
patrickjquinn Jan 3, 2026
73cf7ff
feat(keyboard): add undo auto-correct on backspace
patrickjquinn Jan 3, 2026
a400cc5
fix(keyboard): repair getCurrentWord to return actual current word
patrickjquinn Jan 3, 2026
e5eaed0
style(keyboard): format Dictionary and AutoCorrect with qmlformat
patrickjquinn Jan 3, 2026
d4c07ff
test(keyboard): reorganize tests under tests/keyboard/
patrickjquinn Jan 3, 2026
2b4beb7
fix(shell): remove .pragma library from CalendarWorker.js
patrickjquinn Jan 3, 2026
d5671be
fix(ui): add missing mediumImpact function to MHaptics
patrickjquinn Jan 3, 2026
1c7c8d4
fix(shell): unset WAYLAND_DEBUG to prevent surface log spam
patrickjquinn Jan 3, 2026
5d14c53
feat(keyboard): improve prediction UX per research standards
patrickjquinn Jan 3, 2026
dd1e80e
feat(ui): disable haptic feedback by default
patrickjquinn Jan 3, 2026
9196581
feat(keyboard): add emoji and phrase predictions
patrickjquinn Jan 3, 2026
aa275e9
style(keyboard): apply qmlformat and clang-format
patrickjquinn Jan 3, 2026
f9d2bda
fix(shell): allow tap-through when dismissing keyboard
patrickjquinn Jan 3, 2026
5cd9f72
fix(keyboard): reset all state when keyboard is dismissed
patrickjquinn Jan 3, 2026
8736d3f
fix(keyboard): unify emoji/language button - emoji primary, long-pres…
patrickjquinn Jan 3, 2026
65821b1
fix(search): use MAppIcon to properly render app icons
patrickjquinn Jan 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ cmake/
.cache/
compile_commands.json
build-tests/
CMakeFiles/
19 changes: 18 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.21)

project(MarathonOS VERSION 1.0.0 LANGUAGES CXX)
project(MarathonOS VERSION 1.0.0 LANGUAGES C CXX)

enable_testing()


if(POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
Expand Down Expand Up @@ -156,6 +159,16 @@ find_package(Qt6 6.4 COMPONENTS WaylandCompositor)
if(TARGET Qt6::WaylandCompositor)
message(STATUS "Qt WaylandCompositor found - enabling Wayland support")
set(HAVE_WAYLAND TRUE)

# Also find the private module for text-input-v3 support
find_package(Qt6 6.4 COMPONENTS WaylandCompositorPrivate QUIET)
if(TARGET Qt6::WaylandCompositorPrivate)
message(STATUS "Qt WaylandCompositorPrivate found - enabling text-input-v3 for native apps")
set(HAVE_WAYLAND_PRIVATE TRUE)
else()
message(STATUS "Qt WaylandCompositorPrivate not found - text-input-v3 disabled")
set(HAVE_WAYLAND_PRIVATE FALSE)
endif()
else()
message(STATUS "Qt WaylandCompositor not found - Wayland support disabled")
set(HAVE_WAYLAND FALSE)
Expand Down Expand Up @@ -253,3 +266,7 @@ install(DIRECTORY docs/
FILES_MATCHING PATTERN "*.md"
)

# Tests
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
98 changes: 51 additions & 47 deletions apps/calendar/CalendarApp.qml
Original file line number Diff line number Diff line change
@@ -1,68 +1,82 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import MarathonOS.Shell
import MarathonUI.Containers
import MarathonUI.Core
import MarathonUI.Theme
import MarathonUI.Navigation
import MarathonUI.Theme
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "stores"

MApp {
id: calendarApp
appId: "calendar"
appName: "Calendar"
appIcon: "assets/icon.svg"

property date currentDate: new Date()
property var selectedDate: null
property int currentView: 0

Component.onCompleted: {
CalendarStorage.init();
}
property Item navStack: null

function createEvent(title, date, time, allDay, recurring) {
var event = {
title: title || "Untitled Event",
date: date,
time: time || "12:00",
allDay: allDay || false,
recurring: recurring || "none"
"title": title || "Untitled Event",
"date": date,
"time": time || "12:00",
"allDay": allDay || false,
"recurring": recurring || "none"
};
CalendarStorage.addEvent(event);
calendarStorage.addEvent(event);
return event;
}

function getEventsForDate(date) {
return CalendarStorage.getEventsForDate(date);
return calendarStorage.getEventsForDate(date);
}

function deleteEvent(id) {
return CalendarStorage.deleteEvent(id);
return calendarStorage.deleteEvent(id);
}

property Item navStack: null
// Helper to open detail page
function openEventDetail(event) {
if (navStack)
navStack.push("pages/EventDetailPage.qml", {
"event": event,
"onDelete": eventId => {
calendarApp.deleteEvent(eventId);
}
});
}

appId: "calendar"
appName: "Calendar"
appIcon: "assets/icon.svg"
Component.onCompleted: {
calendarStorage.init();
}
navigationDepth: navStack ? navStack.depth : 0
onBackPressed: {
if (navStack && navStack.depth > 1) {
// If filtering by date, clear filter
// If in list view (without filter), go back to month view
// Let the shell handle closing the app if at root

if (navStack && navStack.depth > 1)
navStack.pop();
} else if (calendarApp.selectedDate !== null) {
// If filtering by date, clear filter
else if (calendarApp.selectedDate !== null)
calendarApp.selectedDate = null;
} else if (calendarApp.currentView === 1) {
// If in list view (without filter), go back to month view
else if (calendarApp.currentView === 1)
calendarApp.currentView = 0;
} else {
// Let the shell handle closing the app if at root
else
navigationDepth = 0;
}
}

CalendarStorage {
id: calendarStorage
}

content: StackView {
id: stackView
anchors.fill: parent

anchors.fill: parent
Component.onCompleted: calendarApp.navStack = stackView

initialItem: Rectangle {
Expand All @@ -88,20 +102,19 @@ MApp {

MTabBar {
id: tabBar

width: parent.width
activeTab: calendarApp.currentView

tabs: [
{
label: "Month",
icon: "calendar"
"label": "Month",
"icon": "calendar"
},
{
label: "List",
icon: "list"
"label": "List",
"icon": "list"
}
]

onTabSelected: index => {
HapticService.light();
calendarApp.currentView = index;
Expand Down Expand Up @@ -137,6 +150,7 @@ MApp {
easing.type: Easing.OutCubic
}
}

pushExit: Transition {
PropertyAnimation {
property: "x"
Expand All @@ -146,6 +160,7 @@ MApp {
easing.type: Easing.OutCubic
}
}

popEnter: Transition {
PropertyAnimation {
property: "x"
Expand All @@ -155,6 +170,7 @@ MApp {
easing.type: Easing.OutCubic
}
}

popExit: Transition {
PropertyAnimation {
property: "x"
Expand All @@ -165,16 +181,4 @@ MApp {
}
}
}

// Helper to open detail page
function openEventDetail(event) {
if (navStack) {
navStack.push("pages/EventDetailPage.qml", {
"event": event,
"onDelete": eventId => {
calendarApp.deleteEvent(eventId);
}
});
}
}
}
74 changes: 36 additions & 38 deletions apps/calendar/stores/CalendarStorage.qml
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
pragma Singleton
import QtQuick
import MarathonOS.Shell
import QtQuick

QtObject {
id: root

property var events: []
property int nextEventId: 1
property Connections calendarConnection

calendarConnection: Connections {
function onEventsLoaded() {
root.events = CalendarManager.events;
root.dataChanged();
}

function onEventCreated(event) {
root.events = CalendarManager.events;
root.dataChanged();
}

function onEventUpdated(event) {
root.events = CalendarManager.events;
root.dataChanged();
}

function onEventDeleted(eventId) {
root.events = CalendarManager.events;
root.dataChanged();
}

target: typeof CalendarManager !== 'undefined' ? CalendarManager : null
enabled: typeof CalendarManager !== 'undefined'
}

// Signal to notify listeners of changes
signal dataChanged
Expand All @@ -21,8 +46,12 @@ QtObject {
events = JSON.parse(savedEvents);
if (events.length > 0) {
// Ensure IDs are numbers
events.forEach(e => e.id = Number(e.id));
nextEventId = Math.max(...events.map(e => e.id)) + 1;
events.forEach(e => {
return e.id = Number(e.id);
});
nextEventId = Math.max(events.map(e => {
return e.id;
})) + 1;
}
} catch (e) {
Logger.error("CalendarStorage", "Failed to load events: " + e);
Expand Down Expand Up @@ -93,33 +122,27 @@ QtObject {
}

function getEventsForDate(date) {
if (typeof CalendarManager !== 'undefined') {
if (typeof CalendarManager !== 'undefined')
return CalendarManager.getEventsForDate(date);
}

var dateStr = Qt.formatDate(date, "yyyy-MM-dd");
var result = [];

for (var i = 0; i < events.length; i++) {
var event = events[i];

if (event.date === dateStr) {
result.push(event);
} else if (event.recurring !== "none") {
var eventDate = new Date(event.date);
var checkDate = new Date(date);

if (event.recurring === "daily" && checkDate >= eventDate) {
result.push(event);
} else if (event.recurring === "weekly" && checkDate >= eventDate) {
var daysDiff = Math.floor((checkDate - eventDate) / (1000 * 60 * 60 * 24));
if (daysDiff % 7 === 0) {
if (daysDiff % 7 === 0)
result.push(event);
}
} else if (event.recurring === "monthly" && checkDate >= eventDate) {
if (checkDate.getDate() === eventDate.getDate()) {
if (checkDate.getDate() === eventDate.getDate())
result.push(event);
}
}
}
}
Expand All @@ -129,29 +152,4 @@ QtObject {
function getAllEvents() {
return events;
}

property Connections calendarConnection: Connections {
target: typeof CalendarManager !== 'undefined' ? CalendarManager : null
enabled: typeof CalendarManager !== 'undefined'

function onEventsLoaded() {
root.events = CalendarManager.events;
root.dataChanged();
}

function onEventCreated(event) {
root.events = CalendarManager.events;
root.dataChanged();
}

function onEventUpdated(event) {
root.events = CalendarManager.events;
root.dataChanged();
}

function onEventDeleted(eventId) {
root.events = CalendarManager.events;
root.dataChanged();
}
}
}
1 change: 1 addition & 0 deletions apps/calendar/stores/qmldir
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
singleton CalendarStorage 1.0 CalendarStorage.qml
Loading